@oneluiz/dual-datepicker 3.0.0 → 3.0.1

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/dist/README.md DELETED
@@ -1,925 +0,0 @@
1
- # ng-dual-datepicker
2
-
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
-
5
- [![npm version](https://img.shields.io/npm/v/@oneluiz/dual-datepicker)](https://www.npmjs.com/package/@oneluiz/dual-datepicker)
6
- ![license](https://img.shields.io/npm/l/@oneluiz/dual-datepicker)
7
- ![Angular](https://img.shields.io/badge/Angular-17%2B-red)
8
-
9
- ```bash
10
- npm install @oneluiz/dual-datepicker
11
- ```
12
-
13
- ## 🎯 [Live Demo](https://oneluiz.github.io/ng-dual-datepicker/)
14
-
15
- **[Check out the interactive examples →](https://oneluiz.github.io/ng-dual-datepicker/)**
16
-
17
- ## Why ng-dual-datepicker?
18
-
19
- | Feature | ng-dual-datepicker | Angular Material DateRangePicker |
20
- |---------|-------------------|----------------------------------|
21
- | **Bundle Size** | ~60 KB gzipped | ~300+ KB (with dependencies) |
22
- | **Dependencies** | Zero | Requires @angular/material, @angular/cdk |
23
- | **Standalone** | ✅ Native | ⚠️ Requires module setup |
24
- | **Signals Support** | ✅ Built-in | ❌ Not yet |
25
- | **Multi-Range Support** | ✅ NEW v2.7.0 | ❌ Not available |
26
- | **Customization** | Full styling control | Theme-constrained |
27
- | **Learning Curve** | Minimal | Requires Material knowledge |
28
- | **Change Detection** | OnPush optimized | Default |
29
- | **Setup Time** | < 1 minute | ~10+ minutes (theming, modules) |
30
-
31
- ## ✨ Key Features
32
-
33
- - 🪶 **Zero Dependencies** – No external libraries required
34
- - 🎯 **Standalone Component** – No NgModule imports needed
35
- - ⚡ **Angular Signals** – Modern reactive state management
36
- - 🔄 **Reactive Forms** – Full ControlValueAccessor implementation
37
- - 🔥 **Multi-Range Support** – Select multiple date ranges (NEW v2.7.0 - Material CAN'T do this!)
38
- - 🎨 **Fully Customizable** – Every color, padding, border configurable
39
- - 📦 **Lightweight** – ~60 KB gzipped total bundle
40
- - 🚀 **Performance** – OnPush change detection + trackBy optimization
41
- - ♿ **Accessible** – ARIA labels, semantic HTML, keyboard navigation (in progress)
42
- - 🌍 **i18n Ready** – Customizable month/day names
43
- - 📱 **Responsive** – Works on desktop and mobile
44
-
45
- ## 🤔 When Should I Use This?
46
-
47
- **Use ng-dual-datepicker if you:**
48
- - Don't want to install Angular Material just for a date picker
49
- - Need precise control over styling and behavior
50
- - Want minimal bundle size impact
51
- - Prefer standalone components over NgModules
52
- - Need Angular Signals support now
53
- - Are building a custom design system
54
-
55
- **Use Angular Material DateRangePicker if you:**
56
- - Already use Angular Material throughout your app
57
- - Need Material Design compliance
58
- - Want a battle-tested enterprise solution with extensive ecosystem support
59
-
60
- ## ⚡ Performance
61
-
62
- ```typescript
63
- @Component({
64
- changeDetection: ChangeDetectionStrategy.OnPush, // ✅ Optimized
65
- standalone: true // ✅ No module overhead
66
- })
67
- ```
68
-
69
- - **OnPush change detection** – Minimal re-renders
70
- - **trackBy functions** – Efficient list rendering
71
- - **No external CSS** – No runtime stylesheet downloads
72
- - **Tree-shakeable** – Only import what you use
73
-
74
- ## ♿ Accessibility (A11y)
75
-
76
- **Current Status:**
77
- - ✅ **Screen reader support** - ARIA labels included for all interactive elements
78
- - ✅ **Semantic HTML** - Proper HTML structure
79
- - 🚧 **Full keyboard navigation** - In active development (see [Roadmap](#-roadmap))
80
- - Mouse/touch interaction: ✅ Fully supported
81
- - Keyboard navigation: 🚧 In progress
82
-
83
- > **Note:** Full keyboard navigation support is planned and will be included in a future release. This includes arrow key navigation, Enter/Space selection, and Escape to close.
84
-
85
- ## 📦 Installation
86
-
87
- ```bash
88
- npm install @oneluiz/dual-datepicker
89
- ```
90
-
91
- ## 🚀 Quick Start
92
-
93
- ### Basic Usage
94
-
95
- ```typescript
96
- import { Component } from '@angular/core';
97
- import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
98
-
99
- @Component({
100
- selector: 'app-root',
101
- standalone: true,
102
- imports: [DualDatepickerComponent],
103
- template: `
104
- <ngx-dual-datepicker
105
- (dateRangeChange)="onRangeChange($event)">
106
- </ngx-dual-datepicker>
107
- `
108
- })
109
- export class AppComponent {
110
- onRangeChange(range: DateRange) {
111
- console.log('Start:', range.fechaInicio);
112
- console.log('End:', range.fechaFin);
113
- }
114
- }
115
- ```
116
-
117
- ### With Reactive Forms
118
-
119
- ```typescript
120
- import { FormControl } from '@angular/forms';
121
- import { DateRange } from '@oneluiz/dual-datepicker';
122
-
123
- dateRange = new FormControl<DateRange | null>(null);
124
- ```
125
-
126
- ```html
127
- <ngx-dual-datepicker [formControl]="dateRange"></ngx-dual-datepicker>
128
- ```
129
-
130
- ### With Angular Signals
131
-
132
- ```typescript
133
- import { signal } from '@angular/core';
134
-
135
- dateRange = signal<DateRange | null>(null);
136
- ```
137
-
138
- ```html
139
- <ngx-dual-datepicker
140
- [(ngModel)]="dateRange()"
141
- (dateRangeChange)="dateRange.set($event)">
142
- </ngx-dual-datepicker>
143
- ```
144
-
145
- ### Custom Styling
146
-
147
- ```html
148
- <ngx-dual-datepicker
149
- inputBackgroundColor="#1a1a2e"
150
- inputTextColor="#eee"
151
- inputBorderColor="#4a5568"
152
- inputBorderColorFocus="#3182ce">
153
- </ngx-dual-datepicker>
154
- ```
155
-
156
- ## 📚 Advanced Usage
157
- }
158
- ```
159
-
160
- ### 4. Use with Angular Signals ⚡ New!
161
-
162
- The component now uses Angular Signals internally for better performance and reactivity:
163
-
164
- ```typescript
165
- import { Component, signal, computed } from '@angular/core';
166
- import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
167
-
168
- @Component({
169
- selector: 'app-signals-example',
170
- standalone: true,
171
- imports: [DualDatepickerComponent],
172
- template: `
173
- <ngx-dual-datepicker
174
- [fechaInicio]="fechaInicio()"
175
- [fechaFin]="fechaFin()"
176
- (dateRangeChange)="onDateChange($event)">
177
- </ngx-dual-datepicker>
178
-
179
- @if (isRangeSelected()) {
180
- <div>
181
- <p>{{ rangeText() }}</p>
182
- <p>Days selected: {{ daysDifference() }}</p>
183
- </div>
184
- }
185
- `
186
- })
187
- export class SignalsExampleComponent {
188
- fechaInicio = signal('');
189
- fechaFin = signal('');
190
-
191
- // Computed values
192
- isRangeSelected = computed(() =>
193
- this.fechaInicio() !== '' && this.fechaFin() !== ''
194
- );
195
-
196
- rangeText = computed(() =>
197
- this.isRangeSelected()
198
- ? `${this.fechaInicio()} to ${this.fechaFin()}`
199
- : 'No range selected'
200
- );
201
-
202
- daysDifference = computed(() => {
203
- if (!this.isRangeSelected()) return 0;
204
- const start = new Date(this.fechaInicio());
205
- const end = new Date(this.fechaFin());
206
- return Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
207
- });
208
-
209
- onDateChange(range: DateRange) {
210
- this.fechaInicio.set(range.fechaInicio);
211
- this.fechaFin.set(range.fechaFin);
212
- }
213
- }
214
- ```
215
-
216
- ### 5. Multi-Range Support 🔥 NEW v2.7.0!
217
-
218
- **Material CAN'T do this!** Select multiple date ranges in a single picker - perfect for booking systems, blackout periods, and complex scheduling.
219
-
220
- ```typescript
221
- import { Component } from '@angular/core';
222
- import { DualDatepickerComponent, MultiDateRange } from '@oneluiz/dual-datepicker';
223
-
224
- @Component({
225
- selector: 'app-multi-range',
226
- standalone: true,
227
- imports: [DualDatepickerComponent],
228
- template: `
229
- <ngx-dual-datepicker
230
- [multiRange]="true"
231
- [showClearButton]="true"
232
- (multiDateRangeChange)="onMultiRangeChange($event)">
233
- </ngx-dual-datepicker>
234
-
235
- @if (selectedRanges && selectedRanges.ranges.length > 0) {
236
- <div class="selected-ranges">
237
- <h3>Selected Ranges ({{ selectedRanges.ranges.length }})</h3>
238
- @for (range of selectedRanges.ranges; track $index) {
239
- <div class="range-item">
240
- <strong>Range {{ $index + 1 }}:</strong> {{ range.rangoTexto }}
241
- <br>
242
- <span>{{ range.fechaInicio }} → {{ range.fechaFin }}</span>
243
- </div>
244
- }
245
- </div>
246
- }
247
- `
248
- })
249
- export class MultiRangeExample {
250
- selectedRanges: MultiDateRange | null = null;
251
-
252
- onMultiRangeChange(ranges: MultiDateRange) {
253
- this.selectedRanges = ranges;
254
- console.log('Selected ranges:', ranges.ranges);
255
- // Output example:
256
- // [
257
- // { fechaInicio: '2026-01-01', fechaFin: '2026-01-05', rangoTexto: 'Jan 1 – Jan 5' },
258
- // { fechaInicio: '2026-01-10', fechaFin: '2026-01-15', rangoTexto: 'Jan 10 – Jan 15' },
259
- // { fechaInicio: '2026-02-01', fechaFin: '2026-02-07', rangoTexto: 'Feb 1 – Feb 7' }
260
- // ]
261
- }
262
- }
263
- ```
264
-
265
- #### Perfect Use Cases
266
-
267
- - 🏨 **Hotel Booking Systems** - Block multiple periods for reservations
268
- - 📅 **Event Blackout Periods** - Mark multiple dates as unavailable
269
- - 🔧 **Maintenance Windows** - Schedule multiple maintenance periods
270
- - 📊 **Availability Calendars** - Show multiple available/unavailable periods
271
- - 👷 **Shift Scheduling** - Select multiple work periods
272
- - 💼 **Business Meetings** - Block out multiple date ranges
273
-
274
- #### Key Features
275
-
276
- - ✅ Select unlimited date ranges
277
- - ✅ Visual feedback - all ranges highlighted in calendar
278
- - ✅ Easy management - add/remove ranges with one click
279
- - ✅ Separate events for multi-range (`multiDateRangeChange`, `multiDateRangeSelected`)
280
- - ✅ Clear all ranges with one button
281
- - ❌ **Angular Material CANNOT do this!**
282
-
283
- ## 🔌 Date Adapter System
284
-
285
- The library supports custom date adapters, allowing you to use different date libraries (DayJS, date-fns, Luxon) or custom backend models instead of native JavaScript `Date` objects.
286
-
287
- ### Using Native Date (Default)
288
-
289
- By default, the component uses `NativeDateAdapter` which works with JavaScript `Date` objects:
290
-
291
- ```typescript
292
- import { DualDatepickerComponent } from '@oneluiz/dual-datepicker';
293
-
294
- @Component({
295
- standalone: true,
296
- imports: [DualDatepickerComponent],
297
- template: `<ngx-dual-datepicker></ngx-dual-datepicker>`
298
- })
299
- export class AppComponent {}
300
- ```
301
-
302
- ### Creating a Custom Adapter
303
-
304
- Example using **date-fns**:
305
-
306
- ```typescript
307
- import { Injectable } from '@angular/core';
308
- import { DateAdapter } from '@oneluiz/dual-datepicker';
309
- import {
310
- parse, format, addDays, addMonths,
311
- getYear, getMonth, getDate, getDay,
312
- isSameDay, isBefore, isAfter, isWithinInterval,
313
- isValid
314
- } from 'date-fns';
315
-
316
- @Injectable()
317
- export class DateFnsAdapter extends DateAdapter<Date> {
318
- parse(value: any): Date | null {
319
- if (!value) return null;
320
- if (value instanceof Date) return value;
321
-
322
- const parsed = parse(value, 'yyyy-MM-dd', new Date());
323
- return isValid(parsed) ? parsed : null;
324
- }
325
-
326
- format(date: Date, formatStr: string = 'yyyy-MM-dd'): string {
327
- return format(date, formatStr);
328
- }
329
-
330
- addDays(date: Date, days: number): Date {
331
- return addDays(date, days);
332
- }
333
-
334
- addMonths(date: Date, months: number): Date {
335
- return addMonths(date, months);
336
- }
337
-
338
- getYear(date: Date): number {
339
- return getYear(date);
340
- }
341
-
342
- getMonth(date: Date): number {
343
- return getMonth(date);
344
- }
345
-
346
- getDate(date: Date): number {
347
- return getDate(date);
348
- }
349
-
350
- getDay(date: Date): number {
351
- return getDay(date);
352
- }
353
-
354
- createDate(year: number, month: number, day: number): Date {
355
- return new Date(year, month, day);
356
- }
357
-
358
- today(): Date {
359
- return new Date();
360
- }
361
-
362
- isSameDay(a: Date | null, b: Date | null): boolean {
363
- if (!a || !b) return false;
364
- return isSameDay(a, b);
365
- }
366
-
367
- isBefore(a: Date | null, b: Date | null): boolean {
368
- if (!a || !b) return false;
369
- return isBefore(a, b);
370
- }
371
-
372
- isAfter(a: Date | null, b: Date | null): boolean {
373
- if (!a || !b) return false;
374
- return isAfter(a, b);
375
- }
376
-
377
- isBetween(date: Date | null, start: Date | null, end: Date | null): boolean {
378
- if (!date || !start || !end) return false;
379
- return isWithinInterval(date, { start, end });
380
- }
381
-
382
- clone(date: Date): Date {
383
- return new Date(date);
384
- }
385
-
386
- isValid(date: any): boolean {
387
- return isValid(date);
388
- }
389
- }
390
- ```
391
-
392
- ### Providing Custom Adapter
393
-
394
- ```typescript
395
- import { Component } from '@angular/core';
396
- import { DualDatepickerComponent, DATE_ADAPTER } from '@oneluiz/dual-datepicker';
397
- import { DateFnsAdapter } from './date-fns-adapter';
398
-
399
- @Component({
400
- standalone: true,
401
- imports: [DualDatepickerComponent],
402
- providers: [
403
- { provide: DATE_ADAPTER, useClass: DateFnsAdapter }
404
- ],
405
- template: `<ngx-dual-datepicker></ngx-dual-datepicker>`
406
- })
407
- export class AppComponent {}
408
- ```
409
-
410
- ### Example: DayJS Adapter
411
-
412
- ```typescript
413
- import { Injectable } from '@angular/core';
414
- import { DateAdapter } from '@oneluiz/dual-datepicker';
415
- import dayjs, { Dayjs } from 'dayjs';
416
-
417
- @Injectable()
418
- export class DayJSAdapter extends DateAdapter<Dayjs> {
419
- parse(value: any): Dayjs | null {
420
- if (!value) return null;
421
- const parsed = dayjs(value);
422
- return parsed.isValid() ? parsed : null;
423
- }
424
-
425
- format(date: Dayjs, format: string = 'YYYY-MM-DD'): string {
426
- return date.format(format);
427
- }
428
-
429
- addDays(date: Dayjs, days: number): Dayjs {
430
- return date.add(days, 'day');
431
- }
432
-
433
- addMonths(date: Dayjs, months: number): Dayjs {
434
- return date.add(months, 'month');
435
- }
436
-
437
- getYear(date: Dayjs): number {
438
- return date.year();
439
- }
440
-
441
- getMonth(date: Dayjs): number {
442
- return date.month();
443
- }
444
-
445
- getDate(date: Dayjs): number {
446
- return date.date();
447
- }
448
-
449
- getDay(date: Dayjs): number {
450
- return date.day();
451
- }
452
-
453
- createDate(year: number, month: number, day: number): Dayjs {
454
- return dayjs().year(year).month(month).date(day);
455
- }
456
-
457
- today(): Dayjs {
458
- return dayjs();
459
- }
460
-
461
- isSameDay(a: Dayjs | null, b: Dayjs | null): boolean {
462
- if (!a || !b) return false;
463
- return a.isSame(b, 'day');
464
- }
465
-
466
- isBefore(a: Dayjs | null, b: Dayjs | null): boolean {
467
- if (!a || !b) return false;
468
- return a.isBefore(b);
469
- }
470
-
471
- isAfter(a: Dayjs | null, b: Dayjs | null): boolean {
472
- if (!a || !b) return false;
473
- return a.isAfter(b);
474
- }
475
-
476
- isBetween(date: Dayjs | null, start: Dayjs | null, end: Dayjs | null): boolean {
477
- if (!date || !start || !end) return false;
478
- return date.isAfter(start) && date.isBefore(end) || date.isSame(start) || date.isSame(end);
479
- }
480
-
481
- clone(date: Dayjs): Dayjs {
482
- return date.clone();
483
- }
484
-
485
- isValid(date: any): boolean {
486
- return dayjs.isDayjs(date) && date.isValid();
487
- }
488
- }
489
- ```
490
-
491
- ### Benefits of Date Adapters
492
-
493
- - ✅ **Zero vendor lock-in** - Use any date library you prefer
494
- - ✅ **Consistency** - Use the same date library across your entire app
495
- - ✅ **Custom backend models** - Adapt to your API's date format
496
- - ✅ **Type safety** - Full TypeScript support with generics
497
-
498
- ## 🎨 Customization
499
-
500
- ### Custom Colors (Bootstrap Style)
501
-
502
- ```typescript
503
- <ngx-dual-datepicker
504
- [(ngModel)]="dateRange"
505
- inputBackgroundColor="#ffffff"
506
- inputTextColor="#495057"
507
- inputBorderColor="#ced4da"
508
- inputBorderColorHover="#80bdff"
509
- inputBorderColorFocus="#80bdff"
510
- inputPadding="0.375rem 0.75rem">
511
- </ngx-dual-datepicker>
512
- ```
513
-
514
- ### Custom Colors (GitHub Style)
515
-
516
- ```typescript
517
- <ngx-dual-datepicker
518
- [(ngModel)]="dateRange"
519
- inputBackgroundColor="#f3f4f6"
520
- inputTextColor="#24292e"
521
- inputBorderColor="transparent"
522
- inputBorderColorHover="#d1d5db"
523
- inputBorderColorFocus="#80bdff"
524
- inputPadding="6px 10px">
525
- </ngx-dual-datepicker>
526
- ```
527
-
528
- ### ⚡ Custom Presets (Power Feature)
529
-
530
- **This is where our library shines!** Unlike Angular Material, we offer an incredibly flexible preset system perfect for dashboards, reporting, POS, BI apps, and ERP systems.
531
-
532
- #### Simple Pattern (Backward Compatible)
533
-
534
- ```typescript
535
- customPresets: PresetConfig[] = [
536
- { label: 'Last 15 days', daysAgo: 15 },
537
- { label: 'Last 3 months', daysAgo: 90 },
538
- { label: 'Last 6 months', daysAgo: 180 },
539
- { label: 'Last year', daysAgo: 365 }
540
- ];
541
- ```
542
-
543
- #### **NEW v2.6.0** - Flexible Pattern with `getValue()` 🔥
544
-
545
- The real power comes with the `getValue()` pattern. Define **any custom logic** you need:
546
-
547
- ```typescript
548
- import { PresetConfig } from '@oneluiz/dual-datepicker';
549
-
550
- customPresets: PresetConfig[] = [
551
- {
552
- label: 'Today',
553
- getValue: () => {
554
- const today = new Date();
555
- return {
556
- start: formatDate(today),
557
- end: formatDate(today)
558
- };
559
- }
560
- },
561
- {
562
- label: 'This Month',
563
- getValue: () => {
564
- const today = new Date();
565
- const start = new Date(today.getFullYear(), today.getMonth(), 1);
566
- const end = new Date(today.getFullYear(), today.getMonth() + 1, 0);
567
- return {
568
- start: formatDate(start),
569
- end: formatDate(end)
570
- };
571
- }
572
- },
573
- {
574
- label: 'Last Month',
575
- getValue: () => {
576
- const today = new Date();
577
- const start = new Date(today.getFullYear(), today.getMonth() - 1, 1);
578
- const end = new Date(today.getFullYear(), today.getMonth(), 0);
579
- return {
580
- start: formatDate(start),
581
- end: formatDate(end)
582
- };
583
- }
584
- },
585
- {
586
- label: 'Quarter to Date',
587
- getValue: () => {
588
- const today = new Date();
589
- const currentMonth = today.getMonth();
590
- const quarterStartMonth = Math.floor(currentMonth / 3) * 3;
591
- const start = new Date(today.getFullYear(), quarterStartMonth, 1);
592
- return {
593
- start: formatDate(start),
594
- end: formatDate(today)
595
- };
596
- }
597
- }
598
- ];
599
- ```
600
-
601
- #### **Even Better** - Use Pre-built Utilities 🚀
602
-
603
- We provide **ready-to-use preset utilities** for common scenarios:
604
-
605
- ```typescript
606
- import { CommonPresets } from '@oneluiz/dual-datepicker';
607
-
608
- // Dashboard presets
609
- presets = CommonPresets.dashboard;
610
- // → Today, Yesterday, Last 7 days, Last 30 days, This month, Last month
611
-
612
- // Reporting presets
613
- presets = CommonPresets.reporting;
614
- // → Today, This week, Last week, This month, Last month, This quarter, Last quarter
615
-
616
- // Financial/ERP presets
617
- presets = CommonPresets.financial;
618
- // → Month to date, Quarter to date, Year to date, Last month, Last quarter, Last year
619
-
620
- // Analytics/BI presets
621
- presets = CommonPresets.analytics;
622
- // → Last 7/14/30/60/90/180/365 days
623
-
624
- // Simple presets
625
- presets = CommonPresets.simple;
626
- // → Today, Last 7 days, Last 30 days, This year
627
- ```
628
-
629
- #### Create Your Own Utilities
630
-
631
- Import individual utilities and mix them:
632
-
633
- ```typescript
634
- import {
635
- getToday,
636
- getThisMonth,
637
- getLastMonth,
638
- getQuarterToDate,
639
- getYearToDate,
640
- PresetConfig
641
- } from '@oneluiz/dual-datepicker';
642
-
643
- customPresets: PresetConfig[] = [
644
- { label: 'Today', getValue: getToday },
645
- { label: 'This Month', getValue: getThisMonth },
646
- { label: 'Last Month', getValue: getLastMonth },
647
- { label: 'Quarter to Date', getValue: getQuarterToDate },
648
- { label: 'Year to Date', getValue: getYearToDate },
649
- {
650
- label: 'Custom Logic',
651
- getValue: () => {
652
- // Your custom date calculation
653
- return { start: '2026-01-01', end: '2026-12-31' };
654
- }
655
- }
656
- ];
657
- ```
658
-
659
- #### Why This Is Powerful
660
-
661
- ✅ **Perfect for Dashboards** - "Last 7 days", "Month to date", "Quarter to date"
662
- ✅ **Perfect for Reporting** - "This week", "Last week", "This quarter"
663
- ✅ **Perfect for Financial Systems** - "Quarter to date", "Year to date", "Fiscal year"
664
- ✅ **Perfect for Analytics** - Consistent date ranges for BI tools
665
- ✅ **Perfect for ERP** - Custom business logic and fiscal calendars
666
-
667
- **Angular Material doesn't offer this level of flexibility!** 🎯
668
-
669
- ```html
670
- <ngx-dual-datepicker
671
- [(ngModel)]="dateRange"
672
- [presets]="customPresets">
673
- </ngx-dual-datepicker>
674
- ```
675
-
676
- ## 📖 API Reference
677
-
678
- ### Inputs
679
-
680
- | Property | Type | Default | Description |
681
- |----------|------|---------|-------------|
682
- | `ngModel` | `DateRange` | `{ start: null, end: null }` | Two-way binding for selected date range |
683
- | `placeholder` | `string` | `'Select date range'` | Input placeholder text |
684
- | `presets` | `PresetConfig[]` | Default presets | Array of preset configurations |
685
- | `showPresets` | `boolean` | `true` | Show/hide the presets sidebar |
686
- | `showClearButton` | `boolean` | `false` | Show/hide the Clear button in dropdown |
687
- | `closeOnSelection` | `boolean` | `false` | Close picker when both dates selected |
688
- | `closeOnPresetSelection` | `boolean` | `false` | Close picker when preset is clicked |
689
- | `closeOnClickOutside` | `boolean` | `true` | Close picker when clicking outside |
690
- | `inputBackgroundColor` | `string` | `'#fff'` | Input background color |
691
- | `inputTextColor` | `string` | `'#495057'` | Input text color |
692
- | `inputBorderColor` | `string` | `'#ced4da'` | Input border color |
693
- | `inputBorderColorHover` | `string` | `'#9ca3af'` | Input border color on hover |
694
- | `inputBorderColorFocus` | `string` | `'#80bdff'` | Input border color on focus |
695
- | `inputPadding` | `string` | `'0.375rem 0.75rem'` | Input padding |
696
- | `locale` | `LocaleConfig` | English defaults | Custom month/day names for i18n |
697
-
698
- ### Outputs
699
-
700
- | Event | Type | Description |
701
- |-------|------|-------------|
702
- | `ngModelChange` | `EventEmitter<DateRange>` | Emitted when date range changes |
703
-
704
- ### Public Methods
705
-
706
- You can call these methods programmatically using a template reference or ViewChild:
707
-
708
- ```typescript
709
- import { Component, ViewChild } from '@angular/core';
710
- import { DualDatepickerComponent } from '@oneluiz/dual-datepicker';
711
-
712
- @Component({
713
- template: `
714
- <div style="display: flex; gap: 10px;">
715
- <ngx-dual-datepicker #datepicker></ngx-dual-datepicker>
716
- <button (click)="clearSelection()">Clear</button>
717
- </div>
718
- `
719
- })
720
- export class MyComponent {
721
- @ViewChild('datepicker') datepicker!: DualDatepickerComponent;
722
-
723
- clearSelection() {
724
- this.datepicker.limpiar(); // Clears the date selection
725
- }
726
- }
727
- ```
728
-
729
- | Method | Description |
730
- |--------|-------------|
731
- | `limpiar()` | Clears the current date selection and resets the component |
732
-
733
- ### Types
734
-
735
- ```typescript
736
- interface DateRange {
737
- fechaInicio: string; // ISO date format: 'YYYY-MM-DD'
738
- fechaFin: string; // ISO date format: 'YYYY-MM-DD'
739
- rangoTexto: string; // Display text: 'DD Mon - DD Mon'
740
- }
741
-
742
- interface PresetRange {
743
- start: string; // ISO date format: 'YYYY-MM-DD'
744
- end: string; // ISO date format: 'YYYY-MM-DD'
745
- }
746
-
747
- interface PresetConfig {
748
- label: string;
749
- /** @deprecated Use getValue() instead for more flexibility */
750
- daysAgo?: number;
751
- /** NEW v2.6.0 - Function that returns date range with custom logic */
752
- getValue?: () => PresetRange;
753
- }
754
-
755
- interface LocaleConfig {
756
- monthNames?: string[]; // Full month names (12 items)
757
- monthNamesShort?: string[]; // Short month names (12 items)
758
- dayNames?: string[]; // Full day names (7 items, starting Sunday)
759
- dayNamesShort?: string[]; // Short day names (7 items, starting Sunday)
760
- firstDayOfWeek?: number; // 0 = Sunday, 1 = Monday, etc. (not yet implemented)
761
- }
762
- ```
763
-
764
- ### Default Presets
765
-
766
- ```typescript
767
- [
768
- { label: 'Last month', daysAgo: 30 },
769
- { label: 'Last 6 months', daysAgo: 180 },
770
- { label: 'Last yea
771
- [fechaInicio]="startDate"
772
- [fechaFin]="endDate"
773
- (dateRangeSelected)="onDateRangeSelected($event)">
774
- </ngx-dual-datepicker>
775
- ```
776
-
777
- ###fechaInicio]="startDate"
778
- [fechaFin]="endDate"
779
- [closeOnSelection]="true"
780
- [closeOnPresetSelection]="true"
781
- (dateRangeSelected)="onDateRangeSelected($event)
782
- spanishLocale: LocaleConfig = {
783
- monthNames: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
784
- 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
785
- monthNamesShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun',
786
- 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
787
- dayNames: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
788
- dayNamesShort: ['D', 'L', 'M', 'X', 'J', 'V', 'S']
789
- };
790
- ```
791
-
792
- ```html
793
- <ngx-dual-datepicker
794
- [fechaInicio]="startDate"
795
- [fechaFin]="endDate"
796
- [fechaInicio]="startDate"
797
- [fechaFin]="endDate"
798
- placeholder="Pick your dates"
799
- inputBackgroundColor="#fef3c7"
800
- inputTextColor="#92400e"
801
- inputBorderColor="#fbbf24"
802
- inputBorderColorHover="#f59e0b"
803
- inputBorderColorFocus="#d97706"
804
- inputPadding="8px 12px"
805
- (dateRangeSelected)="onDateRangeSelected($event)
806
-
807
- ### Minimal Usage
808
-
809
- ```html
810
- <ngx-dual-datepicker [(ngModel)]="dateRange"></ngx-dual-datepicker>
811
- ```
812
-
813
- ### With Auto-close
814
- fechaInicio]="startDate"
815
- [fechaFin]="endDate"
816
- (dateRangeSelected)="onDateRangeSelected($event)"
817
- (dateRangeChange)="onDateRangeChange($event)">
818
- </ngx-dual-datepicker>
819
-
820
- <div *ngIf="selectedRange">
821
- Selected: {{ selectedRange.rangoTexto }}
822
- </div>
823
- `
824
- })
825
- export class ExampleComponent {
826
- startDate: string = '';
827
- endDate: string = '';
828
- selectedRange: DateRange | null = null;
829
-
830
- onDateRangeChange(range: DateRange) {
831
- console.log('Date changed:', range.fechaInicio);
832
- // Emitted when user selects first date (before completing range)
833
- }
834
-
835
- onDateRangeSelected(range: DateRange) {
836
- console.log('Range selected:', range);
837
- this.selectedRange = range;
838
-
839
- // Both dates selected - do something
840
- this.fetchData(range.fechaInicio, range.fechaFin);
841
- }
842
-
843
- fetchData(startDate: string, endDate: string) {
844
- // Your API call here
845
- // Dates are in 'YYYY-MM-DD' format,
846
- template: `
847
- <ngx-dual-datepicker
848
- [(ngModel)]="dateRange"
849
- (ngModelChange)="onDateRangeChange($event)">
850
- </ngx-dual-datepicker>
851
-
852
- <div *ngIf="dateRange.start && dateRange.end">
853
- Selected: {{ formatDateRange() }}
854
- </div>
855
- `
856
- })
857
- export class ExampleComponent {
858
- dateRange: DateRange = { start: null, end: null };
859
-
860
- onDateRangeChange(range: DateRange) {
861
- console.log('Start:', range.start);
862
- console.log('End:', range.end);
863
-
864
- if (range.start && range.end) {
865
- // Both dates selected - do something
866
- this.fetchData(range.start, range.end);
867
- }
868
- }
869
-
870
- formatDateRange(): string {
871
- if (!this.dateRange.start || !this.dateRange.end) return '';
872
- return `${this.dateRange.start.toLocaleDateString()} - ${this.dateRange.end.toLocaleDateString()}`;
873
- }
874
-
875
- fetchData(start: Date, end: Date) {
876
- // Your API call here
877
- }
878
- }
879
- ```
880
-
881
- ## 🛠️ Requirements
882
-
883
- - Angular 17.0.0 or higher
884
- - Angular 18.0.0 or higher
885
- - Angular 19.0.0 or higher
886
- - Angular 20.0.0 or higher
887
-
888
- ## 🗺️ Roadmap
889
-
890
- Recently shipped:
891
-
892
- **v2.6.0:**
893
- - ✅ **Flexible Preset System** - `getValue()` pattern for custom date logic (This month, Last month, Quarter to date, etc.)
894
- - ✅ **Pre-built Preset Utilities** - CommonPresets for Dashboard, Reporting, Financial, Analytics
895
- - ✅ **Real Differentiator** - Perfect for ERP, BI, POS, and Reporting systems
896
-
897
- **v2.5.0:**
898
- - ✅ **Date Adapter System** - Support for DayJS, date-fns, Luxon, and custom date libraries
899
-
900
- Planned features and improvements:
901
-
902
- - ⬜ **Complete keyboard navigation** - Arrow keys, Enter/Space, Tab, Escape
903
- - ⬜ **Full accessibility audit** - WCAG 2.1 AA compliance
904
- - ⬜ **Multi-range support** - Select multiple date ranges
905
- - ⬜ **Theming system** - Pre-built theme presets
906
-
907
- ## 📄 License
908
-
909
- MIT © Luis Cortes
910
-
911
- ## 🤝 Contributing
912
-
913
- Contributions are welcome! Please feel free to submit a Pull Request.
914
-
915
- ## 🐛 Issues
916
-
917
- Found a bug? Please [open an issue](https://github.com/oneluiz/ng-dual-datepicker/issues).
918
-
919
- ## ⭐ Support
920
-
921
- If you find this package useful, please give it a star on [GitHub](https://github.com/oneluiz/ng-dual-datepicker)!
922
-
923
- ---
924
-
925
- Made with ❤️ by [Luis Cortes](https://github.com/oneluiz)