@oneluiz/dual-datepicker 2.6.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,16 @@ A lightweight, zero-dependency date range picker for Angular 17+. Built with sta
10
10
  npm install @oneluiz/dual-datepicker
11
11
  ```
12
12
 
13
+ > ## ⚠️ **BREAKING CHANGES in v3.0.0**
14
+ >
15
+ > - **All DateRange properties renamed to English**: `fechaInicio` → `startDate`, `fechaFin` → `endDate`, `rangoTexto` → `rangeText`
16
+ > - **Deprecated `daysAgo` removed**: Use `getValue: () => PresetRange` pattern or `CommonPresets` instead
17
+ > - **All component methods renamed to English**: `limpiar()` → `clear()`, etc.
18
+ >
19
+ > **📖 See [MIGRATION_V3.md](MIGRATION_V3.md) for complete migration guide**
20
+ >
21
+ > To stay on v2.x: `npm install @oneluiz/dual-datepicker@2.7.0`
22
+
13
23
  ## 🎯 [Live Demo](https://oneluiz.github.io/ng-dual-datepicker/)
14
24
 
15
25
  **[Check out the interactive examples →](https://oneluiz.github.io/ng-dual-datepicker/)**
@@ -22,6 +32,7 @@ npm install @oneluiz/dual-datepicker
22
32
  | **Dependencies** | Zero | Requires @angular/material, @angular/cdk |
23
33
  | **Standalone** | ✅ Native | ⚠️ Requires module setup |
24
34
  | **Signals Support** | ✅ Built-in | ❌ Not yet |
35
+ | **Multi-Range Support** | ✅ NEW v2.7.0 | ❌ Not available |
25
36
  | **Customization** | Full styling control | Theme-constrained |
26
37
  | **Learning Curve** | Minimal | Requires Material knowledge |
27
38
  | **Change Detection** | OnPush optimized | Default |
@@ -33,6 +44,7 @@ npm install @oneluiz/dual-datepicker
33
44
  - 🎯 **Standalone Component** – No NgModule imports needed
34
45
  - ⚡ **Angular Signals** – Modern reactive state management
35
46
  - 🔄 **Reactive Forms** – Full ControlValueAccessor implementation
47
+ - 🔥 **Multi-Range Support** – Select multiple date ranges (NEW v2.7.0 - Material CAN'T do this!)
36
48
  - 🎨 **Fully Customizable** – Every color, padding, border configurable
37
49
  - 📦 **Lightweight** – ~60 KB gzipped total bundle
38
50
  - 🚀 **Performance** – OnPush change detection + trackBy optimization
@@ -106,8 +118,8 @@ import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
106
118
  })
107
119
  export class AppComponent {
108
120
  onRangeChange(range: DateRange) {
109
- console.log('Start:', range.fechaInicio);
110
- console.log('End:', range.fechaFin);
121
+ console.log('Start:', range.startDate);
122
+ console.log('End:', range.endDate);
111
123
  }
112
124
  }
113
125
  ```
@@ -152,8 +164,6 @@ dateRange = signal<DateRange | null>(null);
152
164
  ```
153
165
 
154
166
  ## 📚 Advanced Usage
155
- }
156
- ```
157
167
 
158
168
  ### 4. Use with Angular Signals ⚡ New!
159
169
 
@@ -169,8 +179,8 @@ import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
169
179
  imports: [DualDatepickerComponent],
170
180
  template: `
171
181
  <ngx-dual-datepicker
172
- [fechaInicio]="fechaInicio()"
173
- [fechaFin]="fechaFin()"
182
+ [startDate]="startDate()"
183
+ [endDate]="endDate()"
174
184
  (dateRangeChange)="onDateChange($event)">
175
185
  </ngx-dual-datepicker>
176
186
 
@@ -183,34 +193,101 @@ import { DualDatepickerComponent, DateRange } from '@oneluiz/dual-datepicker';
183
193
  `
184
194
  })
185
195
  export class SignalsExampleComponent {
186
- fechaInicio = signal('');
187
- fechaFin = signal('');
196
+ startDate = signal('');
197
+ endDate = signal('');
188
198
 
189
199
  // Computed values
190
200
  isRangeSelected = computed(() =>
191
- this.fechaInicio() !== '' && this.fechaFin() !== ''
201
+ this.startDate() !== '' && this.endDate() !== ''
192
202
  );
193
203
 
194
204
  rangeText = computed(() =>
195
205
  this.isRangeSelected()
196
- ? `${this.fechaInicio()} to ${this.fechaFin()}`
206
+ ? `${this.startDate()} to ${this.endDate()}`
197
207
  : 'No range selected'
198
208
  );
199
209
 
200
210
  daysDifference = computed(() => {
201
211
  if (!this.isRangeSelected()) return 0;
202
- const start = new Date(this.fechaInicio());
203
- const end = new Date(this.fechaFin());
212
+ const start = new Date(this.startDate());
213
+ const end = new Date(this.endDate());
204
214
  return Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
205
215
  });
206
216
 
207
217
  onDateChange(range: DateRange) {
208
- this.fechaInicio.set(range.fechaInicio);
209
- this.fechaFin.set(range.fechaFin);
218
+ this.startDate.set(range.startDate);
219
+ this.endDate.set(range.endDate);
210
220
  }
211
221
  }
212
222
  ```
213
223
 
224
+ ### 5. Multi-Range Support 🔥 NEW v2.7.0!
225
+
226
+ **Material CAN'T do this!** Select multiple date ranges in a single picker - perfect for booking systems, blackout periods, and complex scheduling.
227
+
228
+ ```typescript
229
+ import { Component } from '@angular/core';
230
+ import { DualDatepickerComponent, MultiDateRange } from '@oneluiz/dual-datepicker';
231
+
232
+ @Component({
233
+ selector: 'app-multi-range',
234
+ standalone: true,
235
+ imports: [DualDatepickerComponent],
236
+ template: `
237
+ <ngx-dual-datepicker
238
+ [multiRange]="true"
239
+ [showClearButton]="true"
240
+ (multiDateRangeChange)="onMultiRangeChange($event)">
241
+ </ngx-dual-datepicker>
242
+
243
+ @if (selectedRanges && selectedRanges.ranges.length > 0) {
244
+ <div class="selected-ranges">
245
+ <h3>Selected Ranges ({{ selectedRanges.ranges.length }})</h3>
246
+ @for (range of selectedRanges.ranges; track $index) {
247
+ <div class="range-item">
248
+ <strong>Range {{ $index + 1 }}:</strong> {{ range.rangeText }}
249
+ <br />
250
+ <span>{{ range.startDate }} → {{ range.endDate }}</span>
251
+ </div>
252
+ }
253
+ </div>
254
+ }
255
+ `
256
+ })
257
+ export class MultiRangeExample {
258
+ selectedRanges: MultiDateRange | null = null;
259
+
260
+ onMultiRangeChange(ranges: MultiDateRange) {
261
+ this.selectedRanges = ranges;
262
+ console.log('Selected ranges:', ranges.ranges);
263
+ // Output example:
264
+ // [
265
+ // { startDate: '2026-01-01', endDate: '2026-01-05', rangeText: 'Jan 1 – Jan 5' },
266
+ // { startDate: '2026-01-10', endDate: '2026-01-15', rangeText: 'Jan 10 – Jan 15' },
267
+ // { startDate: '2026-02-01', endDate: '2026-02-07', rangeText: 'Feb 1 – Feb 7' }
268
+ // ]
269
+ }
270
+ }
271
+ ```
272
+
273
+ #### Perfect Use Cases
274
+
275
+ - 🏨 **Hotel Booking Systems** - Block multiple periods for reservations
276
+ - 📅 **Event Blackout Periods** - Mark multiple dates as unavailable
277
+ - 🔧 **Maintenance Windows** - Schedule multiple maintenance periods
278
+ - 📊 **Availability Calendars** - Show multiple available/unavailable periods
279
+ - 👷 **Shift Scheduling** - Select multiple work periods
280
+ - 💼 **Business Meetings** - Block out multiple date ranges
281
+
282
+ #### Key Features
283
+
284
+ - ✅ Select unlimited date ranges
285
+ - ✅ Visual feedback - all ranges highlighted in calendar
286
+ - ✅ Easy management - add/remove ranges with one click
287
+ - ✅ Separate events for multi-range (`multiDateRangeChange`, `multiDateRangeSelected`)
288
+ - ✅ Clear all ranges with one button
289
+ - ❌ **Angular Material CANNOT do this!**
290
+
214
291
  ## 🔌 Date Adapter System
215
292
 
216
293
  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.
@@ -652,22 +729,22 @@ export class MyComponent {
652
729
  @ViewChild('datepicker') datepicker!: DualDatepickerComponent;
653
730
 
654
731
  clearSelection() {
655
- this.datepicker.limpiar(); // Clears the date selection
732
+ this.datepicker.clear(); // v3.0.0: method renamed from limpiar() to clear()
656
733
  }
657
734
  }
658
735
  ```
659
736
 
660
737
  | Method | Description |
661
- |--------|-------------|
662
- | `limpiar()` | Clears the current date selection and resets the component |
738
+ |--------|----------|
739
+ | `clear()` | Clears the current date selection and resets the component (v3.0.0: renamed from `limpiar()`) |
663
740
 
664
741
  ### Types
665
742
 
666
743
  ```typescript
667
744
  interface DateRange {
668
- fechaInicio: string; // ISO date format: 'YYYY-MM-DD'
669
- fechaFin: string; // ISO date format: 'YYYY-MM-DD'
670
- rangoTexto: string; // Display text: 'DD Mon - DD Mon'
745
+ startDate: string; // v3.0.0: renamed from 'fechaInicio' - ISO format: 'YYYY-MM-DD'
746
+ endDate: string; // v3.0.0: renamed from 'fechaFin' - ISO format: 'YYYY-MM-DD'
747
+ rangeText: string; // v3.0.0: renamed from 'rangoTexto' - Display text: 'DD Mon - DD Mon'
671
748
  }
672
749
 
673
750
  interface PresetRange {
@@ -677,10 +754,7 @@ interface PresetRange {
677
754
 
678
755
  interface PresetConfig {
679
756
  label: string;
680
- /** @deprecated Use getValue() instead for more flexibility */
681
- daysAgo?: number;
682
- /** NEW v2.6.0 - Function that returns date range with custom logic */
683
- getValue?: () => PresetRange;
757
+ getValue: () => PresetRange; // v3.0.0: NOW REQUIRED (daysAgo removed)
684
758
  }
685
759
 
686
760
  interface LocaleConfig {
@@ -692,48 +766,37 @@ interface LocaleConfig {
692
766
  }
693
767
  ```
694
768
 
695
- ### Default Presets
769
+ ### CommonPresets
770
+
771
+ v3.0.0: No default presets shipped with component. Use `CommonPresets` utility or create custom:
696
772
 
697
773
  ```typescript
698
- [
699
- { label: 'Last month', daysAgo: 30 },
700
- { label: 'Last 6 months', daysAgo: 180 },
701
- { label: 'Last yea
702
- [fechaInicio]="startDate"
703
- [fechaFin]="endDate"
704
- (dateRangeSelected)="onDateRangeSelected($event)">
705
- </ngx-dual-datepicker>
706
- ```
774
+ import { CommonPresets, getLastNDays } from '@oneluiz/dual-datepicker';
775
+
776
+ // Use pre-built collections
777
+ presets = CommonPresets.dashboard; // Last 7, 15, 30, 60, 90 days + last 6 months
707
778
 
708
- ###fechaInicio]="startDate"
709
- [fechaFin]="endDate"
710
- [closeOnSelection]="true"
711
- [closeOnPresetSelection]="true"
712
- (dateRangeSelected)="onDateRangeSelected($event)
713
- spanishLocale: LocaleConfig = {
714
- monthNames: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
715
- 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
716
- monthNamesShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun',
717
- 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
718
- dayNames: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
719
- dayNamesShort: ['D', 'L', 'M', 'X', 'J', 'V', 'S']
720
- };
779
+ // Or create custom presets
780
+ presets: PresetConfig[] = [
781
+ { label: 'Last 15 days', getValue: () => getLastNDays(15) },
782
+ { label: 'Last 3 months', getValue: () => getLastNDays(90) },
783
+ { label: 'Last 6 months', getValue: () => getLastNDays(180) },
784
+ { label: 'Last year', getValue: () => getLastNDays(365) }
785
+ ];
721
786
  ```
722
787
 
723
- ```html
724
- <ngx-dual-datepicker
725
- [fechaInicio]="startDate"
726
- [fechaFin]="endDate"
727
- [fechaInicio]="startDate"
728
- [fechaFin]="endDate"
729
- placeholder="Pick your dates"
730
- inputBackgroundColor="#fef3c7"
731
- inputTextColor="#92400e"
732
- inputBorderColor="#fbbf24"
733
- inputBorderColorHover="#f59e0b"
734
- inputBorderColorFocus="#d97706"
735
- inputPadding="8px 12px"
736
- (dateRangeSelected)="onDateRangeSelected($event)
788
+ Available `CommonPresets` collections:
789
+ - `CommonPresets.simple` - Last 7, 30, 60, 90 days
790
+ - `CommonPresets.dashboard` - Last 7, 15, 30, 60, 90 days + last 6 months
791
+ - `CommonPresets.analytics` - Last 30, 60, 90, 180, 365 days + YTD
792
+
793
+ Helper functions:
794
+ - `getLastNDays(n)` - Returns range for last N days
795
+ - `getThisMonth()` - Returns range for current month
796
+ - `getLastMonth()` - Returns range for previous month
797
+ - `getYearToDate()` - Returns range from Jan 1 to today
798
+
799
+ ## Usage Examples
737
800
 
738
801
  ### Minimal Usage
739
802
 
@@ -741,15 +804,31 @@ spanishLocale: LocaleConfig = {
741
804
  <ngx-dual-datepicker [(ngModel)]="dateRange"></ngx-dual-datepicker>
742
805
  ```
743
806
 
744
- ### With Auto-close
745
- fechaInicio]="startDate"
746
- [fechaFin]="endDate"
807
+ ### With Initial Dates
808
+
809
+ ```html
810
+ <ngx-dual-datepicker
811
+ [startDate]="'2024-01-15'"
812
+ [endDate]="'2024-01-30'"
813
+ (dateRangeSelected)="onDateRangeSelected($event)">
814
+ </ngx-dual-datepicker>
815
+ ```
816
+
817
+ ### With Events
818
+
819
+ ```typescript
820
+ @Component({
821
+ selector: 'app-example',
822
+ template: `
823
+ <ngx-dual-datepicker
824
+ [startDate]="startDate"
825
+ [endDate]="endDate"
747
826
  (dateRangeSelected)="onDateRangeSelected($event)"
748
827
  (dateRangeChange)="onDateRangeChange($event)">
749
828
  </ngx-dual-datepicker>
750
829
 
751
830
  <div *ngIf="selectedRange">
752
- Selected: {{ selectedRange.rangoTexto }}
831
+ Selected: {{ selectedRange.rangeText }}
753
832
  </div>
754
833
  `
755
834
  })
@@ -759,7 +838,7 @@ export class ExampleComponent {
759
838
  selectedRange: DateRange | null = null;
760
839
 
761
840
  onDateRangeChange(range: DateRange) {
762
- console.log('Date changed:', range.fechaInicio);
841
+ console.log('Date changed:', range.startDate);
763
842
  // Emitted when user selects first date (before completing range)
764
843
  }
765
844
 
@@ -768,53 +847,65 @@ export class ExampleComponent {
768
847
  this.selectedRange = range;
769
848
 
770
849
  // Both dates selected - do something
771
- this.fetchData(range.fechaInicio, range.fechaFin);
850
+ this.fetchData(range.startDate, range.endDate);
772
851
  }
773
852
 
774
853
  fetchData(startDate: string, endDate: string) {
775
854
  // Your API call here
776
- // Dates are in 'YYYY-MM-DD' format,
855
+ // Dates are in 'YYYY-MM-DD' format
856
+ }
857
+ }
858
+ ```
859
+
860
+ ### With ngModel
861
+
862
+ ```typescript
863
+ @Component({
864
+ selector: 'app-example',
777
865
  template: `
778
866
  <ngx-dual-datepicker
779
867
  [(ngModel)]="dateRange"
780
868
  (ngModelChange)="onDateRangeChange($event)">
781
869
  </ngx-dual-datepicker>
782
870
 
783
- <div *ngIf="dateRange.start && dateRange.end">
784
- Selected: {{ formatDateRange() }}
871
+ <div *ngIf="dateRange">
872
+ Selected: {{ dateRange.rangeText }}
873
+ <br>
874
+ From: {{ dateRange.startDate }} to {{ dateRange.endDate }}
785
875
  </div>
786
876
  `
787
877
  })
788
878
  export class ExampleComponent {
789
- dateRange: DateRange = { start: null, end: null };
879
+ dateRange: DateRange | null = null;
790
880
 
791
881
  onDateRangeChange(range: DateRange) {
792
- console.log('Start:', range.start);
793
- console.log('End:', range.end);
794
-
795
- if (range.start && range.end) {
796
- // Both dates selected - do something
797
- this.fetchData(range.start, range.end);
798
- }
882
+ console.log('Start:', range.startDate);
883
+ console.log('End:', range.endDate);
884
+ console.log('Text:', range.rangeText);
799
885
  }
886
+ }
887
+ ```
800
888
 
801
- formatDateRange(): string {
802
- if (!this.dateRange.start || !this.dateRange.end) return '';
803
- return `${this.dateRange.start.toLocaleDateString()} - ${this.dateRange.end.toLocaleDateString()}`;
804
- }
889
+ ### With Styling
805
890
 
806
- fetchData(start: Date, end: Date) {
807
- // Your API call here
808
- }
809
- }
891
+ ```html
892
+ <ngx-dual-datepicker
893
+ [startDate]="startDate"
894
+ [endDate]="endDate"
895
+ placeholder="Pick your dates"
896
+ inputBackgroundColor="#fef3c7"
897
+ inputTextColor="#92400e"
898
+ inputBorderColor="#fbbf24"
899
+ inputBorderColorHover="#f59e0b"
900
+ inputBorderColorFocus="#d97706"
901
+ inputPadding="12px 16px"
902
+ (dateRangeSelected)="onDateRangeSelected($event)">
903
+ </ngx-dual-datepicker>
810
904
  ```
811
905
 
812
906
  ## 🛠️ Requirements
813
907
 
814
908
  - Angular 17.0.0 or higher
815
- - Angular 18.0.0 or higher
816
- - Angular 19.0.0 or higher
817
- - Angular 20.0.0 or higher
818
909
 
819
910
  ## 🗺️ Roadmap
820
911
 
package/angular.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3
+ "version": 1,
4
+ "newProjectRoot": "projects",
5
+ "projects": {
6
+ "demo": {
7
+ "projectType": "application",
8
+ "schematics": {
9
+ "@schematics/angular:component": {
10
+ "style": "scss"
11
+ }
12
+ },
13
+ "root": "demo",
14
+ "sourceRoot": "demo/src",
15
+ "prefix": "app",
16
+ "architect": {
17
+ "build": {
18
+ "builder": "@angular-devkit/build-angular:application",
19
+ "options": {
20
+ "outputPath": "docs",
21
+ "index": "demo/src/index.html",
22
+ "browser": "demo/src/main.ts",
23
+ "polyfills": [
24
+ "zone.js"
25
+ ],
26
+ "tsConfig": "demo/tsconfig.app.json",
27
+ "inlineStyleLanguage": "scss",
28
+ "assets": [
29
+ "demo/src/favicon.svg"
30
+ ],
31
+ "styles": [
32
+ "demo/src/styles.scss"
33
+ ],
34
+ "scripts": []
35
+ },
36
+ "configurations": {
37
+ "production": {
38
+ "budgets": [
39
+ {
40
+ "type": "initial",
41
+ "maximumWarning": "500kB",
42
+ "maximumError": "1MB"
43
+ },
44
+ {
45
+ "type": "anyComponentStyle",
46
+ "maximumWarning": "4kB",
47
+ "maximumError": "10kB"
48
+ }
49
+ ],
50
+ "outputHashing": "all"
51
+ },
52
+ "development": {
53
+ "optimization": false,
54
+ "extractLicenses": false,
55
+ "sourceMap": true
56
+ }
57
+ },
58
+ "defaultConfiguration": "production"
59
+ },
60
+ "serve": {
61
+ "builder": "@angular-devkit/build-angular:dev-server",
62
+ "configurations": {
63
+ "production": {
64
+ "buildTarget": "demo:build:production"
65
+ },
66
+ "development": {
67
+ "buildTarget": "demo:build:development"
68
+ }
69
+ },
70
+ "defaultConfiguration": "development"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
package/dist/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Luis Cortes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.