ngxsmk-datepicker 2.3.1 → 2.4.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 +942 -922
- package/docs/API.md +2423 -2413
- package/docs/COMPATIBILITY.md +471 -471
- package/docs/INTEGRATION.md +703 -703
- package/docs/IONIC_INTEGRATION.md +228 -228
- package/docs/LOCALE-GUIDE.md +300 -300
- package/docs/PLUGIN-ARCHITECTURE.md +930 -930
- package/docs/SSR-EXAMPLE.md +427 -427
- package/docs/THEME-TOKENS.md +324 -324
- package/docs/TIMEZONE.md +307 -307
- package/docs/extension-points.md +419 -419
- package/docs/signal-forms.md +600 -600
- package/docs/signals.md +266 -266
- package/docs/ssr.md +305 -305
- package/package.json +106 -104
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/schema.json +14 -0
- package/schematics/tsconfig.json +17 -0
- package/CHANGELOG.md +0 -1246
- package/LICENSE +0 -21
- package/MIGRATION.md +0 -1794
- package/docs/FEATURE_SCOPING.md +0 -60
- package/docs/IONIC_TESTING.md +0 -148
- package/docs/REFACTOR_PLAN.md +0 -38
- package/docs/SEO.md +0 -214
- package/fesm2022/ngxsmk-datepicker.mjs +0 -14693
- package/types/ngxsmk-datepicker.d.ts +0 -2489
package/docs/API.md
CHANGED
|
@@ -1,2413 +1,2423 @@
|
|
|
1
|
-
# Public API Documentation
|
|
2
|
-
|
|
3
|
-
This document describes the stable public API of ngxsmk-datepicker with comprehensive real-world examples. APIs marked as **stable** are guaranteed to remain backward-compatible within the same major version. APIs marked as **experimental** may change in future releases.
|
|
4
|
-
|
|
5
|
-
**Version**: 2.
|
|
6
|
-
|
|
7
|
-
## Stable vs experimental
|
|
8
|
-
|
|
9
|
-
- **Stable**: All inputs/outputs in the reference tables below are stable unless marked **Experimental**. Stable APIs will not change in a breaking way within the same major version.
|
|
10
|
-
- **Experimental**: Marked in the tables; may change in minor releases. Use with awareness.
|
|
11
|
-
|
|
12
|
-
## Versioning Policy
|
|
13
|
-
|
|
14
|
-
ngxsmk-datepicker follows [Semantic Versioning](https://semver.org/):
|
|
15
|
-
|
|
16
|
-
- **MAJOR** version for incompatible API changes
|
|
17
|
-
- **MINOR** version for backwards-compatible functionality additions
|
|
18
|
-
- **PATCH** version for backwards-compatible bug fixes
|
|
19
|
-
|
|
20
|
-
## Breaking Changes Policy
|
|
21
|
-
|
|
22
|
-
Breaking changes will only occur in major version releases. When breaking changes are introduced:
|
|
23
|
-
|
|
24
|
-
1. A migration guide will be provided in `MIGRATION.md`
|
|
25
|
-
2. Deprecated APIs will be marked with `@deprecated` JSDoc tags
|
|
26
|
-
3. Deprecated APIs will remain for at least one major version before removal
|
|
27
|
-
4. Clear upgrade instructions will be provided
|
|
28
|
-
|
|
29
|
-
## Exported Components
|
|
30
|
-
|
|
31
|
-
### NgxsmkDatepickerComponent
|
|
32
|
-
|
|
33
|
-
**Status**: Stable
|
|
34
|
-
|
|
35
|
-
The main datepicker component.
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
#### Complete Inputs/Outputs Reference
|
|
42
|
-
|
|
43
|
-
##### Inputs
|
|
44
|
-
|
|
45
|
-
| Input | Type | Default | Status | Description | Example |
|
|
46
|
-
|-------|------|---------|--------|-------------|---------|
|
|
47
|
-
| `mode` | `'single' \| 'range' \| 'multiple'` | `'single'` | Stable | Selection mode | `mode="single"` or `[mode]="'range'"` |
|
|
48
|
-
| `value` | `DatepickerValue` | `null` | Stable | Current value (one-way binding). For two-way binding with a signal, use `[value]="dateSignal()"` and `(valueChange)="dateSignal.set($event)"`. | `[value]="selectedDate"` |
|
|
49
|
-
| `field` | `SignalFormField \| SignalFormFieldConfig` | `null` | Stable | Signal form field (Angular 21+). Automatically tracks dirty state when using `[field]` binding. Supports direct signals, signals with properties, and resolution of nested signals. | `[field]="myForm.dateField"` |
|
|
50
|
-
| `placeholder` | `string \| null` | `'Select Date'` or `'Select Time'` | Stable | Input placeholder text | `placeholder="Choose a date"` |
|
|
51
|
-
| `inputId` | `string` | `''` | Stable | Custom ID for the input element | `inputId="my-date-input"` |
|
|
52
|
-
| `name` | `string` | `''` | Stable | Name attribute for the input element | `name="my-date-field"` |
|
|
53
|
-
| `autocomplete` | `string` | `'off'` | Stable | Autocomplete attribute for the input element | `autocomplete="on"` |
|
|
54
|
-
| `aria-invalid` | `boolean` | `false` | Stable | Sets aria-invalid attribute on the input | `[aria-invalid]="true"` |
|
|
55
|
-
| `disabledState` | `boolean` | `false` | Stable | Disable the datepicker | `[disabledState]="isDisabled"` |
|
|
56
|
-
| `minDate` | `DateInput \| null` | `null` | Stable | Minimum selectable date | `[minDate]="today"` |
|
|
57
|
-
| `maxDate` | `DateInput \| null` | `null` | Stable | Maximum selectable date | `[maxDate]="maxBookingDate"` |
|
|
58
|
-
| `disabledDates` | `(string \| Date)[]` | `[]` | Stable | Array of disabled dates | `[disabledDates]="['10/21/2025', new Date()]"` |
|
|
59
|
-
| `isInvalidDate` | `(date: Date) => boolean` | `() => false` | Stable | Custom date validation function | `[isInvalidDate]="isWeekend"` |
|
|
60
|
-
| `locale` | `string` | `'en-US'` | Stable | Locale for formatting | `locale="de-DE"` or `[locale]="'fr-FR'"` |
|
|
61
|
-
| `theme` | `'light' \| 'dark'` | `'light'` | Stable | Color theme | `[theme]="'dark'"` |
|
|
62
|
-
| `inline` | `boolean \| 'always' \| 'auto'` | `false` | Stable | Inline display mode | `[inline]="true"` or `inline="auto"` |
|
|
63
|
-
| `showTime` | `boolean` | `false` | Stable | Show time selection | `[showTime]="true"` |
|
|
64
|
-
| `timeOnly` | `boolean` | `false` | Stable | Display time picker only (no calendar). Automatically enables `showTime`. | `[timeOnly]="true"` |
|
|
65
|
-
| `allowTyping` | `boolean` | `false` | Stable | Enable manual typing in the input field. Required for native validation. | `[allowTyping]="true"` |
|
|
66
|
-
| `displayFormat` | `string` | `null` | Stable | Custom date format string (e.g., 'MM/DD/YYYY'). | `displayFormat="DD.MM.YYYY"` |
|
|
67
|
-
| `showCalendarButton` | `boolean` | `false` | Stable | Show/hide the calendar icon button. When `false`, users can still open calendar by clicking the input field. | `[showCalendarButton]="true"` |
|
|
68
|
-
| `minuteInterval` | `number` | `1` | Stable | Minute selection interval | `[minuteInterval]="15"` |
|
|
69
|
-
| `showSeconds` | `boolean` | `false` | Stable | Show seconds in time selection | `[showSeconds]="true"` |
|
|
70
|
-
| `secondInterval` | `number` | `1` | Stable | Second selection interval (e.g. 1, 5, 15) | `[secondInterval]="5"` |
|
|
71
|
-
| `use24Hour` | `boolean` | `false` | Stable | Use 24-hour time format (no AM/PM) | `[use24Hour]="true"` |
|
|
72
|
-
| `showRanges` | `boolean` | `true` | Stable | Show predefined ranges (range mode) | `[showRanges]="true"` |
|
|
73
|
-
| `ranges` | `DateRange` | `null` | Stable | Predefined date ranges | `[ranges]="quickRanges"` |
|
|
74
|
-
| `holidayProvider` | `HolidayProvider \| null` | `null` | Stable | Custom holiday provider | `[holidayProvider]="myHolidayProvider"` |
|
|
75
|
-
| `disableHolidays` | `boolean` | `false` | Stable | Disable holiday dates from selection | `[disableHolidays]="true"` |
|
|
76
|
-
| `enableGoogleCalendar` | `boolean` | `false` | Stable | Enable seamless Google Calendar integration and sync. | `[enableGoogleCalendar]="true"` |
|
|
77
|
-
| `googleClientId` | `string \| null` | `null` | Stable | Google API OAuth 2.0 Web Client ID for calendar sync. | `[googleClientId]="'my-client-id'"` |
|
|
78
|
-
| `startAt` | `DateInput \| null` | `null` | Stable | Initial calendar view date | `[startAt]="nextMonth"` |
|
|
79
|
-
| `weekStart` | `number \| null` | `null` | Stable | Override week start day (0=Sunday, 1=Monday, etc.) | `[weekStart]="1"` |
|
|
80
|
-
| `yearRange` | `number` | `10` | Stable | Years to show in year selector | `[yearRange]="20"` |
|
|
81
|
-
| `classes` | `DatepickerClasses \| null` | `null` | Stable | Custom CSS classes. Note: When used as a Web Component, this can also be passed as a JSON string. | `[classes]="{ inputGroup: 'custom-class' }"` |
|
|
82
|
-
| `hooks` | `DatepickerHooks \| null` | `null` | Stable | Extension points for customization | `[hooks]="customHooks"` |
|
|
83
|
-
| `enableKeyboardShortcuts` | `boolean` | `true` | Stable | Enable/disable keyboard shortcuts. Press '?' for help. | `[enableKeyboardShortcuts]="false"` |
|
|
84
|
-
| `customShortcuts` | `{ [key: string]: (context: KeyboardShortcutContext) => boolean } \| null` | `null` | Stable | Custom keyboard shortcuts map | `[customShortcuts]="myShortcuts"` |
|
|
85
|
-
| `autoApplyClose` | `boolean` | `false` | Stable | Auto-close calendar after selection | `[autoApplyClose]="true"` |
|
|
86
|
-
| `clearLabel` | `string` | `'Clear'` | Stable | Custom label for clear button | `clearLabel="Reset"` |
|
|
87
|
-
| `closeLabel` | `string` | `'Close'` | Stable | Custom label for close button | `closeLabel="Done"` |
|
|
88
|
-
| `prevMonthAriaLabel` | `string` | `'Previous month'` | Stable | ARIA label for previous month button | `prevMonthAriaLabel="Go to previous month"` |
|
|
89
|
-
| `nextMonthAriaLabel` | `string` | `'Next month'` | Stable | ARIA label for next month button | `nextMonthAriaLabel="Go to next month"` |
|
|
90
|
-
| `clearAriaLabel` | `string` | `'Clear selection'` | Stable | ARIA label for clear button | `clearAriaLabel="Clear selected date"` |
|
|
91
|
-
| `closeAriaLabel` | `string` | `'Close calendar'` | Stable | ARIA label for close button | `closeAriaLabel="Close date picker"` |
|
|
92
|
-
| `rtl` | `boolean \| null` | `null` | Stable | Right-to-left layout (auto-detects from locale or document.dir) | `[rtl]="true"` |
|
|
93
|
-
| `userAriaDescribedBy` | `string` | `''` | Stable | Aria describedby ID provided by the user or the parent form field. Required for seamless Angular Material integration. | `[userAriaDescribedBy]="'my-help-id'"` |
|
|
94
|
-
| `timeRangeMode` | `boolean` | `false` | Stable | In range mode with `showTime`, show separate start/end time pickers (from/to). | `[timeRangeMode]="true"` |
|
|
95
|
-
| `timezone` | `string` | `undefined` | Stable | IANA timezone name for display and value handling (e.g. `America/New_York`, `UTC`). | `timezone="Europe/London"` |
|
|
96
|
-
| `useNativePicker` | `boolean` | `false` | Stable | Use the browser native date/time input when supported (e.g. on mobile). | `[useNativePicker]="true"` |
|
|
97
|
-
| `autoDetectMobile` | `boolean` | `true` | Stable | When true, append popover to body and use mobile styles on small/touch devices. | `[autoDetectMobile]="false"` |
|
|
98
|
-
| `appendToBody` | `boolean` | `false` | Stable | Append calendar popover to `document.body`. Use inside modals, dialogs, or overlays so the calendar positions correctly and is not clipped. Recommended when using the datepicker in a modal. | `[appendToBody]="true"` |
|
|
99
|
-
| `mobileModalStyle` | `'bottom-sheet' \| 'center' \| 'fullscreen'` | `'bottom-sheet'` | Stable | How the calendar is shown on mobile (when detected). | `mobileModalStyle="fullscreen"` |
|
|
100
|
-
| `mobileTimePickerStyle` | `'wheel' \| 'slider' \| 'native'` | `'slider'` | Stable | Time picker style on mobile. | `mobileTimePickerStyle="wheel"` |
|
|
101
|
-
| `mobileTheme` | `'compact' \| 'comfortable' \| 'spacious'` | `'comfortable'` | Stable | Density of the calendar on mobile. | `mobileTheme="compact"` |
|
|
102
|
-
| `enableHapticFeedback` | `boolean` | `false` | Stable | Trigger haptic feedback on supported devices when selecting/clearing. | `[enableHapticFeedback]="true"` |
|
|
103
|
-
| `enablePullToRefresh` | `boolean` | `false` | Experimental | Reserve for future pull-to-refresh on mobile. | — |
|
|
104
|
-
| `enableVoiceInput` | `boolean` | `false` | Experimental | Reserve for future voice input. | — |
|
|
105
|
-
| `calendars` | `number` | `1` | Stable | Multi-calendar display layout (1, 2, or 3 months side-by-side) | `[calendars]="2"` |
|
|
106
|
-
| `rangePresetFactory` | `(today: Date) => DatePreset[]` | `null` | Stable | Callback function supplying dynamic range presets | `[rangePresetFactory]="myPresetFactory"` |
|
|
107
|
-
| `showTimezoneSelector` | `boolean` | `false` | Stable | Enables searchable timezone selection dropdown UI | `[showTimezoneSelector]="true"` |
|
|
108
|
-
| `enableNaturalLanguage` | `boolean` | `false` | Stable | Enables relative natural language input parsing (e.g. "today", "tomorrow") | `[enableNaturalLanguage]="true"` |
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
|
113
|
-
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
|
125
|
-
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
</
|
|
525
|
-
</ion-
|
|
526
|
-
</ion-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} from '
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
<
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
</ngxsmk-datepicker>
|
|
591
|
-
</ion-item>
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
} from '@
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
} from '@
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
@
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
} from '@
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
</ion-
|
|
828
|
-
</ion-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
<
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
@
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
`
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
this.
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (month ===
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
},
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
```
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
`
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
--datepicker-
|
|
1668
|
-
--datepicker-
|
|
1669
|
-
--datepicker-
|
|
1670
|
-
--datepicker-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
**
|
|
1692
|
-
- `--datepicker-
|
|
1693
|
-
- `--datepicker-
|
|
1694
|
-
- `--datepicker-
|
|
1695
|
-
- `--datepicker-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
- `--datepicker-
|
|
1699
|
-
- `--datepicker-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
- `--datepicker-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
- `--datepicker-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
- `--datepicker-
|
|
1709
|
-
- `--datepicker-
|
|
1710
|
-
- `--datepicker-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
- `--datepicker-
|
|
1716
|
-
- `--datepicker-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
- `--datepicker-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
--datepicker-primary-
|
|
1740
|
-
--datepicker-
|
|
1741
|
-
--datepicker-
|
|
1742
|
-
--datepicker-
|
|
1743
|
-
--datepicker-text-color: #
|
|
1744
|
-
--datepicker-
|
|
1745
|
-
--datepicker-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
`
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
--datepicker-
|
|
1822
|
-
--datepicker-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
<
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
| Date
|
|
2213
|
-
|
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
###
|
|
2229
|
-
|
|
2230
|
-
**Status**: Stable
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
```typescript
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
}
|
|
2296
|
-
```
|
|
2297
|
-
|
|
2298
|
-
###
|
|
2299
|
-
|
|
2300
|
-
**Status**: Stable
|
|
2301
|
-
|
|
2302
|
-
```typescript
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
###
|
|
2345
|
-
|
|
2346
|
-
**Status**: Stable
|
|
2347
|
-
|
|
2348
|
-
```typescript
|
|
2349
|
-
// From './lib/utils/
|
|
2350
|
-
export function
|
|
2351
|
-
export function
|
|
2352
|
-
```
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
```
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
##
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
1
|
+
# Public API Documentation
|
|
2
|
+
|
|
3
|
+
This document describes the stable public API of ngxsmk-datepicker with comprehensive real-world examples. APIs marked as **stable** are guaranteed to remain backward-compatible within the same major version. APIs marked as **experimental** may change in future releases.
|
|
4
|
+
|
|
5
|
+
**Version**: 2.4.0+ (includes unreleased `main` additions) | **Last updated**: July 11, 2026
|
|
6
|
+
|
|
7
|
+
## Stable vs experimental
|
|
8
|
+
|
|
9
|
+
- **Stable**: All inputs/outputs in the reference tables below are stable unless marked **Experimental**. Stable APIs will not change in a breaking way within the same major version.
|
|
10
|
+
- **Experimental**: Marked in the tables; may change in minor releases. Use with awareness.
|
|
11
|
+
|
|
12
|
+
## Versioning Policy
|
|
13
|
+
|
|
14
|
+
ngxsmk-datepicker follows [Semantic Versioning](https://semver.org/):
|
|
15
|
+
|
|
16
|
+
- **MAJOR** version for incompatible API changes
|
|
17
|
+
- **MINOR** version for backwards-compatible functionality additions
|
|
18
|
+
- **PATCH** version for backwards-compatible bug fixes
|
|
19
|
+
|
|
20
|
+
## Breaking Changes Policy
|
|
21
|
+
|
|
22
|
+
Breaking changes will only occur in major version releases. When breaking changes are introduced:
|
|
23
|
+
|
|
24
|
+
1. A migration guide will be provided in `MIGRATION.md`
|
|
25
|
+
2. Deprecated APIs will be marked with `@deprecated` JSDoc tags
|
|
26
|
+
3. Deprecated APIs will remain for at least one major version before removal
|
|
27
|
+
4. Clear upgrade instructions will be provided
|
|
28
|
+
|
|
29
|
+
## Exported Components
|
|
30
|
+
|
|
31
|
+
### NgxsmkDatepickerComponent
|
|
32
|
+
|
|
33
|
+
**Status**: Stable
|
|
34
|
+
|
|
35
|
+
The main datepicker component.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Complete Inputs/Outputs Reference
|
|
42
|
+
|
|
43
|
+
##### Inputs
|
|
44
|
+
|
|
45
|
+
| Input | Type | Default | Status | Description | Example |
|
|
46
|
+
|-------|------|---------|--------|-------------|---------|
|
|
47
|
+
| `mode` | `'single' \| 'range' \| 'multiple'` | `'single'` | Stable | Selection mode | `mode="single"` or `[mode]="'range'"` |
|
|
48
|
+
| `value` | `DatepickerValue` | `null` | Stable | Current value (one-way binding). For two-way binding with a signal, use `[value]="dateSignal()"` and `(valueChange)="dateSignal.set($event)"`. | `[value]="selectedDate"` |
|
|
49
|
+
| `field` | `SignalFormField \| SignalFormFieldConfig` | `null` | Stable | Signal form field (Angular 21+). Automatically tracks dirty state when using `[field]` binding. Supports direct signals, signals with properties, and resolution of nested signals. | `[field]="myForm.dateField"` |
|
|
50
|
+
| `placeholder` | `string \| null` | `'Select Date'` or `'Select Time'` | Stable | Input placeholder text | `placeholder="Choose a date"` |
|
|
51
|
+
| `inputId` | `string` | `''` | Stable | Custom ID for the input element | `inputId="my-date-input"` |
|
|
52
|
+
| `name` | `string` | `''` | Stable | Name attribute for the input element | `name="my-date-field"` |
|
|
53
|
+
| `autocomplete` | `string` | `'off'` | Stable | Autocomplete attribute for the input element | `autocomplete="on"` |
|
|
54
|
+
| `aria-invalid` | `boolean` | `false` | Stable | Sets aria-invalid attribute on the input | `[aria-invalid]="true"` |
|
|
55
|
+
| `disabledState` | `boolean` | `false` | Stable | Disable the datepicker | `[disabledState]="isDisabled"` |
|
|
56
|
+
| `minDate` | `DateInput \| null` | `null` | Stable | Minimum selectable date | `[minDate]="today"` |
|
|
57
|
+
| `maxDate` | `DateInput \| null` | `null` | Stable | Maximum selectable date | `[maxDate]="maxBookingDate"` |
|
|
58
|
+
| `disabledDates` | `(string \| Date)[]` | `[]` | Stable | Array of disabled dates | `[disabledDates]="['10/21/2025', new Date()]"` |
|
|
59
|
+
| `isInvalidDate` | `(date: Date) => boolean` | `() => false` | Stable | Custom date validation function | `[isInvalidDate]="isWeekend"` |
|
|
60
|
+
| `locale` | `string` | `'en-US'` | Stable | Locale for formatting | `locale="de-DE"` or `[locale]="'fr-FR'"` |
|
|
61
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | Stable | Color theme | `[theme]="'dark'"` |
|
|
62
|
+
| `inline` | `boolean \| 'always' \| 'auto'` | `false` | Stable | Inline display mode | `[inline]="true"` or `inline="auto"` |
|
|
63
|
+
| `showTime` | `boolean` | `false` | Stable | Show time selection | `[showTime]="true"` |
|
|
64
|
+
| `timeOnly` | `boolean` | `false` | Stable | Display time picker only (no calendar). Automatically enables `showTime`. | `[timeOnly]="true"` |
|
|
65
|
+
| `allowTyping` | `boolean` | `false` | Stable | Enable manual typing in the input field. Required for native validation. | `[allowTyping]="true"` |
|
|
66
|
+
| `displayFormat` | `string` | `null` | Stable | Custom date format string (e.g., 'MM/DD/YYYY'). | `displayFormat="DD.MM.YYYY"` |
|
|
67
|
+
| `showCalendarButton` | `boolean` | `false` | Stable | Show/hide the calendar icon button. When `false`, users can still open calendar by clicking the input field. | `[showCalendarButton]="true"` |
|
|
68
|
+
| `minuteInterval` | `number` | `1` | Stable | Minute selection interval | `[minuteInterval]="15"` |
|
|
69
|
+
| `showSeconds` | `boolean` | `false` | Stable | Show seconds in time selection | `[showSeconds]="true"` |
|
|
70
|
+
| `secondInterval` | `number` | `1` | Stable | Second selection interval (e.g. 1, 5, 15) | `[secondInterval]="5"` |
|
|
71
|
+
| `use24Hour` | `boolean` | `false` | Stable | Use 24-hour time format (no AM/PM) | `[use24Hour]="true"` |
|
|
72
|
+
| `showRanges` | `boolean` | `true` | Stable | Show predefined ranges (range mode) | `[showRanges]="true"` |
|
|
73
|
+
| `ranges` | `DateRange` | `null` | Stable | Predefined date ranges | `[ranges]="quickRanges"` |
|
|
74
|
+
| `holidayProvider` | `HolidayProvider \| null` | `null` | Stable | Custom holiday provider | `[holidayProvider]="myHolidayProvider"` |
|
|
75
|
+
| `disableHolidays` | `boolean` | `false` | Stable | Disable holiday dates from selection | `[disableHolidays]="true"` |
|
|
76
|
+
| `enableGoogleCalendar` | `boolean` | `false` | Stable | Enable seamless Google Calendar integration and sync. | `[enableGoogleCalendar]="true"` |
|
|
77
|
+
| `googleClientId` | `string \| null` | `null` | Stable | Google API OAuth 2.0 Web Client ID for calendar sync. | `[googleClientId]="'my-client-id'"` |
|
|
78
|
+
| `startAt` | `DateInput \| null` | `null` | Stable | Initial calendar view date | `[startAt]="nextMonth"` |
|
|
79
|
+
| `weekStart` | `number \| null` | `null` | Stable | Override week start day (0=Sunday, 1=Monday, etc.) | `[weekStart]="1"` |
|
|
80
|
+
| `yearRange` | `number` | `10` | Stable | Years to show in year selector | `[yearRange]="20"` |
|
|
81
|
+
| `classes` | `DatepickerClasses \| null` | `null` | Stable | Custom CSS classes. Note: When used as a Web Component, this can also be passed as a JSON string. | `[classes]="{ inputGroup: 'custom-class' }"` |
|
|
82
|
+
| `hooks` | `DatepickerHooks \| null` | `null` | Stable | Extension points for customization | `[hooks]="customHooks"` |
|
|
83
|
+
| `enableKeyboardShortcuts` | `boolean` | `true` | Stable | Enable/disable keyboard shortcuts. Press '?' for help. | `[enableKeyboardShortcuts]="false"` |
|
|
84
|
+
| `customShortcuts` | `{ [key: string]: (context: KeyboardShortcutContext) => boolean } \| null` | `null` | Stable | Custom keyboard shortcuts map | `[customShortcuts]="myShortcuts"` |
|
|
85
|
+
| `autoApplyClose` | `boolean` | `false` | Stable | Auto-close calendar after selection | `[autoApplyClose]="true"` |
|
|
86
|
+
| `clearLabel` | `string` | `'Clear'` | Stable | Custom label for clear button | `clearLabel="Reset"` |
|
|
87
|
+
| `closeLabel` | `string` | `'Close'` | Stable | Custom label for close button | `closeLabel="Done"` |
|
|
88
|
+
| `prevMonthAriaLabel` | `string` | `'Previous month'` | Stable | ARIA label for previous month button | `prevMonthAriaLabel="Go to previous month"` |
|
|
89
|
+
| `nextMonthAriaLabel` | `string` | `'Next month'` | Stable | ARIA label for next month button | `nextMonthAriaLabel="Go to next month"` |
|
|
90
|
+
| `clearAriaLabel` | `string` | `'Clear selection'` | Stable | ARIA label for clear button | `clearAriaLabel="Clear selected date"` |
|
|
91
|
+
| `closeAriaLabel` | `string` | `'Close calendar'` | Stable | ARIA label for close button | `closeAriaLabel="Close date picker"` |
|
|
92
|
+
| `rtl` | `boolean \| null` | `null` | Stable | Right-to-left layout (auto-detects from locale or document.dir) | `[rtl]="true"` |
|
|
93
|
+
| `userAriaDescribedBy` | `string` | `''` | Stable | Aria describedby ID provided by the user or the parent form field. Required for seamless Angular Material integration. | `[userAriaDescribedBy]="'my-help-id'"` |
|
|
94
|
+
| `timeRangeMode` | `boolean` | `false` | Stable | In range mode with `showTime`, show separate start/end time pickers (from/to). | `[timeRangeMode]="true"` |
|
|
95
|
+
| `timezone` | `string` | `undefined` | Stable | IANA timezone name for display and value handling (e.g. `America/New_York`, `UTC`). | `timezone="Europe/London"` |
|
|
96
|
+
| `useNativePicker` | `boolean` | `false` | Stable | Use the browser native date/time input when supported (e.g. on mobile). | `[useNativePicker]="true"` |
|
|
97
|
+
| `autoDetectMobile` | `boolean` | `true` | Stable | When true, append popover to body and use mobile styles on small/touch devices. | `[autoDetectMobile]="false"` |
|
|
98
|
+
| `appendToBody` | `boolean` | `false` | Stable | Append calendar popover to `document.body`. Use inside modals, dialogs, or overlays so the calendar positions correctly and is not clipped. Recommended when using the datepicker in a modal. | `[appendToBody]="true"` |
|
|
99
|
+
| `mobileModalStyle` | `'bottom-sheet' \| 'center' \| 'fullscreen'` | `'bottom-sheet'` | Stable | How the calendar is shown on mobile (when detected). | `mobileModalStyle="fullscreen"` |
|
|
100
|
+
| `mobileTimePickerStyle` | `'wheel' \| 'slider' \| 'native'` | `'slider'` | Stable | Time picker style on mobile. | `mobileTimePickerStyle="wheel"` |
|
|
101
|
+
| `mobileTheme` | `'compact' \| 'comfortable' \| 'spacious'` | `'comfortable'` | Stable | Density of the calendar on mobile. | `mobileTheme="compact"` |
|
|
102
|
+
| `enableHapticFeedback` | `boolean` | `false` | Stable | Trigger haptic feedback on supported devices when selecting/clearing. | `[enableHapticFeedback]="true"` |
|
|
103
|
+
| `enablePullToRefresh` | `boolean` | `false` | Experimental | Reserve for future pull-to-refresh on mobile. | — |
|
|
104
|
+
| `enableVoiceInput` | `boolean` | `false` | Experimental | Reserve for future voice input. | — |
|
|
105
|
+
| `calendars` | `number` | `1` | Stable | Multi-calendar display layout (1, 2, or 3 months side-by-side) | `[calendars]="2"` |
|
|
106
|
+
| `rangePresetFactory` | `(today: Date) => DatePreset[]` | `null` | Stable | Callback function supplying dynamic range presets | `[rangePresetFactory]="myPresetFactory"` |
|
|
107
|
+
| `showTimezoneSelector` | `boolean` | `false` | Stable | Enables searchable timezone selection dropdown UI | `[showTimezoneSelector]="true"` |
|
|
108
|
+
| `enableNaturalLanguage` | `boolean` | `false` | Stable | Enables relative natural language input parsing (e.g. "today", "tomorrow") | `[enableNaturalLanguage]="true"` |
|
|
109
|
+
| `showWeekNumbers` | `boolean` | `false` | Stable | Shows an ISO 8601 week-number column on the left of the day grid | `[showWeekNumbers]="true"` |
|
|
110
|
+
| `weekNumberLabel` | `string` | `'Wk'` | Stable | Header label for the week-number column | `weekNumberLabel="KW"` |
|
|
111
|
+
| `inputMask` | `boolean \| string` | `false` | Stable | Guided input masking while typing (requires `allowTyping`); `true` uses `displayFormat`/MM/DD/YYYY, a string sets the pattern | `inputMask="DD.MM.YYYY"` |
|
|
112
|
+
| `asyncDateFilter` | `(start: Date, end: Date) => Promise<DateInput[]>` | `null` | Stable | Server-driven disabled dates for the visible range; stale responses discarded | `[asyncDateFilter]="loadBlocked"` |
|
|
113
|
+
| `secondaryCalendar` | `CalendarSystem` | `null` | Stable | Annotates day cells with a second calendar system (islamic, persian, hebrew, buddhist, japanese) via Intl | `secondaryCalendar="persian"` |
|
|
114
|
+
| `dayMetadata` | `(date: Date) => DayMetadata` | `null` | Stable | Per-day label/indicator dot/classes/tooltip without custom templates | `[dayMetadata]="priceMeta"` |
|
|
115
|
+
| `calendarHeaderTemplate` | `TemplateRef` | `null` | Stable | Custom content at the top of the popover/inline calendar; context `{ clear(), close() }` | `[calendarHeaderTemplate]="hdr"` |
|
|
116
|
+
| `calendarFooterTemplate` | `TemplateRef` | `null` | Stable | Replaces the default Clear/Close footer; also renders in inline mode | `[calendarFooterTemplate]="ftr"` |
|
|
117
|
+
|
|
118
|
+
##### Outputs
|
|
119
|
+
|
|
120
|
+
| Output | Type | Status | Description | Example |
|
|
121
|
+
|--------|------|--------|-------------|---------|
|
|
122
|
+
| `valueChange` | `EventEmitter<DatepickerValue>` | Stable | Emitted when value changes | `(valueChange)="onDateChange($event)"` |
|
|
123
|
+
| `action` | `EventEmitter<{ type: string; payload?: any }>` | Stable | Emitted on user actions (dateSelected, timeChanged, etc.) | `(action)="handleAction($event)"` |
|
|
124
|
+
| `googleSyncClick` | `EventEmitter<void>` | Stable | Emitted when the user clicks the Google Calendar sync button. | `(googleSyncClick)="onSyncClick()"` |
|
|
125
|
+
| `validationError` | `EventEmitter<{ code: string; message: string }>` | Stable | Emitted when validation fails (e.g. invalid typed date, date before min, after max). Message is translated. | `(validationError)="onValidationError($event)"` |
|
|
126
|
+
| `invalidRange` | `EventEmitter<{ start: Date; end: Date; disabledDatesInside: Date[] }>` | Stable | Emitted when selected date range contains disabled dates | `(invalidRange)="onInvalidRange($event)"` |
|
|
127
|
+
| `naturalLanguageResolved` | `EventEmitter<Date \| { start: Date; end: Date }>` | Stable | Emitted when natural language input is parsed successfully | `(naturalLanguageResolved)="onResolved($event)"` |
|
|
128
|
+
| `timezoneChange` | `EventEmitter<string>` | Stable | Emitted when timezone is changed via selector UI | `(timezoneChange)="onTimezoneChange($event)"` |
|
|
129
|
+
| `asyncDateFilterLoading` | `OutputEmitterRef<boolean>` | Stable | True while an `asyncDateFilter` request is in flight, false when it settles | `(asyncDateFilterLoading)="loading = $event"` |
|
|
130
|
+
| `asyncDateFilterError` | `OutputEmitterRef<unknown>` | Stable | Emitted when `asyncDateFilter` rejects; previous disabled set is kept | `(asyncDateFilterError)="onErr($event)"` |
|
|
131
|
+
|
|
132
|
+
#### Methods
|
|
133
|
+
|
|
134
|
+
| Method | Parameters | Return | Status | Description |
|
|
135
|
+
|--------|-----------|--------|--------|-------------|
|
|
136
|
+
| `writeValue` | `val: DatepickerValue` | `void` | Stable | ControlValueAccessor implementation |
|
|
137
|
+
| `registerOnChange` | `fn: (value: DatepickerValue) => void` | `void` | Stable | ControlValueAccessor implementation |
|
|
138
|
+
| `registerOnTouched` | `fn: () => void` | `void` | Stable | ControlValueAccessor implementation |
|
|
139
|
+
| `setDisabledState` | `isDisabled: boolean` | `void` | Stable | ControlValueAccessor implementation |
|
|
140
|
+
|
|
141
|
+
## Form Integration Examples
|
|
142
|
+
|
|
143
|
+
### Reactive Forms Integration
|
|
144
|
+
|
|
145
|
+
The datepicker implements `ControlValueAccessor`, making it fully compatible with Angular Reactive Forms.
|
|
146
|
+
|
|
147
|
+
#### Basic Reactive Forms Example
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { Component } from '@angular/core';
|
|
151
|
+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
152
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
153
|
+
import { CommonModule } from '@angular/common';
|
|
154
|
+
|
|
155
|
+
@Component({
|
|
156
|
+
selector: 'app-reactive-form',
|
|
157
|
+
standalone: true,
|
|
158
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule],
|
|
159
|
+
template: `
|
|
160
|
+
<form [formGroup]="bookingForm" (ngSubmit)="onSubmit()">
|
|
161
|
+
<div class="form-group">
|
|
162
|
+
<label>Check-in Date</label>
|
|
163
|
+
<ngxsmk-datepicker
|
|
164
|
+
mode="single"
|
|
165
|
+
placeholder="Select check-in date"
|
|
166
|
+
formControlName="checkInDate">
|
|
167
|
+
</ngxsmk-datepicker>
|
|
168
|
+
|
|
169
|
+
@if (bookingForm.get('checkInDate')?.hasError('required') &&
|
|
170
|
+
bookingForm.get('checkInDate')?.touched) {
|
|
171
|
+
<div class="error">Check-in date is required</div>
|
|
172
|
+
}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="form-group">
|
|
176
|
+
<label>Check-out Date</label>
|
|
177
|
+
<ngxsmk-datepicker
|
|
178
|
+
mode="single"
|
|
179
|
+
placeholder="Select check-out date"
|
|
180
|
+
formControlName="checkOutDate"
|
|
181
|
+
[minDate]="bookingForm.get('checkInDate')?.value">
|
|
182
|
+
</ngxsmk-datepicker>
|
|
183
|
+
|
|
184
|
+
@if (bookingForm.get('checkOutDate')?.hasError('required') &&
|
|
185
|
+
bookingForm.get('checkOutDate')?.touched) {
|
|
186
|
+
<div class="error">Check-out date is required</div>
|
|
187
|
+
}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<button type="submit" [disabled]="bookingForm.invalid">Book Now</button>
|
|
191
|
+
</form>
|
|
192
|
+
`
|
|
193
|
+
})
|
|
194
|
+
export class ReactiveFormComponent {
|
|
195
|
+
bookingForm = new FormGroup({
|
|
196
|
+
checkInDate: new FormControl<DatepickerValue>(null, [Validators.required]),
|
|
197
|
+
checkOutDate: new FormControl<DatepickerValue>(null, [Validators.required])
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
onSubmit() {
|
|
201
|
+
if (this.bookingForm.valid) {
|
|
202
|
+
console.log('Form value:', this.bookingForm.value);
|
|
203
|
+
// Submit to API
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Reactive Forms with Date Range
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { Component } from '@angular/core';
|
|
213
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
214
|
+
import { NgxsmkDatepickerComponent, DatepickerValue, DateRange } from 'ngxsmk-datepicker';
|
|
215
|
+
|
|
216
|
+
@Component({
|
|
217
|
+
selector: 'app-range-form',
|
|
218
|
+
standalone: true,
|
|
219
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule],
|
|
220
|
+
template: `
|
|
221
|
+
<form [formGroup]="rangeForm">
|
|
222
|
+
<ngxsmk-datepicker
|
|
223
|
+
mode="range"
|
|
224
|
+
[showTime]="true"
|
|
225
|
+
[minuteInterval]="15"
|
|
226
|
+
[ranges]="quickRanges"
|
|
227
|
+
[showRanges]="true"
|
|
228
|
+
placeholder="Select date range"
|
|
229
|
+
formControlName="dateRange">
|
|
230
|
+
</ngxsmk-datepicker>
|
|
231
|
+
|
|
232
|
+
@if (rangeForm.get('dateRange')?.value) {
|
|
233
|
+
<p>Selected range: {{ rangeForm.get('dateRange')?.value | json }}</p>
|
|
234
|
+
}
|
|
235
|
+
</form>
|
|
236
|
+
`
|
|
237
|
+
})
|
|
238
|
+
export class RangeFormComponent {
|
|
239
|
+
today = new Date();
|
|
240
|
+
|
|
241
|
+
quickRanges: DateRange = {
|
|
242
|
+
'Today': [this.today, this.today],
|
|
243
|
+
'Last 7 Days': [
|
|
244
|
+
new Date(this.today.getTime() - 6 * 86400000),
|
|
245
|
+
this.today
|
|
246
|
+
],
|
|
247
|
+
'This Month': [
|
|
248
|
+
new Date(this.today.getFullYear(), this.today.getMonth(), 1),
|
|
249
|
+
new Date(this.today.getFullYear(), this.today.getMonth() + 1, 0)
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
rangeForm = new FormGroup({
|
|
254
|
+
dateRange: new FormControl<DatepickerValue>(null)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Reactive Forms with Custom Validation
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { Component } from '@angular/core';
|
|
263
|
+
import { FormControl, FormGroup, ReactiveFormsModule, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
|
|
264
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
265
|
+
|
|
266
|
+
@Component({
|
|
267
|
+
selector: 'app-validated-form',
|
|
268
|
+
standalone: true,
|
|
269
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule],
|
|
270
|
+
template: `
|
|
271
|
+
<form [formGroup]="validatedForm">
|
|
272
|
+
<ngxsmk-datepicker
|
|
273
|
+
mode="single"
|
|
274
|
+
placeholder="Select appointment date"
|
|
275
|
+
[minDate]="today"
|
|
276
|
+
[isInvalidDate]="isWeekend"
|
|
277
|
+
formControlName="appointmentDate">
|
|
278
|
+
</ngxsmk-datepicker>
|
|
279
|
+
|
|
280
|
+
@if (validatedForm.get('appointmentDate')?.hasError('required')) {
|
|
281
|
+
<div class="error">Date is required</div>
|
|
282
|
+
}
|
|
283
|
+
@if (validatedForm.get('appointmentDate')?.hasError('minDate')) {
|
|
284
|
+
<div class="error">Date must be in the future</div>
|
|
285
|
+
}
|
|
286
|
+
</form>
|
|
287
|
+
`
|
|
288
|
+
})
|
|
289
|
+
export class ValidatedFormComponent {
|
|
290
|
+
today = new Date();
|
|
291
|
+
|
|
292
|
+
validatedForm = new FormGroup({
|
|
293
|
+
appointmentDate: new FormControl<DatepickerValue>(null, [
|
|
294
|
+
Validators.required,
|
|
295
|
+
this.minDateValidator
|
|
296
|
+
])
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
isWeekend = (date: Date): boolean => {
|
|
300
|
+
const day = date.getDay();
|
|
301
|
+
return day === 0 || day === 6; // Sunday or Saturday
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
minDateValidator(control: AbstractControl): ValidationErrors | null {
|
|
305
|
+
const value = control.value;
|
|
306
|
+
if (!value) return null;
|
|
307
|
+
|
|
308
|
+
const today = new Date();
|
|
309
|
+
today.setHours(0, 0, 0, 0);
|
|
310
|
+
|
|
311
|
+
if (value instanceof Date) {
|
|
312
|
+
const selectedDate = new Date(value);
|
|
313
|
+
selectedDate.setHours(0, 0, 0, 0);
|
|
314
|
+
|
|
315
|
+
if (selectedDate < today) {
|
|
316
|
+
return { minDate: true };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### Reactive Forms with Multiple Date Selection
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { Component } from '@angular/core';
|
|
329
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
330
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
331
|
+
import { CommonModule, DatePipe } from '@angular/common';
|
|
332
|
+
|
|
333
|
+
@Component({
|
|
334
|
+
selector: 'app-multiple-form',
|
|
335
|
+
standalone: true,
|
|
336
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule, DatePipe],
|
|
337
|
+
template: `
|
|
338
|
+
<form [formGroup]="multipleForm">
|
|
339
|
+
<ngxsmk-datepicker
|
|
340
|
+
mode="multiple"
|
|
341
|
+
placeholder="Select available dates"
|
|
342
|
+
formControlName="availableDates">
|
|
343
|
+
</ngxsmk-datepicker>
|
|
344
|
+
|
|
345
|
+
@if (selectedDates.length > 0) {
|
|
346
|
+
<div class="selected-dates">
|
|
347
|
+
<h4>Selected Dates ({{ selectedDates.length }}):</h4>
|
|
348
|
+
<ul>
|
|
349
|
+
@for (date of selectedDates; track date) {
|
|
350
|
+
<li>{{ date | date:'short' }}</li>
|
|
351
|
+
}
|
|
352
|
+
</ul>
|
|
353
|
+
</div>
|
|
354
|
+
}
|
|
355
|
+
</form>
|
|
356
|
+
`
|
|
357
|
+
})
|
|
358
|
+
export class MultipleFormComponent {
|
|
359
|
+
multipleForm = new FormGroup({
|
|
360
|
+
availableDates: new FormControl<DatepickerValue>(null)
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
get selectedDates(): Date[] {
|
|
364
|
+
const value = this.multipleForm.get('availableDates')?.value;
|
|
365
|
+
return Array.isArray(value) ? value : [];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Template-Driven Forms Integration
|
|
371
|
+
|
|
372
|
+
The datepicker also works seamlessly with Angular Template-Driven Forms using `ngModel`.
|
|
373
|
+
|
|
374
|
+
#### Basic Template-Driven Forms Example
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { Component } from '@angular/core';
|
|
378
|
+
import { FormsModule } from '@angular/forms';
|
|
379
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
380
|
+
import { CommonModule } from '@angular/common';
|
|
381
|
+
|
|
382
|
+
@Component({
|
|
383
|
+
selector: 'app-template-form',
|
|
384
|
+
standalone: true,
|
|
385
|
+
imports: [NgxsmkDatepickerComponent, FormsModule, CommonModule],
|
|
386
|
+
template: `
|
|
387
|
+
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
|
|
388
|
+
<div class="form-group">
|
|
389
|
+
<label>Event Date</label>
|
|
390
|
+
<ngxsmk-datepicker
|
|
391
|
+
mode="single"
|
|
392
|
+
placeholder="Select event date"
|
|
393
|
+
[(ngModel)]="eventDate"
|
|
394
|
+
name="eventDate"
|
|
395
|
+
#dateField="ngModel"
|
|
396
|
+
required>
|
|
397
|
+
</ngxsmk-datepicker>
|
|
398
|
+
|
|
399
|
+
@if (dateField.invalid && dateField.touched) {
|
|
400
|
+
<div class="error">Event date is required</div>
|
|
401
|
+
}
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<button type="submit" [disabled]="myForm.invalid">Submit</button>
|
|
405
|
+
</form>
|
|
406
|
+
|
|
407
|
+
@if (eventDate) {
|
|
408
|
+
<p>Selected: {{ eventDate | date:'medium' }}</p>
|
|
409
|
+
}
|
|
410
|
+
`
|
|
411
|
+
})
|
|
412
|
+
export class TemplateFormComponent {
|
|
413
|
+
eventDate: DatepickerValue = null;
|
|
414
|
+
|
|
415
|
+
onSubmit(form: any) {
|
|
416
|
+
if (form.valid) {
|
|
417
|
+
console.log('Form value:', form.value);
|
|
418
|
+
console.log('Event date:', this.eventDate);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### Template-Driven Forms with Date Range
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { Component } from '@angular/core';
|
|
428
|
+
import { FormsModule } from '@angular/forms';
|
|
429
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
430
|
+
|
|
431
|
+
@Component({
|
|
432
|
+
selector: 'app-template-range',
|
|
433
|
+
standalone: true,
|
|
434
|
+
imports: [NgxsmkDatepickerComponent, FormsModule],
|
|
435
|
+
template: `
|
|
436
|
+
<form #rangeForm="ngForm">
|
|
437
|
+
<ngxsmk-datepicker
|
|
438
|
+
mode="range"
|
|
439
|
+
[showTime]="true"
|
|
440
|
+
placeholder="Select date range"
|
|
441
|
+
[(ngModel)]="dateRange"
|
|
442
|
+
name="dateRange">
|
|
443
|
+
</ngxsmk-datepicker>
|
|
444
|
+
|
|
445
|
+
@if (dateRange) {
|
|
446
|
+
<p>
|
|
447
|
+
From: {{ dateRange.start | date:'short' }}
|
|
448
|
+
To: {{ dateRange.end | date:'short' }}
|
|
449
|
+
</p>
|
|
450
|
+
}
|
|
451
|
+
</form>
|
|
452
|
+
`
|
|
453
|
+
})
|
|
454
|
+
export class TemplateRangeComponent {
|
|
455
|
+
dateRange: DatepickerValue = null;
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### Template-Driven Forms with Validation
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import { Component } from '@angular/core';
|
|
463
|
+
import { FormsModule } from '@angular/forms';
|
|
464
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
465
|
+
|
|
466
|
+
@Component({
|
|
467
|
+
selector: 'app-template-validated',
|
|
468
|
+
standalone: true,
|
|
469
|
+
imports: [NgxsmkDatepickerComponent, FormsModule],
|
|
470
|
+
template: `
|
|
471
|
+
<form #validatedForm="ngForm">
|
|
472
|
+
<ngxsmk-datepicker
|
|
473
|
+
mode="single"
|
|
474
|
+
placeholder="Select date"
|
|
475
|
+
[(ngModel)]="selectedDate"
|
|
476
|
+
name="selectedDate"
|
|
477
|
+
#dateCtrl="ngModel"
|
|
478
|
+
required
|
|
479
|
+
[minDate]="today">
|
|
480
|
+
</ngxsmk-datepicker>
|
|
481
|
+
|
|
482
|
+
@if (dateCtrl.errors?.['required'] && dateCtrl.touched) {
|
|
483
|
+
<div class="error">Date is required</div>
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
<button type="submit" [disabled]="validatedForm.invalid">Submit</button>
|
|
487
|
+
</form>
|
|
488
|
+
`
|
|
489
|
+
})
|
|
490
|
+
export class TemplateValidatedComponent {
|
|
491
|
+
today = new Date();
|
|
492
|
+
selectedDate: DatepickerValue = null;
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Ionic Framework Integration
|
|
497
|
+
|
|
498
|
+
The datepicker is fully compatible with Ionic Angular applications. Here are comprehensive examples for different Ionic use cases.
|
|
499
|
+
|
|
500
|
+
#### Basic Ionic Integration
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
import { Component } from '@angular/core';
|
|
504
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
505
|
+
import { IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel } from '@ionic/angular/standalone';
|
|
506
|
+
import { FormsModule } from '@angular/forms';
|
|
507
|
+
|
|
508
|
+
@Component({
|
|
509
|
+
selector: 'app-ionic-datepicker',
|
|
510
|
+
standalone: true,
|
|
511
|
+
imports: [
|
|
512
|
+
NgxsmkDatepickerComponent,
|
|
513
|
+
IonContent,
|
|
514
|
+
IonHeader,
|
|
515
|
+
IonTitle,
|
|
516
|
+
IonToolbar,
|
|
517
|
+
IonItem,
|
|
518
|
+
IonLabel,
|
|
519
|
+
FormsModule
|
|
520
|
+
],
|
|
521
|
+
template: `
|
|
522
|
+
<ion-header>
|
|
523
|
+
<ion-toolbar>
|
|
524
|
+
<ion-title>Date Picker</ion-title>
|
|
525
|
+
</ion-toolbar>
|
|
526
|
+
</ion-header>
|
|
527
|
+
|
|
528
|
+
<ion-content class="ion-padding">
|
|
529
|
+
<ion-item>
|
|
530
|
+
<ion-label position="stacked">Select Date</ion-label>
|
|
531
|
+
<ngxsmk-datepicker
|
|
532
|
+
mode="single"
|
|
533
|
+
placeholder="Choose a date">
|
|
534
|
+
</ngxsmk-datepicker>
|
|
535
|
+
</ion-item>
|
|
536
|
+
</ion-content>
|
|
537
|
+
`
|
|
538
|
+
})
|
|
539
|
+
export class IonicDatepickerComponent {}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### Ionic with Reactive Forms
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
import { Component } from '@angular/core';
|
|
546
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
547
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
548
|
+
import {
|
|
549
|
+
IonContent,
|
|
550
|
+
IonHeader,
|
|
551
|
+
IonTitle,
|
|
552
|
+
IonToolbar,
|
|
553
|
+
IonItem,
|
|
554
|
+
IonLabel,
|
|
555
|
+
IonButton,
|
|
556
|
+
IonList
|
|
557
|
+
} from '@ionic/angular/standalone';
|
|
558
|
+
|
|
559
|
+
@Component({
|
|
560
|
+
selector: 'app-ionic-reactive',
|
|
561
|
+
standalone: true,
|
|
562
|
+
imports: [
|
|
563
|
+
NgxsmkDatepickerComponent,
|
|
564
|
+
ReactiveFormsModule,
|
|
565
|
+
IonContent,
|
|
566
|
+
IonHeader,
|
|
567
|
+
IonTitle,
|
|
568
|
+
IonToolbar,
|
|
569
|
+
IonItem,
|
|
570
|
+
IonLabel,
|
|
571
|
+
IonButton,
|
|
572
|
+
IonList
|
|
573
|
+
],
|
|
574
|
+
template: `
|
|
575
|
+
<ion-header>
|
|
576
|
+
<ion-toolbar>
|
|
577
|
+
<ion-title>Booking Form</ion-title>
|
|
578
|
+
</ion-toolbar>
|
|
579
|
+
</ion-header>
|
|
580
|
+
|
|
581
|
+
<ion-content class="ion-padding">
|
|
582
|
+
<form [formGroup]="bookingForm" (ngSubmit)="onSubmit()">
|
|
583
|
+
<ion-list>
|
|
584
|
+
<ion-item>
|
|
585
|
+
<ion-label position="stacked">Check-in Date</ion-label>
|
|
586
|
+
<ngxsmk-datepicker
|
|
587
|
+
mode="single"
|
|
588
|
+
placeholder="Select check-in"
|
|
589
|
+
formControlName="checkIn">
|
|
590
|
+
</ngxsmk-datepicker>
|
|
591
|
+
</ion-item>
|
|
592
|
+
|
|
593
|
+
<ion-item>
|
|
594
|
+
<ion-label position="stacked">Check-out Date</ion-label>
|
|
595
|
+
<ngxsmk-datepicker
|
|
596
|
+
mode="single"
|
|
597
|
+
placeholder="Select check-out"
|
|
598
|
+
formControlName="checkOut"
|
|
599
|
+
[minDate]="bookingForm.get('checkIn')?.value">
|
|
600
|
+
</ngxsmk-datepicker>
|
|
601
|
+
</ion-item>
|
|
602
|
+
</ion-list>
|
|
603
|
+
|
|
604
|
+
<ion-button expand="block" type="submit" [disabled]="bookingForm.invalid">
|
|
605
|
+
Book Now
|
|
606
|
+
</ion-button>
|
|
607
|
+
</form>
|
|
608
|
+
</ion-content>
|
|
609
|
+
`
|
|
610
|
+
})
|
|
611
|
+
export class IonicReactiveComponent {
|
|
612
|
+
bookingForm = new FormGroup({
|
|
613
|
+
checkIn: new FormControl<DatepickerValue>(null),
|
|
614
|
+
checkOut: new FormControl<DatepickerValue>(null)
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
onSubmit() {
|
|
618
|
+
if (this.bookingForm.valid) {
|
|
619
|
+
console.log('Booking:', this.bookingForm.value);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
#### Ionic with Date Range and Time
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
import { Component } from '@angular/core';
|
|
629
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
630
|
+
import { NgxsmkDatepickerComponent, DatepickerValue, DateRange } from 'ngxsmk-datepicker';
|
|
631
|
+
import {
|
|
632
|
+
IonContent,
|
|
633
|
+
IonHeader,
|
|
634
|
+
IonTitle,
|
|
635
|
+
IonToolbar,
|
|
636
|
+
IonItem,
|
|
637
|
+
IonLabel
|
|
638
|
+
} from '@ionic/angular/standalone';
|
|
639
|
+
|
|
640
|
+
@Component({
|
|
641
|
+
selector: 'app-ionic-range',
|
|
642
|
+
standalone: true,
|
|
643
|
+
imports: [
|
|
644
|
+
NgxsmkDatepickerComponent,
|
|
645
|
+
ReactiveFormsModule,
|
|
646
|
+
IonContent,
|
|
647
|
+
IonHeader,
|
|
648
|
+
IonTitle,
|
|
649
|
+
IonToolbar,
|
|
650
|
+
IonItem,
|
|
651
|
+
IonLabel
|
|
652
|
+
],
|
|
653
|
+
template: `
|
|
654
|
+
<ion-header>
|
|
655
|
+
<ion-toolbar>
|
|
656
|
+
<ion-title>Event Range</ion-title>
|
|
657
|
+
</ion-toolbar>
|
|
658
|
+
</ion-header>
|
|
659
|
+
|
|
660
|
+
<ion-content class="ion-padding">
|
|
661
|
+
<form [formGroup]="eventForm">
|
|
662
|
+
<ion-item>
|
|
663
|
+
<ion-label position="stacked">Event Date & Time Range</ion-label>
|
|
664
|
+
<ngxsmk-datepicker
|
|
665
|
+
mode="range"
|
|
666
|
+
[showTime]="true"
|
|
667
|
+
[minuteInterval]="15"
|
|
668
|
+
[ranges]="quickRanges"
|
|
669
|
+
[showRanges]="true"
|
|
670
|
+
placeholder="Select event period"
|
|
671
|
+
formControlName="eventRange">
|
|
672
|
+
</ngxsmk-datepicker>
|
|
673
|
+
</ion-item>
|
|
674
|
+
</form>
|
|
675
|
+
</ion-content>
|
|
676
|
+
`
|
|
677
|
+
})
|
|
678
|
+
export class IonicRangeComponent {
|
|
679
|
+
today = new Date();
|
|
680
|
+
|
|
681
|
+
quickRanges: DateRange = {
|
|
682
|
+
'Today': [this.today, this.today],
|
|
683
|
+
'This Week': [
|
|
684
|
+
new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - this.today.getDay()),
|
|
685
|
+
this.today
|
|
686
|
+
]
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
eventForm = new FormGroup({
|
|
690
|
+
eventRange: new FormControl<DatepickerValue>(null)
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
#### Ionic with Time-Only Picker
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
import { Component } from '@angular/core';
|
|
699
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
700
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
701
|
+
import {
|
|
702
|
+
IonContent,
|
|
703
|
+
IonHeader,
|
|
704
|
+
IonTitle,
|
|
705
|
+
IonToolbar,
|
|
706
|
+
IonItem,
|
|
707
|
+
IonLabel
|
|
708
|
+
} from '@ionic/angular/standalone';
|
|
709
|
+
|
|
710
|
+
@Component({
|
|
711
|
+
selector: 'app-ionic-time',
|
|
712
|
+
standalone: true,
|
|
713
|
+
imports: [
|
|
714
|
+
NgxsmkDatepickerComponent,
|
|
715
|
+
ReactiveFormsModule,
|
|
716
|
+
IonContent,
|
|
717
|
+
IonHeader,
|
|
718
|
+
IonTitle,
|
|
719
|
+
IonToolbar,
|
|
720
|
+
IonItem,
|
|
721
|
+
IonLabel
|
|
722
|
+
],
|
|
723
|
+
template: `
|
|
724
|
+
<ion-header>
|
|
725
|
+
<ion-toolbar>
|
|
726
|
+
<ion-title>Appointment Time</ion-title>
|
|
727
|
+
</ion-toolbar>
|
|
728
|
+
</ion-header>
|
|
729
|
+
|
|
730
|
+
<ion-content class="ion-padding">
|
|
731
|
+
<form [formGroup]="timeForm">
|
|
732
|
+
<ion-item>
|
|
733
|
+
<ion-label position="stacked">Select Time</ion-label>
|
|
734
|
+
<ngxsmk-datepicker
|
|
735
|
+
mode="single"
|
|
736
|
+
[timeOnly]="true"
|
|
737
|
+
[minuteInterval]="15"
|
|
738
|
+
placeholder="Choose time"
|
|
739
|
+
formControlName="appointmentTime">
|
|
740
|
+
</ngxsmk-datepicker>
|
|
741
|
+
</ion-item>
|
|
742
|
+
</form>
|
|
743
|
+
</ion-content>
|
|
744
|
+
`
|
|
745
|
+
})
|
|
746
|
+
export class IonicTimeComponent {
|
|
747
|
+
timeForm = new FormGroup({
|
|
748
|
+
appointmentTime: new FormControl<DatepickerValue>(null)
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
#### Ionic with Inline Calendar
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
import { Component } from '@angular/core';
|
|
757
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
758
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
759
|
+
import {
|
|
760
|
+
IonContent,
|
|
761
|
+
IonHeader,
|
|
762
|
+
IonTitle,
|
|
763
|
+
IonToolbar
|
|
764
|
+
} from '@ionic/angular/standalone';
|
|
765
|
+
|
|
766
|
+
@Component({
|
|
767
|
+
selector: 'app-ionic-inline',
|
|
768
|
+
standalone: true,
|
|
769
|
+
imports: [
|
|
770
|
+
NgxsmkDatepickerComponent,
|
|
771
|
+
ReactiveFormsModule,
|
|
772
|
+
IonContent,
|
|
773
|
+
IonHeader,
|
|
774
|
+
IonTitle,
|
|
775
|
+
IonToolbar
|
|
776
|
+
],
|
|
777
|
+
template: `
|
|
778
|
+
<ion-header>
|
|
779
|
+
<ion-toolbar>
|
|
780
|
+
<ion-title>Calendar View</ion-title>
|
|
781
|
+
</ion-toolbar>
|
|
782
|
+
</ion-header>
|
|
783
|
+
|
|
784
|
+
<ion-content class="ion-padding">
|
|
785
|
+
<ngxsmk-datepicker
|
|
786
|
+
mode="range"
|
|
787
|
+
[inline]="true"
|
|
788
|
+
placeholder="Select date range">
|
|
789
|
+
</ngxsmk-datepicker>
|
|
790
|
+
</ion-content>
|
|
791
|
+
`
|
|
792
|
+
})
|
|
793
|
+
export class IonicInlineComponent {}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
#### Ionic with Dark Theme
|
|
797
|
+
|
|
798
|
+
```typescript
|
|
799
|
+
import { Component, signal } from '@angular/core';
|
|
800
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
801
|
+
import {
|
|
802
|
+
IonContent,
|
|
803
|
+
IonHeader,
|
|
804
|
+
IonTitle,
|
|
805
|
+
IonToolbar,
|
|
806
|
+
IonItem,
|
|
807
|
+
IonLabel,
|
|
808
|
+
IonToggle
|
|
809
|
+
} from '@ionic/angular/standalone';
|
|
810
|
+
|
|
811
|
+
@Component({
|
|
812
|
+
selector: 'app-ionic-theme',
|
|
813
|
+
standalone: true,
|
|
814
|
+
imports: [
|
|
815
|
+
NgxsmkDatepickerComponent,
|
|
816
|
+
IonContent,
|
|
817
|
+
IonHeader,
|
|
818
|
+
IonTitle,
|
|
819
|
+
IonToolbar,
|
|
820
|
+
IonItem,
|
|
821
|
+
IonLabel,
|
|
822
|
+
IonToggle
|
|
823
|
+
],
|
|
824
|
+
template: `
|
|
825
|
+
<ion-header>
|
|
826
|
+
<ion-toolbar>
|
|
827
|
+
<ion-title>Theme Toggle</ion-title>
|
|
828
|
+
</ion-toolbar>
|
|
829
|
+
</ion-header>
|
|
830
|
+
|
|
831
|
+
<ion-content class="ion-padding">
|
|
832
|
+
<ion-item>
|
|
833
|
+
<ion-label>Dark Mode</ion-label>
|
|
834
|
+
<ion-toggle
|
|
835
|
+
[checked]="isDark()"
|
|
836
|
+
(ionChange)="toggleTheme()">
|
|
837
|
+
</ion-toggle>
|
|
838
|
+
</ion-item>
|
|
839
|
+
|
|
840
|
+
<ion-item>
|
|
841
|
+
<ion-label position="stacked">Select Date</ion-label>
|
|
842
|
+
<ngxsmk-datepicker
|
|
843
|
+
mode="single"
|
|
844
|
+
[theme]="isDark() ? 'dark' : 'light'"
|
|
845
|
+
placeholder="Choose a date">
|
|
846
|
+
</ngxsmk-datepicker>
|
|
847
|
+
</ion-item>
|
|
848
|
+
</ion-content>
|
|
849
|
+
`
|
|
850
|
+
})
|
|
851
|
+
export class IonicThemeComponent {
|
|
852
|
+
isDark = signal(false);
|
|
853
|
+
|
|
854
|
+
toggleTheme() {
|
|
855
|
+
this.isDark.update(v => !v);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
## Real-World Usage Examples
|
|
861
|
+
|
|
862
|
+
### 1. Basic Single Date Selection
|
|
863
|
+
|
|
864
|
+
**Scenario**: Simple date picker for selecting a single date.
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
import { Component } from '@angular/core';
|
|
868
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
869
|
+
|
|
870
|
+
@Component({
|
|
871
|
+
selector: 'app-basic',
|
|
872
|
+
standalone: true,
|
|
873
|
+
imports: [NgxsmkDatepickerComponent],
|
|
874
|
+
template: `
|
|
875
|
+
<ngxsmk-datepicker
|
|
876
|
+
mode="single"
|
|
877
|
+
placeholder="Select a date"
|
|
878
|
+
(valueChange)="onDateChange($event)">
|
|
879
|
+
</ngxsmk-datepicker>
|
|
880
|
+
`
|
|
881
|
+
})
|
|
882
|
+
export class BasicComponent {
|
|
883
|
+
onDateChange(date: Date | null) {
|
|
884
|
+
console.log('Selected date:', date);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### 2. Reactive Forms Integration
|
|
890
|
+
|
|
891
|
+
**Scenario**: Using with Angular Reactive Forms for form validation and submission.
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
import { Component } from '@angular/core';
|
|
895
|
+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
896
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
897
|
+
import { CommonModule } from '@angular/common';
|
|
898
|
+
|
|
899
|
+
@Component({
|
|
900
|
+
selector: 'app-reactive-form',
|
|
901
|
+
standalone: true,
|
|
902
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule],
|
|
903
|
+
template: `
|
|
904
|
+
<form [formGroup]="bookingForm" (ngSubmit)="onSubmit()">
|
|
905
|
+
<ngxsmk-datepicker
|
|
906
|
+
mode="single"
|
|
907
|
+
placeholder="Select check-in date"
|
|
908
|
+
formControlName="checkInDate">
|
|
909
|
+
</ngxsmk-datepicker>
|
|
910
|
+
|
|
911
|
+
@if (bookingForm.get('checkInDate')?.hasError('required')) {
|
|
912
|
+
<div>Check-in date is required</div>
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
<button type="submit" [disabled]="bookingForm.invalid">Book</button>
|
|
916
|
+
</form>
|
|
917
|
+
`
|
|
918
|
+
})
|
|
919
|
+
export class ReactiveFormComponent {
|
|
920
|
+
bookingForm = new FormGroup({
|
|
921
|
+
checkInDate: new FormControl<DatepickerValue>(null, [Validators.required])
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
onSubmit() {
|
|
925
|
+
if (this.bookingForm.valid) {
|
|
926
|
+
console.log('Form value:', this.bookingForm.value);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### 3. Signal Binding (Angular 17+)
|
|
933
|
+
|
|
934
|
+
**Scenario**: Using writable signals for reactive date selection.
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
import { Component, signal } from '@angular/core';
|
|
938
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
939
|
+
|
|
940
|
+
@Component({
|
|
941
|
+
selector: 'app-signal-binding',
|
|
942
|
+
standalone: true,
|
|
943
|
+
imports: [NgxsmkDatepickerComponent],
|
|
944
|
+
template: `
|
|
945
|
+
<ngxsmk-datepicker
|
|
946
|
+
mode="single"
|
|
947
|
+
[value]="selectedDate()"
|
|
948
|
+
(valueChange)="selectedDate.set($event)">
|
|
949
|
+
</ngxsmk-datepicker>
|
|
950
|
+
|
|
951
|
+
<p>Selected: {{ selectedDate() | date:'medium' }}</p>
|
|
952
|
+
`
|
|
953
|
+
})
|
|
954
|
+
export class SignalBindingComponent {
|
|
955
|
+
selectedDate = signal<DatepickerValue>(null);
|
|
956
|
+
}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### 4. Signal Forms with Server Data (Angular 21+)
|
|
960
|
+
|
|
961
|
+
**Scenario**: Populating datepicker from server-side data using httpResource and Signal Forms.
|
|
962
|
+
|
|
963
|
+
```typescript
|
|
964
|
+
import { Component, inject, signal, linkedSignal } from '@angular/core';
|
|
965
|
+
import { httpResource } from '@angular/common/http';
|
|
966
|
+
import { HttpClient } from '@angular/common/http';
|
|
967
|
+
import { form, objectSchema } from '@angular/forms';
|
|
968
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
969
|
+
|
|
970
|
+
interface BookingData {
|
|
971
|
+
checkInDate: Date;
|
|
972
|
+
checkOutDate: Date;
|
|
973
|
+
guestName: string;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
@Component({
|
|
977
|
+
selector: 'app-server-form',
|
|
978
|
+
standalone: true,
|
|
979
|
+
imports: [NgxsmkDatepickerComponent],
|
|
980
|
+
template: `
|
|
981
|
+
<form>
|
|
982
|
+
<label>Check-in Date</label>
|
|
983
|
+
<ngxsmk-datepicker
|
|
984
|
+
[field]="bookingForm.checkInDate"
|
|
985
|
+
mode="single"
|
|
986
|
+
placeholder="Select check-in date">
|
|
987
|
+
</ngxsmk-datepicker>
|
|
988
|
+
|
|
989
|
+
<label>Check-out Date</label>
|
|
990
|
+
<ngxsmk-datepicker
|
|
991
|
+
[field]="bookingForm.checkOutDate"
|
|
992
|
+
mode="single"
|
|
993
|
+
placeholder="Select check-out date">
|
|
994
|
+
</ngxsmk-datepicker>
|
|
995
|
+
|
|
996
|
+
<button (click)="saveBooking()">Save Booking</button>
|
|
997
|
+
</form>
|
|
998
|
+
`
|
|
999
|
+
})
|
|
1000
|
+
export class ServerFormComponent {
|
|
1001
|
+
private http = inject(HttpClient);
|
|
1002
|
+
|
|
1003
|
+
// Fetch booking data from server
|
|
1004
|
+
resource = httpResource({
|
|
1005
|
+
request: () => this.http.get<BookingData>('/api/bookings/123'),
|
|
1006
|
+
loader: signal(false)
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
// Link server response to local signal
|
|
1010
|
+
localObject = linkedSignal(() => this.resource.response.value() || {
|
|
1011
|
+
checkInDate: new Date(),
|
|
1012
|
+
checkOutDate: new Date(),
|
|
1013
|
+
guestName: ''
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// Create signal form
|
|
1017
|
+
bookingForm = form(this.localObject, objectSchema({
|
|
1018
|
+
checkInDate: objectSchema<Date>(),
|
|
1019
|
+
checkOutDate: objectSchema<Date>(),
|
|
1020
|
+
guestName: objectSchema<string>()
|
|
1021
|
+
}));
|
|
1022
|
+
|
|
1023
|
+
saveBooking() {
|
|
1024
|
+
// Form automatically syncs with localObject signal
|
|
1025
|
+
this.http.put('/api/bookings/123', this.localObject()).subscribe();
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
### 5. Signal Forms with Manual Binding (Stabilized Pattern)
|
|
1031
|
+
|
|
1032
|
+
**Scenario**: Using manual `[value]` and `(valueChange)` binding with Signal Forms to prevent stability issues. **Important**: To ensure dirty state tracking works correctly, use the field's `setValue()` or `updateValue()` methods instead of direct mutation.
|
|
1033
|
+
|
|
1034
|
+
```typescript
|
|
1035
|
+
import { Component, signal, computed, form, objectSchema } from '@angular/core';
|
|
1036
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1037
|
+
|
|
1038
|
+
@Component({
|
|
1039
|
+
selector: 'app-stable-form',
|
|
1040
|
+
standalone: true,
|
|
1041
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1042
|
+
template: `
|
|
1043
|
+
<ngxsmk-datepicker
|
|
1044
|
+
class="w-full border:none"
|
|
1045
|
+
[value]="myDate()"
|
|
1046
|
+
(valueChange)="onMyDateChange($any($event))"
|
|
1047
|
+
mode="single"
|
|
1048
|
+
placeholder="Select a date">
|
|
1049
|
+
</ngxsmk-datepicker>
|
|
1050
|
+
`
|
|
1051
|
+
})
|
|
1052
|
+
export class StableFormComponent {
|
|
1053
|
+
localObject = signal({ myDate: new Date() });
|
|
1054
|
+
|
|
1055
|
+
myForm = form(this.localObject, objectSchema({
|
|
1056
|
+
myDate: objectSchema<Date>()
|
|
1057
|
+
}));
|
|
1058
|
+
|
|
1059
|
+
// Get a computed signal reference to the date field value
|
|
1060
|
+
myDate = computed(() => this.myForm.value().myDate);
|
|
1061
|
+
|
|
1062
|
+
onMyDateChange(newDate: Date): void {
|
|
1063
|
+
// Use setValue to ensure dirty state tracking works correctly
|
|
1064
|
+
if (typeof this.myForm.myDate.setValue === 'function') {
|
|
1065
|
+
this.myForm.myDate.setValue(newDate);
|
|
1066
|
+
} else if (typeof this.myForm.myDate.updateValue === 'function') {
|
|
1067
|
+
this.myForm.myDate.updateValue(() => newDate);
|
|
1068
|
+
} else {
|
|
1069
|
+
// Fallback: directly mutate (may not track dirty state)
|
|
1070
|
+
this.myForm.value().myDate = newDate;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
**When to use this pattern:**
|
|
1077
|
+
- When `[field]` binding causes stability issues or change detection loops
|
|
1078
|
+
- When you need more explicit control over form updates
|
|
1079
|
+
- When direct mutation is preferred over creating new object references
|
|
1080
|
+
|
|
1081
|
+
**Note:** The `$any($event)` cast may be needed if there's a type mismatch between `DatepickerValue` and your expected `Date` type.
|
|
1082
|
+
|
|
1083
|
+
**Important:** If you're using this pattern with server-side data and initial values aren't populating, update the underlying `localObject` signal instead of directly mutating the form value. This ensures the form stays in sync:
|
|
1084
|
+
|
|
1085
|
+
```typescript
|
|
1086
|
+
export class ServerFormComponent {
|
|
1087
|
+
localObject = signal<{ myDate: Date | null }>({
|
|
1088
|
+
myDate: null
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
myForm = form(this.localObject, objectSchema({
|
|
1092
|
+
myDate: objectSchema<Date | null>()
|
|
1093
|
+
}));
|
|
1094
|
+
|
|
1095
|
+
myDate = computed(() => this.myForm.value().myDate);
|
|
1096
|
+
|
|
1097
|
+
onMyDateChange(newDate: DatepickerValue | null): void {
|
|
1098
|
+
this.localObject.update(obj => ({
|
|
1099
|
+
...obj,
|
|
1100
|
+
myDate: newDate instanceof Date ? newDate : new Date(newDate.toLocaleString())
|
|
1101
|
+
}));
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
updateFromServer(serverDate: Date | string): void {
|
|
1105
|
+
const dateValue = serverDate instanceof Date ? serverDate : new Date(serverDate);
|
|
1106
|
+
this.localObject.update(obj => ({
|
|
1107
|
+
...obj,
|
|
1108
|
+
myDate: dateValue
|
|
1109
|
+
}));
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### 6. Time-Only Picker
|
|
1115
|
+
|
|
1116
|
+
**Scenario**: Display only time selection without calendar. Perfect for time selection scenarios where date is not needed.
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
import { Component } from '@angular/core';
|
|
1120
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
1121
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
1122
|
+
|
|
1123
|
+
@Component({
|
|
1124
|
+
selector: 'app-time-only',
|
|
1125
|
+
standalone: true,
|
|
1126
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule],
|
|
1127
|
+
template: `
|
|
1128
|
+
<form [formGroup]="timeForm">
|
|
1129
|
+
<ngxsmk-datepicker
|
|
1130
|
+
mode="single"
|
|
1131
|
+
[timeOnly]="true"
|
|
1132
|
+
[minuteInterval]="15"
|
|
1133
|
+
formControlName="appointmentTime">
|
|
1134
|
+
</ngxsmk-datepicker>
|
|
1135
|
+
|
|
1136
|
+
@if (timeForm.controls.appointmentTime.value) {
|
|
1137
|
+
<p>Selected time: {{ timeForm.controls.appointmentTime.value | date:'shortTime' }}</p>
|
|
1138
|
+
}
|
|
1139
|
+
</form>
|
|
1140
|
+
`
|
|
1141
|
+
})
|
|
1142
|
+
export class TimeOnlyComponent {
|
|
1143
|
+
timeForm = new FormGroup({
|
|
1144
|
+
appointmentTime: new FormControl<DatepickerValue>(null)
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
**Key Features:**
|
|
1150
|
+
- Hides calendar grid completely
|
|
1151
|
+
- Shows only time selection controls (hour, minute, AM/PM)
|
|
1152
|
+
- Value is still a Date object (uses today's date with selected time)
|
|
1153
|
+
- Automatically enables `showTime` when `timeOnly` is true
|
|
1154
|
+
- Placeholder defaults to "Select Time"
|
|
1155
|
+
|
|
1156
|
+
### 7. Date Range Selection with Time
|
|
1157
|
+
|
|
1158
|
+
**Scenario**: Booking system with check-in/check-out dates and times.
|
|
1159
|
+
|
|
1160
|
+
```typescript
|
|
1161
|
+
import { Component } from '@angular/core';
|
|
1162
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
1163
|
+
import { NgxsmkDatepickerComponent, DateRange, DatepickerValue } from 'ngxsmk-datepicker';
|
|
1164
|
+
import { CommonModule } from '@angular/common';
|
|
1165
|
+
|
|
1166
|
+
@Component({
|
|
1167
|
+
selector: 'app-booking',
|
|
1168
|
+
standalone: true,
|
|
1169
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule],
|
|
1170
|
+
template: `
|
|
1171
|
+
<form [formGroup]="bookingForm">
|
|
1172
|
+
<ngxsmk-datepicker
|
|
1173
|
+
mode="range"
|
|
1174
|
+
[showTime]="true"
|
|
1175
|
+
[minuteInterval]="15"
|
|
1176
|
+
[ranges]="quickRanges"
|
|
1177
|
+
[showRanges]="true"
|
|
1178
|
+
[minDate]="today"
|
|
1179
|
+
placeholder="Select check-in and check-out dates"
|
|
1180
|
+
formControlName="dateRange">
|
|
1181
|
+
</ngxsmk-datepicker>
|
|
1182
|
+
</form>
|
|
1183
|
+
`
|
|
1184
|
+
})
|
|
1185
|
+
export class BookingComponent {
|
|
1186
|
+
today = new Date();
|
|
1187
|
+
|
|
1188
|
+
bookingForm = new FormGroup({
|
|
1189
|
+
dateRange: new FormControl<DatepickerValue>(null)
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
quickRanges: DateRange = {
|
|
1193
|
+
'Today': [this.today, this.today],
|
|
1194
|
+
'Tomorrow': [
|
|
1195
|
+
new Date(this.today.getTime() + 86400000),
|
|
1196
|
+
new Date(this.today.getTime() + 86400000)
|
|
1197
|
+
],
|
|
1198
|
+
'This Week': [
|
|
1199
|
+
new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - this.today.getDay()),
|
|
1200
|
+
this.today
|
|
1201
|
+
],
|
|
1202
|
+
'Next 7 Days': [
|
|
1203
|
+
this.today,
|
|
1204
|
+
new Date(this.today.getTime() + 7 * 86400000)
|
|
1205
|
+
]
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### 8. Multiple Date Selection
|
|
1211
|
+
|
|
1212
|
+
**Scenario**: Event calendar where users can select multiple dates for availability.
|
|
1213
|
+
|
|
1214
|
+
```typescript
|
|
1215
|
+
import { Component } from '@angular/core';
|
|
1216
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
1217
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
1218
|
+
import { CommonModule } from '@angular/common';
|
|
1219
|
+
import { DatePipe } from '@angular/common';
|
|
1220
|
+
|
|
1221
|
+
@Component({
|
|
1222
|
+
selector: 'app-event-calendar',
|
|
1223
|
+
standalone: true,
|
|
1224
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule, DatePipe],
|
|
1225
|
+
template: `
|
|
1226
|
+
<form [formGroup]="eventForm">
|
|
1227
|
+
<ngxsmk-datepicker
|
|
1228
|
+
mode="multiple"
|
|
1229
|
+
placeholder="Select available dates"
|
|
1230
|
+
formControlName="availableDates">
|
|
1231
|
+
</ngxsmk-datepicker>
|
|
1232
|
+
|
|
1233
|
+
@if (selectedDates.length > 0) {
|
|
1234
|
+
<div>
|
|
1235
|
+
<p>Selected {{ selectedDates.length }} dates:</p>
|
|
1236
|
+
<ul>
|
|
1237
|
+
@for (date of selectedDates; track date) {
|
|
1238
|
+
<li>{{ date | date:'short' }}</li>
|
|
1239
|
+
}
|
|
1240
|
+
</ul>
|
|
1241
|
+
</div>
|
|
1242
|
+
}
|
|
1243
|
+
</form>
|
|
1244
|
+
`
|
|
1245
|
+
})
|
|
1246
|
+
export class EventCalendarComponent {
|
|
1247
|
+
eventForm = new FormGroup({
|
|
1248
|
+
availableDates: new FormControl<DatepickerValue>(null)
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
get selectedDates(): Date[] {
|
|
1252
|
+
const value = this.eventForm.get('availableDates')?.value;
|
|
1253
|
+
return Array.isArray(value) ? value : [];
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
### 7. Disabled Dates and Custom Validation
|
|
1259
|
+
|
|
1260
|
+
**Scenario**: Appointment booking with business hours and blackout dates.
|
|
1261
|
+
|
|
1262
|
+
```typescript
|
|
1263
|
+
import { Component } from '@angular/core';
|
|
1264
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1265
|
+
|
|
1266
|
+
@Component({
|
|
1267
|
+
selector: 'app-appointment',
|
|
1268
|
+
standalone: true,
|
|
1269
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1270
|
+
template: `
|
|
1271
|
+
<ngxsmk-datepicker
|
|
1272
|
+
mode="single"
|
|
1273
|
+
[minDate]="today"
|
|
1274
|
+
[maxDate]="maxBookingDate"
|
|
1275
|
+
[disabledDates]="blackoutDates"
|
|
1276
|
+
[isInvalidDate]="isWeekend"
|
|
1277
|
+
placeholder="Select appointment date">
|
|
1278
|
+
</ngxsmk-datepicker>
|
|
1279
|
+
`
|
|
1280
|
+
})
|
|
1281
|
+
export class AppointmentComponent {
|
|
1282
|
+
today = new Date();
|
|
1283
|
+
maxBookingDate = new Date(this.today.getTime() + 90 * 86400000); // 90 days ahead
|
|
1284
|
+
|
|
1285
|
+
// Blackout dates (holidays, maintenance days)
|
|
1286
|
+
blackoutDates: Date[] = [
|
|
1287
|
+
new Date(2025, 0, 1), // New Year's Day
|
|
1288
|
+
new Date(2025, 6, 4), // Independence Day
|
|
1289
|
+
new Date(2025, 11, 25), // Christmas
|
|
1290
|
+
];
|
|
1291
|
+
|
|
1292
|
+
// Disable weekends
|
|
1293
|
+
isWeekend = (date: Date): boolean => {
|
|
1294
|
+
const day = date.getDay();
|
|
1295
|
+
return day === 0 || day === 6; // Sunday or Saturday
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
### 9. Auto-Close After Selection
|
|
1301
|
+
|
|
1302
|
+
**Scenario**: Automatically close the calendar after date selection for better UX.
|
|
1303
|
+
|
|
1304
|
+
```typescript
|
|
1305
|
+
import { Component } from '@angular/core';
|
|
1306
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1307
|
+
|
|
1308
|
+
@Component({
|
|
1309
|
+
selector: 'app-auto-close',
|
|
1310
|
+
standalone: true,
|
|
1311
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1312
|
+
template: `
|
|
1313
|
+
<ngxsmk-datepicker
|
|
1314
|
+
mode="single"
|
|
1315
|
+
[autoApplyClose]="true"
|
|
1316
|
+
placeholder="Select a date">
|
|
1317
|
+
</ngxsmk-datepicker>
|
|
1318
|
+
|
|
1319
|
+
<ngxsmk-datepicker
|
|
1320
|
+
mode="range"
|
|
1321
|
+
[autoApplyClose]="true"
|
|
1322
|
+
placeholder="Select date range">
|
|
1323
|
+
</ngxsmk-datepicker>
|
|
1324
|
+
|
|
1325
|
+
<ngxsmk-datepicker
|
|
1326
|
+
mode="single"
|
|
1327
|
+
[showTime]="true"
|
|
1328
|
+
[autoApplyClose]="true"
|
|
1329
|
+
placeholder="Select date and time">
|
|
1330
|
+
</ngxsmk-datepicker>
|
|
1331
|
+
`
|
|
1332
|
+
})
|
|
1333
|
+
export class AutoCloseComponent {}
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
**Important Notes:**
|
|
1337
|
+
- `autoApplyClose` is automatically disabled when `showTime` is `true` (users need time to select time)
|
|
1338
|
+
- For single mode: closes immediately after date selection
|
|
1339
|
+
- For range mode: closes after both start and end dates are selected
|
|
1340
|
+
- Does not apply to inline mode calendars
|
|
1341
|
+
- Also works with predefined range selections
|
|
1342
|
+
|
|
1343
|
+
### 9. Programmatic Value Setting
|
|
1344
|
+
|
|
1345
|
+
**Scenario**: Setting datepicker value from API response or user action.
|
|
1346
|
+
|
|
1347
|
+
```typescript
|
|
1348
|
+
import { Component, signal } from '@angular/core';
|
|
1349
|
+
import { HttpClient } from '@angular/common/http';
|
|
1350
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
1351
|
+
import { CommonModule } from '@angular/common';
|
|
1352
|
+
|
|
1353
|
+
@Component({
|
|
1354
|
+
selector: 'app-programmatic',
|
|
1355
|
+
standalone: true,
|
|
1356
|
+
imports: [NgxsmkDatepickerComponent, CommonModule],
|
|
1357
|
+
template: `
|
|
1358
|
+
<button (click)="setToday()">Set Today</button>
|
|
1359
|
+
<button (click)="setNextWeek()">Set Next Week</button>
|
|
1360
|
+
<button (click)="loadFromApi()">Load from API</button>
|
|
1361
|
+
|
|
1362
|
+
<ngxsmk-datepicker
|
|
1363
|
+
mode="single"
|
|
1364
|
+
[value]="selectedDate()"
|
|
1365
|
+
(valueChange)="selectedDate.set($event)">
|
|
1366
|
+
</ngxsmk-datepicker>
|
|
1367
|
+
`
|
|
1368
|
+
})
|
|
1369
|
+
export class ProgrammaticComponent {
|
|
1370
|
+
selectedDate = signal<DatepickerValue>(null);
|
|
1371
|
+
|
|
1372
|
+
constructor(private http: HttpClient) {}
|
|
1373
|
+
|
|
1374
|
+
setToday() {
|
|
1375
|
+
this.selectedDate.set(new Date());
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
setNextWeek() {
|
|
1379
|
+
const nextWeek = new Date();
|
|
1380
|
+
nextWeek.setDate(nextWeek.getDate() + 7);
|
|
1381
|
+
this.selectedDate.set(nextWeek);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
loadFromApi() {
|
|
1385
|
+
this.http.get<{ date: string }>('/api/user-preferences').subscribe(response => {
|
|
1386
|
+
this.selectedDate.set(new Date(response.date));
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
### 9. Inline Calendar Display
|
|
1393
|
+
|
|
1394
|
+
**Scenario**: Always-visible calendar for dashboard or embedded views.
|
|
1395
|
+
|
|
1396
|
+
```typescript
|
|
1397
|
+
import { Component } from '@angular/core';
|
|
1398
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
1399
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1400
|
+
|
|
1401
|
+
@Component({
|
|
1402
|
+
selector: 'app-dashboard',
|
|
1403
|
+
standalone: true,
|
|
1404
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule],
|
|
1405
|
+
template: `
|
|
1406
|
+
<div class="dashboard">
|
|
1407
|
+
<h2>Calendar View</h2>
|
|
1408
|
+
<ngxsmk-datepicker
|
|
1409
|
+
mode="range"
|
|
1410
|
+
[inline]="true"
|
|
1411
|
+
formControlName="dateRange">
|
|
1412
|
+
</ngxsmk-datepicker>
|
|
1413
|
+
</div>
|
|
1414
|
+
`
|
|
1415
|
+
})
|
|
1416
|
+
export class DashboardComponent {
|
|
1417
|
+
dateRangeForm = new FormGroup({
|
|
1418
|
+
dateRange: new FormControl(null)
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
### 10. Custom Holiday Provider
|
|
1424
|
+
|
|
1425
|
+
**Scenario**: Displaying custom holidays or company-specific dates.
|
|
1426
|
+
|
|
1427
|
+
```typescript
|
|
1428
|
+
import { Component } from '@angular/core';
|
|
1429
|
+
import { NgxsmkDatepickerComponent, HolidayProvider } from 'ngxsmk-datepicker';
|
|
1430
|
+
|
|
1431
|
+
@Component({
|
|
1432
|
+
selector: 'app-holidays',
|
|
1433
|
+
standalone: true,
|
|
1434
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1435
|
+
template: `
|
|
1436
|
+
<ngxsmk-datepicker
|
|
1437
|
+
mode="single"
|
|
1438
|
+
[holidayProvider]="customHolidays"
|
|
1439
|
+
placeholder="Select date">
|
|
1440
|
+
</ngxsmk-datepicker>
|
|
1441
|
+
`
|
|
1442
|
+
})
|
|
1443
|
+
export class HolidaysComponent {
|
|
1444
|
+
customHolidays: HolidayProvider = {
|
|
1445
|
+
getHolidays: (year: number, month: number): Date[] => {
|
|
1446
|
+
const holidays: Date[] = [];
|
|
1447
|
+
|
|
1448
|
+
// New Year's Day
|
|
1449
|
+
if (month === 0) holidays.push(new Date(year, 0, 1));
|
|
1450
|
+
|
|
1451
|
+
// Company Anniversary (June 15)
|
|
1452
|
+
if (month === 5) holidays.push(new Date(year, 5, 15));
|
|
1453
|
+
|
|
1454
|
+
// Christmas
|
|
1455
|
+
if (month === 11) holidays.push(new Date(year, 11, 25));
|
|
1456
|
+
|
|
1457
|
+
return holidays;
|
|
1458
|
+
},
|
|
1459
|
+
|
|
1460
|
+
getHolidayLabel: (date: Date): string | null => {
|
|
1461
|
+
const month = date.getMonth();
|
|
1462
|
+
const day = date.getDate();
|
|
1463
|
+
|
|
1464
|
+
if (month === 0 && day === 1) return 'New Year\'s Day';
|
|
1465
|
+
if (month === 5 && day === 15) return 'Company Anniversary';
|
|
1466
|
+
if (month === 11 && day === 25) return 'Christmas';
|
|
1467
|
+
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
### 11. Custom Styling with Classes
|
|
1475
|
+
|
|
1476
|
+
**Scenario**: Applying custom CSS classes for branding or theme integration.
|
|
1477
|
+
|
|
1478
|
+
```typescript
|
|
1479
|
+
import { Component } from '@angular/core';
|
|
1480
|
+
import { NgxsmkDatepickerComponent, DatepickerClasses } from 'ngxsmk-datepicker';
|
|
1481
|
+
|
|
1482
|
+
@Component({
|
|
1483
|
+
selector: 'app-branded',
|
|
1484
|
+
standalone: true,
|
|
1485
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1486
|
+
template: `
|
|
1487
|
+
<ngxsmk-datepicker
|
|
1488
|
+
mode="single"
|
|
1489
|
+
[classes]="customClasses"
|
|
1490
|
+
placeholder="Select date">
|
|
1491
|
+
</ngxsmk-datepicker>
|
|
1492
|
+
`
|
|
1493
|
+
})
|
|
1494
|
+
export class BrandedComponent {
|
|
1495
|
+
customClasses: DatepickerClasses = {
|
|
1496
|
+
wrapper: 'custom-wrapper',
|
|
1497
|
+
inputGroup: 'custom-input-group border-2 border-blue-500',
|
|
1498
|
+
input: 'custom-input px-4 py-3 text-lg',
|
|
1499
|
+
popover: 'custom-popover shadow-xl',
|
|
1500
|
+
container: 'custom-container bg-gradient-to-br from-blue-50 to-purple-50',
|
|
1501
|
+
calendar: 'custom-calendar p-4',
|
|
1502
|
+
header: 'custom-header mb-4',
|
|
1503
|
+
dayCell: 'custom-day-cell hover:bg-blue-100',
|
|
1504
|
+
footer: 'custom-footer flex justify-end gap-2',
|
|
1505
|
+
clearBtn: 'custom-clear-btn',
|
|
1506
|
+
calendarBtn: 'custom-calendar-btn',
|
|
1507
|
+
closeBtn: 'custom-close-btn bg-blue-600 text-white'
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
### 12. Extension Points and Hooks
|
|
1513
|
+
|
|
1514
|
+
**Scenario**: Custom validation, formatting, and event handling.
|
|
1515
|
+
|
|
1516
|
+
```typescript
|
|
1517
|
+
import { Component } from '@angular/core';
|
|
1518
|
+
import { NgxsmkDatepickerComponent, DatepickerHooks, KeyboardShortcutContext } from 'ngxsmk-datepicker';
|
|
1519
|
+
|
|
1520
|
+
@Component({
|
|
1521
|
+
selector: 'app-custom-hooks',
|
|
1522
|
+
standalone: true,
|
|
1523
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1524
|
+
template: `
|
|
1525
|
+
<ngxsmk-datepicker
|
|
1526
|
+
mode="single"
|
|
1527
|
+
[hooks]="customHooks"
|
|
1528
|
+
[enableKeyboardShortcuts]="true"
|
|
1529
|
+
[customShortcuts]="customShortcuts"
|
|
1530
|
+
placeholder="Select date">
|
|
1531
|
+
</ngxsmk-datepicker>
|
|
1532
|
+
`
|
|
1533
|
+
})
|
|
1534
|
+
export class CustomHooksComponent {
|
|
1535
|
+
customHooks: DatepickerHooks = {
|
|
1536
|
+
// Custom CSS classes for day cells
|
|
1537
|
+
getDayCellClasses: (date, isSelected, isDisabled, isToday, isHoliday) => {
|
|
1538
|
+
const classes: string[] = [];
|
|
1539
|
+
if (isToday) classes.push('highlight-today');
|
|
1540
|
+
if (isHoliday) classes.push('holiday-cell');
|
|
1541
|
+
if (date.getDay() === 5) classes.push('friday-cell'); // Highlight Fridays
|
|
1542
|
+
return classes;
|
|
1543
|
+
},
|
|
1544
|
+
|
|
1545
|
+
// Custom tooltips
|
|
1546
|
+
getDayCellTooltip: (date, holidayLabel) => {
|
|
1547
|
+
if (holidayLabel) return holidayLabel;
|
|
1548
|
+
if (date.getDay() === 5) return 'TGIF!';
|
|
1549
|
+
return null;
|
|
1550
|
+
},
|
|
1551
|
+
|
|
1552
|
+
// Custom date formatting
|
|
1553
|
+
formatDisplayValue: (value, mode) => {
|
|
1554
|
+
if (!value) return '';
|
|
1555
|
+
if (value instanceof Date) {
|
|
1556
|
+
return value.toLocaleDateString('en-US', {
|
|
1557
|
+
weekday: 'long',
|
|
1558
|
+
year: 'numeric',
|
|
1559
|
+
month: 'long',
|
|
1560
|
+
day: 'numeric'
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
return String(value);
|
|
1564
|
+
},
|
|
1565
|
+
|
|
1566
|
+
// Custom validation
|
|
1567
|
+
validateDate: (date, currentValue, mode) => {
|
|
1568
|
+
// Prevent selecting dates more than 1 year in the future
|
|
1569
|
+
const oneYearFromNow = new Date();
|
|
1570
|
+
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
|
|
1571
|
+
return date <= oneYearFromNow;
|
|
1572
|
+
},
|
|
1573
|
+
|
|
1574
|
+
// Event hooks
|
|
1575
|
+
beforeDateSelect: (date, currentValue) => {
|
|
1576
|
+
console.log('About to select:', date);
|
|
1577
|
+
return true; // Return false to prevent selection
|
|
1578
|
+
},
|
|
1579
|
+
|
|
1580
|
+
afterDateSelect: (date, newValue) => {
|
|
1581
|
+
console.log('Date selected:', date, 'New value:', newValue);
|
|
1582
|
+
},
|
|
1583
|
+
|
|
1584
|
+
onCalendarOpen: () => {
|
|
1585
|
+
console.log('Calendar opened');
|
|
1586
|
+
},
|
|
1587
|
+
|
|
1588
|
+
onCalendarClose: () => {
|
|
1589
|
+
console.log('Calendar closed');
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
customShortcuts = {
|
|
1594
|
+
'Q': (context: KeyboardShortcutContext) => {
|
|
1595
|
+
// Custom shortcut: 'Q' for quarter selection
|
|
1596
|
+
const today = new Date();
|
|
1597
|
+
const quarter = Math.floor(today.getMonth() / 3);
|
|
1598
|
+
const quarterStart = new Date(today.getFullYear(), quarter * 3, 1);
|
|
1599
|
+
// Implementation would set the date
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
```
|
|
1605
|
+
|
|
1606
|
+
### 13. Theming and Customization
|
|
1607
|
+
|
|
1608
|
+
The datepicker supports comprehensive theming through CSS custom properties (CSS variables) and the `theme` input. All styling can be customized to match your application's design system.
|
|
1609
|
+
|
|
1610
|
+
#### 13.1. Built-in Light/Dark Themes
|
|
1611
|
+
|
|
1612
|
+
**Scenario**: Switching between light and dark themes.
|
|
1613
|
+
|
|
1614
|
+
```typescript
|
|
1615
|
+
import { Component, signal, computed } from '@angular/core';
|
|
1616
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1617
|
+
|
|
1618
|
+
@Component({
|
|
1619
|
+
selector: 'app-theme-switcher',
|
|
1620
|
+
standalone: true,
|
|
1621
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1622
|
+
template: `
|
|
1623
|
+
<button (click)="toggleTheme()">
|
|
1624
|
+
Switch to {{ isDark() ? 'Light' : 'Dark' }} Mode
|
|
1625
|
+
</button>
|
|
1626
|
+
|
|
1627
|
+
<ngxsmk-datepicker
|
|
1628
|
+
mode="single"
|
|
1629
|
+
[theme]="currentTheme()"
|
|
1630
|
+
placeholder="Select date">
|
|
1631
|
+
</ngxsmk-datepicker>
|
|
1632
|
+
`
|
|
1633
|
+
})
|
|
1634
|
+
export class ThemeSwitcherComponent {
|
|
1635
|
+
isDark = signal(false);
|
|
1636
|
+
|
|
1637
|
+
currentTheme = computed(() => this.isDark() ? 'dark' : 'light');
|
|
1638
|
+
|
|
1639
|
+
toggleTheme() {
|
|
1640
|
+
this.isDark.update(value => !value);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
```
|
|
1644
|
+
|
|
1645
|
+
#### 13.2. Custom CSS Variables Theming
|
|
1646
|
+
|
|
1647
|
+
**Scenario**: Customizing colors, spacing, and other design tokens using CSS custom properties.
|
|
1648
|
+
|
|
1649
|
+
```typescript
|
|
1650
|
+
import { Component } from '@angular/core';
|
|
1651
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1652
|
+
|
|
1653
|
+
@Component({
|
|
1654
|
+
selector: 'app-custom-theme',
|
|
1655
|
+
standalone: true,
|
|
1656
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1657
|
+
template: `
|
|
1658
|
+
<div class="custom-datepicker-wrapper">
|
|
1659
|
+
<ngxsmk-datepicker
|
|
1660
|
+
mode="single"
|
|
1661
|
+
placeholder="Select date">
|
|
1662
|
+
</ngxsmk-datepicker>
|
|
1663
|
+
</div>
|
|
1664
|
+
`,
|
|
1665
|
+
styles: [`
|
|
1666
|
+
.custom-datepicker-wrapper {
|
|
1667
|
+
--datepicker-primary-color: #3b82f6;
|
|
1668
|
+
--datepicker-primary-contrast: #ffffff;
|
|
1669
|
+
--datepicker-range-background: #dbeafe;
|
|
1670
|
+
--datepicker-background: #ffffff;
|
|
1671
|
+
--datepicker-text-color: #111827;
|
|
1672
|
+
--datepicker-subtle-text-color: #6b7280;
|
|
1673
|
+
--datepicker-border-color: #d1d5db;
|
|
1674
|
+
--datepicker-hover-background: #f3f4f6;
|
|
1675
|
+
--datepicker-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
1676
|
+
--datepicker-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
1677
|
+
--datepicker-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
1678
|
+
--datepicker-font-size-base: 15px;
|
|
1679
|
+
--datepicker-spacing-md: 14px;
|
|
1680
|
+
--datepicker-radius-md: 10px;
|
|
1681
|
+
}
|
|
1682
|
+
`]
|
|
1683
|
+
})
|
|
1684
|
+
export class CustomThemeComponent {}
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
#### 13.3. Complete CSS Variables Reference
|
|
1688
|
+
|
|
1689
|
+
All available CSS custom properties for theming:
|
|
1690
|
+
|
|
1691
|
+
**Colors:**
|
|
1692
|
+
- `--datepicker-primary-color`: Primary accent color (default: `#6d28d9`)
|
|
1693
|
+
- `--datepicker-primary-contrast`: Text color on primary background (default: `#ffffff`)
|
|
1694
|
+
- `--datepicker-range-background`: Background for date ranges (default: `#f5f3ff`)
|
|
1695
|
+
- `--datepicker-background`: Main background color (default: `#ffffff`)
|
|
1696
|
+
- `--datepicker-text-color`: Primary text color (default: `#1f2937`)
|
|
1697
|
+
- `--datepicker-subtle-text-color`: Secondary/subtle text color (default: `#6b7280`)
|
|
1698
|
+
- `--datepicker-border-color`: Border color (default: `#e5e7eb`)
|
|
1699
|
+
- `--datepicker-hover-background`: Hover state background (default: `#f3f4f6`)
|
|
1700
|
+
|
|
1701
|
+
**Shadows:**
|
|
1702
|
+
- `--datepicker-shadow-sm`: Small shadow (default: `0 1px 2px 0 rgba(0, 0, 0, 0.05)`)
|
|
1703
|
+
- `--datepicker-shadow-md`: Medium shadow (default: `0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)`)
|
|
1704
|
+
- `--datepicker-shadow-lg`: Large shadow (default: `0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)`)
|
|
1705
|
+
- `--datepicker-shadow-xl`: Extra large shadow (default: `0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)`)
|
|
1706
|
+
|
|
1707
|
+
**Typography:**
|
|
1708
|
+
- `--datepicker-font-size-base`: Base font size (default: `14px`)
|
|
1709
|
+
- `--datepicker-font-size-sm`: Small font size (default: `12px`)
|
|
1710
|
+
- `--datepicker-font-size-lg`: Large font size (default: `16px`)
|
|
1711
|
+
- `--datepicker-font-size-xl`: Extra large font size (default: `18px`)
|
|
1712
|
+
- `--datepicker-line-height`: Line height (default: `1.5`)
|
|
1713
|
+
|
|
1714
|
+
**Spacing:**
|
|
1715
|
+
- `--datepicker-spacing-xs`: Extra small spacing (default: `4px`)
|
|
1716
|
+
- `--datepicker-spacing-sm`: Small spacing (default: `8px`)
|
|
1717
|
+
- `--datepicker-spacing-md`: Medium spacing (default: `12px`)
|
|
1718
|
+
- `--datepicker-spacing-lg`: Large spacing (default: `16px`)
|
|
1719
|
+
- `--datepicker-spacing-xl`: Extra large spacing (default: `20px`)
|
|
1720
|
+
- `--datepicker-spacing-2xl`: 2X large spacing (default: `24px`)
|
|
1721
|
+
|
|
1722
|
+
**Border Radius:**
|
|
1723
|
+
- `--datepicker-radius-sm`: Small border radius (default: `6px`)
|
|
1724
|
+
- `--datepicker-radius-md`: Medium border radius (default: `8px`)
|
|
1725
|
+
- `--datepicker-radius-lg`: Large border radius (default: `12px`)
|
|
1726
|
+
- `--datepicker-radius-xl`: Extra large border radius (default: `16px`)
|
|
1727
|
+
|
|
1728
|
+
**Transitions:**
|
|
1729
|
+
- `--datepicker-transition`: Default transition (default: `opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1), transform 0.15s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.15s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.15s cubic-bezier(0.4, 0, 0.2, 1)`)
|
|
1730
|
+
|
|
1731
|
+
#### 13.4. Global Theme Application
|
|
1732
|
+
|
|
1733
|
+
**Scenario**: Applying theme to all datepickers in your application.
|
|
1734
|
+
|
|
1735
|
+
```typescript
|
|
1736
|
+
// styles.scss (global styles)
|
|
1737
|
+
:root {
|
|
1738
|
+
--datepicker-primary-color: #3b82f6;
|
|
1739
|
+
--datepicker-primary-contrast: #ffffff;
|
|
1740
|
+
--datepicker-range-background: #dbeafe;
|
|
1741
|
+
--datepicker-background: #ffffff;
|
|
1742
|
+
--datepicker-text-color: #111827;
|
|
1743
|
+
--datepicker-subtle-text-color: #6b7280;
|
|
1744
|
+
--datepicker-border-color: #d1d5db;
|
|
1745
|
+
--datepicker-hover-background: #f3f4f6;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
[data-theme="dark"] {
|
|
1749
|
+
--datepicker-primary-color: #60a5fa;
|
|
1750
|
+
--datepicker-primary-contrast: #1e293b;
|
|
1751
|
+
--datepicker-range-background: rgba(96, 165, 250, 0.15);
|
|
1752
|
+
--datepicker-background: #1e293b;
|
|
1753
|
+
--datepicker-text-color: #f1f5f9;
|
|
1754
|
+
--datepicker-subtle-text-color: #94a3b8;
|
|
1755
|
+
--datepicker-border-color: #334155;
|
|
1756
|
+
--datepicker-hover-background: #334155;
|
|
1757
|
+
}
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
#### 13.5. Component-Level Theming
|
|
1761
|
+
|
|
1762
|
+
**Scenario**: Different themes for different datepickers in the same application.
|
|
1763
|
+
|
|
1764
|
+
```typescript
|
|
1765
|
+
import { Component } from '@angular/core';
|
|
1766
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1767
|
+
|
|
1768
|
+
@Component({
|
|
1769
|
+
selector: 'app-multi-theme',
|
|
1770
|
+
standalone: true,
|
|
1771
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1772
|
+
template: `
|
|
1773
|
+
<div class="blue-theme">
|
|
1774
|
+
<h3>Blue Theme</h3>
|
|
1775
|
+
<ngxsmk-datepicker mode="single" placeholder="Select date"></ngxsmk-datepicker>
|
|
1776
|
+
</div>
|
|
1777
|
+
|
|
1778
|
+
<div class="green-theme">
|
|
1779
|
+
<h3>Green Theme</h3>
|
|
1780
|
+
<ngxsmk-datepicker mode="single" placeholder="Select date"></ngxsmk-datepicker>
|
|
1781
|
+
</div>
|
|
1782
|
+
`,
|
|
1783
|
+
styles: [`
|
|
1784
|
+
.blue-theme {
|
|
1785
|
+
--datepicker-primary-color: #3b82f6;
|
|
1786
|
+
--datepicker-range-background: #dbeafe;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
.green-theme {
|
|
1790
|
+
--datepicker-primary-color: #10b981;
|
|
1791
|
+
--datepicker-range-background: #d1fae5;
|
|
1792
|
+
}
|
|
1793
|
+
`]
|
|
1794
|
+
})
|
|
1795
|
+
export class MultiThemeComponent {}
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
#### 13.6. Integration with Design Systems
|
|
1799
|
+
|
|
1800
|
+
**Scenario**: Using with design token systems like TokiForge, Tailwind CSS, or Material Design.
|
|
1801
|
+
|
|
1802
|
+
```typescript
|
|
1803
|
+
import { Component } from '@angular/core';
|
|
1804
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1805
|
+
|
|
1806
|
+
@Component({
|
|
1807
|
+
selector: 'app-design-system',
|
|
1808
|
+
standalone: true,
|
|
1809
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1810
|
+
template: `
|
|
1811
|
+
<ngxsmk-datepicker
|
|
1812
|
+
mode="single"
|
|
1813
|
+
placeholder="Select date">
|
|
1814
|
+
</ngxsmk-datepicker>
|
|
1815
|
+
`,
|
|
1816
|
+
styles: [`
|
|
1817
|
+
:host {
|
|
1818
|
+
/* TokiForge integration example */
|
|
1819
|
+
--datepicker-primary-color: var(--toki-color-primary, #6d28d9);
|
|
1820
|
+
--datepicker-primary-contrast: var(--toki-color-on-primary, #ffffff);
|
|
1821
|
+
--datepicker-background: var(--toki-color-surface, #ffffff);
|
|
1822
|
+
--datepicker-text-color: var(--toki-color-on-surface, #1f2937);
|
|
1823
|
+
--datepicker-border-color: var(--toki-color-outline, #e5e7eb);
|
|
1824
|
+
|
|
1825
|
+
/* Spacing from design system */
|
|
1826
|
+
--datepicker-spacing-sm: var(--toki-spacing-sm, 8px);
|
|
1827
|
+
--datepicker-spacing-md: var(--toki-spacing-md, 12px);
|
|
1828
|
+
--datepicker-spacing-lg: var(--toki-spacing-lg, 16px);
|
|
1829
|
+
|
|
1830
|
+
/* Border radius from design system */
|
|
1831
|
+
--datepicker-radius-md: var(--toki-radius-md, 8px);
|
|
1832
|
+
--datepicker-radius-lg: var(--toki-radius-lg, 12px);
|
|
1833
|
+
}
|
|
1834
|
+
`]
|
|
1835
|
+
})
|
|
1836
|
+
export class DesignSystemComponent {}
|
|
1837
|
+
```
|
|
1838
|
+
|
|
1839
|
+
#### 13.7. Dark Theme Customization
|
|
1840
|
+
|
|
1841
|
+
**Scenario**: Customizing dark theme beyond the default.
|
|
1842
|
+
|
|
1843
|
+
```typescript
|
|
1844
|
+
import { Component } from '@angular/core';
|
|
1845
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1846
|
+
|
|
1847
|
+
@Component({
|
|
1848
|
+
selector: 'app-custom-dark',
|
|
1849
|
+
standalone: true,
|
|
1850
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1851
|
+
template: `
|
|
1852
|
+
<ngxsmk-datepicker
|
|
1853
|
+
mode="single"
|
|
1854
|
+
theme="dark"
|
|
1855
|
+
placeholder="Select date">
|
|
1856
|
+
</ngxsmk-datepicker>
|
|
1857
|
+
`,
|
|
1858
|
+
styles: [`
|
|
1859
|
+
:host ::ng-deep ngxsmk-datepicker.dark-theme {
|
|
1860
|
+
--datepicker-primary-color: #8b5cf6;
|
|
1861
|
+
--datepicker-range-background: rgba(139, 92, 246, 0.2);
|
|
1862
|
+
--datepicker-background: #0f172a;
|
|
1863
|
+
--datepicker-text-color: #f8fafc;
|
|
1864
|
+
--datepicker-border-color: #1e293b;
|
|
1865
|
+
}
|
|
1866
|
+
`]
|
|
1867
|
+
})
|
|
1868
|
+
export class CustomDarkComponent {}
|
|
1869
|
+
```
|
|
1870
|
+
|
|
1871
|
+
#### 13.8. Responsive Theming
|
|
1872
|
+
|
|
1873
|
+
**Scenario**: Adjusting theme variables based on screen size.
|
|
1874
|
+
|
|
1875
|
+
```typescript
|
|
1876
|
+
import { Component } from '@angular/core';
|
|
1877
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1878
|
+
|
|
1879
|
+
@Component({
|
|
1880
|
+
selector: 'app-responsive-theme',
|
|
1881
|
+
standalone: true,
|
|
1882
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1883
|
+
template: `
|
|
1884
|
+
<ngxsmk-datepicker mode="single" placeholder="Select date"></ngxsmk-datepicker>
|
|
1885
|
+
`,
|
|
1886
|
+
styles: [`
|
|
1887
|
+
:host {
|
|
1888
|
+
--datepicker-font-size-base: 14px;
|
|
1889
|
+
--datepicker-spacing-md: 12px;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
@media (min-width: 768px) {
|
|
1893
|
+
:host {
|
|
1894
|
+
--datepicker-font-size-base: 16px;
|
|
1895
|
+
--datepicker-spacing-md: 16px;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
`]
|
|
1899
|
+
})
|
|
1900
|
+
export class ResponsiveThemeComponent {}
|
|
1901
|
+
```
|
|
1902
|
+
|
|
1903
|
+
#### 13.9. Animation Customization
|
|
1904
|
+
|
|
1905
|
+
**Scenario**: Customizing transition speeds and easing functions.
|
|
1906
|
+
|
|
1907
|
+
```typescript
|
|
1908
|
+
import { Component } from '@angular/core';
|
|
1909
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1910
|
+
|
|
1911
|
+
@Component({
|
|
1912
|
+
selector: 'app-animation-theme',
|
|
1913
|
+
standalone: true,
|
|
1914
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1915
|
+
template: `
|
|
1916
|
+
<ngxsmk-datepicker mode="single" placeholder="Select date"></ngxsmk-datepicker>
|
|
1917
|
+
`,
|
|
1918
|
+
styles: [`
|
|
1919
|
+
:host {
|
|
1920
|
+
--datepicker-transition: all 0.2s ease-in-out;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/* For reduced motion preference */
|
|
1924
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1925
|
+
:host {
|
|
1926
|
+
--datepicker-transition: none;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
`]
|
|
1930
|
+
})
|
|
1931
|
+
export class AnimationThemeComponent {}
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
### 14. Locale and Internationalization
|
|
1935
|
+
|
|
1936
|
+
**Scenario**: Multi-language support with different date formats.
|
|
1937
|
+
|
|
1938
|
+
```typescript
|
|
1939
|
+
import { Component, signal } from '@angular/core';
|
|
1940
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1941
|
+
|
|
1942
|
+
@Component({
|
|
1943
|
+
selector: 'app-i18n',
|
|
1944
|
+
standalone: true,
|
|
1945
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1946
|
+
template: `
|
|
1947
|
+
<select [value]="currentLocale()" (change)="changeLocale($event)">
|
|
1948
|
+
<option value="en-US">English (US)</option>
|
|
1949
|
+
<option value="fr-FR">Français</option>
|
|
1950
|
+
<option value="de-DE">Deutsch</option>
|
|
1951
|
+
<option value="ja-JP">日本語</option>
|
|
1952
|
+
</select>
|
|
1953
|
+
|
|
1954
|
+
<ngxsmk-datepicker
|
|
1955
|
+
mode="single"
|
|
1956
|
+
[locale]="currentLocale()"
|
|
1957
|
+
placeholder="Select date">
|
|
1958
|
+
</ngxsmk-datepicker>
|
|
1959
|
+
`
|
|
1960
|
+
})
|
|
1961
|
+
export class I18nComponent {
|
|
1962
|
+
currentLocale = signal('en-US');
|
|
1963
|
+
|
|
1964
|
+
changeLocale(event: Event) {
|
|
1965
|
+
const select = event.target as HTMLSelectElement;
|
|
1966
|
+
this.currentLocale.set(select.value);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
```
|
|
1970
|
+
|
|
1971
|
+
### 15. SSR-Compatible Implementation
|
|
1972
|
+
|
|
1973
|
+
**Scenario**: Server-side rendering with platform detection.
|
|
1974
|
+
|
|
1975
|
+
```typescript
|
|
1976
|
+
import { Component, inject, PLATFORM_ID } from '@angular/core';
|
|
1977
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
1978
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
1979
|
+
|
|
1980
|
+
@Component({
|
|
1981
|
+
selector: 'app-ssr',
|
|
1982
|
+
standalone: true,
|
|
1983
|
+
imports: [NgxsmkDatepickerComponent],
|
|
1984
|
+
template: `
|
|
1985
|
+
<ngxsmk-datepicker
|
|
1986
|
+
mode="single"
|
|
1987
|
+
[value]="selectedDate"
|
|
1988
|
+
(valueChange)="onDateChange($event)">
|
|
1989
|
+
</ngxsmk-datepicker>
|
|
1990
|
+
`
|
|
1991
|
+
})
|
|
1992
|
+
export class SSRComponent {
|
|
1993
|
+
private platformId = inject(PLATFORM_ID);
|
|
1994
|
+
selectedDate: Date | null = null;
|
|
1995
|
+
|
|
1996
|
+
onDateChange(date: Date | null) {
|
|
1997
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
1998
|
+
// Browser-only logic (e.g., localStorage)
|
|
1999
|
+
localStorage.setItem('selectedDate', date?.toISOString() || '');
|
|
2000
|
+
}
|
|
2001
|
+
this.selectedDate = date;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
```
|
|
2005
|
+
|
|
2006
|
+
### 16. Ionic Framework Integration
|
|
2007
|
+
|
|
2008
|
+
**Scenario**: Using datepicker in Ionic Angular app.
|
|
2009
|
+
|
|
2010
|
+
```typescript
|
|
2011
|
+
import { Component } from '@angular/core';
|
|
2012
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
2013
|
+
import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
|
|
2014
|
+
|
|
2015
|
+
@Component({
|
|
2016
|
+
selector: 'app-ionic-datepicker',
|
|
2017
|
+
standalone: true,
|
|
2018
|
+
imports: [
|
|
2019
|
+
NgxsmkDatepickerComponent,
|
|
2020
|
+
IonContent,
|
|
2021
|
+
IonHeader,
|
|
2022
|
+
IonTitle,
|
|
2023
|
+
IonToolbar
|
|
2024
|
+
],
|
|
2025
|
+
template: `
|
|
2026
|
+
<ion-header>
|
|
2027
|
+
<ion-toolbar>
|
|
2028
|
+
<ion-title>Date Picker</ion-title>
|
|
2029
|
+
</ion-toolbar>
|
|
2030
|
+
</ion-header>
|
|
2031
|
+
|
|
2032
|
+
<ion-content class="ion-padding">
|
|
2033
|
+
<ngxsmk-datepicker
|
|
2034
|
+
mode="single"
|
|
2035
|
+
placeholder="Select date">
|
|
2036
|
+
</ngxsmk-datepicker>
|
|
2037
|
+
</ion-content>
|
|
2038
|
+
`
|
|
2039
|
+
})
|
|
2040
|
+
export class IonicDatepickerComponent {}
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
### 17. Complex Form with Multiple Datepickers
|
|
2044
|
+
|
|
2045
|
+
**Scenario**: Multi-step form with validation and conditional datepickers.
|
|
2046
|
+
|
|
2047
|
+
```typescript
|
|
2048
|
+
import { Component, signal } from '@angular/core';
|
|
2049
|
+
import { FormControl, FormGroup, ReactiveFormsModule, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
|
|
2050
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
2051
|
+
import { CommonModule } from '@angular/common';
|
|
2052
|
+
|
|
2053
|
+
@Component({
|
|
2054
|
+
selector: 'app-complex-form',
|
|
2055
|
+
standalone: true,
|
|
2056
|
+
imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule],
|
|
2057
|
+
template: `
|
|
2058
|
+
<form [formGroup]="complexForm">
|
|
2059
|
+
<div>
|
|
2060
|
+
<label>Start Date</label>
|
|
2061
|
+
<ngxsmk-datepicker
|
|
2062
|
+
mode="single"
|
|
2063
|
+
formControlName="startDate"
|
|
2064
|
+
[minDate]="today">
|
|
2065
|
+
</ngxsmk-datepicker>
|
|
2066
|
+
</div>
|
|
2067
|
+
|
|
2068
|
+
<div>
|
|
2069
|
+
<label>End Date</label>
|
|
2070
|
+
<ngxsmk-datepicker
|
|
2071
|
+
mode="single"
|
|
2072
|
+
formControlName="endDate"
|
|
2073
|
+
[minDate]="startDateValue()">
|
|
2074
|
+
</ngxsmk-datepicker>
|
|
2075
|
+
</div>
|
|
2076
|
+
|
|
2077
|
+
@if (complexForm.get('endDate')?.value && startDateValue()) {
|
|
2078
|
+
<div>
|
|
2079
|
+
<p>Duration: {{ calculateDuration() }} days</p>
|
|
2080
|
+
</div>
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
<button type="submit" [disabled]="complexForm.invalid">Submit</button>
|
|
2084
|
+
</form>
|
|
2085
|
+
`
|
|
2086
|
+
})
|
|
2087
|
+
export class ComplexFormComponent {
|
|
2088
|
+
today = new Date();
|
|
2089
|
+
|
|
2090
|
+
complexForm = new FormGroup({
|
|
2091
|
+
startDate: new FormControl<DatepickerValue>(null, [Validators.required]),
|
|
2092
|
+
endDate: new FormControl<DatepickerValue>(null, [Validators.required])
|
|
2093
|
+
}, {
|
|
2094
|
+
validators: this.dateRangeValidator
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
startDateValue = signal<Date | null>(null);
|
|
2098
|
+
|
|
2099
|
+
constructor() {
|
|
2100
|
+
this.complexForm.get('startDate')?.valueChanges.subscribe(value => {
|
|
2101
|
+
if (value instanceof Date) {
|
|
2102
|
+
this.startDateValue.set(value);
|
|
2103
|
+
}
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
dateRangeValidator(group: AbstractControl): ValidationErrors | null {
|
|
2108
|
+
const formGroup = group as FormGroup;
|
|
2109
|
+
const start = formGroup.get('startDate')?.value;
|
|
2110
|
+
const end = formGroup.get('endDate')?.value;
|
|
2111
|
+
|
|
2112
|
+
if (start && end && start instanceof Date && end instanceof Date) {
|
|
2113
|
+
if (end < start) {
|
|
2114
|
+
return { dateRange: true };
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
return null;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
calculateDuration(): number {
|
|
2121
|
+
const start = this.complexForm.get('startDate')?.value;
|
|
2122
|
+
const end = this.complexForm.get('endDate')?.value;
|
|
2123
|
+
|
|
2124
|
+
if (start instanceof Date && end instanceof Date) {
|
|
2125
|
+
const diff = end.getTime() - start.getTime();
|
|
2126
|
+
return Math.ceil(diff / (1000 * 60 * 60 * 24));
|
|
2127
|
+
}
|
|
2128
|
+
return 0;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
```
|
|
2132
|
+
|
|
2133
|
+
### 18. Computed Signal with Datepicker
|
|
2134
|
+
|
|
2135
|
+
**Scenario**: Deriving values from selected dates using computed signals.
|
|
2136
|
+
|
|
2137
|
+
```typescript
|
|
2138
|
+
import { Component, signal, computed } from '@angular/core';
|
|
2139
|
+
import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker';
|
|
2140
|
+
import { CommonModule, CurrencyPipe } from '@angular/common';
|
|
2141
|
+
|
|
2142
|
+
@Component({
|
|
2143
|
+
selector: 'app-computed',
|
|
2144
|
+
standalone: true,
|
|
2145
|
+
imports: [NgxsmkDatepickerComponent, CommonModule, CurrencyPipe],
|
|
2146
|
+
template: `
|
|
2147
|
+
<ngxsmk-datepicker
|
|
2148
|
+
mode="range"
|
|
2149
|
+
[value]="dateRange()"
|
|
2150
|
+
(valueChange)="dateRange.set($event)">
|
|
2151
|
+
</ngxsmk-datepicker>
|
|
2152
|
+
|
|
2153
|
+
@if (dateRange() && isRange(dateRange())) {
|
|
2154
|
+
<div>
|
|
2155
|
+
<p>Start: {{ formattedStart() }}</p>
|
|
2156
|
+
<p>End: {{ formattedEnd() }}</p>
|
|
2157
|
+
<p>Duration: {{ duration() }} days</p>
|
|
2158
|
+
<p>Total Cost: {{ totalCost() | currency }}</p>
|
|
2159
|
+
</div>
|
|
2160
|
+
}
|
|
2161
|
+
`
|
|
2162
|
+
})
|
|
2163
|
+
export class ComputedComponent {
|
|
2164
|
+
dateRange = signal<DatepickerValue>(null);
|
|
2165
|
+
dailyRate = signal(100); // $100 per day
|
|
2166
|
+
|
|
2167
|
+
isRange(value: DatepickerValue): value is { start: Date; end: Date } {
|
|
2168
|
+
return value !== null && typeof value === 'object' && 'start' in value;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
formattedStart = computed(() => {
|
|
2172
|
+
const range = this.dateRange();
|
|
2173
|
+
if (this.isRange(range)) {
|
|
2174
|
+
return range.start.toLocaleDateString();
|
|
2175
|
+
}
|
|
2176
|
+
return '';
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
formattedEnd = computed(() => {
|
|
2180
|
+
const range = this.dateRange();
|
|
2181
|
+
if (this.isRange(range)) {
|
|
2182
|
+
return range.end.toLocaleDateString();
|
|
2183
|
+
}
|
|
2184
|
+
return '';
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
duration = computed(() => {
|
|
2188
|
+
const range = this.dateRange();
|
|
2189
|
+
if (this.isRange(range)) {
|
|
2190
|
+
const diff = range.end.getTime() - range.start.getTime();
|
|
2191
|
+
return Math.ceil(diff / (1000 * 60 * 60 * 24)) + 1;
|
|
2192
|
+
}
|
|
2193
|
+
return 0;
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
totalCost = computed(() => {
|
|
2197
|
+
return this.duration() * this.dailyRate();
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
```
|
|
2201
|
+
|
|
2202
|
+
## Exported Types
|
|
2203
|
+
|
|
2204
|
+
### DatepickerValue
|
|
2205
|
+
|
|
2206
|
+
**Status**: Stable
|
|
2207
|
+
|
|
2208
|
+
```typescript
|
|
2209
|
+
type DatepickerValue =
|
|
2210
|
+
| Date // Single mode
|
|
2211
|
+
| { start: Date; end: Date } // Range mode
|
|
2212
|
+
| Date[] // Multiple mode
|
|
2213
|
+
| null;
|
|
2214
|
+
```
|
|
2215
|
+
|
|
2216
|
+
### DateInput
|
|
2217
|
+
|
|
2218
|
+
**Status**: Stable
|
|
2219
|
+
|
|
2220
|
+
```typescript
|
|
2221
|
+
type DateInput =
|
|
2222
|
+
| Date
|
|
2223
|
+
| string
|
|
2224
|
+
| { toDate: () => Date; _isAMomentObject?: boolean; $d?: Date };
|
|
2225
|
+
```
|
|
2226
|
+
|
|
2227
|
+
|
|
2228
|
+
### SignalFormField (v2.0.5+)
|
|
2229
|
+
|
|
2230
|
+
**Status**: Stable
|
|
2231
|
+
|
|
2232
|
+
Type representing a signal-based form field.
|
|
2233
|
+
|
|
2234
|
+
```typescript
|
|
2235
|
+
type SignalFormField = any; // Compatible with Angular 21 FieldTree
|
|
2236
|
+
```
|
|
2237
|
+
|
|
2238
|
+
### SignalFormFieldConfig (v2.0.5+)
|
|
2239
|
+
|
|
2240
|
+
**Status**: Stable
|
|
2241
|
+
|
|
2242
|
+
Interface for a resolved or direct signal form field configuration.
|
|
2243
|
+
|
|
2244
|
+
```typescript
|
|
2245
|
+
interface SignalFormFieldConfig {
|
|
2246
|
+
value: Signal<DatepickerValue> | WritableSignal<DatepickerValue> | (() => DatepickerValue);
|
|
2247
|
+
disabled: Signal<boolean> | (() => boolean) | boolean;
|
|
2248
|
+
required?: Signal<boolean> | (() => boolean) | boolean;
|
|
2249
|
+
setValue?: (value: DatepickerValue) => void;
|
|
2250
|
+
updateValue?: (updater: (prev: DatepickerValue) => DatepickerValue) => void;
|
|
2251
|
+
markAsDirty?: () => void;
|
|
2252
|
+
errors?: Signal<ValidationError[]> | (() => ValidationError[]);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
### DatepickerHooks
|
|
2256
|
+
|
|
2257
|
+
**Status**: Stable (v1.9.22+)
|
|
2258
|
+
|
|
2259
|
+
Comprehensive hook interface for customizing datepicker behavior.
|
|
2260
|
+
|
|
2261
|
+
```typescript
|
|
2262
|
+
interface DatepickerHooks {
|
|
2263
|
+
getDayCellClasses?: (date: Date, isSelected: boolean, isDisabled: boolean, isToday: boolean, isHoliday: boolean) => string[];
|
|
2264
|
+
getDayCellTooltip?: (date: Date, holidayLabel: string | null) => string | null;
|
|
2265
|
+
formatDayNumber?: (date: Date) => string;
|
|
2266
|
+
validateDate?: (date: Date, currentValue: DatepickerValue, mode: 'single' | 'range' | 'multiple') => boolean;
|
|
2267
|
+
validateRange?: (startDate: Date, endDate: Date) => boolean;
|
|
2268
|
+
getValidationError?: (date: Date) => string | null;
|
|
2269
|
+
handleShortcut?: (event: KeyboardEvent, context: KeyboardShortcutContext) => boolean;
|
|
2270
|
+
formatDisplayValue?: (value: DatepickerValue, mode: 'single' | 'range' | 'multiple') => string;
|
|
2271
|
+
formatAriaLabel?: (date: Date) => string;
|
|
2272
|
+
beforeDateSelect?: (date: Date, currentValue: DatepickerValue) => boolean;
|
|
2273
|
+
afterDateSelect?: (date: Date, newValue: DatepickerValue) => void;
|
|
2274
|
+
onCalendarOpen?: () => void;
|
|
2275
|
+
onCalendarClose?: () => void;
|
|
2276
|
+
}
|
|
2277
|
+
```
|
|
2278
|
+
|
|
2279
|
+
### KeyboardShortcutContext
|
|
2280
|
+
|
|
2281
|
+
**Status**: Stable (v1.9.22+)
|
|
2282
|
+
|
|
2283
|
+
Context object provided to keyboard shortcut handlers.
|
|
2284
|
+
|
|
2285
|
+
```typescript
|
|
2286
|
+
interface KeyboardShortcutContext {
|
|
2287
|
+
currentDate: Date;
|
|
2288
|
+
selectedDate: Date | null;
|
|
2289
|
+
startDate: Date | null;
|
|
2290
|
+
endDate: Date | null;
|
|
2291
|
+
selectedDates: Date[];
|
|
2292
|
+
mode: 'single' | 'range' | 'multiple';
|
|
2293
|
+
focusedDate: Date | null;
|
|
2294
|
+
isCalendarOpen: boolean;
|
|
2295
|
+
}
|
|
2296
|
+
```
|
|
2297
|
+
|
|
2298
|
+
### DateRange
|
|
2299
|
+
|
|
2300
|
+
**Status**: Stable
|
|
2301
|
+
|
|
2302
|
+
```typescript
|
|
2303
|
+
type DateRange = {
|
|
2304
|
+
[key: string]: [Date, Date];
|
|
2305
|
+
};
|
|
2306
|
+
```
|
|
2307
|
+
|
|
2308
|
+
### HolidayProvider
|
|
2309
|
+
|
|
2310
|
+
**Status**: Stable
|
|
2311
|
+
|
|
2312
|
+
```typescript
|
|
2313
|
+
interface HolidayProvider {
|
|
2314
|
+
getHolidays(year: number, month: number): Date[];
|
|
2315
|
+
getHolidayLabel(date: Date): string | null;
|
|
2316
|
+
}
|
|
2317
|
+
```
|
|
2318
|
+
|
|
2319
|
+
### DatepickerClasses
|
|
2320
|
+
|
|
2321
|
+
**Status**: Stable
|
|
2322
|
+
|
|
2323
|
+
```typescript
|
|
2324
|
+
interface DatepickerClasses {
|
|
2325
|
+
wrapper?: string;
|
|
2326
|
+
inputGroup?: string;
|
|
2327
|
+
input?: string;
|
|
2328
|
+
popover?: string;
|
|
2329
|
+
container?: string;
|
|
2330
|
+
calendar?: string;
|
|
2331
|
+
header?: string;
|
|
2332
|
+
dayCell?: string;
|
|
2333
|
+
footer?: string;
|
|
2334
|
+
clearBtn?: string;
|
|
2335
|
+
calendarBtn?: string;
|
|
2336
|
+
closeBtn?: string;
|
|
2337
|
+
navPrev?: string;
|
|
2338
|
+
navNext?: string;
|
|
2339
|
+
}
|
|
2340
|
+
```
|
|
2341
|
+
|
|
2342
|
+
## Exported Utilities
|
|
2343
|
+
|
|
2344
|
+
### Date Utilities
|
|
2345
|
+
|
|
2346
|
+
**Status**: Stable
|
|
2347
|
+
|
|
2348
|
+
```typescript
|
|
2349
|
+
// From './lib/utils/date.utils'
|
|
2350
|
+
export function isSameDay(d1: Date | null, d2: Date | null): boolean;
|
|
2351
|
+
export function normalizeDate(date: DateInput | null): Date | null;
|
|
2352
|
+
```
|
|
2353
|
+
|
|
2354
|
+
### Calendar Utilities
|
|
2355
|
+
|
|
2356
|
+
**Status**: Stable
|
|
2357
|
+
|
|
2358
|
+
```typescript
|
|
2359
|
+
// From './lib/utils/calendar.utils'
|
|
2360
|
+
export function getDaysInMonth(year: number, month: number): number;
|
|
2361
|
+
export function getFirstDayOfMonth(year: number, month: number): number;
|
|
2362
|
+
```
|
|
2363
|
+
|
|
2364
|
+
## Virtual scrolling
|
|
2365
|
+
|
|
2366
|
+
Year and decade views use the internal `calculateVirtualScroll` utility and visible-index signals so that only visible items are rendered when year ranges are large (e.g. 100+ years). The main calendar grid uses lazy month rendering via `_visibleCalendarIndicesSignal`. For very large ranges, consider limiting `yearRange` for optimal performance.
|
|
2367
|
+
|
|
2368
|
+
## Animation and reduced motion
|
|
2369
|
+
|
|
2370
|
+
Animation duration and transitions can be configured via `provideDatepickerConfig` or the global config. The component respects the `prefers-reduced-motion` media query when `animations.respectReducedMotion` is `true` (default): transitions are disabled for users who request reduced motion.
|
|
2371
|
+
|
|
2372
|
+
```typescript
|
|
2373
|
+
import { provideDatepickerConfig, DEFAULT_ANIMATION_CONFIG } from 'ngxsmk-datepicker';
|
|
2374
|
+
|
|
2375
|
+
// In app config or providers
|
|
2376
|
+
provideDatepickerConfig({
|
|
2377
|
+
animations: {
|
|
2378
|
+
...DEFAULT_ANIMATION_CONFIG,
|
|
2379
|
+
duration: 200,
|
|
2380
|
+
respectReducedMotion: true,
|
|
2381
|
+
},
|
|
2382
|
+
})
|
|
2383
|
+
```
|
|
2384
|
+
|
|
2385
|
+
## Keyboard Support
|
|
2386
|
+
|
|
2387
|
+
The datepicker comes with comprehensive keyboard support:
|
|
2388
|
+
|
|
2389
|
+
- **Navigation**: Arrow keys to navigate dates.
|
|
2390
|
+
- **Selection**: Enter/Space to select.
|
|
2391
|
+
- **Views**: Page Up/Down for months, Shift + Page Up/Down for years.
|
|
2392
|
+
- **Shortcuts**:
|
|
2393
|
+
- `T`: Today
|
|
2394
|
+
- `Y`: Yesterday
|
|
2395
|
+
- `N`: Tomorrow
|
|
2396
|
+
- `W`: Next Week
|
|
2397
|
+
- `?` or `Shift + /`: **Toggle Keyboard Help Dialog**
|
|
2398
|
+
- `Esc`: Close calendar
|
|
2399
|
+
|
|
2400
|
+
## Experimental APIs
|
|
2401
|
+
|
|
2402
|
+
The following APIs are experimental and may change:
|
|
2403
|
+
|
|
2404
|
+
- None currently
|
|
2405
|
+
|
|
2406
|
+
## Deprecated APIs
|
|
2407
|
+
|
|
2408
|
+
The following APIs are deprecated and will be removed in the next major version:
|
|
2409
|
+
|
|
2410
|
+
- None currently
|
|
2411
|
+
|
|
2412
|
+
## Migration Guides
|
|
2413
|
+
|
|
2414
|
+
When upgrading between major versions, see `MIGRATION.md` for detailed migration instructions.
|
|
2415
|
+
|
|
2416
|
+
## Support
|
|
2417
|
+
|
|
2418
|
+
For questions about the API:
|
|
2419
|
+
|
|
2420
|
+
- Check the [README.md](../../README.md) for usage examples
|
|
2421
|
+
- Review the [documentation](./) for detailed guides
|
|
2422
|
+
- Open an issue on GitHub for questions
|
|
2423
|
+
|