UNPKG

ngxsmk-datepicker

Version:

<!-- SEO Keywords: Angular DatePicker, Angular Date Range Picker, Lightweight Calendar Component, Angular Signals DatePicker, SSR Ready DatePicker, Zoneless Angular, A11y DatePicker, Mobile-Friendly DatePicker, Ionic DatePicker Meta Description: The

1,507 lines (1,294 loc) 76.6 kB
# Public API Documentation 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. **Version**: 2.2.8+ | **Last updated**: March 21, 2026 ## Stable vs experimental - **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. - **Experimental**: Marked in the tables; may change in minor releases. Use with awareness. ## Versioning Policy ngxsmk-datepicker follows [Semantic Versioning](https://semver.org/): - **MAJOR** version for incompatible API changes - **MINOR** version for backwards-compatible functionality additions - **PATCH** version for backwards-compatible bug fixes ## Breaking Changes Policy Breaking changes will only occur in major version releases. When breaking changes are introduced: 1. A migration guide will be provided in `MIGRATION.md` 2. Deprecated APIs will be marked with `@deprecated` JSDoc tags 3. Deprecated APIs will remain for at least one major version before removal 4. Clear upgrade instructions will be provided ## Exported Components ### NgxsmkDatepickerComponent **Status**: Stable The main datepicker component. ```typescript import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; ``` #### Complete Inputs/Outputs Reference ##### Inputs | Input | Type | Default | Status | Description | Example | |-------|------|---------|--------|-------------|---------| | `mode` | `'single' \| 'range' \| 'multiple'` | `'single'` | Stable | Selection mode | `mode="single"` or `[mode]="'range'"` | | `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"` | | `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"` | | `placeholder` | `string \| null` | `'Select Date'` or `'Select Time'` | Stable | Input placeholder text | `placeholder="Choose a date"` | | `inputId` | `string` | `''` | Stable | Custom ID for the input element | `inputId="my-date-input"` | | `name` | `string` | `''` | Stable | Name attribute for the input element | `name="my-date-field"` | | `autocomplete` | `string` | `'off'` | Stable | Autocomplete attribute for the input element | `autocomplete="on"` | | `aria-invalid` | `boolean` | `false` | Stable | Sets aria-invalid attribute on the input | `[aria-invalid]="true"` | | `disabledState` | `boolean` | `false` | Stable | Disable the datepicker | `[disabledState]="isDisabled"` | | `minDate` | `DateInput \| null` | `null` | Stable | Minimum selectable date | `[minDate]="today"` | | `maxDate` | `DateInput \| null` | `null` | Stable | Maximum selectable date | `[maxDate]="maxBookingDate"` | | `disabledDates` | `(string \| Date)[]` | `[]` | Stable | Array of disabled dates | `[disabledDates]="['10/21/2025', new Date()]"` | | `isInvalidDate` | `(date: Date) => boolean` | `() => false` | Stable | Custom date validation function | `[isInvalidDate]="isWeekend"` | | `locale` | `string` | `'en-US'` | Stable | Locale for formatting | `locale="de-DE"` or `[locale]="'fr-FR'"` | | `theme` | `'light' \| 'dark'` | `'light'` | Stable | Color theme | `[theme]="'dark'"` | | `inline` | `boolean \| 'always' \| 'auto'` | `false` | Stable | Inline display mode | `[inline]="true"` or `inline="auto"` | | `showTime` | `boolean` | `false` | Stable | Show time selection | `[showTime]="true"` | | `timeOnly` | `boolean` | `false` | Stable | Display time picker only (no calendar). Automatically enables `showTime`. | `[timeOnly]="true"` | | `allowTyping` | `boolean` | `false` | Stable | Enable manual typing in the input field. Required for native validation. | `[allowTyping]="true"` | | `displayFormat` | `string` | `null` | Stable | Custom date format string (e.g., 'MM/DD/YYYY'). | `displayFormat="DD.MM.YYYY"` | | `showCalendarButton` | `boolean` | `false` | Stable | Show/hide the calendar icon button. When `false`, users can still open calendar by clicking the input field. | `[showCalendarButton]="true"` | | `minuteInterval` | `number` | `1` | Stable | Minute selection interval | `[minuteInterval]="15"` | | `showSeconds` | `boolean` | `false` | Stable | Show seconds in time selection | `[showSeconds]="true"` | | `secondInterval` | `number` | `1` | Stable | Second selection interval (e.g. 1, 5, 15) | `[secondInterval]="5"` | | `use24Hour` | `boolean` | `false` | Stable | Use 24-hour time format (no AM/PM) | `[use24Hour]="true"` | | `showRanges` | `boolean` | `true` | Stable | Show predefined ranges (range mode) | `[showRanges]="true"` | | `ranges` | `DateRange` | `null` | Stable | Predefined date ranges | `[ranges]="quickRanges"` | | `holidayProvider` | `HolidayProvider \| null` | `null` | Stable | Custom holiday provider | `[holidayProvider]="myHolidayProvider"` | | `disableHolidays` | `boolean` | `false` | Stable | Disable holiday dates from selection | `[disableHolidays]="true"` | | `enableGoogleCalendar` | `boolean` | `false` | Stable | Enable seamless Google Calendar integration and sync. | `[enableGoogleCalendar]="true"` | | `googleClientId` | `string \| null` | `null` | Stable | Google API OAuth 2.0 Web Client ID for calendar sync. | `[googleClientId]="'my-client-id'"` | | `startAt` | `DateInput \| null` | `null` | Stable | Initial calendar view date | `[startAt]="nextMonth"` | | `weekStart` | `number \| null` | `null` | Stable | Override week start day (0=Sunday, 1=Monday, etc.) | `[weekStart]="1"` | | `yearRange` | `number` | `10` | Stable | Years to show in year selector | `[yearRange]="20"` | | `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' }"` | | `hooks` | `DatepickerHooks \| null` | `null` | Stable | Extension points for customization | `[hooks]="customHooks"` | | `enableKeyboardShortcuts` | `boolean` | `true` | Stable | Enable/disable keyboard shortcuts. Press '?' for help. | `[enableKeyboardShortcuts]="false"` | | `customShortcuts` | `{ [key: string]: (context: KeyboardShortcutContext) => boolean } \| null` | `null` | Stable | Custom keyboard shortcuts map | `[customShortcuts]="myShortcuts"` | | `autoApplyClose` | `boolean` | `false` | Stable | Auto-close calendar after selection | `[autoApplyClose]="true"` | | `clearLabel` | `string` | `'Clear'` | Stable | Custom label for clear button | `clearLabel="Reset"` | | `closeLabel` | `string` | `'Close'` | Stable | Custom label for close button | `closeLabel="Done"` | | `prevMonthAriaLabel` | `string` | `'Previous month'` | Stable | ARIA label for previous month button | `prevMonthAriaLabel="Go to previous month"` | | `nextMonthAriaLabel` | `string` | `'Next month'` | Stable | ARIA label for next month button | `nextMonthAriaLabel="Go to next month"` | | `clearAriaLabel` | `string` | `'Clear selection'` | Stable | ARIA label for clear button | `clearAriaLabel="Clear selected date"` | | `closeAriaLabel` | `string` | `'Close calendar'` | Stable | ARIA label for close button | `closeAriaLabel="Close date picker"` | | `rtl` | `boolean \| null` | `null` | Stable | Right-to-left layout (auto-detects from locale or document.dir) | `[rtl]="true"` | | `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'"` | | `timeRangeMode` | `boolean` | `false` | Stable | In range mode with `showTime`, show separate start/end time pickers (from/to). | `[timeRangeMode]="true"` | | `timezone` | `string` | `undefined` | Stable | IANA timezone name for display and value handling (e.g. `America/New_York`, `UTC`). | `timezone="Europe/London"` | | `useNativePicker` | `boolean` | `false` | Stable | Use the browser native date/time input when supported (e.g. on mobile). | `[useNativePicker]="true"` | | `autoDetectMobile` | `boolean` | `true` | Stable | When true, append popover to body and use mobile styles on small/touch devices. | `[autoDetectMobile]="false"` | | `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"` | | `mobileModalStyle` | `'bottom-sheet' \| 'center' \| 'fullscreen'` | `'bottom-sheet'` | Stable | How the calendar is shown on mobile (when detected). | `mobileModalStyle="fullscreen"` | | `mobileTimePickerStyle` | `'wheel' \| 'slider' \| 'native'` | `'slider'` | Stable | Time picker style on mobile. | `mobileTimePickerStyle="wheel"` | | `mobileTheme` | `'compact' \| 'comfortable' \| 'spacious'` | `'comfortable'` | Stable | Density of the calendar on mobile. | `mobileTheme="compact"` | | `enableHapticFeedback` | `boolean` | `false` | Stable | Trigger haptic feedback on supported devices when selecting/clearing. | `[enableHapticFeedback]="true"` | | `enablePullToRefresh` | `boolean` | `false` | Experimental | Reserve for future pull-to-refresh on mobile. | — | | `enableVoiceInput` | `boolean` | `false` | Experimental | Reserve for future voice input. | — | ##### Outputs | Output | Type | Status | Description | Example | |--------|------|--------|-------------|---------| | `valueChange` | `EventEmitter<DatepickerValue>` | Stable | Emitted when value changes | `(valueChange)="onDateChange($event)"` | | `action` | `EventEmitter<{ type: string; payload?: any }>` | Stable | Emitted on user actions (dateSelected, timeChanged, etc.) | `(action)="handleAction($event)"` | | `googleSyncClick` | `EventEmitter<void>` | Stable | Emitted when the user clicks the Google Calendar sync button. | `(googleSyncClick)="onSyncClick()"` | | `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)"` | #### Methods | Method | Parameters | Return | Status | Description | |--------|-----------|--------|--------|-------------| | `writeValue` | `val: DatepickerValue` | `void` | Stable | ControlValueAccessor implementation | | `registerOnChange` | `fn: (value: DatepickerValue) => void` | `void` | Stable | ControlValueAccessor implementation | | `registerOnTouched` | `fn: () => void` | `void` | Stable | ControlValueAccessor implementation | | `setDisabledState` | `isDisabled: boolean` | `void` | Stable | ControlValueAccessor implementation | ## Form Integration Examples ### Reactive Forms Integration The datepicker implements `ControlValueAccessor`, making it fully compatible with Angular Reactive Forms. #### Basic Reactive Forms Example ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-reactive-form', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule], template: ` <form [formGroup]="bookingForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label>Check-in Date</label> <ngxsmk-datepicker mode="single" placeholder="Select check-in date" formControlName="checkInDate"> </ngxsmk-datepicker> @if (bookingForm.get('checkInDate')?.hasError('required') && bookingForm.get('checkInDate')?.touched) { <div class="error">Check-in date is required</div> } </div> <div class="form-group"> <label>Check-out Date</label> <ngxsmk-datepicker mode="single" placeholder="Select check-out date" formControlName="checkOutDate" [minDate]="bookingForm.get('checkInDate')?.value"> </ngxsmk-datepicker> @if (bookingForm.get('checkOutDate')?.hasError('required') && bookingForm.get('checkOutDate')?.touched) { <div class="error">Check-out date is required</div> } </div> <button type="submit" [disabled]="bookingForm.invalid">Book Now</button> </form> ` }) export class ReactiveFormComponent { bookingForm = new FormGroup({ checkInDate: new FormControl<DatepickerValue>(null, [Validators.required]), checkOutDate: new FormControl<DatepickerValue>(null, [Validators.required]) }); onSubmit() { if (this.bookingForm.valid) { console.log('Form value:', this.bookingForm.value); // Submit to API } } } ``` #### Reactive Forms with Date Range ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue, DateRange } from 'ngxsmk-datepicker'; @Component({ selector: 'app-range-form', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule], template: ` <form [formGroup]="rangeForm"> <ngxsmk-datepicker mode="range" [showTime]="true" [minuteInterval]="15" [ranges]="quickRanges" [showRanges]="true" placeholder="Select date range" formControlName="dateRange"> </ngxsmk-datepicker> @if (rangeForm.get('dateRange')?.value) { <p>Selected range: {{ rangeForm.get('dateRange')?.value | json }}</p> } </form> ` }) export class RangeFormComponent { today = new Date(); quickRanges: DateRange = { 'Today': [this.today, this.today], 'Last 7 Days': [ new Date(this.today.getTime() - 6 * 86400000), this.today ], 'This Month': [ new Date(this.today.getFullYear(), this.today.getMonth(), 1), new Date(this.today.getFullYear(), this.today.getMonth() + 1, 0) ] }; rangeForm = new FormGroup({ dateRange: new FormControl<DatepickerValue>(null) }); } ``` #### Reactive Forms with Custom Validation ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators, AbstractControl, ValidationErrors } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; @Component({ selector: 'app-validated-form', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule], template: ` <form [formGroup]="validatedForm"> <ngxsmk-datepicker mode="single" placeholder="Select appointment date" [minDate]="today" [isInvalidDate]="isWeekend" formControlName="appointmentDate"> </ngxsmk-datepicker> @if (validatedForm.get('appointmentDate')?.hasError('required')) { <div class="error">Date is required</div> } @if (validatedForm.get('appointmentDate')?.hasError('minDate')) { <div class="error">Date must be in the future</div> } </form> ` }) export class ValidatedFormComponent { today = new Date(); validatedForm = new FormGroup({ appointmentDate: new FormControl<DatepickerValue>(null, [ Validators.required, this.minDateValidator ]) }); isWeekend = (date: Date): boolean => { const day = date.getDay(); return day === 0 || day === 6; // Sunday or Saturday }; minDateValidator(control: AbstractControl): ValidationErrors | null { const value = control.value; if (!value) return null; const today = new Date(); today.setHours(0, 0, 0, 0); if (value instanceof Date) { const selectedDate = new Date(value); selectedDate.setHours(0, 0, 0, 0); if (selectedDate < today) { return { minDate: true }; } } return null; } } ``` #### Reactive Forms with Multiple Date Selection ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule, DatePipe } from '@angular/common'; @Component({ selector: 'app-multiple-form', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule, DatePipe], template: ` <form [formGroup]="multipleForm"> <ngxsmk-datepicker mode="multiple" placeholder="Select available dates" formControlName="availableDates"> </ngxsmk-datepicker> @if (selectedDates.length > 0) { <div class="selected-dates"> <h4>Selected Dates ({{ selectedDates.length }}):</h4> <ul> @for (date of selectedDates; track date) { <li>{{ date | date:'short' }}</li> } </ul> </div> } </form> ` }) export class MultipleFormComponent { multipleForm = new FormGroup({ availableDates: new FormControl<DatepickerValue>(null) }); get selectedDates(): Date[] { const value = this.multipleForm.get('availableDates')?.value; return Array.isArray(value) ? value : []; } } ``` ### Template-Driven Forms Integration The datepicker also works seamlessly with Angular Template-Driven Forms using `ngModel`. #### Basic Template-Driven Forms Example ```typescript import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-template-form', standalone: true, imports: [NgxsmkDatepickerComponent, FormsModule, CommonModule], template: ` <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group"> <label>Event Date</label> <ngxsmk-datepicker mode="single" placeholder="Select event date" [(ngModel)]="eventDate" name="eventDate" #dateField="ngModel" required> </ngxsmk-datepicker> @if (dateField.invalid && dateField.touched) { <div class="error">Event date is required</div> } </div> <button type="submit" [disabled]="myForm.invalid">Submit</button> </form> @if (eventDate) { <p>Selected: {{ eventDate | date:'medium' }}</p> } ` }) export class TemplateFormComponent { eventDate: DatepickerValue = null; onSubmit(form: any) { if (form.valid) { console.log('Form value:', form.value); console.log('Event date:', this.eventDate); } } } ``` #### Template-Driven Forms with Date Range ```typescript import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; @Component({ selector: 'app-template-range', standalone: true, imports: [NgxsmkDatepickerComponent, FormsModule], template: ` <form #rangeForm="ngForm"> <ngxsmk-datepicker mode="range" [showTime]="true" placeholder="Select date range" [(ngModel)]="dateRange" name="dateRange"> </ngxsmk-datepicker> @if (dateRange) { <p> From: {{ dateRange.start | date:'short' }} To: {{ dateRange.end | date:'short' }} </p> } </form> ` }) export class TemplateRangeComponent { dateRange: DatepickerValue = null; } ``` #### Template-Driven Forms with Validation ```typescript import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; @Component({ selector: 'app-template-validated', standalone: true, imports: [NgxsmkDatepickerComponent, FormsModule], template: ` <form #validatedForm="ngForm"> <ngxsmk-datepicker mode="single" placeholder="Select date" [(ngModel)]="selectedDate" name="selectedDate" #dateCtrl="ngModel" required [minDate]="today"> </ngxsmk-datepicker> @if (dateCtrl.errors?.['required'] && dateCtrl.touched) { <div class="error">Date is required</div> } <button type="submit" [disabled]="validatedForm.invalid">Submit</button> </form> ` }) export class TemplateValidatedComponent { today = new Date(); selectedDate: DatepickerValue = null; } ``` ### Ionic Framework Integration The datepicker is fully compatible with Ionic Angular applications. Here are comprehensive examples for different Ionic use cases. #### Basic Ionic Integration ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; import { IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel } from '@ionic/angular/standalone'; import { FormsModule } from '@angular/forms'; @Component({ selector: 'app-ionic-datepicker', standalone: true, imports: [ NgxsmkDatepickerComponent, IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel, FormsModule ], template: ` <ion-header> <ion-toolbar> <ion-title>Date Picker</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding"> <ion-item> <ion-label position="stacked">Select Date</ion-label> <ngxsmk-datepicker mode="single" placeholder="Choose a date"> </ngxsmk-datepicker> </ion-item> </ion-content> ` }) export class IonicDatepickerComponent {} ``` #### Ionic with Reactive Forms ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel, IonButton, IonList } from '@ionic/angular/standalone'; @Component({ selector: 'app-ionic-reactive', standalone: true, imports: [ NgxsmkDatepickerComponent, ReactiveFormsModule, IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel, IonButton, IonList ], template: ` <ion-header> <ion-toolbar> <ion-title>Booking Form</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding"> <form [formGroup]="bookingForm" (ngSubmit)="onSubmit()"> <ion-list> <ion-item> <ion-label position="stacked">Check-in Date</ion-label> <ngxsmk-datepicker mode="single" placeholder="Select check-in" formControlName="checkIn"> </ngxsmk-datepicker> </ion-item> <ion-item> <ion-label position="stacked">Check-out Date</ion-label> <ngxsmk-datepicker mode="single" placeholder="Select check-out" formControlName="checkOut" [minDate]="bookingForm.get('checkIn')?.value"> </ngxsmk-datepicker> </ion-item> </ion-list> <ion-button expand="block" type="submit" [disabled]="bookingForm.invalid"> Book Now </ion-button> </form> </ion-content> ` }) export class IonicReactiveComponent { bookingForm = new FormGroup({ checkIn: new FormControl<DatepickerValue>(null), checkOut: new FormControl<DatepickerValue>(null) }); onSubmit() { if (this.bookingForm.valid) { console.log('Booking:', this.bookingForm.value); } } } ``` #### Ionic with Date Range and Time ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue, DateRange } from 'ngxsmk-datepicker'; import { IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel } from '@ionic/angular/standalone'; @Component({ selector: 'app-ionic-range', standalone: true, imports: [ NgxsmkDatepickerComponent, ReactiveFormsModule, IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel ], template: ` <ion-header> <ion-toolbar> <ion-title>Event Range</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding"> <form [formGroup]="eventForm"> <ion-item> <ion-label position="stacked">Event Date & Time Range</ion-label> <ngxsmk-datepicker mode="range" [showTime]="true" [minuteInterval]="15" [ranges]="quickRanges" [showRanges]="true" placeholder="Select event period" formControlName="eventRange"> </ngxsmk-datepicker> </ion-item> </form> </ion-content> ` }) export class IonicRangeComponent { today = new Date(); quickRanges: DateRange = { 'Today': [this.today, this.today], 'This Week': [ new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - this.today.getDay()), this.today ] }; eventForm = new FormGroup({ eventRange: new FormControl<DatepickerValue>(null) }); } ``` #### Ionic with Time-Only Picker ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel } from '@ionic/angular/standalone'; @Component({ selector: 'app-ionic-time', standalone: true, imports: [ NgxsmkDatepickerComponent, ReactiveFormsModule, IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel ], template: ` <ion-header> <ion-toolbar> <ion-title>Appointment Time</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding"> <form [formGroup]="timeForm"> <ion-item> <ion-label position="stacked">Select Time</ion-label> <ngxsmk-datepicker mode="single" [timeOnly]="true" [minuteInterval]="15" placeholder="Choose time" formControlName="appointmentTime"> </ngxsmk-datepicker> </ion-item> </form> </ion-content> ` }) export class IonicTimeComponent { timeForm = new FormGroup({ appointmentTime: new FormControl<DatepickerValue>(null) }); } ``` #### Ionic with Inline Calendar ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone'; @Component({ selector: 'app-ionic-inline', standalone: true, imports: [ NgxsmkDatepickerComponent, ReactiveFormsModule, IonContent, IonHeader, IonTitle, IonToolbar ], template: ` <ion-header> <ion-toolbar> <ion-title>Calendar View</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding"> <ngxsmk-datepicker mode="range" [inline]="true" placeholder="Select date range"> </ngxsmk-datepicker> </ion-content> ` }) export class IonicInlineComponent {} ``` #### Ionic with Dark Theme ```typescript import { Component, signal } from '@angular/core'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; import { IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel, IonToggle } from '@ionic/angular/standalone'; @Component({ selector: 'app-ionic-theme', standalone: true, imports: [ NgxsmkDatepickerComponent, IonContent, IonHeader, IonTitle, IonToolbar, IonItem, IonLabel, IonToggle ], template: ` <ion-header> <ion-toolbar> <ion-title>Theme Toggle</ion-title> </ion-toolbar> </ion-header> <ion-content class="ion-padding"> <ion-item> <ion-label>Dark Mode</ion-label> <ion-toggle [checked]="isDark()" (ionChange)="toggleTheme()"> </ion-toggle> </ion-item> <ion-item> <ion-label position="stacked">Select Date</ion-label> <ngxsmk-datepicker mode="single" [theme]="isDark() ? 'dark' : 'light'" placeholder="Choose a date"> </ngxsmk-datepicker> </ion-item> </ion-content> ` }) export class IonicThemeComponent { isDark = signal(false); toggleTheme() { this.isDark.update(v => !v); } } ``` ## Real-World Usage Examples ### 1. Basic Single Date Selection **Scenario**: Simple date picker for selecting a single date. ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; @Component({ selector: 'app-basic', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker mode="single" placeholder="Select a date" (valueChange)="onDateChange($event)"> </ngxsmk-datepicker> ` }) export class BasicComponent { onDateChange(date: Date | null) { console.log('Selected date:', date); } } ``` ### 2. Reactive Forms Integration **Scenario**: Using with Angular Reactive Forms for form validation and submission. ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-reactive-form', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule], template: ` <form [formGroup]="bookingForm" (ngSubmit)="onSubmit()"> <ngxsmk-datepicker mode="single" placeholder="Select check-in date" formControlName="checkInDate"> </ngxsmk-datepicker> @if (bookingForm.get('checkInDate')?.hasError('required')) { <div>Check-in date is required</div> } <button type="submit" [disabled]="bookingForm.invalid">Book</button> </form> ` }) export class ReactiveFormComponent { bookingForm = new FormGroup({ checkInDate: new FormControl<DatepickerValue>(null, [Validators.required]) }); onSubmit() { if (this.bookingForm.valid) { console.log('Form value:', this.bookingForm.value); } } } ``` ### 3. Signal Binding (Angular 17+) **Scenario**: Using writable signals for reactive date selection. ```typescript import { Component, signal } from '@angular/core'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; @Component({ selector: 'app-signal-binding', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker mode="single" [value]="selectedDate()" (valueChange)="selectedDate.set($event)"> </ngxsmk-datepicker> <p>Selected: {{ selectedDate() | date:'medium' }}</p> ` }) export class SignalBindingComponent { selectedDate = signal<DatepickerValue>(null); } ``` ### 4. Signal Forms with Server Data (Angular 21+) **Scenario**: Populating datepicker from server-side data using httpResource and Signal Forms. ```typescript import { Component, inject, signal, linkedSignal } from '@angular/core'; import { httpResource } from '@angular/common/http'; import { HttpClient } from '@angular/common/http'; import { form, objectSchema } from '@angular/forms'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; interface BookingData { checkInDate: Date; checkOutDate: Date; guestName: string; } @Component({ selector: 'app-server-form', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <form> <label>Check-in Date</label> <ngxsmk-datepicker [field]="bookingForm.checkInDate" mode="single" placeholder="Select check-in date"> </ngxsmk-datepicker> <label>Check-out Date</label> <ngxsmk-datepicker [field]="bookingForm.checkOutDate" mode="single" placeholder="Select check-out date"> </ngxsmk-datepicker> <button (click)="saveBooking()">Save Booking</button> </form> ` }) export class ServerFormComponent { private http = inject(HttpClient); // Fetch booking data from server resource = httpResource({ request: () => this.http.get<BookingData>('/api/bookings/123'), loader: signal(false) }); // Link server response to local signal localObject = linkedSignal(() => this.resource.response.value() || { checkInDate: new Date(), checkOutDate: new Date(), guestName: '' }); // Create signal form bookingForm = form(this.localObject, objectSchema({ checkInDate: objectSchema<Date>(), checkOutDate: objectSchema<Date>(), guestName: objectSchema<string>() })); saveBooking() { // Form automatically syncs with localObject signal this.http.put('/api/bookings/123', this.localObject()).subscribe(); } } ``` ### 5. Signal Forms with Manual Binding (Stabilized Pattern) **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. ```typescript import { Component, signal, computed, form, objectSchema } from '@angular/core'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; @Component({ selector: 'app-stable-form', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker class="w-full border:none" [value]="myDate()" (valueChange)="onMyDateChange($any($event))" mode="single" placeholder="Select a date"> </ngxsmk-datepicker> ` }) export class StableFormComponent { localObject = signal({ myDate: new Date() }); myForm = form(this.localObject, objectSchema({ myDate: objectSchema<Date>() })); // Get a computed signal reference to the date field value myDate = computed(() => this.myForm.value().myDate); onMyDateChange(newDate: Date): void { // Use setValue to ensure dirty state tracking works correctly if (typeof this.myForm.myDate.setValue === 'function') { this.myForm.myDate.setValue(newDate); } else if (typeof this.myForm.myDate.updateValue === 'function') { this.myForm.myDate.updateValue(() => newDate); } else { // Fallback: directly mutate (may not track dirty state) this.myForm.value().myDate = newDate; } } } ``` **When to use this pattern:** - When `[field]` binding causes stability issues or change detection loops - When you need more explicit control over form updates - When direct mutation is preferred over creating new object references **Note:** The `$any($event)` cast may be needed if there's a type mismatch between `DatepickerValue` and your expected `Date` type. **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: ```typescript export class ServerFormComponent { localObject = signal<{ myDate: Date | null }>({ myDate: null }); myForm = form(this.localObject, objectSchema({ myDate: objectSchema<Date | null>() })); myDate = computed(() => this.myForm.value().myDate); onMyDateChange(newDate: DatepickerValue | null): void { this.localObject.update(obj => ({ ...obj, myDate: newDate instanceof Date ? newDate : new Date(newDate.toLocaleString()) })); } updateFromServer(serverDate: Date | string): void { const dateValue = serverDate instanceof Date ? serverDate : new Date(serverDate); this.localObject.update(obj => ({ ...obj, myDate: dateValue })); } } ``` ### 6. Time-Only Picker **Scenario**: Display only time selection without calendar. Perfect for time selection scenarios where date is not needed. ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; @Component({ selector: 'app-time-only', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule], template: ` <form [formGroup]="timeForm"> <ngxsmk-datepicker mode="single" [timeOnly]="true" [minuteInterval]="15" formControlName="appointmentTime"> </ngxsmk-datepicker> @if (timeForm.controls.appointmentTime.value) { <p>Selected time: {{ timeForm.controls.appointmentTime.value | date:'shortTime' }}</p> } </form> ` }) export class TimeOnlyComponent { timeForm = new FormGroup({ appointmentTime: new FormControl<DatepickerValue>(null) }); } ``` **Key Features:** - Hides calendar grid completely - Shows only time selection controls (hour, minute, AM/PM) - Value is still a Date object (uses today's date with selected time) - Automatically enables `showTime` when `timeOnly` is true - Placeholder defaults to "Select Time" ### 7. Date Range Selection with Time **Scenario**: Booking system with check-in/check-out dates and times. ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DateRange, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-booking', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule], template: ` <form [formGroup]="bookingForm"> <ngxsmk-datepicker mode="range" [showTime]="true" [minuteInterval]="15" [ranges]="quickRanges" [showRanges]="true" [minDate]="today" placeholder="Select check-in and check-out dates" formControlName="dateRange"> </ngxsmk-datepicker> </form> ` }) export class BookingComponent { today = new Date(); bookingForm = new FormGroup({ dateRange: new FormControl<DatepickerValue>(null) }); quickRanges: DateRange = { 'Today': [this.today, this.today], 'Tomorrow': [ new Date(this.today.getTime() + 86400000), new Date(this.today.getTime() + 86400000) ], 'This Week': [ new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - this.today.getDay()), this.today ], 'Next 7 Days': [ this.today, new Date(this.today.getTime() + 7 * 86400000) ] }; } ``` ### 8. Multiple Date Selection **Scenario**: Event calendar where users can select multiple dates for availability. ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule } from '@angular/common'; import { DatePipe } from '@angular/common'; @Component({ selector: 'app-event-calendar', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule, CommonModule, DatePipe], template: ` <form [formGroup]="eventForm"> <ngxsmk-datepicker mode="multiple" placeholder="Select available dates" formControlName="availableDates"> </ngxsmk-datepicker> @if (selectedDates.length > 0) { <div> <p>Selected {{ selectedDates.length }} dates:</p> <ul> @for (date of selectedDates; track date) { <li>{{ date | date:'short' }}</li> } </ul> </div> } </form> ` }) export class EventCalendarComponent { eventForm = new FormGroup({ availableDates: new FormControl<DatepickerValue>(null) }); get selectedDates(): Date[] { const value = this.eventForm.get('availableDates')?.value; return Array.isArray(value) ? value : []; } } ``` ### 7. Disabled Dates and Custom Validation **Scenario**: Appointment booking with business hours and blackout dates. ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; @Component({ selector: 'app-appointment', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker mode="single" [minDate]="today" [maxDate]="maxBookingDate" [disabledDates]="blackoutDates" [isInvalidDate]="isWeekend" placeholder="Select appointment date"> </ngxsmk-datepicker> ` }) export class AppointmentComponent { today = new Date(); maxBookingDate = new Date(this.today.getTime() + 90 * 86400000); // 90 days ahead // Blackout dates (holidays, maintenance days) blackoutDates: Date[] = [ new Date(2025, 0, 1), // New Year's Day new Date(2025, 6, 4), // Independence Day new Date(2025, 11, 25), // Christmas ]; // Disable weekends isWeekend = (date: Date): boolean => { const day = date.getDay(); return day === 0 || day === 6; // Sunday or Saturday }; } ``` ### 9. Auto-Close After Selection **Scenario**: Automatically close the calendar after date selection for better UX. ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; @Component({ selector: 'app-auto-close', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker mode="single" [autoApplyClose]="true" placeholder="Select a date"> </ngxsmk-datepicker> <ngxsmk-datepicker mode="range" [autoApplyClose]="true" placeholder="Select date range"> </ngxsmk-datepicker> <ngxsmk-datepicker mode="single" [showTime]="true" [autoApplyClose]="true" placeholder="Select date and time"> </ngxsmk-datepicker> ` }) export class AutoCloseComponent {} ``` **Important Notes:** - `autoApplyClose` is automatically disabled when `showTime` is `true` (users need time to select time) - For single mode: closes immediately after date selection - For range mode: closes after both start and end dates are selected - Does not apply to inline mode calendars - Also works with predefined range selections ### 9. Programmatic Value Setting **Scenario**: Setting datepicker value from API response or user action. ```typescript import { Component, signal } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { NgxsmkDatepickerComponent, DatepickerValue } from 'ngxsmk-datepicker'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-programmatic', standalone: true, imports: [NgxsmkDatepickerComponent, CommonModule], template: ` <button (click)="setToday()">Set Today</button> <button (click)="setNextWeek()">Set Next Week</button> <button (click)="loadFromApi()">Load from API</button> <ngxsmk-datepicker mode="single" [value]="selectedDate()" (valueChange)="selectedDate.set($event)"> </ngxsmk-datepicker> ` }) export class ProgrammaticComponent { selectedDate = signal<DatepickerValue>(null); constructor(private http: HttpClient) {} setToday() { this.selectedDate.set(new Date()); } setNextWeek() { const nextWeek = new Date(); nextWeek.setDate(nextWeek.getDate() + 7); this.selectedDate.set(nextWeek); } loadFromApi() { this.http.get<{ date: string }>('/api/user-preferences').subscribe(response => { this.selectedDate.set(new Date(response.date)); }); } } ``` ### 9. Inline Calendar Display **Scenario**: Always-visible calendar for dashboard or embedded views. ```typescript import { Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker'; @Component({ selector: 'app-dashboard', standalone: true, imports: [NgxsmkDatepickerComponent, ReactiveFormsModule], template: ` <div class="dashboard"> <h2>Calendar View</h2> <ngxsmk-datepicker mode="range" [inline]="true" formControlName="dateRange"> </ngxsmk-datepicker> </div> ` }) export class DashboardComponent { dateRangeForm = new FormGroup({ dateRange: new FormControl(null) }); } ``` ### 10. Custom Holiday Provider **Scenario**: Displaying custom holidays or company-specific dates. ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent, HolidayProvider } from 'ngxsmk-datepicker'; @Component({ selector: 'app-holidays', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker mode="single" [holidayProvider]="customHolidays" placeholder="Select date"> </ngxsmk-datepicker> ` }) export class HolidaysComponent { customHolidays: HolidayProvider = { getHolidays: (year: number, month: number): Date[] => { const holidays: Date[] = []; // New Year's Day if (month === 0) holidays.push(new Date(year, 0, 1)); // Company Anniversary (June 15) if (month === 5) holidays.push(new Date(year, 5, 15)); // Christmas if (month === 11) holidays.push(new Date(year, 11, 25)); return holidays; }, getHolidayLabel: (date: Date): string | null => { const month = date.getMonth(); const day = date.getDate(); if (month === 0 && day === 1) return 'New Year\'s Day'; if (month === 5 && day === 15) return 'Company Anniversary'; if (month === 11 && day === 25) return 'Christmas'; return null; } }; } ``` ### 11. Custom Styling with Classes **Scenario**: Applying custom CSS classes for branding or theme integration. ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent, DatepickerClasses } from 'ngxsmk-datepicker'; @Component({ selector: 'app-branded', standalone: true, imports: [NgxsmkDatepickerComponent], template: ` <ngxsmk-datepicker mode="single" [classes]="customClasses" placeholder="Select date"> </ngxsmk-datepicker> ` }) export class BrandedComponent { customClasses: DatepickerClasses = { wrapper: 'custom-wrapper', inputGroup: 'custom-input-group border-2 border-blue-500', input: 'custom-input px-4 py-3 text-lg', popover: 'custom-popover shadow-xl', container: 'custom-container bg-gradient-to-br from-blue-50 to-purple-50', calendar: 'custom-calendar p-4', header: 'custom-header mb-4', dayCell: 'custom-day-cell hover:bg-blue-100', footer: 'custom-footer flex justify-end gap-2', clearBtn: 'custom-clear-btn', calendarBtn: 'custom-calendar-btn', closeBtn: 'custom-close-btn bg-blue-600 text-white' }; } ``` ### 12. Extension Points and Hooks **Scenario**: Custom validation, formatting, and event handling. ```typescript import { Component } from '@angular/core'; import { NgxsmkDatepickerComponent, DatepickerHooks, KeyboardShortcutContext } from 'ngxsmk-datepicker'; @Component({ selector: 'app-custom-hooks', standalone: true, imports: [NgxsmkDatepickerComponent],