@oneluiz/dual-datepicker 2.3.0 → 2.5.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 +386 -38
- package/date-adapter.d.ts +116 -0
- package/dual-datepicker.component.d.ts +21 -8
- package/esm2022/date-adapter.mjs +12 -0
- package/esm2022/dual-datepicker.component.mjs +141 -54
- package/esm2022/native-date-adapter.mjs +112 -0
- package/esm2022/public-api.mjs +4 -1
- package/fesm2022/oneluiz-dual-datepicker.mjs +259 -54
- package/fesm2022/oneluiz-dual-datepicker.mjs.map +1 -1
- package/native-date-adapter.d.ts +26 -0
- package/package.json +6 -3
- package/public-api.d.ts +2 -0
package/README.md
CHANGED
|
@@ -1,28 +1,84 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ng-dual-datepicker
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight, zero-dependency date range picker for Angular 17+. Built with standalone components, Reactive Forms, and Angular Signals. No Angular Material required.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@oneluiz/dual-datepicker)
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
+
```bash
|
|
10
|
+
npm install @oneluiz/dual-datepicker
|
|
11
|
+
```
|
|
12
|
+
|
|
9
13
|
## 🎯 [Live Demo](https://oneluiz.github.io/ng-dual-datepicker/)
|
|
10
14
|
|
|
11
15
|
**[Check out the interactive examples →](https://oneluiz.github.io/ng-dual-datepicker/)**
|
|
12
16
|
|
|
13
|
-
##
|
|
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
|
+
| **Customization** | Full styling control | Theme-constrained |
|
|
26
|
+
| **Learning Curve** | Minimal | Requires Material knowledge |
|
|
27
|
+
| **Change Detection** | OnPush optimized | Default |
|
|
28
|
+
| **Setup Time** | < 1 minute | ~10+ minutes (theming, modules) |
|
|
29
|
+
|
|
30
|
+
## ✨ Key Features
|
|
31
|
+
|
|
32
|
+
- 🪶 **Zero Dependencies** – No external libraries required
|
|
33
|
+
- 🎯 **Standalone Component** – No NgModule imports needed
|
|
34
|
+
- ⚡ **Angular Signals** – Modern reactive state management
|
|
35
|
+
- 🔄 **Reactive Forms** – Full ControlValueAccessor implementation
|
|
36
|
+
- 🎨 **Fully Customizable** – Every color, padding, border configurable
|
|
37
|
+
- 📦 **Lightweight** – ~60 KB gzipped total bundle
|
|
38
|
+
- 🚀 **Performance** – OnPush change detection + trackBy optimization
|
|
39
|
+
- ♿ **Accessible** – ARIA labels, semantic HTML, keyboard navigation (in progress)
|
|
40
|
+
- 🌍 **i18n Ready** – Customizable month/day names
|
|
41
|
+
- 📱 **Responsive** – Works on desktop and mobile
|
|
42
|
+
|
|
43
|
+
## 🤔 When Should I Use This?
|
|
44
|
+
|
|
45
|
+
**Use ng-dual-datepicker if you:**
|
|
46
|
+
- Don't want to install Angular Material just for a date picker
|
|
47
|
+
- Need precise control over styling and behavior
|
|
48
|
+
- Want minimal bundle size impact
|
|
49
|
+
- Prefer standalone components over NgModules
|
|
50
|
+
- Need Angular Signals support now
|
|
51
|
+
- Are building a custom design system
|
|
52
|
+
|
|
53
|
+
**Use Angular Material DateRangePicker if you:**
|
|
54
|
+
- Already use Angular Material throughout your app
|
|
55
|
+
- Need Material Design compliance
|
|
56
|
+
- Want a battle-tested enterprise solution with extensive ecosystem support
|
|
57
|
+
|
|
58
|
+
## ⚡ Performance
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
@Component({
|
|
62
|
+
changeDetection: ChangeDetectionStrategy.OnPush, // ✅ Optimized
|
|
63
|
+
standalone: true // ✅ No module overhead
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- **OnPush change detection** – Minimal re-renders
|
|
68
|
+
- **trackBy functions** – Efficient list rendering
|
|
69
|
+
- **No external CSS** – No runtime stylesheet downloads
|
|
70
|
+
- **Tree-shakeable** – Only import what you use
|
|
14
71
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- 🎭 **Flexible Behavior** - Control when the picker closes
|
|
72
|
+
## ♿ Accessibility (A11y)
|
|
73
|
+
|
|
74
|
+
**Current Status:**
|
|
75
|
+
- ✅ **Screen reader support** - ARIA labels included for all interactive elements
|
|
76
|
+
- ✅ **Semantic HTML** - Proper HTML structure
|
|
77
|
+
- 🚧 **Full keyboard navigation** - In active development (see [Roadmap](#-roadmap))
|
|
78
|
+
- Mouse/touch interaction: ✅ Fully supported
|
|
79
|
+
- Keyboard navigation: 🚧 In progress
|
|
80
|
+
|
|
81
|
+
> **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.
|
|
26
82
|
|
|
27
83
|
## 📦 Installation
|
|
28
84
|
|
|
@@ -32,7 +88,7 @@ npm install @oneluiz/dual-datepicker
|
|
|
32
88
|
|
|
33
89
|
## 🚀 Quick Start
|
|
34
90
|
|
|
35
|
-
###
|
|
91
|
+
### Basic Usage
|
|
36
92
|
|
|
37
93
|
```typescript
|
|
38
94
|
import { Component } from '@angular/core';
|
|
@@ -44,50 +100,332 @@ import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
|
|
|
44
100
|
imports: [DualDatepickerComponent],
|
|
45
101
|
template: `
|
|
46
102
|
<ngx-dual-datepicker
|
|
47
|
-
|
|
48
|
-
placeholder="Select date range">
|
|
103
|
+
(dateRangeChange)="onRangeChange($event)">
|
|
49
104
|
</ngx-dual-datepicker>
|
|
50
105
|
`
|
|
51
106
|
})
|
|
52
107
|
export class AppComponent {
|
|
53
|
-
|
|
108
|
+
onRangeChange(range: DateRange) {
|
|
109
|
+
console.log('Start:', range.fechaInicio);
|
|
110
|
+
console.log('End:', range.fechaFin);
|
|
111
|
+
}
|
|
54
112
|
}
|
|
55
113
|
```
|
|
56
114
|
|
|
57
|
-
###
|
|
115
|
+
### With Reactive Forms
|
|
58
116
|
|
|
59
117
|
```typescript
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
|
|
118
|
+
import { FormControl } from '@angular/forms';
|
|
119
|
+
import { DateRange } from '@oneluiz/dual-datepicker';
|
|
120
|
+
|
|
121
|
+
dateRange = new FormControl<DateRange | null>(null);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<ngx-dual-datepicker [formControl]="dateRange"></ngx-dual-datepicker>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### With Angular Signals
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { signal } from '@angular/core';
|
|
132
|
+
|
|
133
|
+
dateRange = signal<DateRange | null>(null);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<ngx-dual-datepicker
|
|
138
|
+
[(ngModel)]="dateRange()"
|
|
139
|
+
(dateRangeChange)="dateRange.set($event)">
|
|
140
|
+
</ngx-dual-datepicker>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom Styling
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<ngx-dual-datepicker
|
|
147
|
+
inputBackgroundColor="#1a1a2e"
|
|
148
|
+
inputTextColor="#eee"
|
|
149
|
+
inputBorderColor="#4a5568"
|
|
150
|
+
inputBorderColorFocus="#3182ce">
|
|
151
|
+
</ngx-dual-datepicker>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 📚 Advanced Usage
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 4. Use with Angular Signals ⚡ New!
|
|
159
|
+
|
|
160
|
+
The component now uses Angular Signals internally for better performance and reactivity:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { Component, signal, computed } from '@angular/core';
|
|
164
|
+
import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
|
|
63
165
|
|
|
64
166
|
@Component({
|
|
65
|
-
selector: 'app-example',
|
|
167
|
+
selector: 'app-signals-example',
|
|
66
168
|
standalone: true,
|
|
67
|
-
imports: [
|
|
169
|
+
imports: [DualDatepickerComponent],
|
|
68
170
|
template: `
|
|
69
171
|
<ngx-dual-datepicker
|
|
70
|
-
[
|
|
71
|
-
[
|
|
72
|
-
(
|
|
172
|
+
[fechaInicio]="fechaInicio()"
|
|
173
|
+
[fechaFin]="fechaFin()"
|
|
174
|
+
(dateRangeChange)="onDateChange($event)">
|
|
73
175
|
</ngx-dual-datepicker>
|
|
176
|
+
|
|
177
|
+
@if (isRangeSelected()) {
|
|
178
|
+
<div>
|
|
179
|
+
<p>{{ rangeText() }}</p>
|
|
180
|
+
<p>Days selected: {{ daysDifference() }}</p>
|
|
181
|
+
</div>
|
|
182
|
+
}
|
|
74
183
|
`
|
|
75
184
|
})
|
|
76
|
-
export class
|
|
77
|
-
|
|
185
|
+
export class SignalsExampleComponent {
|
|
186
|
+
fechaInicio = signal('');
|
|
187
|
+
fechaFin = signal('');
|
|
188
|
+
|
|
189
|
+
// Computed values
|
|
190
|
+
isRangeSelected = computed(() =>
|
|
191
|
+
this.fechaInicio() !== '' && this.fechaFin() !== ''
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
rangeText = computed(() =>
|
|
195
|
+
this.isRangeSelected()
|
|
196
|
+
? `${this.fechaInicio()} to ${this.fechaFin()}`
|
|
197
|
+
: 'No range selected'
|
|
198
|
+
);
|
|
78
199
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
200
|
+
daysDifference = computed(() => {
|
|
201
|
+
if (!this.isRangeSelected()) return 0;
|
|
202
|
+
const start = new Date(this.fechaInicio());
|
|
203
|
+
const end = new Date(this.fechaFin());
|
|
204
|
+
return Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
onDateChange(range: DateRange) {
|
|
208
|
+
this.fechaInicio.set(range.fechaInicio);
|
|
209
|
+
this.fechaFin.set(range.fechaFin);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## 🔌 Date Adapter System
|
|
215
|
+
|
|
216
|
+
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.
|
|
217
|
+
|
|
218
|
+
### Using Native Date (Default)
|
|
84
219
|
|
|
85
|
-
|
|
86
|
-
|
|
220
|
+
By default, the component uses `NativeDateAdapter` which works with JavaScript `Date` objects:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { DualDatepickerComponent } from '@oneluiz/dual-datepicker';
|
|
224
|
+
|
|
225
|
+
@Component({
|
|
226
|
+
standalone: true,
|
|
227
|
+
imports: [DualDatepickerComponent],
|
|
228
|
+
template: `<ngx-dual-datepicker></ngx-dual-datepicker>`
|
|
229
|
+
})
|
|
230
|
+
export class AppComponent {}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Creating a Custom Adapter
|
|
234
|
+
|
|
235
|
+
Example using **date-fns**:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { Injectable } from '@angular/core';
|
|
239
|
+
import { DateAdapter } from '@oneluiz/dual-datepicker';
|
|
240
|
+
import {
|
|
241
|
+
parse, format, addDays, addMonths,
|
|
242
|
+
getYear, getMonth, getDate, getDay,
|
|
243
|
+
isSameDay, isBefore, isAfter, isWithinInterval,
|
|
244
|
+
isValid
|
|
245
|
+
} from 'date-fns';
|
|
246
|
+
|
|
247
|
+
@Injectable()
|
|
248
|
+
export class DateFnsAdapter extends DateAdapter<Date> {
|
|
249
|
+
parse(value: any): Date | null {
|
|
250
|
+
if (!value) return null;
|
|
251
|
+
if (value instanceof Date) return value;
|
|
252
|
+
|
|
253
|
+
const parsed = parse(value, 'yyyy-MM-dd', new Date());
|
|
254
|
+
return isValid(parsed) ? parsed : null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
format(date: Date, formatStr: string = 'yyyy-MM-dd'): string {
|
|
258
|
+
return format(date, formatStr);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
addDays(date: Date, days: number): Date {
|
|
262
|
+
return addDays(date, days);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
addMonths(date: Date, months: number): Date {
|
|
266
|
+
return addMonths(date, months);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
getYear(date: Date): number {
|
|
270
|
+
return getYear(date);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
getMonth(date: Date): number {
|
|
274
|
+
return getMonth(date);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
getDate(date: Date): number {
|
|
278
|
+
return getDate(date);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getDay(date: Date): number {
|
|
282
|
+
return getDay(date);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
createDate(year: number, month: number, day: number): Date {
|
|
286
|
+
return new Date(year, month, day);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
today(): Date {
|
|
290
|
+
return new Date();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
isSameDay(a: Date | null, b: Date | null): boolean {
|
|
294
|
+
if (!a || !b) return false;
|
|
295
|
+
return isSameDay(a, b);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
isBefore(a: Date | null, b: Date | null): boolean {
|
|
299
|
+
if (!a || !b) return false;
|
|
300
|
+
return isBefore(a, b);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
isAfter(a: Date | null, b: Date | null): boolean {
|
|
304
|
+
if (!a || !b) return false;
|
|
305
|
+
return isAfter(a, b);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
isBetween(date: Date | null, start: Date | null, end: Date | null): boolean {
|
|
309
|
+
if (!date || !start || !end) return false;
|
|
310
|
+
return isWithinInterval(date, { start, end });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
clone(date: Date): Date {
|
|
314
|
+
return new Date(date);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
isValid(date: any): boolean {
|
|
318
|
+
return isValid(date);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Providing Custom Adapter
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { Component } from '@angular/core';
|
|
327
|
+
import { DualDatepickerComponent, DATE_ADAPTER } from '@oneluiz/dual-datepicker';
|
|
328
|
+
import { DateFnsAdapter } from './date-fns-adapter';
|
|
329
|
+
|
|
330
|
+
@Component({
|
|
331
|
+
standalone: true,
|
|
332
|
+
imports: [DualDatepickerComponent],
|
|
333
|
+
providers: [
|
|
334
|
+
{ provide: DATE_ADAPTER, useClass: DateFnsAdapter }
|
|
335
|
+
],
|
|
336
|
+
template: `<ngx-dual-datepicker></ngx-dual-datepicker>`
|
|
337
|
+
})
|
|
338
|
+
export class AppComponent {}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Example: DayJS Adapter
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { Injectable } from '@angular/core';
|
|
345
|
+
import { DateAdapter } from '@oneluiz/dual-datepicker';
|
|
346
|
+
import dayjs, { Dayjs } from 'dayjs';
|
|
347
|
+
|
|
348
|
+
@Injectable()
|
|
349
|
+
export class DayJSAdapter extends DateAdapter<Dayjs> {
|
|
350
|
+
parse(value: any): Dayjs | null {
|
|
351
|
+
if (!value) return null;
|
|
352
|
+
const parsed = dayjs(value);
|
|
353
|
+
return parsed.isValid() ? parsed : null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
format(date: Dayjs, format: string = 'YYYY-MM-DD'): string {
|
|
357
|
+
return date.format(format);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
addDays(date: Dayjs, days: number): Dayjs {
|
|
361
|
+
return date.add(days, 'day');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
addMonths(date: Dayjs, months: number): Dayjs {
|
|
365
|
+
return date.add(months, 'month');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
getYear(date: Dayjs): number {
|
|
369
|
+
return date.year();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
getMonth(date: Dayjs): number {
|
|
373
|
+
return date.month();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
getDate(date: Dayjs): number {
|
|
377
|
+
return date.date();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
getDay(date: Dayjs): number {
|
|
381
|
+
return date.day();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
createDate(year: number, month: number, day: number): Dayjs {
|
|
385
|
+
return dayjs().year(year).month(month).date(day);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
today(): Dayjs {
|
|
389
|
+
return dayjs();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
isSameDay(a: Dayjs | null, b: Dayjs | null): boolean {
|
|
393
|
+
if (!a || !b) return false;
|
|
394
|
+
return a.isSame(b, 'day');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
isBefore(a: Dayjs | null, b: Dayjs | null): boolean {
|
|
398
|
+
if (!a || !b) return false;
|
|
399
|
+
return a.isBefore(b);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
isAfter(a: Dayjs | null, b: Dayjs | null): boolean {
|
|
403
|
+
if (!a || !b) return false;
|
|
404
|
+
return a.isAfter(b);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
isBetween(date: Dayjs | null, start: Dayjs | null, end: Dayjs | null): boolean {
|
|
408
|
+
if (!date || !start || !end) return false;
|
|
409
|
+
return date.isAfter(start) && date.isBefore(end) || date.isSame(start) || date.isSame(end);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
clone(date: Dayjs): Dayjs {
|
|
413
|
+
return date.clone();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
isValid(date: any): boolean {
|
|
417
|
+
return dayjs.isDayjs(date) && date.isValid();
|
|
87
418
|
}
|
|
88
419
|
}
|
|
89
420
|
```
|
|
90
421
|
|
|
422
|
+
### Benefits of Date Adapters
|
|
423
|
+
|
|
424
|
+
- ✅ **Zero vendor lock-in** - Use any date library you prefer
|
|
425
|
+
- ✅ **Consistency** - Use the same date library across your entire app
|
|
426
|
+
- ✅ **Custom backend models** - Adapt to your API's date format
|
|
427
|
+
- ✅ **Type safety** - Full TypeScript support with generics
|
|
428
|
+
|
|
91
429
|
## 🎨 Customization
|
|
92
430
|
|
|
93
431
|
### Custom Colors (Bootstrap Style)
|
|
@@ -146,7 +484,7 @@ customPresets: PresetConfig[] = [
|
|
|
146
484
|
| `placeholder` | `string` | `'Select date range'` | Input placeholder text |
|
|
147
485
|
| `presets` | `PresetConfig[]` | Default presets | Array of preset configurations |
|
|
148
486
|
| `showPresets` | `boolean` | `true` | Show/hide the presets sidebar |
|
|
149
|
-
| `showClearButton` | `boolean` | `
|
|
487
|
+
| `showClearButton` | `boolean` | `false` | Show/hide the Clear button in dropdown |
|
|
150
488
|
| `closeOnSelection` | `boolean` | `false` | Close picker when both dates selected |
|
|
151
489
|
| `closeOnPresetSelection` | `boolean` | `false` | Close picker when preset is clicked |
|
|
152
490
|
| `closeOnClickOutside` | `boolean` | `true` | Close picker when clicking outside |
|
|
@@ -340,7 +678,17 @@ export class ExampleComponent {
|
|
|
340
678
|
- Angular 19.0.0 or higher
|
|
341
679
|
- Angular 20.0.0 or higher
|
|
342
680
|
|
|
343
|
-
##
|
|
681
|
+
## �️ Roadmap
|
|
682
|
+
|
|
683
|
+
Planned features and improvements:
|
|
684
|
+
|
|
685
|
+
- ⬜ **Complete keyboard navigation** - Arrow keys, Enter/Space, Tab, Escape
|
|
686
|
+
- ⬜ **Full accessibility audit** - WCAG 2.1 AA compliance
|
|
687
|
+
- ⬜ **Presets improvements** - More flexible preset configurations
|
|
688
|
+
- ⬜ **Multi-range support** - Select multiple date ranges
|
|
689
|
+
- ⬜ **Theming system** - Pre-built theme presets
|
|
690
|
+
|
|
691
|
+
## �📄 License
|
|
344
692
|
|
|
345
693
|
MIT © Luis Cortes
|
|
346
694
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract class for date adapters.
|
|
4
|
+
* Allows the component to work with different date libraries (Date, DayJS, date-fns, Luxon, etc.)
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class DateAdapter<T = any> {
|
|
7
|
+
/**
|
|
8
|
+
* Parses a value into a date object
|
|
9
|
+
* @param value - Value to parse (string, number, or date object)
|
|
10
|
+
* @returns Parsed date object
|
|
11
|
+
*/
|
|
12
|
+
abstract parse(value: any): T | null;
|
|
13
|
+
/**
|
|
14
|
+
* Formats a date object into a string
|
|
15
|
+
* @param date - Date object to format
|
|
16
|
+
* @param format - Optional format string
|
|
17
|
+
* @returns Formatted date string
|
|
18
|
+
*/
|
|
19
|
+
abstract format(date: T, format?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Adds days to a date
|
|
22
|
+
* @param date - Date object
|
|
23
|
+
* @param days - Number of days to add (can be negative)
|
|
24
|
+
* @returns New date object
|
|
25
|
+
*/
|
|
26
|
+
abstract addDays(date: T, days: number): T;
|
|
27
|
+
/**
|
|
28
|
+
* Adds months to a date
|
|
29
|
+
* @param date - Date object
|
|
30
|
+
* @param months - Number of months to add (can be negative)
|
|
31
|
+
* @returns New date object
|
|
32
|
+
*/
|
|
33
|
+
abstract addMonths(date: T, months: number): T;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the year from a date
|
|
36
|
+
* @param date - Date object
|
|
37
|
+
* @returns Year number
|
|
38
|
+
*/
|
|
39
|
+
abstract getYear(date: T): number;
|
|
40
|
+
/**
|
|
41
|
+
* Gets the month from a date (0-11)
|
|
42
|
+
* @param date - Date object
|
|
43
|
+
* @returns Month number (0-11)
|
|
44
|
+
*/
|
|
45
|
+
abstract getMonth(date: T): number;
|
|
46
|
+
/**
|
|
47
|
+
* Gets the day of month from a date
|
|
48
|
+
* @param date - Date object
|
|
49
|
+
* @returns Day of month (1-31)
|
|
50
|
+
*/
|
|
51
|
+
abstract getDate(date: T): number;
|
|
52
|
+
/**
|
|
53
|
+
* Gets the day of week from a date (0-6, Sunday = 0)
|
|
54
|
+
* @param date - Date object
|
|
55
|
+
* @returns Day of week (0-6)
|
|
56
|
+
*/
|
|
57
|
+
abstract getDay(date: T): number;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new date object
|
|
60
|
+
* @param year - Year
|
|
61
|
+
* @param month - Month (0-11)
|
|
62
|
+
* @param date - Day of month (1-31)
|
|
63
|
+
* @returns New date object
|
|
64
|
+
*/
|
|
65
|
+
abstract createDate(year: number, month: number, date: number): T;
|
|
66
|
+
/**
|
|
67
|
+
* Gets today's date
|
|
68
|
+
* @returns Today's date object
|
|
69
|
+
*/
|
|
70
|
+
abstract today(): T;
|
|
71
|
+
/**
|
|
72
|
+
* Checks if two dates are the same day
|
|
73
|
+
* @param a - First date
|
|
74
|
+
* @param b - Second date
|
|
75
|
+
* @returns True if same day
|
|
76
|
+
*/
|
|
77
|
+
abstract isSameDay(a: T | null, b: T | null): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Checks if a date is before another
|
|
80
|
+
* @param a - First date
|
|
81
|
+
* @param b - Second date
|
|
82
|
+
* @returns True if a is before b
|
|
83
|
+
*/
|
|
84
|
+
abstract isBefore(a: T | null, b: T | null): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Checks if a date is after another
|
|
87
|
+
* @param a - First date
|
|
88
|
+
* @param b - Second date
|
|
89
|
+
* @returns True if a is after b
|
|
90
|
+
*/
|
|
91
|
+
abstract isAfter(a: T | null, b: T | null): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Checks if a date is between two other dates
|
|
94
|
+
* @param date - Date to check
|
|
95
|
+
* @param start - Start date
|
|
96
|
+
* @param end - End date
|
|
97
|
+
* @returns True if date is between start and end
|
|
98
|
+
*/
|
|
99
|
+
abstract isBetween(date: T | null, start: T | null, end: T | null): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Clones a date object
|
|
102
|
+
* @param date - Date to clone
|
|
103
|
+
* @returns Cloned date object
|
|
104
|
+
*/
|
|
105
|
+
abstract clone(date: T): T;
|
|
106
|
+
/**
|
|
107
|
+
* Checks if a value is a valid date
|
|
108
|
+
* @param date - Value to check
|
|
109
|
+
* @returns True if valid date
|
|
110
|
+
*/
|
|
111
|
+
abstract isValid(date: any): boolean;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Injection token for DateAdapter
|
|
115
|
+
*/
|
|
116
|
+
export declare const DATE_ADAPTER: InjectionToken<DateAdapter<any>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter, OnInit, OnChanges, SimpleChanges, ElementRef } from '@angular/core';
|
|
2
|
+
import { ControlValueAccessor } from '@angular/forms';
|
|
2
3
|
import * as i0 from "@angular/core";
|
|
3
4
|
export interface DateRange {
|
|
4
5
|
fechaInicio: string;
|
|
@@ -16,7 +17,7 @@ export interface LocaleConfig {
|
|
|
16
17
|
dayNamesShort?: string[];
|
|
17
18
|
firstDayOfWeek?: number;
|
|
18
19
|
}
|
|
19
|
-
export declare class DualDatepickerComponent implements OnInit, OnChanges {
|
|
20
|
+
export declare class DualDatepickerComponent implements OnInit, OnChanges, ControlValueAccessor {
|
|
20
21
|
private elementRef;
|
|
21
22
|
placeholder: string;
|
|
22
23
|
fechaInicio: string;
|
|
@@ -36,17 +37,24 @@ export declare class DualDatepickerComponent implements OnInit, OnChanges {
|
|
|
36
37
|
locale: LocaleConfig;
|
|
37
38
|
dateRangeChange: EventEmitter<DateRange>;
|
|
38
39
|
dateRangeSelected: EventEmitter<DateRange>;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
private dateAdapter;
|
|
41
|
+
mostrarDatePicker: import("@angular/core").WritableSignal<boolean>;
|
|
42
|
+
rangoFechas: import("@angular/core").WritableSignal<string>;
|
|
43
|
+
fechaSeleccionandoInicio: import("@angular/core").WritableSignal<boolean>;
|
|
44
|
+
mesActual: import("@angular/core").WritableSignal<any>;
|
|
45
|
+
mesAnterior: import("@angular/core").WritableSignal<any>;
|
|
46
|
+
diasMesActual: import("@angular/core").WritableSignal<any[]>;
|
|
47
|
+
diasMesAnterior: import("@angular/core").WritableSignal<any[]>;
|
|
48
|
+
isDisabled: import("@angular/core").WritableSignal<boolean>;
|
|
49
|
+
nombreMesActual: import("@angular/core").Signal<string>;
|
|
50
|
+
nombreMesAnterior: import("@angular/core").Signal<string>;
|
|
51
|
+
diasSemana: import("@angular/core").Signal<string[]>;
|
|
46
52
|
private readonly defaultMonthNames;
|
|
47
53
|
private readonly defaultMonthNamesShort;
|
|
48
54
|
private readonly defaultDayNames;
|
|
49
55
|
private readonly defaultDayNamesShort;
|
|
56
|
+
private onChange;
|
|
57
|
+
private onTouched;
|
|
50
58
|
constructor(elementRef: ElementRef);
|
|
51
59
|
onClickOutside(event: MouseEvent): void;
|
|
52
60
|
ngOnInit(): void;
|
|
@@ -67,6 +75,11 @@ export declare class DualDatepickerComponent implements OnInit, OnChanges {
|
|
|
67
75
|
limpiar(): void;
|
|
68
76
|
private emitirCambio;
|
|
69
77
|
private emitirSeleccion;
|
|
78
|
+
private getDateRangeValue;
|
|
79
|
+
writeValue(value: DateRange | null): void;
|
|
80
|
+
registerOnChange(fn: (value: DateRange | null) => void): void;
|
|
81
|
+
registerOnTouched(fn: () => void): void;
|
|
82
|
+
setDisabledState(isDisabled: boolean): void;
|
|
70
83
|
static ɵfac: i0.ɵɵFactoryDeclaration<DualDatepickerComponent, never>;
|
|
71
84
|
static ɵcmp: i0.ɵɵComponentDeclaration<DualDatepickerComponent, "ngx-dual-datepicker", never, { "placeholder": { "alias": "placeholder"; "required": false; }; "fechaInicio": { "alias": "fechaInicio"; "required": false; }; "fechaFin": { "alias": "fechaFin"; "required": false; }; "showPresets": { "alias": "showPresets"; "required": false; }; "showClearButton": { "alias": "showClearButton"; "required": false; }; "closeOnSelection": { "alias": "closeOnSelection"; "required": false; }; "closeOnPresetSelection": { "alias": "closeOnPresetSelection"; "required": false; }; "closeOnClickOutside": { "alias": "closeOnClickOutside"; "required": false; }; "presets": { "alias": "presets"; "required": false; }; "inputBackgroundColor": { "alias": "inputBackgroundColor"; "required": false; }; "inputTextColor": { "alias": "inputTextColor"; "required": false; }; "inputBorderColor": { "alias": "inputBorderColor"; "required": false; }; "inputBorderColorHover": { "alias": "inputBorderColorHover"; "required": false; }; "inputBorderColorFocus": { "alias": "inputBorderColorFocus"; "required": false; }; "inputPadding": { "alias": "inputPadding"; "required": false; }; "locale": { "alias": "locale"; "required": false; }; }, { "dateRangeChange": "dateRangeChange"; "dateRangeSelected": "dateRangeSelected"; }, never, never, true, never>;
|
|
72
85
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract class for date adapters.
|
|
4
|
+
* Allows the component to work with different date libraries (Date, DayJS, date-fns, Luxon, etc.)
|
|
5
|
+
*/
|
|
6
|
+
export class DateAdapter {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Injection token for DateAdapter
|
|
10
|
+
*/
|
|
11
|
+
export const DATE_ADAPTER = new InjectionToken('DATE_ADAPTER');
|
|
12
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0ZS1hZGFwdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2RhdGUtYWRhcHRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRS9DOzs7R0FHRztBQUNILE1BQU0sT0FBZ0IsV0FBVztDQXlIaEM7QUFFRDs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxJQUFJLGNBQWMsQ0FBYyxjQUFjLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGlvblRva2VuIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbi8qKlxuICogQWJzdHJhY3QgY2xhc3MgZm9yIGRhdGUgYWRhcHRlcnMuXG4gKiBBbGxvd3MgdGhlIGNvbXBvbmVudCB0byB3b3JrIHdpdGggZGlmZmVyZW50IGRhdGUgbGlicmFyaWVzIChEYXRlLCBEYXlKUywgZGF0ZS1mbnMsIEx1eG9uLCBldGMuKVxuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgRGF0ZUFkYXB0ZXI8VCA9IGFueT4ge1xuICAvKipcbiAgICogUGFyc2VzIGEgdmFsdWUgaW50byBhIGRhdGUgb2JqZWN0XG4gICAqIEBwYXJhbSB2YWx1ZSAtIFZhbHVlIHRvIHBhcnNlIChzdHJpbmcsIG51bWJlciwgb3IgZGF0ZSBvYmplY3QpXG4gICAqIEByZXR1cm5zIFBhcnNlZCBkYXRlIG9iamVjdFxuICAgKi9cbiAgYWJzdHJhY3QgcGFyc2UodmFsdWU6IGFueSk6IFQgfCBudWxsO1xuXG4gIC8qKlxuICAgKiBGb3JtYXRzIGEgZGF0ZSBvYmplY3QgaW50byBhIHN0cmluZ1xuICAgKiBAcGFyYW0gZGF0ZSAtIERhdGUgb2JqZWN0IHRvIGZvcm1hdFxuICAgKiBAcGFyYW0gZm9ybWF0IC0gT3B0aW9uYWwgZm9ybWF0IHN0cmluZ1xuICAgKiBAcmV0dXJucyBGb3JtYXR0ZWQgZGF0ZSBzdHJpbmdcbiAgICovXG4gIGFic3RyYWN0IGZvcm1hdChkYXRlOiBULCBmb3JtYXQ/OiBzdHJpbmcpOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIEFkZHMgZGF5cyB0byBhIGRhdGVcbiAgICogQHBhcmFtIGRhdGUgLSBEYXRlIG9iamVjdFxuICAgKiBAcGFyYW0gZGF5cyAtIE51bWJlciBvZiBkYXlzIHRvIGFkZCAoY2FuIGJlIG5lZ2F0aXZlKVxuICAgKiBAcmV0dXJucyBOZXcgZGF0ZSBvYmplY3RcbiAgICovXG4gIGFic3RyYWN0IGFkZERheXMoZGF0ZTogVCwgZGF5czogbnVtYmVyKTogVDtcblxuICAvKipcbiAgICogQWRkcyBtb250aHMgdG8gYSBkYXRlXG4gICAqIEBwYXJhbSBkYXRlIC0gRGF0ZSBvYmplY3RcbiAgICogQHBhcmFtIG1vbnRocyAtIE51bWJlciBvZiBtb250aHMgdG8gYWRkIChjYW4gYmUgbmVnYXRpdmUpXG4gICAqIEByZXR1cm5zIE5ldyBkYXRlIG9iamVjdFxuICAgKi9cbiAgYWJzdHJhY3QgYWRkTW9udGhzKGRhdGU6IFQsIG1vbnRoczogbnVtYmVyKTogVDtcblxuICAvKipcbiAgICogR2V0cyB0aGUgeWVhciBmcm9tIGEgZGF0ZVxuICAgKiBAcGFyYW0gZGF0ZSAtIERhdGUgb2JqZWN0XG4gICAqIEByZXR1cm5zIFllYXIgbnVtYmVyXG4gICAqL1xuICBhYnN0cmFjdCBnZXRZZWFyKGRhdGU6IFQpOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIEdldHMgdGhlIG1vbnRoIGZyb20gYSBkYXRlICgwLTExKVxuICAgKiBAcGFyYW0gZGF0ZSAtIERhdGUgb2JqZWN0XG4gICAqIEByZXR1cm5zIE1vbnRoIG51bWJlciAoMC0xMSlcbiAgICovXG4gIGFic3RyYWN0IGdldE1vbnRoKGRhdGU6IFQpOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIEdldHMgdGhlIGRheSBvZiBtb250aCBmcm9tIGEgZGF0ZVxuICAgKiBAcGFyYW0gZGF0ZSAtIERhdGUgb2JqZWN0XG4gICAqIEByZXR1cm5zIERheSBvZiBtb250aCAoMS0zMSlcbiAgICovXG4gIGFic3RyYWN0IGdldERhdGUoZGF0ZTogVCk6IG51bWJlcjtcblxuICAvKipcbiAgICogR2V0cyB0aGUgZGF5IG9mIHdlZWsgZnJvbSBhIGRhdGUgKDAtNiwgU3VuZGF5ID0gMClcbiAgICogQHBhcmFtIGRhdGUgLSBEYXRlIG9iamVjdFxuICAgKiBAcmV0dXJucyBEYXkgb2Ygd2VlayAoMC02KVxuICAgKi9cbiAgYWJzdHJhY3QgZ2V0RGF5KGRhdGU6IFQpOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBuZXcgZGF0ZSBvYmplY3RcbiAgICogQHBhcmFtIHllYXIgLSBZZWFyXG4gICAqIEBwYXJhbSBtb250aCAtIE1vbnRoICgwLTExKVxuICAgKiBAcGFyYW0gZGF0ZSAtIERheSBvZiBtb250aCAoMS0zMSlcbiAgICogQHJldHVybnMgTmV3IGRhdGUgb2JqZWN0XG4gICAqL1xuICBhYnN0cmFjdCBjcmVhdGVEYXRlKHllYXI6IG51bWJlciwgbW9udGg6IG51bWJlciwgZGF0ZTogbnVtYmVyKTogVDtcblxuICAvKipcbiAgICogR2V0cyB0b2RheSdzIGRhdGVcbiAgICogQHJldHVybnMgVG9kYXkncyBkYXRlIG9iamVjdFxuICAgKi9cbiAgYWJzdHJhY3QgdG9kYXkoKTogVDtcblxuICAvKipcbiAgICogQ2hlY2tzIGlmIHR3byBkYXRlcyBhcmUgdGhlIHNhbWUgZGF5XG4gICAqIEBwYXJhbSBhIC0gRmlyc3QgZGF0ZVxuICAgKiBAcGFyYW0gYiAtIFNlY29uZCBkYXRlXG4gICAqIEByZXR1cm5zIFRydWUgaWYgc2FtZSBkYXlcbiAgICovXG4gIGFic3RyYWN0IGlzU2FtZURheShhOiBUIHwgbnVsbCwgYjogVCB8IG51bGwpOiBib29sZWFuO1xuXG4gIC8qKlxuICAgKiBDaGVja3MgaWYgYSBkYXRlIGlzIGJlZm9yZSBhbm90aGVyXG4gICAqIEBwYXJhbSBhIC0gRmlyc3QgZGF0ZVxuICAgKiBAcGFyYW0gYiAtIFNlY29uZCBkYXRlXG4gICAqIEByZXR1cm5zIFRydWUgaWYgYSBpcyBiZWZvcmUgYlxuICAgKi9cbiAgYWJzdHJhY3QgaXNCZWZvcmUoYTogVCB8IG51bGwsIGI6IFQgfCBudWxsKTogYm9vbGVhbjtcblxuICAvKipcbiAgICogQ2hlY2tzIGlmIGEgZGF0ZSBpcyBhZnRlciBhbm90aGVyXG4gICAqIEBwYXJhbSBhIC0gRmlyc3QgZGF0ZVxuICAgKiBAcGFyYW0gYiAtIFNlY29uZCBkYXRlXG4gICAqIEByZXR1cm5zIFRydWUgaWYgYSBpcyBhZnRlciBiXG4gICAqL1xuICBhYnN0cmFjdCBpc0FmdGVyKGE6IFQgfCBudWxsLCBiOiBUIHwgbnVsbCk6IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIENoZWNrcyBpZiBhIGRhdGUgaXMgYmV0d2VlbiB0d28gb3RoZXIgZGF0ZXNcbiAgICogQHBhcmFtIGRhdGUgLSBEYXRlIHRvIGNoZWNrXG4gICAqIEBwYXJhbSBzdGFydCAtIFN0YXJ0IGRhdGVcbiAgICogQHBhcmFtIGVuZCAtIEVuZCBkYXRlXG4gICAqIEByZXR1cm5zIFRydWUgaWYgZGF0ZSBpcyBiZXR3ZWVuIHN0YXJ0IGFuZCBlbmRcbiAgICovXG4gIGFic3RyYWN0IGlzQmV0d2VlbihkYXRlOiBUIHwgbnVsbCwgc3RhcnQ6IFQgfCBudWxsLCBlbmQ6IFQgfCBudWxsKTogYm9vbGVhbjtcblxuICAvKipcbiAgICogQ2xvbmVzIGEgZGF0ZSBvYmplY3RcbiAgICogQHBhcmFtIGRhdGUgLSBEYXRlIHRvIGNsb25lXG4gICAqIEByZXR1cm5zIENsb25lZCBkYXRlIG9iamVjdFxuICAgKi9cbiAgYWJzdHJhY3QgY2xvbmUoZGF0ZTogVCk6IFQ7XG5cbiAgLyoqXG4gICAqIENoZWNrcyBpZiBhIHZhbHVlIGlzIGEgdmFsaWQgZGF0ZVxuICAgKiBAcGFyYW0gZGF0ZSAtIFZhbHVlIHRvIGNoZWNrXG4gICAqIEByZXR1cm5zIFRydWUgaWYgdmFsaWQgZGF0ZVxuICAgKi9cbiAgYWJzdHJhY3QgaXNWYWxpZChkYXRlOiBhbnkpOiBib29sZWFuO1xufVxuXG4vKipcbiAqIEluamVjdGlvbiB0b2tlbiBmb3IgRGF0ZUFkYXB0ZXJcbiAqL1xuZXhwb3J0IGNvbnN0IERBVEVfQURBUFRFUiA9IG5ldyBJbmplY3Rpb25Ub2tlbjxEYXRlQWRhcHRlcj4oJ0RBVEVfQURBUFRFUicpO1xuIl19
|