@oneluiz/dual-datepicker 3.5.0 → 3.6.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.
@@ -0,0 +1,152 @@
1
+ import { DateAdapter } from './date-adapter';
2
+ import * as i0 from "@angular/core";
3
+ export declare class NativeDateAdapter implements DateAdapter {
4
+ /**
5
+ * Normalize date to start of day (00:00:00.000) in local timezone
6
+ *
7
+ * This is the foundation of timezone-safe operations.
8
+ * All other methods use normalized dates for comparisons.
9
+ */
10
+ normalize(date: Date): Date;
11
+ /**
12
+ * Check if two dates are the same calendar day
13
+ *
14
+ * Implementation: Compare YYYY-MM-DD components directly
15
+ * Avoids timezone issues from valueOf() comparisons
16
+ */
17
+ isSameDay(a: Date, b: Date): boolean;
18
+ /**
19
+ * Check if date A is before date B (calendar day level)
20
+ *
21
+ * Implementation: Compare normalized dates using valueOf()
22
+ */
23
+ isBeforeDay(a: Date, b: Date): boolean;
24
+ /**
25
+ * Check if date A is after date B (calendar day level)
26
+ *
27
+ * Implementation: Compare normalized dates using valueOf()
28
+ */
29
+ isAfterDay(a: Date, b: Date): boolean;
30
+ /**
31
+ * Add days to a date
32
+ *
33
+ * Implementation: Use setDate() which handles month rollover automatically
34
+ *
35
+ * Example:
36
+ * Jan 31 + 3 days → Feb 3 ✅
37
+ * Feb 28 + 1 day → Mar 1 ✅ (non-leap year)
38
+ */
39
+ addDays(date: Date, days: number): Date;
40
+ /**
41
+ * Add months to a date
42
+ *
43
+ * CRITICAL: Handles month overflow correctly
44
+ *
45
+ * Algorithm:
46
+ * 1. Add months using setMonth()
47
+ * 2. If day-of-month changed (overflow), set to last day of target month
48
+ *
49
+ * Examples:
50
+ * - Jan 31 + 1 month → Feb 28 (or Feb 29 in leap year) ✅
51
+ * - Jan 31 + 2 months → Mar 31 ✅
52
+ * - Mar 31 + 1 month → Apr 30 ✅
53
+ * - Dec 31 + 1 month → Jan 31 (next year) ✅
54
+ */
55
+ addMonths(date: Date, months: number): Date;
56
+ /**
57
+ * Get start of day (00:00:00.000)
58
+ *
59
+ * Alias for normalize() with explicit intent
60
+ */
61
+ startOfDay(date: Date): Date;
62
+ /**
63
+ * Get end of day (23:59:59.999)
64
+ *
65
+ * Useful for inclusive range queries
66
+ */
67
+ endOfDay(date: Date): Date;
68
+ /**
69
+ * Get first day of month (00:00:00.000)
70
+ */
71
+ startOfMonth(date: Date): Date;
72
+ /**
73
+ * Get last day of month (23:59:59.999)
74
+ *
75
+ * Algorithm: Go to 1st of next month, subtract 1 day
76
+ */
77
+ endOfMonth(date: Date): Date;
78
+ /**
79
+ * Get year (4-digit)
80
+ */
81
+ getYear(date: Date): number;
82
+ /**
83
+ * Get month (0-11)
84
+ */
85
+ getMonth(date: Date): number;
86
+ /**
87
+ * Get day of month (1-31)
88
+ */
89
+ getDate(date: Date): number;
90
+ /**
91
+ * Get day of week (0-6, Sunday=0)
92
+ */
93
+ getDay(date: Date): number;
94
+ /**
95
+ * Convert Date to ISO date string (YYYY-MM-DD)
96
+ *
97
+ * CRITICAL: DO NOT use toISOString() - it converts to UTC!
98
+ *
99
+ * Manual construction ensures local timezone is preserved:
100
+ *
101
+ * Example problem with toISOString():
102
+ * ```
103
+ * // Local timezone: GMT-6 (CST)
104
+ * const date = new Date('2026-02-21T23:00:00'); // 11 PM Feb 21 local
105
+ *
106
+ * // WRONG ❌
107
+ * date.toISOString().split('T')[0]
108
+ * // Returns "2026-02-22" (converted to UTC = Feb 22 05:00 AM)
109
+ *
110
+ * // CORRECT ✅
111
+ * toISODate(date)
112
+ * // Returns "2026-02-21" (local date preserved)
113
+ * ```
114
+ *
115
+ * Implementation: Build YYYY-MM-DD manually from local date components
116
+ */
117
+ toISODate(date: Date): string;
118
+ /**
119
+ * Parse ISO date string (YYYY-MM-DD) to Date
120
+ *
121
+ * CRITICAL: DO NOT use new Date(isoString) - may parse as UTC!
122
+ *
123
+ * Example problem with Date constructor:
124
+ * ```
125
+ * // Local timezone: GMT-6 (CST)
126
+ *
127
+ * // WRONG ❌
128
+ * new Date('2026-02-21')
129
+ * // Parsed as UTC: 2026-02-21T00:00:00Z
130
+ * // In local timezone: Feb 20, 2026 6:00 PM (previous day!)
131
+ *
132
+ * // CORRECT ✅
133
+ * parseISODate('2026-02-21')
134
+ * // Returns: 2026-02-21T00:00:00 local time
135
+ * ```
136
+ *
137
+ * Implementation: Parse components and construct Date in local timezone
138
+ */
139
+ parseISODate(isoDate: string): Date | null;
140
+ /**
141
+ * Get week start day for locale
142
+ *
143
+ * Default: Sunday (0) for most locales
144
+ * Monday (1) for Europe, ISO 8601
145
+ *
146
+ * Implementation: Simple locale detection
147
+ * For advanced needs, use Intl.Locale or external library
148
+ */
149
+ getWeekStart(locale?: string): 0 | 1 | 2 | 3 | 4 | 5 | 6;
150
+ static ɵfac: i0.ɵɵFactoryDeclaration<NativeDateAdapter, never>;
151
+ static ɵprov: i0.ɵɵInjectableDeclaration<NativeDateAdapter>;
152
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Provider Functions for Built-in Presets
3
+ *
4
+ * Version: 3.6.0
5
+ *
6
+ * Automatic registration of built-in date range presets.
7
+ * These providers ensure backward compatibility by auto-registering
8
+ * all standard presets (TODAY, LAST_7_DAYS, THIS_MONTH, etc.)
9
+ *
10
+ * USAGE IN LIBRARY (Internal):
11
+ * Built-in presets are registered automatically via Angular providers.
12
+ * Library consumers don't need to do anything.
13
+ *
14
+ * USAGE IN APP (Custom Presets):
15
+ * ```typescript
16
+ * // app.config.ts
17
+ * export const appConfig: ApplicationConfig = {
18
+ * providers: [
19
+ * // ... other providers
20
+ * provideCustomPresets([
21
+ * {
22
+ * key: 'THIS_FISCAL_QUARTER',
23
+ * resolve: (clock, adapter) => {
24
+ * const now = clock.now();
25
+ * // ... fiscal logic
26
+ * return { start, end };
27
+ * }
28
+ * }
29
+ * ])
30
+ * ]
31
+ * };
32
+ * ```
33
+ */
34
+ import { EnvironmentProviders } from '@angular/core';
35
+ import { RangePresetPlugin } from './range-preset.plugin';
36
+ /**
37
+ * Provide built-in date range presets
38
+ *
39
+ * This provider is automatically included in the library's root providers.
40
+ * Library consumers don't need to add this manually.
41
+ *
42
+ * @returns EnvironmentProviders for built-in presets
43
+ *
44
+ * @internal
45
+ */
46
+ export declare function provideBuiltInPresets(): EnvironmentProviders;
47
+ /**
48
+ * Provide custom date range presets
49
+ *
50
+ * Use this to register your own industry-specific presets:
51
+ * - Fiscal presets
52
+ * - Hotel/hospitality presets
53
+ * - Logistics presets
54
+ * - Custom business logic
55
+ *
56
+ * @param presets - Array of custom RangePresetPlugin implementations
57
+ * @returns EnvironmentProviders for custom presets
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * // Fiscal presets
62
+ * const FISCAL_PRESETS: RangePresetPlugin[] = [
63
+ * {
64
+ * key: 'THIS_FISCAL_QUARTER',
65
+ * resolve: (clock, adapter) => {
66
+ * const now = clock.now();
67
+ * const month = adapter.getMonth(now);
68
+ *
69
+ * // Fiscal year starts April (month 3)
70
+ * const fiscalMonth = (month + 9) % 12;
71
+ * const quarterStart = Math.floor(fiscalMonth / 3) * 3;
72
+ * const calendarMonth = (quarterStart - 9 + 12) % 12;
73
+ *
74
+ * const year = adapter.getYear(now);
75
+ * const fiscalYear = month < 3 ? year - 1 : year;
76
+ *
77
+ * const start = new Date(fiscalYear, calendarMonth, 1);
78
+ * const end = new Date(fiscalYear, calendarMonth + 3, 0);
79
+ *
80
+ * return {
81
+ * start: adapter.normalize(start),
82
+ * end: adapter.normalize(end)
83
+ * };
84
+ * }
85
+ * },
86
+ * {
87
+ * key: 'FISCAL_YEAR_TO_DATE',
88
+ * resolve: (clock, adapter) => {
89
+ * const now = clock.now();
90
+ * const month = adapter.getMonth(now);
91
+ * const year = adapter.getYear(now);
92
+ *
93
+ * // Fiscal year starts April 1
94
+ * const fiscalYearStart = month >= 3
95
+ * ? new Date(year, 3, 1)
96
+ * : new Date(year - 1, 3, 1);
97
+ *
98
+ * return {
99
+ * start: adapter.normalize(fiscalYearStart),
100
+ * end: adapter.normalize(now)
101
+ * };
102
+ * }
103
+ * }
104
+ * ];
105
+ *
106
+ * // In app.config.ts
107
+ * export const appConfig: ApplicationConfig = {
108
+ * providers: [
109
+ * provideCustomPresets(FISCAL_PRESETS)
110
+ * ]
111
+ * };
112
+ *
113
+ * // Use in components
114
+ * store.applyPreset('THIS_FISCAL_QUARTER');
115
+ * store.applyPreset('FISCAL_YEAR_TO_DATE');
116
+ * ```
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // Hotel presets
121
+ * const HOTEL_PRESETS: RangePresetPlugin[] = [
122
+ * {
123
+ * key: 'CHECK_IN_WEEK',
124
+ * resolve: (clock, adapter) => {
125
+ * const now = clock.now();
126
+ * const dayOfWeek = adapter.getDayOfWeek(now);
127
+ *
128
+ * // Check-in week: Friday to Friday
129
+ * const daysToNextFriday = dayOfWeek <= 5
130
+ * ? 5 - dayOfWeek
131
+ * : 7 - dayOfWeek + 5;
132
+ *
133
+ * const nextFriday = adapter.addDays(now, daysToNextFriday);
134
+ * const followingFriday = adapter.addDays(nextFriday, 7);
135
+ *
136
+ * return { start: nextFriday, end: followingFriday };
137
+ * }
138
+ * },
139
+ * {
140
+ * key: 'NEXT_30_NIGHTS',
141
+ * resolve: (clock, adapter) => {
142
+ * const now = clock.now();
143
+ * const tomorrow = adapter.addDays(now, 1);
144
+ * const end = adapter.addDays(tomorrow, 30);
145
+ * return { start: tomorrow, end };
146
+ * }
147
+ * }
148
+ * ];
149
+ *
150
+ * providers: [provideCustomPresets(HOTEL_PRESETS)]
151
+ * ```
152
+ */
153
+ export declare function provideCustomPresets(presets: RangePresetPlugin[]): EnvironmentProviders;
154
+ /**
155
+ * Provide preset package
156
+ *
157
+ * Convenience function for external preset packages.
158
+ *
159
+ * @param packageName - Name of the preset package (for logging)
160
+ * @param presets - Array of presets from the package
161
+ * @returns EnvironmentProviders
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // @acme/fiscal-presets package
166
+ * export function provideFiscalPresets(): EnvironmentProviders {
167
+ * return providePresetPackage('@acme/fiscal-presets', FISCAL_PRESETS);
168
+ * }
169
+ *
170
+ * // In app
171
+ * import { provideFiscalPresets } from '@acme/fiscal-presets';
172
+ *
173
+ * providers: [provideFiscalPresets()]
174
+ * ```
175
+ */
176
+ export declare function providePresetPackage(packageName: string, presets: RangePresetPlugin[]): EnvironmentProviders;
@@ -0,0 +1,181 @@
1
+ import { RangePresetPlugin } from './range-preset.plugin';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Preset Registry Service
5
+ *
6
+ * Manages registration and retrieval of date range preset plugins.
7
+ *
8
+ * DESIGN:
9
+ * - Uses Map for O(1) lookups
10
+ * - Immutable getAll() returns copy
11
+ * - Supports plugin override (last registration wins)
12
+ * - Validates plugins before registration
13
+ */
14
+ export declare class PresetRegistry {
15
+ /**
16
+ * Internal map of registered presets
17
+ * Key: preset key (e.g., 'TODAY', 'LAST_7_DAYS')
18
+ * Value: RangePresetPlugin instance
19
+ */
20
+ private readonly presets;
21
+ /**
22
+ * Register a date range preset plugin
23
+ *
24
+ * If a preset with the same key already exists, it will be overridden.
25
+ * This is useful for:
26
+ * - Testing (override built-in presets with mocks)
27
+ * - Customization (replace default behavior)
28
+ * - Hot-reload scenarios
29
+ *
30
+ * @param plugin - The preset plugin to register
31
+ * @throws Error if plugin is invalid (missing key or resolve function)
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * registry.register({
36
+ * key: 'LAST_BUSINESS_WEEK',
37
+ * resolve: (clock, adapter) => {
38
+ * const now = clock.now();
39
+ * const dayOfWeek = adapter.getDayOfWeek(now);
40
+ *
41
+ * // Go back to last Friday
42
+ * const daysToLastFriday = dayOfWeek === 0 ? 2 : (dayOfWeek + 2) % 7;
43
+ * const lastFriday = adapter.addDays(now, -daysToLastFriday);
44
+ *
45
+ * // Business week: Monday to Friday
46
+ * const monday = adapter.addDays(lastFriday, -4);
47
+ *
48
+ * return { start: monday, end: lastFriday };
49
+ * }
50
+ * });
51
+ * ```
52
+ */
53
+ register(plugin: RangePresetPlugin): void;
54
+ /**
55
+ * Register multiple preset plugins at once
56
+ *
57
+ * Convenience method for bulk registration.
58
+ * Useful when importing preset packages.
59
+ *
60
+ * @param plugins - Array of preset plugins to register
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * import { FISCAL_PRESETS } from '@acme/fiscal-presets';
65
+ *
66
+ * registry.registerAll(FISCAL_PRESETS);
67
+ * ```
68
+ */
69
+ registerAll(plugins: RangePresetPlugin[]): void;
70
+ /**
71
+ * Get a preset plugin by key
72
+ *
73
+ * @param key - Preset key (case-insensitive, e.g., 'today' or 'TODAY')
74
+ * @returns The preset plugin or undefined if not found
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const preset = registry.get('LAST_7_DAYS');
79
+ * if (preset) {
80
+ * const range = preset.resolve(clock, adapter);
81
+ * console.log('Range:', range);
82
+ * }
83
+ * ```
84
+ */
85
+ get(key: string): RangePresetPlugin | undefined;
86
+ /**
87
+ * Check if a preset exists in the registry
88
+ *
89
+ * @param key - Preset key (case-insensitive)
90
+ * @returns true if preset exists, false otherwise
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * if (registry.has('THIS_FISCAL_QUARTER')) {
95
+ * store.applyPreset('THIS_FISCAL_QUARTER');
96
+ * } else {
97
+ * console.error('Fiscal quarter preset not registered');
98
+ * }
99
+ * ```
100
+ */
101
+ has(key: string): boolean;
102
+ /**
103
+ * Get all registered preset plugins
104
+ *
105
+ * Returns a NEW array (immutable) to prevent external modification.
106
+ *
107
+ * @returns Array of all registered preset plugins
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const allPresets = registry.getAll();
112
+ * console.log('Available presets:');
113
+ * allPresets.forEach(preset => {
114
+ * console.log(`- ${preset.key}`);
115
+ * });
116
+ * ```
117
+ */
118
+ getAll(): RangePresetPlugin[];
119
+ /**
120
+ * Get all preset keys
121
+ *
122
+ * Convenience method to list available preset identifiers.
123
+ *
124
+ * @returns Array of preset keys (uppercase)
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const keys = registry.getAllKeys();
129
+ * // ['TODAY', 'YESTERDAY', 'LAST_7_DAYS', 'THIS_MONTH', ...]
130
+ * ```
131
+ */
132
+ getAllKeys(): string[];
133
+ /**
134
+ * Get count of registered presets
135
+ *
136
+ * @returns Number of registered presets
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * console.log(`${registry.count()} presets available`);
141
+ * ```
142
+ */
143
+ count(): number;
144
+ /**
145
+ * Unregister a preset plugin
146
+ *
147
+ * Useful for:
148
+ * - Testing cleanup
149
+ * - Dynamic preset management
150
+ * - Removing deprecated presets
151
+ *
152
+ * @param key - Preset key to remove (case-insensitive)
153
+ * @returns true if preset was removed, false if it didn't exist
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * registry.unregister('MY_TEMPORARY_PRESET');
158
+ * ```
159
+ */
160
+ unregister(key: string): boolean;
161
+ /**
162
+ * Clear all registered presets
163
+ *
164
+ * ⚠️ USE WITH CAUTION: This removes ALL presets including built-ins.
165
+ *
166
+ * Useful for:
167
+ * - Test teardown
168
+ * - Complete re-initialization scenarios
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * // In test cleanup
173
+ * afterEach(() => {
174
+ * registry.clear();
175
+ * });
176
+ * ```
177
+ */
178
+ clear(): void;
179
+ static ɵfac: i0.ɵɵFactoryDeclaration<PresetRegistry, never>;
180
+ static ɵprov: i0.ɵɵInjectableDeclaration<PresetRegistry>;
181
+ }
@@ -1,7 +1,7 @@
1
+ import * as i0 from "@angular/core";
1
2
  /**
2
- * Headless Preset Engine
3
- * Pure functions that resolve date ranges WITHOUT render dependency
4
- * Perfect for SSR, global state, dashboard filters
3
+ * @deprecated Use RangePresetPlugin from './range-preset.plugin' instead
4
+ * Kept for backward compatibility
5
5
  */
6
6
  export interface RangePreset {
7
7
  /**
@@ -18,37 +18,107 @@ export interface PresetRange {
18
18
  end: string;
19
19
  }
20
20
  /**
21
- * Registry of built-in presets
22
- * Can be extended by consumers
21
+ * Preset Engine - Plugin-Driven Architecture
22
+ *
23
+ * ARCHITECTURE (v3.6.0):
24
+ * - NO longer contains presets internally
25
+ * - Uses PresetRegistry for plugin management
26
+ * - Injects DateClock for SSR-safe time
27
+ * - Injects DateAdapter for timezone-safe operations
28
+ * - Follows Open/Closed Principle
29
+ *
30
+ * BACKWARD COMPATIBILITY:
31
+ * - Old API unchanged: resolve(), register(), getPresetKeys()
32
+ * - Built-in presets auto-registered via provider
33
+ * - Existing code continues to work
34
+ *
35
+ * EXTENSIBILITY:
36
+ * ```typescript
37
+ * // Register custom preset via registry
38
+ * const registry = inject(PresetRegistry);
39
+ * registry.register({
40
+ * key: 'MY_PRESET',
41
+ * resolve: (clock, adapter) => {
42
+ * const now = clock.now();
43
+ * return { start: now, end: now };
44
+ * }
45
+ * });
46
+ *
47
+ * // Use via engine
48
+ * const engine = inject(PresetEngine);
49
+ * const range = engine.resolve('MY_PRESET');
50
+ * ```
23
51
  */
24
52
  export declare class PresetEngine {
25
- private presets;
53
+ private clock;
54
+ private adapter;
55
+ private registry;
26
56
  constructor();
27
57
  /**
28
58
  * Register a custom preset
59
+ *
60
+ * @deprecated Use PresetRegistry.register() directly for new code
61
+ * Kept for backward compatibility
62
+ *
63
+ * @param key - Preset key (e.g., 'MY_CUSTOM_PRESET')
64
+ * @param preset - Legacy RangePreset object
29
65
  */
30
66
  register(key: string, preset: RangePreset): void;
31
67
  /**
32
68
  * Resolve a preset to date range
69
+ *
70
+ * Plugin Architecture:
71
+ * 1. Looks up plugin in PresetRegistry
72
+ * 2. Calls plugin.resolve(clock, adapter)
73
+ * 3. Returns ISO date range
74
+ *
75
+ * SSR Note: Uses injected DateClock for deterministic resolution
76
+ * Timezone Note: Uses injected DateAdapter for consistent operations
77
+ *
78
+ * @param key - Preset key (e.g., 'TODAY', 'LAST_7_DAYS')
79
+ * @param now - Optional override for current date (defaults to clock.now())
80
+ * @returns ISO date range or null if preset not found
33
81
  */
34
82
  resolve(key: string, now?: Date): PresetRange | null;
35
83
  /**
36
84
  * Get all available preset keys
85
+ *
86
+ * Delegates to PresetRegistry
87
+ *
88
+ * @returns Array of registered preset keys
37
89
  */
38
90
  getPresetKeys(): string[];
39
91
  /**
40
- * Register all built-in presets
92
+ * Check if a preset exists
93
+ *
94
+ * @param key - Preset key to check
95
+ * @returns true if preset is registered
41
96
  */
42
- private registerBuiltInPresets;
97
+ hasPreset(key: string): boolean;
98
+ static ɵfac: i0.ɵɵFactoryDeclaration<PresetEngine, never>;
99
+ static ɵprov: i0.ɵɵInjectableDeclaration<PresetEngine>;
43
100
  }
44
101
  /**
45
102
  * Create a custom preset from a function
103
+ *
104
+ * @deprecated Use RangePresetPlugin interface instead
105
+ * Kept for backward compatibility
46
106
  */
47
107
  export declare function createPreset(resolver: (now: Date) => {
48
108
  start: Date;
49
109
  end: Date;
50
110
  }): RangePreset;
51
111
  /**
52
- * Singleton preset engine instance
112
+ * @deprecated Use dependency injection instead:
113
+ * ```typescript
114
+ * private engine = inject(PresetEngine);
115
+ * ```
116
+ *
117
+ * Singleton preset engine instance for backward compatibility
118
+ *
119
+ * WARNING: This singleton uses SystemClock directly and is NOT SSR-safe.
120
+ * For SSR applications, inject PresetEngine and override DATE_CLOCK token.
121
+ *
122
+ * This export will be removed in v4.0.0
53
123
  */
54
124
  export declare const presetEngine: PresetEngine;