https://github.com/deciosfernandes/ng-date-hour-range-selector
A repository for a date/datetime range selector for modern Angular
https://github.com/deciosfernandes/ng-date-hour-range-selector
Last synced: 2 days ago
JSON representation
A repository for a date/datetime range selector for modern Angular
- Host: GitHub
- URL: https://github.com/deciosfernandes/ng-date-hour-range-selector
- Owner: deciosfernandes
- License: gpl-3.0
- Created: 2026-03-02T20:32:14.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-05-22T17:49:44.000Z (28 days ago)
- Last Synced: 2026-06-16T16:06:38.929Z (3 days ago)
- Language: TypeScript
- Size: 472 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- fucking-awesome-angular - ng-date-hour-range-selector - A flexible Angular date / date-time range selector built on Angular CDK Overlay. (Third Party Components / Dates)
- awesome-angular - ng-date-hour-range-selector - A flexible Angular date / date-time range selector built on Angular CDK Overlay. (Third Party Components / Dates)
README
# ng-date-hour-range-selector
[](https://deciosfernandes.github.io/ng-date-hour-range-selector/)
[](https://www.npmjs.com/package/ng-date-hour-range-selector)
A flexible Angular **date / date-time range selector** built on Angular CDK Overlay. Supports predefined range shortcuts, time picking, localization, and full CSS customization — with zero third-party date-library dependency.
**[→ Live Demo](https://deciosfernandes.github.io/ng-date-hour-range-selector/)**
---
## Features
- Date **and** time range selection, or date-only mode
- 12-hour (AM/PM) and 24-hour time formats
- Configurable minute step
- Optional manual time inputs for direct hour/minute editing
- Sidebar with predefined range shortcuts (Today, Yesterday, This/Last Week…)
- Configurable calendar icon position (`left`, `right`, or hidden)
- Optional reset button in the sidebar
- Works as a `ControlValueAccessor` — drop into any reactive or template-driven form
- Fully localizable via the `PICKER_LOCALE` injection token
- Pre-select a range on load via the `initialRange` input
- `nextRange()` / `previousRange()` / `setRange()` public API methods
- No third-party date library required
- Built on Angular CDK Overlay
- Standalone components — no NgModule needed
- A directive variant (`drsDateRangePicker`) to attach the picker to any ``
- Accessible: keyboard navigation, ARIA attributes, meets WCAG AA
- Dark theme included; fully themeable via CSS custom properties
---
## Requirements
| Dependency | Version |
| -------------- | ---------- |
| Angular | `>=19.0.0` |
| `@angular/cdk` | `>=19.0.0` |
---
## Installation
```bash
npm install ng-date-hour-range-selector @angular/cdk
```
Import the built-in dark theme once in your global styles:
```scss
@use 'ng-date-hour-range-selector/styles/theme';
```
Add `provideAnimationsAsync()` to your application config:
```ts
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
export const appConfig: ApplicationConfig = {
providers: [provideAnimationsAsync()],
};
```
---
## Quick start
### Component (``)
```ts
import { DateRange, DateRangePickerComponent } from 'ng-date-hour-range-selector';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
imports: [ReactiveFormsModule, DateRangePickerComponent],
template: `
`,
})
export class MyComponent {
readonly rangeControl = new FormControl(null);
onRangeChange(range: DateRange | null): void {
console.log(range?.start, range?.end);
}
}
```
### Directive (`[drsDateRangePicker]`)
Attach the picker to any `` element:
```ts
import { DateRangePickerDirective } from 'ng-date-hour-range-selector';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
imports: [ReactiveFormsModule, DateRangePickerDirective],
template: `
`,
})
export class MyComponent {
readonly rangeControl = new FormControl(null);
onRangeChange(range: DateRange | null): void {
console.log(range?.start, range?.end);
}
}
```
---
## Component API — ``
### Inputs
| Input | Type | Default | Description |
| ------------------ | ------------------------------- | --------------------- | --------------------------------------------------------------------------------- |
| `showTime` | `boolean` | `true` | Show the time-picker section |
| `timeFormat` | `'12h' \| '24h'` | `'24h'` | 12-hour (AM/PM) or 24-hour format |
| `minuteStep` | `number` | `1` | Minute increment step |
| `allowManualTimeInput` | `boolean` | `false` | Enable editable hour/minute text inputs in the time picker |
| `weekStartsOn` | `0 \| 1` | `1` | First day of week — `0` Sunday, `1` Monday |
| `predefinedRanges` | `PredefinedRange[]` | built-in | Sidebar shortcut definitions |
| `minDate` | `Date` | — | Minimum selectable date (inclusive) |
| `maxDate` | `Date` | — | Maximum selectable date (inclusive) |
| `position` | `ConnectedPosition[]` | bottom-start | CDK Overlay connected positions |
| `showResetButton` | `boolean` | `true` | Show or hide the reset button in the sidebar |
| `calendarIcon` | `'left' \| 'right' \| 'hidden'` | `'right'` | Position of the calendar icon in the trigger button, or hide it |
| `showApplyButton` | `boolean` | `false` | Show an Apply button inside the overlay that closes it when clicked |
| `closeOnSelect` | `boolean` | `true` | Automatically close the overlay after a complete range is selected or pre-defined |
| `rangeMatchMode` | `'day' \| 'exact'` | `'day'` | How selected ranges are matched to predefined labels — `'day'` ignores time, `'exact'` requires identical timestamps |
| `emitOn` | `'change' \| 'close'` | `'change'` | Controls when `rangeChange` is emitted. `'change'` — emit immediately on every date/time selection (default). `'close'` — defer emission until the overlay is closed or Apply is clicked; reset always emits immediately. |
| `initialRange` | `DateRange \| PredefinedRange` | — | Range or predefined-range factory to pre-select on component load |
| `ariaLabel` | `string` | `'Select date range'` | Accessible label for the trigger button |
### Output
| Output | Payload | Description |
| ------------- | ------------------- | ----------------------------------------------------- |
| `rangeChange` | `DateRange \| null` | Emitted when a complete range is committed or cleared |
### Public methods
| Method | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `nextRange()` | Advance the current range forward by its own duration (e.g. Mon–Sun → next Mon–Sun) |
| `previousRange()` | Rewind the current range backward by its own duration |
| `setRange(range, emitEvent?)` | Programmatically set `DateRange \| null`; pass `emitEvent: false` to suppress `rangeChange` and CVA `onChange` |
### ControlValueAccessor
`DateRangePickerComponent` implements `ControlValueAccessor`, so it works with both `[formControl]` and `[(ngModel)]`:
```html
```
---
## Directive API — `[drsDateRangePicker]`
The directive exposes the **same inputs and output** as the component, **except** `calendarIcon` (which is specific to the component's trigger button).
```html
```
---
## Models
```ts
interface DateRange {
start: Date;
end: Date;
}
interface PredefinedRange {
/** Label shown in the sidebar */
label: string;
/** Factory function — called on each click to produce a fresh range */
range: () => DateRange;
}
```
---
## Global configuration — `PICKER_CONFIG`
Override defaults for every picker in your application (or a specific feature):
```ts
import { PICKER_CONFIG } from 'ng-date-hour-range-selector';
// app.config.ts
providers: [
{
provide: PICKER_CONFIG,
useValue: { showTime: false, timeFormat: '12h', weekStartsOn: 0 },
},
];
```
Individual component/directive inputs always take precedence over the global config.
### `PickerConfig` interface
| Property | Type | Default | Description |
| ------------------ | ------------------------------- | ------------ | --------------------------------------------------------------------------------- |
| `showTime` | `boolean` | `true` | Show the time-picker section |
| `timeFormat` | `'12h' \| '24h'` | `'24h'` | Hour format |
| `minuteStep` | `number` | `1` | Minute increment step |
| `allowManualTimeInput` | `boolean` | `false` | Enable editable hour/minute text inputs in the time picker |
| `weekStartsOn` | `0 \| 1` | `1` | First day of week |
| `predefinedRanges` | `PredefinedRange[]` | built-in | Override all shortcuts globally |
| `minDate` | `Date` | — | Global minimum date |
| `maxDate` | `Date` | — | Global maximum date |
| `position` | `ConnectedPosition[]` | bottom-start | CDK overlay positions |
| `showResetButton` | `boolean` | `true` | Show or hide the reset button |
| `calendarIcon` | `'left' \| 'right' \| 'hidden'` | `'right'` | Calendar icon position in the trigger button |
| `showApplyButton` | `boolean` | `false` | Show an Apply button inside the overlay |
| `closeOnSelect` | `boolean` | `true` | Automatically close the overlay after a complete range is selected or pre-defined |
| `rangeMatchMode` | `'day' \| 'exact'` | `'day'` | How selected ranges are matched to predefined labels — `'day'` ignores time, `'exact'` requires identical timestamps |
| `emitOn` | `'change' \| 'close'` | `'change'` | Controls when `rangeChange` is emitted. `'change'` — emit immediately on every date/time selection. `'close'` — defer emission until the overlay is closed or Apply is clicked; reset always emits immediately. |
---
## Localization — `PICKER_LOCALE`
Provide a `PickerLocale` object to translate every visible string:
```ts
import { PICKER_LOCALE, PickerLocale } from 'ng-date-hour-range-selector';
const ptBrLocale: PickerLocale = {
daysOfWeek: ['Do', 'Se', 'Te', 'Qu', 'Qi', 'Se', 'Sa'],
monthNames: [
'Janeiro',
'Fevereiro',
'Março',
'Abril',
'Maio',
'Junho',
'Julho',
'Agosto',
'Setembro',
'Outubro',
'Novembro',
'Dezembro',
],
am: 'AM',
pm: 'PM',
startTime: 'Início:',
endTime: 'Fim:',
reset: 'Limpar',
apply: 'Aplicar',
placeholder: 'Selecione um período',
formatRange: (start, end) =>
`${start.toLocaleDateString('pt-BR')} – ${end.toLocaleDateString('pt-BR')}`,
formatRangeWithTime: (start, end) => {
const fmt = (d: Date) =>
`${d.toLocaleDateString('pt-BR')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
return `${fmt(start)} – ${fmt(end)}`;
},
};
providers: [{ provide: PICKER_LOCALE, useValue: ptBrLocale }];
```
> `formatRangeWithTime` is optional. When `showTime` is `true` and it is provided, the trigger will include times in the display value. Falls back to `formatRange` if omitted.
### `PickerLocale` interface
| Property | Type | Description |
| --------------------- | ------------------------------------ | ------------------------------------------------------------------------ |
| `daysOfWeek` | `[string × 7]` | Abbreviated day labels — Sunday first |
| `monthNames` | `[string × 12]` | Full month names — January first |
| `am` | `string` | AM toggle label |
| `pm` | `string` | PM toggle label |
| `startTime` | `string` | Label above the start time picker |
| `endTime` | `string` | Label above the end time picker |
| `reset` | `string` | Reset/clear button label |
| `apply` | `string` | Apply button label (used when `showApplyButton` is `true`) |
| `placeholder` | `string?` | Trigger placeholder when no range is selected |
| `formatRange` | `(start: Date, end: Date) => string` | Formats the selected range for display in the trigger |
| `formatRangeWithTime` | `(start: Date, end: Date) => string` | Formats the range including time; falls back to `formatRange` if omitted |
---
## Predefined ranges
The sidebar shows these built-in shortcuts by default:
- Today
- Yesterday
- This week / Last week
- This month / Last month
- This quarter / Last quarter
Replace them per-picker via the `predefinedRanges` input, or globally via `PICKER_CONFIG`:
```ts
import { PredefinedRange } from 'ng-date-hour-range-selector';
const customRanges: PredefinedRange[] = [
{
label: 'Last 7 days',
range: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 0);
return { start, end };
},
},
{
label: 'Last 30 days',
range: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 29);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 0);
return { start, end };
},
},
];
```
```html
```
---
## Examples
### Date-only picker
```html
```
### 12-hour format, Sunday start
```html
```
### Calendar icon on the left, no reset button
```html
```
### Pre-selected range on load
```ts
readonly initialRange: PredefinedRange = {
label: 'Last 7 days',
range: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
return { start, end };
},
};
```
```html
```
### Navigate range programmatically
```ts
private picker = viewChild(DateRangePickerComponent);
next(): void { this.picker()?.nextRange(); }
prev(): void { this.picker()?.previousRange(); }
```
### Directive on a plain input
```html
```
---
## Theming — CSS custom properties
Import the built-in dark theme and override variables at `:root` or on specific elements:
```scss
@use 'ng-date-hour-range-selector/styles/theme';
// Global accent colour
:root {
--drs-primary: #3b82f6;
}
// Light theme override
drs-date-range-picker.light {
--drs-bg: #ffffff;
--drs-text: #111111;
--drs-border: rgba(0, 0, 0, 0.12);
--drs-hover: rgba(0, 0, 0, 0.06);
--drs-range-bg: rgba(59, 130, 246, 0.12);
}
```
You can also use the `style` attribute inline:
```html
```
### Full variable reference
| Variable | Description | Default |
| -------------------------- | --------------------------------- | ----------- |
| `--drs-radius` | Overlay panel border radius | `10px` |
| `--drs-radius-sm` | Button border radius | `5px` |
| `--drs-sidebar-width` | Predefined-ranges sidebar width | `148px` |
| `--drs-overlay-z` | z-index of the overlay panel | `1000` |
| `--drs-shadow` | Overlay panel box shadow | dark shadow |
| `--drs-bg` | Overlay / modal background | `#1e1f22` |
| `--drs-trigger-bg` | Trigger button background | `--drs-bg` |
| `--drs-primary` | Accent / highlight colour | `#f97316` |
| `--drs-primary-fg` | Foreground on accent colour | `#ffffff` |
| `--drs-text` | Primary text colour | `#f1f1f1` |
| `--drs-text-muted` | Dimmed / secondary text | 35% opacity |
| `--drs-border` | Border and divider colour | 8% white |
| `--drs-hover` | Hover background | 7% white |
| `--drs-range-bg` | In-range day background | orange 14% |
| `--drs-time-bg` | Time-picker box background | 5% white |
| `--drs-font-family` | Font family | `inherit` |
| `--drs-font-size` | Base font size | `0.875rem` |
| `--drs-header-font-size` | Month / year header size | `0.9375rem` |
| `--drs-header-font-weight` | Month / year header weight | `700` |
| `--drs-weekday-font-size` | Day-of-week label size | `0.6875rem` |
| `--drs-day-font-size` | Day number size | `0.875rem` |
| `--drs-sidebar-font-size` | Predefined-range label size | `0.875rem` |
| `--drs-time-font-size` | Hour / minute number size | `1.375rem` |
| `--drs-ampm-font-size` | AM/PM toggle size | `0.9375rem` |
| `--drs-label-font-size` | "Start time:" / "End time:" label | `0.8125rem` |
| `--drs-trigger-font-size` | Trigger button text size | `0.875rem` |
| `--drs-apply-font-size` | Apply button text size | `0.875rem` |
---
## Exported API surface
```ts
// Components & Directive
export { DateRangePickerComponent } from 'ng-date-hour-range-selector';
export { DateRangePickerDirective } from 'ng-date-hour-range-selector';
export { DateRangePickerPanelComponent } from 'ng-date-hour-range-selector';
export { CalendarComponent } from 'ng-date-hour-range-selector';
export { TimePickerComponent } from 'ng-date-hour-range-selector';
export { PredefinedRangesComponent } from 'ng-date-hour-range-selector';
// Models
export type { DateRange, PredefinedRange } from 'ng-date-hour-range-selector';
export type { PickerConfig } from 'ng-date-hour-range-selector';
export type { PickerLocale } from 'ng-date-hour-range-selector';
export type { TimeValue } from 'ng-date-hour-range-selector';
export type { CalendarCell } from 'ng-date-hour-range-selector';
export type { ResolvedPickerConfig } from 'ng-date-hour-range-selector';
// Tokens & defaults
export { PICKER_CONFIG, DEFAULT_PICKER_CONFIG } from 'ng-date-hour-range-selector';
export { PICKER_LOCALE, DEFAULT_PICKER_LOCALE } from 'ng-date-hour-range-selector';
// Service
export { DateUtilsService } from 'ng-date-hour-range-selector';
```
---
## Development
```bash
# Install dependencies
npm install
# Start the demo app at http://localhost:4200
npm start
# Build the library
npm run build:lib
# Build library + demo
npm run build
# Run unit tests
npm test
```
---
## License
MIT