@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 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,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 |