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