@oneluiz/dual-datepicker 3.1.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +949 -10
- package/THEMING.md +313 -0
- package/dual-datepicker.component.d.ts +16 -1
- package/esm2022/dual-datepicker.component.mjs +224 -41
- package/esm2022/public-api.mjs +1 -1
- package/fesm2022/oneluiz-dual-datepicker.mjs +223 -40
- package/fesm2022/oneluiz-dual-datepicker.mjs.map +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +1 -1
- package/src/themes/bootstrap.scss +202 -0
- package/src/themes/bulma.scss +209 -0
- package/src/themes/custom.scss +236 -0
- package/src/themes/foundation.scss +201 -0
- package/src/themes/tailwind.scss +109 -0
- package/themes/bootstrap.scss +202 -0
- package/themes/bulma.scss +209 -0
- package/themes/custom.scss +236 -0
- package/themes/foundation.scss +201 -0
- package/themes/tailwind.scss +109 -0
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
A lightweight, zero-dependency date range picker for Angular 17+. Built with standalone components, Reactive Forms, and Angular Signals. No Angular Material required.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@oneluiz/dual-datepicker)
|
|
6
|
+
[](https://www.npmjs.com/package/@oneluiz/dual-datepicker)
|
|
6
7
|

|
|
7
8
|

|
|
8
9
|
|
|
@@ -10,20 +11,958 @@ A lightweight, zero-dependency date range picker for Angular 17+. Built with sta
|
|
|
10
11
|
npm install @oneluiz/dual-datepicker
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
> ## ⚠️ **BREAKING CHANGES in v3.0.0**
|
|
14
|
-
>
|
|
15
|
-
> - **All DateRange properties renamed to English**: `fechaInicio` → `startDate`, `fechaFin` → `endDate`, `rangoTexto` → `rangeText`
|
|
16
|
-
> - **Deprecated `daysAgo` removed**: Use `getValue: () => PresetRange` pattern or `CommonPresets` instead
|
|
17
|
-
> - **All component methods renamed to English**: `limpiar()` → `clear()`, etc.
|
|
18
|
-
>
|
|
19
|
-
> **📖 See [MIGRATION_V3.md](MIGRATION_V3.md) for complete migration guide**
|
|
20
|
-
>
|
|
21
|
-
> To stay on v2.x: `npm install @oneluiz/dual-datepicker@2.7.0`
|
|
22
|
-
|
|
23
14
|
## 🎯 [Live Demo](https://oneluiz.github.io/ng-dual-datepicker/)
|
|
24
15
|
|
|
25
16
|
**[Check out the interactive examples →](https://oneluiz.github.io/ng-dual-datepicker/)**
|
|
26
17
|
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 📋 Table of Contents
|
|
21
|
+
|
|
22
|
+
- [Features](#-features)
|
|
23
|
+
- [Why Choose This Library?](#-why-choose-this-library)
|
|
24
|
+
- [Installation](#-installation)
|
|
25
|
+
- [Quick Start](#-quick-start)
|
|
26
|
+
- [Basic Usage](#basic-usage)
|
|
27
|
+
- [Reactive Forms](#with-reactive-forms)
|
|
28
|
+
- [Angular Signals](#with-angular-signals)
|
|
29
|
+
- [Advanced Features](#-advanced-features)
|
|
30
|
+
- [Multi-Range Selection](#multi-range-support)
|
|
31
|
+
- [Disabled Dates](#disabled-dates)
|
|
32
|
+
- [Display Format](#display-format)
|
|
33
|
+
- [Apply/Confirm Button](#applyconfirm-button)
|
|
34
|
+
- [Hover Range Preview](#hover-range-preview)
|
|
35
|
+
- [Custom Presets](#custom-presets)
|
|
36
|
+
- [Date Adapter System](#date-adapter-system)
|
|
37
|
+
- [Keyboard Navigation](#keyboard-navigation)
|
|
38
|
+
- [Customization](#-customization)
|
|
39
|
+
- [Theming System](#theming-system)
|
|
40
|
+
- [Styling Options](#styling-options)
|
|
41
|
+
- [Localization (i18n)](#localization-i18n)
|
|
42
|
+
- [API Reference](#-api-reference)
|
|
43
|
+
- [Inputs](#inputs)
|
|
44
|
+
- [Outputs](#outputs)
|
|
45
|
+
- [Methods](#public-methods)
|
|
46
|
+
- [Types](#types)
|
|
47
|
+
- [Examples](#-usage-examples)
|
|
48
|
+
- [Accessibility](#-accessibility)
|
|
49
|
+
- [Requirements](#-requirements)
|
|
50
|
+
- [License & Support](#-license--support)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## ✨ Features
|
|
55
|
+
|
|
56
|
+
- 🪶 **Zero Dependencies** – No external libraries required
|
|
57
|
+
- 🎯 **Standalone Component** – No NgModule imports needed
|
|
58
|
+
- ⚡ **Angular Signals** – Modern reactive state management
|
|
59
|
+
- 🔄 **Reactive Forms** – Full ControlValueAccessor implementation
|
|
60
|
+
- 🔥 **Multi-Range Support** – Select multiple date ranges (Material CAN'T do this!)
|
|
61
|
+
- 🚫 **Disabled Dates** – Block weekends, holidays, or custom logic
|
|
62
|
+
- 🎨 **Display Format** – Customize date display (DD/MM/YYYY, MM/DD/YYYY, etc.)
|
|
63
|
+
- ✅ **Apply/Confirm Button** – Require confirmation before emitting (perfect for dashboards)
|
|
64
|
+
- � **Theming System** – Built-in themes for Bootstrap, Bulma, Foundation, Tailwind, + Custom
|
|
65
|
+
- �🎨 **Fully Customizable** – Every color, padding, border configurable
|
|
66
|
+
- 📦 **Lightweight** – ~60 KB gzipped total bundle
|
|
67
|
+
- 🚀 **Performance** – OnPush change detection + trackBy optimization
|
|
68
|
+
- ♿ **Accessible** – Full keyboard navigation, ARIA labels, WCAG 2.1 AA compliant
|
|
69
|
+
- 🌍 **i18n Ready** – Customizable month/day names
|
|
70
|
+
- 📱 **Responsive** – Works on desktop and mobile
|
|
71
|
+
- 🔌 **Date Adapters** – Use DayJS, date-fns, Luxon, or custom libraries
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🤔 Why Choose This Library?
|
|
76
|
+
|
|
77
|
+
| Feature | ng-dual-datepicker | Angular Material DateRangePicker |
|
|
78
|
+
|---------|-------------------|----------------------------------|
|
|
79
|
+
| **Bundle Size** | ~60 KB gzipped | ~300+ KB (with dependencies) |
|
|
80
|
+
| **Dependencies** | Zero | Requires @angular/material, @angular/cdk |
|
|
81
|
+
| **Standalone** | ✅ Native | ⚠️ Requires module setup |
|
|
82
|
+
| **Signals Support** | ✅ Built-in | ❌ Not yet |
|
|
83
|
+
| **Multi-Range Support** | ✅ Yes | ❌ Not available |
|
|
84
|
+
| **Customization** | Full styling control | Theme-constrained |
|
|
85
|
+
| **Learning Curve** | Minimal | Requires Material knowledge |
|
|
86
|
+
| **Change Detection** | OnPush optimized | Default |
|
|
87
|
+
| **Setup Time** | < 1 minute | ~10+ minutes (theming, modules) |
|
|
88
|
+
|
|
89
|
+
### When to Use This
|
|
90
|
+
|
|
91
|
+
**✅ Use ng-dual-datepicker if you:**
|
|
92
|
+
- Don't want to install Angular Material just for a date picker
|
|
93
|
+
- Need precise control over styling and behavior
|
|
94
|
+
- Want minimal bundle size impact
|
|
95
|
+
- Prefer standalone components
|
|
96
|
+
- Need Angular Signals support now
|
|
97
|
+
- Need multi-range selection
|
|
98
|
+
- Are building a custom design system
|
|
99
|
+
|
|
100
|
+
**⚠️ Use Angular Material DateRangePicker if you:**
|
|
101
|
+
- Already use Angular Material throughout your app
|
|
102
|
+
- Need Material Design compliance
|
|
103
|
+
- Want a battle-tested enterprise solution with extensive ecosystem
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📦 Installation
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm install @oneluiz/dual-datepicker
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Requirements:** Angular 17.0.0 or higher
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 🚀 Quick Start
|
|
118
|
+
|
|
119
|
+
### Basic Usage
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { Component } from '@angular/core';
|
|
123
|
+
import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
|
|
124
|
+
|
|
125
|
+
@Component({
|
|
126
|
+
selector: 'app-root',
|
|
127
|
+
standalone: true,
|
|
128
|
+
imports: [DualDatepickerComponent],
|
|
129
|
+
template: `
|
|
130
|
+
<ngx-dual-datepicker
|
|
131
|
+
(dateRangeChange)="onRangeChange($event)">
|
|
132
|
+
</ngx-dual-datepicker>
|
|
133
|
+
`
|
|
134
|
+
})
|
|
135
|
+
export class AppComponent {
|
|
136
|
+
onRangeChange(range: DateRange) {
|
|
137
|
+
console.log('Start:', range.startDate);
|
|
138
|
+
console.log('End:', range.endDate);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### With Reactive Forms
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { FormControl } from '@angular/forms';
|
|
147
|
+
import { DateRange } from '@oneluiz/dual-datepicker';
|
|
148
|
+
|
|
149
|
+
dateRange = new FormControl<DateRange | null>(null);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```html
|
|
153
|
+
<ngx-dual-datepicker [formControl]="dateRange"></ngx-dual-datepicker>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### With Angular Signals
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { signal } from '@angular/core';
|
|
160
|
+
|
|
161
|
+
dateRange = signal<DateRange | null>(null);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<ngx-dual-datepicker
|
|
166
|
+
[(ngModel)]="dateRange()"
|
|
167
|
+
(dateRangeChange)="dateRange.set($event)">
|
|
168
|
+
</ngx-dual-datepicker>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🎯 Advanced Features
|
|
174
|
+
|
|
175
|
+
### Multi-Range Support
|
|
176
|
+
|
|
177
|
+
**🔥 Material CAN'T do this!** Select multiple date ranges in a single picker - perfect for booking systems, blackout periods, and complex scheduling.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { Component } from '@angular/core';
|
|
181
|
+
import { MultiDateRange } from '@oneluiz/dual-datepicker';
|
|
182
|
+
|
|
183
|
+
@Component({
|
|
184
|
+
template: `
|
|
185
|
+
<ngx-dual-datepicker
|
|
186
|
+
[multiRange]="true"
|
|
187
|
+
(multiDateRangeChange)="onMultiRangeChange($event)">
|
|
188
|
+
</ngx-dual-datepicker>
|
|
189
|
+
|
|
190
|
+
@if (selectedRanges && selectedRanges.ranges.length > 0) {
|
|
191
|
+
<div class="selected-ranges">
|
|
192
|
+
<h3>Selected Ranges ({{ selectedRanges.ranges.length }})</h3>
|
|
193
|
+
@for (range of selectedRanges.ranges; track $index) {
|
|
194
|
+
<div class="range-item">
|
|
195
|
+
{{ range.startDate }} → {{ range.endDate }}
|
|
196
|
+
</div>
|
|
197
|
+
}
|
|
198
|
+
</div>
|
|
199
|
+
}
|
|
200
|
+
`
|
|
201
|
+
})
|
|
202
|
+
export class MultiRangeExample {
|
|
203
|
+
selectedRanges: MultiDateRange | null = null;
|
|
204
|
+
|
|
205
|
+
onMultiRangeChange(ranges: MultiDateRange) {
|
|
206
|
+
this.selectedRanges = ranges;
|
|
207
|
+
console.log('Selected ranges:', ranges.ranges);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Perfect Use Cases:**
|
|
213
|
+
- 🏨 Hotel booking systems
|
|
214
|
+
- 📅 Event blackout periods
|
|
215
|
+
- 🔧 Maintenance windows
|
|
216
|
+
- 📊 Availability calendars
|
|
217
|
+
- 👷 Shift scheduling
|
|
218
|
+
|
|
219
|
+
### Disabled Dates
|
|
220
|
+
|
|
221
|
+
**Block specific dates or apply custom logic to disable dates.** Perfect for booking systems, business day selection, and holiday management.
|
|
222
|
+
|
|
223
|
+
#### Option 1: Disable Specific Dates (Array)
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { Component } from '@angular/core';
|
|
227
|
+
|
|
228
|
+
@Component({
|
|
229
|
+
template: `
|
|
230
|
+
<ngx-dual-datepicker
|
|
231
|
+
[disabledDates]="holidays"
|
|
232
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
233
|
+
</ngx-dual-datepicker>
|
|
234
|
+
`
|
|
235
|
+
})
|
|
236
|
+
export class DisabledDatesExample {
|
|
237
|
+
holidays: Date[] = [
|
|
238
|
+
new Date(2026, 0, 1), // New Year
|
|
239
|
+
new Date(2026, 11, 25), // Christmas
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
onDateRangeChange(range: DateRange) {
|
|
243
|
+
console.log('Selected range:', range);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### Option 2: Disable with Function (Weekends + Holidays)
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { Component } from '@angular/core';
|
|
252
|
+
|
|
253
|
+
@Component({
|
|
254
|
+
template: `
|
|
255
|
+
<ngx-dual-datepicker
|
|
256
|
+
[disabledDates]="isDateDisabled"
|
|
257
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
258
|
+
</ngx-dual-datepicker>
|
|
259
|
+
`
|
|
260
|
+
})
|
|
261
|
+
export class BusinessDaysExample {
|
|
262
|
+
holidays: Date[] = [
|
|
263
|
+
new Date(2026, 0, 1), // New Year
|
|
264
|
+
new Date(2026, 11, 25), // Christmas
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
// Disable weekends and holidays
|
|
268
|
+
isDateDisabled = (date: Date): boolean => {
|
|
269
|
+
const day = date.getDay();
|
|
270
|
+
|
|
271
|
+
// Disable weekends (0 = Sunday, 6 = Saturday)
|
|
272
|
+
if (day === 0 || day === 6) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check if date is a holiday
|
|
277
|
+
return this.holidays.some(holiday =>
|
|
278
|
+
holiday.getFullYear() === date.getFullYear() &&
|
|
279
|
+
holiday.getMonth() === date.getMonth() &&
|
|
280
|
+
holiday.getDate() === date.getDate()
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
onDateRangeChange(range: DateRange) {
|
|
285
|
+
console.log('Selected range:', range);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Perfect Use Cases:**
|
|
291
|
+
- 🏢 Business day selection (no weekends)
|
|
292
|
+
- 📅 Booking systems (unavailable dates)
|
|
293
|
+
- 🎉 Holiday management
|
|
294
|
+
- 🚫 Blackout dates for reservations
|
|
295
|
+
- 📆 Appointment scheduling
|
|
296
|
+
|
|
297
|
+
**Features:**
|
|
298
|
+
- ✅ Two modes: Array or Function
|
|
299
|
+
- ✅ Visual styling (strikethrough, grayed out)
|
|
300
|
+
- ✅ Cannot be selected via mouse or keyboard
|
|
301
|
+
- ✅ Flexible custom logic support
|
|
302
|
+
|
|
303
|
+
### Display Format
|
|
304
|
+
|
|
305
|
+
**Customize how dates appear in the input field.** Use flexible format tokens to match regional preferences and localization needs.
|
|
306
|
+
|
|
307
|
+
#### Basic Usage
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { Component } from '@angular/core';
|
|
311
|
+
|
|
312
|
+
@Component({
|
|
313
|
+
template: `
|
|
314
|
+
<!-- Default: "D MMM" (1 Jan, 15 Feb) -->
|
|
315
|
+
<ngx-dual-datepicker
|
|
316
|
+
displayFormat="D MMM"
|
|
317
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
318
|
+
</ngx-dual-datepicker>
|
|
319
|
+
|
|
320
|
+
<!-- European: "DD/MM/YYYY" (01/01/2026, 15/02/2026) -->
|
|
321
|
+
<ngx-dual-datepicker
|
|
322
|
+
displayFormat="DD/MM/YYYY"
|
|
323
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
324
|
+
</ngx-dual-datepicker>
|
|
325
|
+
|
|
326
|
+
<!-- US: "MM/DD/YYYY" (01/01/2026, 02/15/2026) -->
|
|
327
|
+
<ngx-dual-datepicker
|
|
328
|
+
displayFormat="MM/DD/YYYY"
|
|
329
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
330
|
+
</ngx-dual-datepicker>
|
|
331
|
+
|
|
332
|
+
<!-- ISO: "YYYY-MM-DD" (2026-01-01, 2026-02-15) -->
|
|
333
|
+
<ngx-dual-datepicker
|
|
334
|
+
displayFormat="YYYY-MM-DD"
|
|
335
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
336
|
+
</ngx-dual-datepicker>
|
|
337
|
+
|
|
338
|
+
<!-- Long: "MMM DD, YYYY" (Jan 01, 2026) -->
|
|
339
|
+
<ngx-dual-datepicker
|
|
340
|
+
displayFormat="MMM DD, YYYY"
|
|
341
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
342
|
+
</ngx-dual-datepicker>
|
|
343
|
+
`
|
|
344
|
+
})
|
|
345
|
+
export class DisplayFormatExample {
|
|
346
|
+
onDateRangeChange(range: DateRange) {
|
|
347
|
+
console.log('Selected range:', range);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### Available Format Tokens
|
|
353
|
+
|
|
354
|
+
| Token | Output | Description |
|
|
355
|
+
|-------|--------|-------------|
|
|
356
|
+
| `YYYY` | 2026 | Full year (4 digits) |
|
|
357
|
+
| `YY` | 26 | 2-digit year |
|
|
358
|
+
| `MMMM` | January | Full month name |
|
|
359
|
+
| `MMM` | Jan | Short month name (3 letters) |
|
|
360
|
+
| `MM` | 01-12 | 2-digit month (zero-padded) |
|
|
361
|
+
| `M` | 1-12 | Month number (no padding) |
|
|
362
|
+
| `DD` | 01-31 | 2-digit day (zero-padded) |
|
|
363
|
+
| `D` | 1-31 | Day number (no padding) |
|
|
364
|
+
|
|
365
|
+
#### Custom Format Examples
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// Mix and match tokens with any separators
|
|
369
|
+
displayFormat="D/M/YY" // 1/2/26
|
|
370
|
+
displayFormat="DD-MM-YYYY" // 01-02-2026
|
|
371
|
+
displayFormat="MMMM D, YYYY" // January 1, 2026
|
|
372
|
+
displayFormat="D. MMMM YYYY" // 1. January 2026
|
|
373
|
+
displayFormat="YY.MM.DD" // 26.01.01
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Perfect Use Cases:**
|
|
377
|
+
- 🌍 Localization (match regional formats)
|
|
378
|
+
- 🇪🇺 European format (DD/MM/YYYY)
|
|
379
|
+
- 🇺🇸 US format (MM/DD/YYYY)
|
|
380
|
+
- 💻 ISO format (YYYY-MM-DD for APIs)
|
|
381
|
+
- 📱 Mobile-friendly short formats
|
|
382
|
+
- 📄 Long formats for reports
|
|
383
|
+
|
|
384
|
+
**Features:**
|
|
385
|
+
- ✅ Flexible token system
|
|
386
|
+
- ✅ Any separator (/, -, space, comma, dot)
|
|
387
|
+
- ✅ Mix and match freely
|
|
388
|
+
- ✅ Works with locale month names
|
|
389
|
+
- ✅ No external dependencies
|
|
390
|
+
|
|
391
|
+
### Apply/Confirm Button
|
|
392
|
+
|
|
393
|
+
**Require explicit confirmation before emitting changes.** Perfect for dashboards and enterprise applications where recalculating data or making API calls is expensive.
|
|
394
|
+
|
|
395
|
+
#### How It Works
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { Component } from '@angular/core';
|
|
399
|
+
import { DateRange } from '@oneluiz/dual-datepicker';
|
|
400
|
+
|
|
401
|
+
@Component({
|
|
402
|
+
template: `
|
|
403
|
+
<ngx-dual-datepicker
|
|
404
|
+
[requireApply]="true"
|
|
405
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
406
|
+
</ngx-dual-datepicker>
|
|
407
|
+
|
|
408
|
+
@if (selectedRange) {
|
|
409
|
+
<div class="data-display">
|
|
410
|
+
<!-- Expensive data loaded only after Apply -->
|
|
411
|
+
<app-dashboard-data [range]="selectedRange"></app-dashboard-data>
|
|
412
|
+
</div>
|
|
413
|
+
}
|
|
414
|
+
`
|
|
415
|
+
})
|
|
416
|
+
export class DashboardExample {
|
|
417
|
+
selectedRange: DateRange | null = null;
|
|
418
|
+
|
|
419
|
+
onDateRangeChange(range: DateRange) {
|
|
420
|
+
this.selectedRange = range;
|
|
421
|
+
// This only fires AFTER user clicks Apply button
|
|
422
|
+
// Prevents unnecessary API calls during date selection
|
|
423
|
+
this.loadExpensiveData(range.startDate, range.endDate);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
loadExpensiveData(start: string, end: string) {
|
|
427
|
+
// API call, database query, heavy calculation, etc.
|
|
428
|
+
this.apiService.fetchDashboardData(start, end).subscribe(...);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### User Flow
|
|
434
|
+
|
|
435
|
+
1. **User selects dates** - Clicks start and end dates in calendar
|
|
436
|
+
2. **Pending state** - Selection highlighted as "pending" (not confirmed)
|
|
437
|
+
3. **No events yet** - `dateRangeChange` is NOT emitted
|
|
438
|
+
4. **Apply** - User clicks "Apply" button → dates confirmed, event emitted
|
|
439
|
+
5. **Cancel** - Or clicks "Cancel" button → pending selection discarded
|
|
440
|
+
|
|
441
|
+
#### Visual Behavior
|
|
442
|
+
|
|
443
|
+
- Selected dates show in calendar with pending styling
|
|
444
|
+
- Apply button enabled when both dates selected
|
|
445
|
+
- Cancel button enabled when there are pending changes
|
|
446
|
+
- Apply button triggers event emission and closes picker
|
|
447
|
+
- Cancel clears pending selection and keeps current dates
|
|
448
|
+
|
|
449
|
+
**Perfect Use Cases:**
|
|
450
|
+
- 📊 Dashboards that load data on date change
|
|
451
|
+
- 📈 Reports with expensive calculations/aggregations
|
|
452
|
+
- 🔍 Analytics with API calls to fetch metrics
|
|
453
|
+
- 💰 Financial systems with complex data processing
|
|
454
|
+
- 📉 BI tools with heavy database queries
|
|
455
|
+
- 🧮 Any scenario where immediate updates are costly
|
|
456
|
+
|
|
457
|
+
**Key Benefits:**
|
|
458
|
+
- ✅ Prevent unwanted API calls during selection
|
|
459
|
+
- ✅ User control - explicit confirmation required
|
|
460
|
+
- ✅ Professional enterprise UX pattern
|
|
461
|
+
- ✅ Reduces server load and improves performance
|
|
462
|
+
- ✅ Works with all other features (presets, formats, etc.)
|
|
463
|
+
|
|
464
|
+
**Comparison with Immediate Mode:**
|
|
465
|
+
|
|
466
|
+
| Behavior | Immediate Mode | Apply Mode |
|
|
467
|
+
|----------|---------------|------------|
|
|
468
|
+
| Event emission | On each date click | Only on Apply click |
|
|
469
|
+
| API calls | Multiple (start + end) | Single (only Apply) |
|
|
470
|
+
| User control | Automatic | Explicit confirmation |
|
|
471
|
+
| Best for | Simple forms | Dashboards, reports |
|
|
472
|
+
|
|
473
|
+
### Hover Range Preview
|
|
474
|
+
|
|
475
|
+
**Automatic visual feedback while selecting dates.** Provides instant visual preview of the date range when hovering over dates before clicking to confirm.
|
|
476
|
+
|
|
477
|
+
#### How It Works
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { Component } from '@angular/core';
|
|
481
|
+
import { DateRange } from '@oneluiz/dual-datepicker';
|
|
482
|
+
|
|
483
|
+
@Component({
|
|
484
|
+
template: `
|
|
485
|
+
<ngx-dual-datepicker
|
|
486
|
+
(dateRangeChange)="onDateRangeChange($event)">
|
|
487
|
+
</ngx-dual-datepicker>
|
|
488
|
+
`
|
|
489
|
+
})
|
|
490
|
+
export class SimpleExample {
|
|
491
|
+
selectedRange: DateRange | null = null;
|
|
492
|
+
|
|
493
|
+
onDateRangeChange(range: DateRange) {
|
|
494
|
+
this.selectedRange = range;
|
|
495
|
+
console.log('Range selected:', range);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### User Flow
|
|
501
|
+
|
|
502
|
+
1. **Select start date** - User clicks on any date
|
|
503
|
+
2. **Hover over dates** - Move mouse over other dates
|
|
504
|
+
3. **Visual preview** - See potential range highlighted instantly
|
|
505
|
+
4. **Select end date** - Click to confirm the range
|
|
506
|
+
|
|
507
|
+
#### Visual Styling
|
|
508
|
+
|
|
509
|
+
- **Light purple background** (#e0e7ff) for preview range
|
|
510
|
+
- **Purple dashed border** (#6366f1) around preview dates
|
|
511
|
+
- **70% opacity** - subtle, non-intrusive preview
|
|
512
|
+
- **Clear distinction** from confirmed selected range
|
|
513
|
+
|
|
514
|
+
**Key Benefits:**
|
|
515
|
+
- ✅ Better UX - visual preview before confirming selection
|
|
516
|
+
- ✅ Instant feedback - see range immediately on mouse hover
|
|
517
|
+
- ✅ Intuitive interaction - natural mouse behavior
|
|
518
|
+
- ✅ Zero configuration - automatic, always enabled
|
|
519
|
+
- ✅ Professional feel - premium date picker experience
|
|
520
|
+
|
|
521
|
+
**Works With All Modes:**
|
|
522
|
+
- ✅ Single range mode
|
|
523
|
+
- ✅ Multi-range mode
|
|
524
|
+
- ✅ requireApply mode
|
|
525
|
+
- ✅ All presets, formats, and disabled dates
|
|
526
|
+
|
|
527
|
+
**No Configuration Needed:**
|
|
528
|
+
Hover preview is automatically enabled and works seamlessly with all other features. Just use the datepicker normally!
|
|
529
|
+
|
|
530
|
+
### Custom Presets
|
|
531
|
+
|
|
532
|
+
**Power feature for dashboards, reporting, ERP, and BI systems!**
|
|
533
|
+
|
|
534
|
+
#### Using Pre-built Presets
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import { CommonPresets } from '@oneluiz/dual-datepicker';
|
|
538
|
+
|
|
539
|
+
// Dashboard presets
|
|
540
|
+
presets = CommonPresets.dashboard;
|
|
541
|
+
// → Last 7, 15, 30, 60, 90 days + last 6 months
|
|
542
|
+
|
|
543
|
+
// Reporting presets
|
|
544
|
+
presets = CommonPresets.reporting;
|
|
545
|
+
// → Today, This week, Last week, This month, Last month, This quarter
|
|
546
|
+
|
|
547
|
+
// Financial/ERP presets
|
|
548
|
+
presets = CommonPresets.financial;
|
|
549
|
+
// → Month to date, Quarter to date, Year to date
|
|
550
|
+
|
|
551
|
+
// Analytics presets
|
|
552
|
+
presets = CommonPresets.analytics;
|
|
553
|
+
// → Last 7/14/30/60/90/180/365 days
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### Creating Custom Presets
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
import { PresetConfig, getToday, getThisMonth, getLastMonth } from '@oneluiz/dual-datepicker';
|
|
560
|
+
|
|
561
|
+
customPresets: PresetConfig[] = [
|
|
562
|
+
{ label: 'Today', getValue: getToday },
|
|
563
|
+
{ label: 'This Month', getValue: getThisMonth },
|
|
564
|
+
{ label: 'Last Month', getValue: getLastMonth },
|
|
565
|
+
{
|
|
566
|
+
label: 'Custom Logic',
|
|
567
|
+
getValue: () => {
|
|
568
|
+
// Your custom date calculation
|
|
569
|
+
const today = new Date();
|
|
570
|
+
const start = new Date(today.getFullYear(), today.getMonth(), 1);
|
|
571
|
+
return {
|
|
572
|
+
start: formatDate(start),
|
|
573
|
+
end: formatDate(today)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
];
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
```html
|
|
581
|
+
<ngx-dual-datepicker [presets]="customPresets"></ngx-dual-datepicker>
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Why This Is Powerful:**
|
|
585
|
+
- ✅ Perfect for dashboards: "Last 7 days", "Month to date"
|
|
586
|
+
- ✅ Perfect for reporting: "This quarter", "Last quarter"
|
|
587
|
+
- ✅ Perfect for financial systems: "Quarter to date", "Year to date"
|
|
588
|
+
- ✅ Perfect for analytics: Consistent date ranges for BI tools
|
|
589
|
+
|
|
590
|
+
### Date Adapter System
|
|
591
|
+
|
|
592
|
+
Use custom date libraries (DayJS, date-fns, Luxon) instead of native JavaScript `Date` objects.
|
|
593
|
+
|
|
594
|
+
#### Example: date-fns Adapter
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
import { Injectable } from '@angular/core';
|
|
598
|
+
import { DateAdapter } from '@oneluiz/dual-datepicker';
|
|
599
|
+
import { parse, format, addDays, isValid } from 'date-fns';
|
|
600
|
+
|
|
601
|
+
@Injectable()
|
|
602
|
+
export class DateFnsAdapter extends DateAdapter<Date> {
|
|
603
|
+
parse(value: any): Date | null {
|
|
604
|
+
if (!value) return null;
|
|
605
|
+
const parsed = parse(value, 'yyyy-MM-dd', new Date());
|
|
606
|
+
return isValid(parsed) ? parsed : null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
format(date: Date, formatStr: string = 'yyyy-MM-dd'): string {
|
|
610
|
+
return format(date, formatStr);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
addDays(date: Date, days: number): Date {
|
|
614
|
+
return addDays(date, days);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ... implement other required methods
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
#### Providing the Adapter
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
import { DATE_ADAPTER } from '@oneluiz/dual-datepicker';
|
|
625
|
+
|
|
626
|
+
@Component({
|
|
627
|
+
providers: [
|
|
628
|
+
{ provide: DATE_ADAPTER, useClass: DateFnsAdapter }
|
|
629
|
+
]
|
|
630
|
+
})
|
|
631
|
+
export class AppComponent {}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Benefits:**
|
|
635
|
+
- ✅ Zero vendor lock-in
|
|
636
|
+
- ✅ Use same date library across your app
|
|
637
|
+
- ✅ Adapt to custom backend formats
|
|
638
|
+
- ✅ Full TypeScript support
|
|
639
|
+
|
|
640
|
+
### Keyboard Navigation
|
|
641
|
+
|
|
642
|
+
**Full keyboard control** for accessibility (WCAG 2.1 AA compliant):
|
|
643
|
+
|
|
644
|
+
| Key(s) | Action |
|
|
645
|
+
|--------|--------|
|
|
646
|
+
| `←` / `→` | Navigate between days |
|
|
647
|
+
| `↑` / `↓` | Navigate by weeks |
|
|
648
|
+
| `Enter` / `Space` | Select focused day |
|
|
649
|
+
| `Escape` | Close datepicker |
|
|
650
|
+
| `Home` / `End` | Jump to first/last day |
|
|
651
|
+
| `PageUp` / `PageDown` | Navigate months |
|
|
652
|
+
| `Shift + PageUp/Down` | Navigate years |
|
|
653
|
+
| `Tab` | Navigate between input, presets, and calendar |
|
|
654
|
+
|
|
655
|
+
```html
|
|
656
|
+
<!-- Enabled by default -->
|
|
657
|
+
<ngx-dual-datepicker></ngx-dual-datepicker>
|
|
658
|
+
|
|
659
|
+
<!-- Disable if needed -->
|
|
660
|
+
<ngx-dual-datepicker [enableKeyboardNavigation]="false"></ngx-dual-datepicker>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## 🎨 Customization
|
|
666
|
+
|
|
667
|
+
### Theming System
|
|
668
|
+
|
|
669
|
+
The datepicker now supports 6 built-in themes: `default`, `bootstrap`, `bulma`, `foundation`, `tailwind`, and `custom`.
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
<ngx-dual-datepicker theme="bootstrap"></ngx-dual-datepicker>
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
```scss
|
|
676
|
+
// In your styles.scss
|
|
677
|
+
@import '@oneluiz/dual-datepicker/themes/bootstrap';
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Available Themes:**
|
|
681
|
+
- `default` - Original styling (no import needed)
|
|
682
|
+
- `bootstrap` - Bootstrap 5 compatible styling
|
|
683
|
+
- `bulma` - Bulma CSS compatible styling
|
|
684
|
+
- `foundation` - Foundation CSS compatible styling
|
|
685
|
+
- `tailwind` - Tailwind CSS compatible styling
|
|
686
|
+
- `custom` - CSS variables-based customization
|
|
687
|
+
|
|
688
|
+
**📚 For detailed theming documentation, see [THEMING.md](./THEMING.md)**
|
|
689
|
+
|
|
690
|
+
### Styling Options
|
|
691
|
+
|
|
692
|
+
```html
|
|
693
|
+
<ngx-dual-datepicker
|
|
694
|
+
inputBackgroundColor="#ffffff"
|
|
695
|
+
inputTextColor="#495057"
|
|
696
|
+
inputBorderColor="#ced4da"
|
|
697
|
+
inputBorderColorHover="#80bdff"
|
|
698
|
+
inputBorderColorFocus="#0d6efd"
|
|
699
|
+
inputPadding="0.375rem 0.75rem">
|
|
700
|
+
</ngx-dual-datepicker>
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
#### Pre-styled Examples
|
|
704
|
+
|
|
705
|
+
**Bootstrap Style:**
|
|
706
|
+
```html
|
|
707
|
+
<ngx-dual-datepicker
|
|
708
|
+
inputBackgroundColor="#ffffff"
|
|
709
|
+
inputBorderColor="#ced4da"
|
|
710
|
+
inputBorderColorFocus="#80bdff">
|
|
711
|
+
</ngx-dual-datepicker>
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**GitHub Style:**
|
|
715
|
+
```html
|
|
716
|
+
<ngx-dual-datepicker
|
|
717
|
+
inputBackgroundColor="#f3f4f6"
|
|
718
|
+
inputBorderColor="transparent"
|
|
719
|
+
inputBorderColorHover="#d1d5db">
|
|
720
|
+
</ngx-dual-datepicker>
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Localization (i18n)
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
import { LocaleConfig } from '@oneluiz/dual-datepicker';
|
|
727
|
+
|
|
728
|
+
spanishLocale: LocaleConfig = {
|
|
729
|
+
monthNames: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
|
730
|
+
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
|
|
731
|
+
monthNamesShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun',
|
|
732
|
+
'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
|
|
733
|
+
dayNames: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
|
|
734
|
+
dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']
|
|
735
|
+
};
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
```html
|
|
739
|
+
<ngx-dual-datepicker [locale]="spanishLocale"></ngx-dual-datepicker>
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## 📖 API Reference
|
|
745
|
+
|
|
746
|
+
### Inputs
|
|
747
|
+
|
|
748
|
+
| Property | Type | Default | Description |
|
|
749
|
+
|----------|------|---------|-------------|
|
|
750
|
+
| `ngModel` | `DateRange \| null` | `null` | Two-way binding for selected date range |
|
|
751
|
+
| `placeholder` | `string` | `'Select date range'` | Input placeholder text |
|
|
752
|
+
| `presets` | `PresetConfig[]` | `[]` | Array of preset configurations |
|
|
753
|
+
| `showPresets` | `boolean` | `true` | Show/hide the presets sidebar |
|
|
754
|
+
| `showClearButton` | `boolean` | `false` | Show/hide the Clear button |
|
|
755
|
+
| `closeOnSelection` | `boolean` | `false` | Close picker when both dates selected |
|
|
756
|
+
| `closeOnPresetSelection` | `boolean` | `false` | Close picker when preset clicked |
|
|
757
|
+
| `closeOnClickOutside` | `boolean` | `true` | Close picker when clicking outside |
|
|
758
|
+
| `multiRange` | `boolean` | `false` | Enable multi-range selection mode |
|
|
759
|
+
| `disabledDates` | `Date[] \| ((date: Date) => boolean)` | `undefined` | Array of dates or function to disable specific dates |
|
|
760
|
+
| `displayFormat` | `string` | `'D MMM'` | Format for displaying dates in input (tokens: YYYY, YY, MMMM, MMM, MM, M, DD, D) |
|
|
761
|
+
| `requireApply` | `boolean` | `false` | Require Apply button confirmation before emitting changes |
|
|
762
|
+
| `enableKeyboardNavigation` | `boolean` | `true` | Enable keyboard navigation |
|
|
763
|
+
| `inputBackgroundColor` | `string` | `'#fff'` | Input background color |
|
|
764
|
+
| `inputTextColor` | `string` | `'#495057'` | Input text color |
|
|
765
|
+
| `inputBorderColor` | `string` | `'#ced4da'` | Input border color |
|
|
766
|
+
| `inputBorderColorHover` | `string` | `'#9ca3af'` | Input border color on hover |
|
|
767
|
+
| `inputBorderColorFocus` | `string` | `'#80bdff'` | Input border color on focus |
|
|
768
|
+
| `inputPadding` | `string` | `'0.375rem 0.75rem'` | Input padding |
|
|
769
|
+
| `locale` | `LocaleConfig` | English defaults | Custom month/day names for i18n |
|
|
770
|
+
|
|
771
|
+
### Outputs
|
|
772
|
+
|
|
773
|
+
| Event | Type | Description |
|
|
774
|
+
|-------|------|-------------|
|
|
775
|
+
| `dateRangeChange` | `EventEmitter<DateRange>` | Emitted when date range changes |
|
|
776
|
+
| `dateRangeSelected` | `EventEmitter<DateRange>` | Emitted when both dates are selected |
|
|
777
|
+
| `multiDateRangeChange` | `EventEmitter<MultiDateRange>` | Emitted in multi-range mode |
|
|
778
|
+
| `multiDateRangeSelected` | `EventEmitter<MultiDateRange>` | Emitted when multi-range selection is complete |
|
|
779
|
+
|
|
780
|
+
### Public Methods
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
import { Component, ViewChild } from '@angular/core';
|
|
784
|
+
import { DualDatepickerComponent } from '@oneluiz/dual-datepicker';
|
|
785
|
+
|
|
786
|
+
@Component({
|
|
787
|
+
template: `
|
|
788
|
+
<ngx-dual-datepicker #datepicker></ngx-dual-datepicker>
|
|
789
|
+
<button (click)="clearSelection()">Clear</button>
|
|
790
|
+
`
|
|
791
|
+
})
|
|
792
|
+
export class MyComponent {
|
|
793
|
+
@ViewChild('datepicker') datepicker!: DualDatepickerComponent;
|
|
794
|
+
|
|
795
|
+
clearSelection() {
|
|
796
|
+
this.datepicker.clear();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
| Method | Description |
|
|
802
|
+
|--------|-------------|
|
|
803
|
+
| `clear()` | Clears current selection and resets component |
|
|
804
|
+
|
|
805
|
+
### Types
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
interface DateRange {
|
|
809
|
+
startDate: string; // ISO format: 'YYYY-MM-DD'
|
|
810
|
+
endDate: string; // ISO format: 'YYYY-MM-DD'
|
|
811
|
+
rangeText: string; // Display text: 'DD Mon - DD Mon'
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
interface MultiDateRange {
|
|
815
|
+
ranges: DateRange[]; // Array of selected date ranges
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
interface PresetRange {
|
|
819
|
+
start: string; // ISO format: 'YYYY-MM-DD'
|
|
820
|
+
end: string; // ISO format: 'YYYY-MM-DD'
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
interface PresetConfig {
|
|
824
|
+
label: string;
|
|
825
|
+
getValue: () => PresetRange;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
interface LocaleConfig {
|
|
829
|
+
monthNames?: string[]; // Full month names (12 items)
|
|
830
|
+
monthNamesShort?: string[]; // Short month names (12 items)
|
|
831
|
+
dayNames?: string[]; // Full day names (7 items, starting Sunday)
|
|
832
|
+
dayNamesShort?: string[]; // Short day names (7 items, starting Sunday)
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
## 💡 Usage Examples
|
|
839
|
+
|
|
840
|
+
### With Events
|
|
841
|
+
|
|
842
|
+
```typescript
|
|
843
|
+
@Component({
|
|
844
|
+
template: `
|
|
845
|
+
<ngx-dual-datepicker
|
|
846
|
+
(dateRangeSelected)="onDateRangeSelected($event)">
|
|
847
|
+
</ngx-dual-datepicker>
|
|
848
|
+
|
|
849
|
+
@if (selectedRange) {
|
|
850
|
+
<div>Selected: {{ selectedRange.rangeText }}</div>
|
|
851
|
+
}
|
|
852
|
+
`
|
|
853
|
+
})
|
|
854
|
+
export class ExampleComponent {
|
|
855
|
+
selectedRange: DateRange | null = null;
|
|
856
|
+
|
|
857
|
+
onDateRangeSelected(range: DateRange) {
|
|
858
|
+
this.selectedRange = range;
|
|
859
|
+
// Both dates selected - fetch data
|
|
860
|
+
this.fetchData(range.startDate, range.endDate);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
fetchData(start: string, end: string) {
|
|
864
|
+
// Dates are in 'YYYY-MM-DD' format
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
### With Angular Signals
|
|
870
|
+
|
|
871
|
+
```typescript
|
|
872
|
+
import { Component, signal, computed } from '@angular/core';
|
|
873
|
+
|
|
874
|
+
@Component({
|
|
875
|
+
template: `
|
|
876
|
+
<ngx-dual-datepicker
|
|
877
|
+
(dateRangeChange)="onDateChange($event)">
|
|
878
|
+
</ngx-dual-datepicker>
|
|
879
|
+
|
|
880
|
+
@if (isRangeSelected()) {
|
|
881
|
+
<div>
|
|
882
|
+
<p>{{ rangeText() }}</p>
|
|
883
|
+
<p>Days: {{ daysDifference() }}</p>
|
|
884
|
+
</div>
|
|
885
|
+
}
|
|
886
|
+
`
|
|
887
|
+
})
|
|
888
|
+
export class SignalsExample {
|
|
889
|
+
startDate = signal('');
|
|
890
|
+
endDate = signal('');
|
|
891
|
+
|
|
892
|
+
isRangeSelected = computed(() =>
|
|
893
|
+
this.startDate() !== '' && this.endDate() !== ''
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
rangeText = computed(() =>
|
|
897
|
+
`${this.startDate()} to ${this.endDate()}`
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
daysDifference = computed(() => {
|
|
901
|
+
if (!this.isRangeSelected()) return 0;
|
|
902
|
+
const start = new Date(this.startDate());
|
|
903
|
+
const end = new Date(this.endDate());
|
|
904
|
+
return Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
onDateChange(range: DateRange) {
|
|
908
|
+
this.startDate.set(range.startDate);
|
|
909
|
+
this.endDate.set(range.endDate);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### With Complete Customization
|
|
915
|
+
|
|
916
|
+
```html
|
|
917
|
+
<ngx-dual-datepicker
|
|
918
|
+
placeholder="Pick your dates"
|
|
919
|
+
[presets]="customPresets"
|
|
920
|
+
[showClearButton]="true"
|
|
921
|
+
[closeOnSelection]="true"
|
|
922
|
+
[locale]="spanishLocale"
|
|
923
|
+
inputBackgroundColor="#fef3c7"
|
|
924
|
+
inputTextColor="#92400e"
|
|
925
|
+
inputBorderColor="#fbbf24"
|
|
926
|
+
inputBorderColorFocus="#d97706"
|
|
927
|
+
inputPadding="12px 16px"
|
|
928
|
+
(dateRangeSelected)="onDateRangeSelected($event)">
|
|
929
|
+
</ngx-dual-datepicker>
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
---
|
|
933
|
+
|
|
934
|
+
## ♿ Accessibility
|
|
935
|
+
|
|
936
|
+
**WCAG 2.1 Level AA Compliant**
|
|
937
|
+
|
|
938
|
+
- ✅ Full keyboard navigation
|
|
939
|
+
- ✅ Screen reader support with ARIA labels
|
|
940
|
+
- ✅ Semantic HTML with proper `role` attributes
|
|
941
|
+
- ✅ Focus management with visual indicators
|
|
942
|
+
- ✅ High contrast support
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## 🛠️ Requirements
|
|
947
|
+
|
|
948
|
+
- **Angular:** 17.0.0 or higher
|
|
949
|
+
- **TypeScript:** 5.0+ (recommended)
|
|
950
|
+
|
|
951
|
+
---
|
|
952
|
+
|
|
953
|
+
## 📄 License & Support
|
|
954
|
+
|
|
955
|
+
**License:** MIT © Luis Cortes
|
|
956
|
+
|
|
957
|
+
**Issues:** [Report bugs](https://github.com/oneluiz/ng-dual-datepicker/issues)
|
|
958
|
+
|
|
959
|
+
**Star this project:** If you find it useful, please ⭐ the [GitHub repository](https://github.com/oneluiz/ng-dual-datepicker)!
|
|
960
|
+
|
|
961
|
+
---
|
|
962
|
+
|
|
963
|
+
Made with ❤️ by [Luis Cortes](https://github.com/oneluiz)
|
|
964
|
+
**
|
|
965
|
+
|
|
27
966
|
## Why ng-dual-datepicker?
|
|
28
967
|
|
|
29
968
|
| Feature | ng-dual-datepicker | Angular Material DateRangePicker |
|