@oneluiz/dual-datepicker 2.4.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 +331 -98
- package/date-adapter.d.ts +116 -0
- package/dual-datepicker.component.d.ts +3 -2
- package/esm2022/date-adapter.mjs +12 -0
- package/esm2022/dual-datepicker.component.mjs +45 -24
- package/esm2022/native-date-adapter.mjs +112 -0
- package/esm2022/public-api.mjs +4 -1
- package/fesm2022/oneluiz-dual-datepicker.mjs +163 -24
- package/fesm2022/oneluiz-dual-datepicker.mjs.map +1 -1
- package/native-date-adapter.d.ts +26 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
package/README.md
CHANGED
|
@@ -1,30 +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
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
71
|
+
|
|
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.
|
|
28
82
|
|
|
29
83
|
## ๐ฆ Installation
|
|
30
84
|
|
|
@@ -34,7 +88,7 @@ npm install @oneluiz/dual-datepicker
|
|
|
34
88
|
|
|
35
89
|
## ๐ Quick Start
|
|
36
90
|
|
|
37
|
-
###
|
|
91
|
+
### Basic Usage
|
|
38
92
|
|
|
39
93
|
```typescript
|
|
40
94
|
import { Component } from '@angular/core';
|
|
@@ -46,104 +100,58 @@ import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
|
|
|
46
100
|
imports: [DualDatepickerComponent],
|
|
47
101
|
template: `
|
|
48
102
|
<ngx-dual-datepicker
|
|
49
|
-
|
|
50
|
-
placeholder="Select date range">
|
|
103
|
+
(dateRangeChange)="onRangeChange($event)">
|
|
51
104
|
</ngx-dual-datepicker>
|
|
52
105
|
`
|
|
53
106
|
})
|
|
54
107
|
export class AppComponent {
|
|
55
|
-
|
|
108
|
+
onRangeChange(range: DateRange) {
|
|
109
|
+
console.log('Start:', range.fechaInicio);
|
|
110
|
+
console.log('End:', range.fechaFin);
|
|
111
|
+
}
|
|
56
112
|
}
|
|
57
113
|
```
|
|
58
114
|
|
|
59
|
-
###
|
|
115
|
+
### With Reactive Forms
|
|
60
116
|
|
|
61
117
|
```typescript
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import { DualDatepickerComponent } from '@oneluiz/dual-datepicker';
|
|
118
|
+
import { FormControl } from '@angular/forms';
|
|
119
|
+
import { DateRange } from '@oneluiz/dual-datepicker';
|
|
65
120
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
standalone: true,
|
|
69
|
-
imports: [FormsModule, DualDatepickerComponent],
|
|
70
|
-
template: `
|
|
71
|
-
<ngx-dual-datepicker
|
|
72
|
-
[fechaInicio]="fechaInicio"
|
|
73
|
-
[fechaFin]="fechaFin"
|
|
74
|
-
[presets]="customPresets"
|
|
75
|
-
(dateRangeChange)="onDateChange($event)">
|
|
76
|
-
</ngx-dual-datepicker>
|
|
77
|
-
`
|
|
78
|
-
})
|
|
79
|
-
export class ExampleComponent {
|
|
80
|
-
fechaInicio = '';
|
|
81
|
-
fechaFin = '';
|
|
82
|
-
|
|
83
|
-
customPresets = [
|
|
84
|
-
{ label: 'Last 7 days', daysAgo: 7 },
|
|
85
|
-
{ label: 'Last 30 days', daysAgo: 30 },
|
|
86
|
-
{ label: 'Last 90 days', daysAgo: 90 }
|
|
87
|
-
];
|
|
121
|
+
dateRange = new FormControl<DateRange | null>(null);
|
|
122
|
+
```
|
|
88
123
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.fechaInicio = range.fechaInicio;
|
|
92
|
-
this.fechaFin = range.fechaFin;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
124
|
+
```html
|
|
125
|
+
<ngx-dual-datepicker [formControl]="dateRange"></ngx-dual-datepicker>
|
|
95
126
|
```
|
|
96
127
|
|
|
97
|
-
###
|
|
128
|
+
### With Angular Signals
|
|
98
129
|
|
|
99
130
|
```typescript
|
|
100
|
-
import {
|
|
101
|
-
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
102
|
-
import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
|
|
131
|
+
import { signal } from '@angular/core';
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
standalone: true,
|
|
107
|
-
imports: [ReactiveFormsModule, DualDatepickerComponent],
|
|
108
|
-
template: `
|
|
109
|
-
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
|
110
|
-
<label>Select Date Range:</label>
|
|
111
|
-
<ngx-dual-datepicker
|
|
112
|
-
formControlName="dateRange"
|
|
113
|
-
placeholder="Choose dates"
|
|
114
|
-
[showClearButton]="true">
|
|
115
|
-
</ngx-dual-datepicker>
|
|
116
|
-
|
|
117
|
-
<button type="submit" [disabled]="!form.valid">Submit</button>
|
|
118
|
-
|
|
119
|
-
@if (form.value.dateRange) {
|
|
120
|
-
<div>
|
|
121
|
-
Selected: {{ form.value.dateRange.fechaInicio }} to {{ form.value.dateRange.fechaFin }}
|
|
122
|
-
</div>
|
|
123
|
-
}
|
|
124
|
-
</form>
|
|
125
|
-
`
|
|
126
|
-
})
|
|
127
|
-
export class ReactiveFormComponent implements OnInit {
|
|
128
|
-
form!: FormGroup;
|
|
133
|
+
dateRange = signal<DateRange | null>(null);
|
|
134
|
+
```
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
```html
|
|
137
|
+
<ngx-dual-datepicker
|
|
138
|
+
[(ngModel)]="dateRange()"
|
|
139
|
+
(dateRangeChange)="dateRange.set($event)">
|
|
140
|
+
</ngx-dual-datepicker>
|
|
141
|
+
```
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
this.form = this.fb.group({
|
|
134
|
-
dateRange: [null] // Will receive DateRange object
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Listen to changes
|
|
138
|
-
this.form.get('dateRange')?.valueChanges.subscribe(value => {
|
|
139
|
-
console.log('Date range changed:', value);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
143
|
+
### Custom Styling
|
|
142
144
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
155
|
}
|
|
148
156
|
```
|
|
149
157
|
|
|
@@ -203,6 +211,221 @@ export class SignalsExampleComponent {
|
|
|
203
211
|
}
|
|
204
212
|
```
|
|
205
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)
|
|
219
|
+
|
|
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();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
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
|
+
|
|
206
429
|
## ๐จ Customization
|
|
207
430
|
|
|
208
431
|
### Custom Colors (Bootstrap Style)
|
|
@@ -455,7 +678,17 @@ export class ExampleComponent {
|
|
|
455
678
|
- Angular 19.0.0 or higher
|
|
456
679
|
- Angular 20.0.0 or higher
|
|
457
680
|
|
|
458
|
-
##
|
|
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
|
|
459
692
|
|
|
460
693
|
MIT ยฉ Luis Cortes
|
|
461
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>>;
|
|
@@ -37,11 +37,12 @@ export declare class DualDatepickerComponent implements OnInit, OnChanges, Contr
|
|
|
37
37
|
locale: LocaleConfig;
|
|
38
38
|
dateRangeChange: EventEmitter<DateRange>;
|
|
39
39
|
dateRangeSelected: EventEmitter<DateRange>;
|
|
40
|
+
private dateAdapter;
|
|
40
41
|
mostrarDatePicker: import("@angular/core").WritableSignal<boolean>;
|
|
41
42
|
rangoFechas: import("@angular/core").WritableSignal<string>;
|
|
42
43
|
fechaSeleccionandoInicio: import("@angular/core").WritableSignal<boolean>;
|
|
43
|
-
mesActual: import("@angular/core").WritableSignal<
|
|
44
|
-
mesAnterior: import("@angular/core").WritableSignal<
|
|
44
|
+
mesActual: import("@angular/core").WritableSignal<any>;
|
|
45
|
+
mesAnterior: import("@angular/core").WritableSignal<any>;
|
|
45
46
|
diasMesActual: import("@angular/core").WritableSignal<any[]>;
|
|
46
47
|
diasMesAnterior: import("@angular/core").WritableSignal<any[]>;
|
|
47
48
|
isDisabled: import("@angular/core").WritableSignal<boolean>;
|
|
@@ -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
|