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