ngxsmk-datepicker 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +942 -922
- package/docs/API.md +2423 -2413
- package/docs/COMPATIBILITY.md +471 -471
- package/docs/INTEGRATION.md +703 -703
- package/docs/IONIC_INTEGRATION.md +228 -228
- package/docs/LOCALE-GUIDE.md +300 -300
- package/docs/PLUGIN-ARCHITECTURE.md +930 -930
- package/docs/SSR-EXAMPLE.md +427 -427
- package/docs/THEME-TOKENS.md +324 -324
- package/docs/TIMEZONE.md +307 -307
- package/docs/extension-points.md +419 -419
- package/docs/signal-forms.md +600 -600
- package/docs/signals.md +266 -266
- package/docs/ssr.md +305 -305
- package/package.json +106 -104
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/schema.json +14 -0
- package/schematics/tsconfig.json +17 -0
- package/CHANGELOG.md +0 -1246
- package/LICENSE +0 -21
- package/MIGRATION.md +0 -1794
- package/docs/FEATURE_SCOPING.md +0 -60
- package/docs/IONIC_TESTING.md +0 -148
- package/docs/REFACTOR_PLAN.md +0 -38
- package/docs/SEO.md +0 -214
- package/fesm2022/ngxsmk-datepicker.mjs +0 -14693
- package/types/ngxsmk-datepicker.d.ts +0 -2489
package/docs/extension-points.md
CHANGED
|
@@ -1,419 +1,419 @@
|
|
|
1
|
-
# Extension Points and Hooks
|
|
2
|
-
|
|
3
|
-
**Last updated:**
|
|
4
|
-
|
|
5
|
-
ngxsmk-datepicker provides comprehensive extension points through the `hooks` input, allowing you to customize rendering, validation, keyboard shortcuts, formatting, and event handling.
|
|
6
|
-
|
|
7
|
-
> **📚 For a complete understanding of the plugin architecture, see the [Plugin Architecture Guide](./PLUGIN-ARCHITECTURE.md)** which covers architecture principles, plugin patterns, lifecycle, and advanced use cases.
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
The `hooks` input accepts a `DatepickerHooks` object that implements various hook interfaces:
|
|
12
|
-
|
|
13
|
-
- **DayCellRenderHook**: Customize day cell rendering
|
|
14
|
-
- **ValidationHook**: Custom validation and range rules
|
|
15
|
-
- **KeyboardShortcutHook**: Custom keyboard shortcuts
|
|
16
|
-
- **DateFormatHook**: Custom date formatting
|
|
17
|
-
- **EventHook**: Event lifecycle hooks
|
|
18
|
-
|
|
19
|
-
## Basic Usage
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { Component } from '@angular/core';
|
|
23
|
-
import { NgxsmkDatepickerComponent, DatepickerHooks } from 'ngxsmk-datepicker';
|
|
24
|
-
|
|
25
|
-
@Component({
|
|
26
|
-
selector: 'app-custom',
|
|
27
|
-
standalone: true,
|
|
28
|
-
imports: [NgxsmkDatepickerComponent],
|
|
29
|
-
template: `
|
|
30
|
-
<ngxsmk-datepicker
|
|
31
|
-
[hooks]="myHooks"
|
|
32
|
-
mode="single">
|
|
33
|
-
</ngxsmk-datepicker>
|
|
34
|
-
`
|
|
35
|
-
})
|
|
36
|
-
export class CustomComponent {
|
|
37
|
-
myHooks: DatepickerHooks = {
|
|
38
|
-
// Implement hook methods here
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Day Cell Rendering Hooks
|
|
44
|
-
|
|
45
|
-
### Custom CSS Classes
|
|
46
|
-
|
|
47
|
-
Add custom CSS classes to day cells:
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
myHooks: DatepickerHooks = {
|
|
51
|
-
getDayCellClasses: (date, isSelected, isDisabled, isToday, isHoliday) => {
|
|
52
|
-
const classes: string[] = [];
|
|
53
|
-
|
|
54
|
-
// Add custom class for weekends
|
|
55
|
-
if (date.getDay() === 0 || date.getDay() === 6) {
|
|
56
|
-
classes.push('weekend-day');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Add custom class for first of month
|
|
60
|
-
if (date.getDate() === 1) {
|
|
61
|
-
classes.push('first-of-month');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return classes;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Custom Tooltips
|
|
70
|
-
|
|
71
|
-
Customize tooltip text for day cells:
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
myHooks: DatepickerHooks = {
|
|
75
|
-
getDayCellTooltip: (date, holidayLabel) => {
|
|
76
|
-
if (holidayLabel) {
|
|
77
|
-
return `Holiday: ${holidayLabel}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Add custom tooltip for specific dates
|
|
81
|
-
if (date.getDate() === 15) {
|
|
82
|
-
return 'Mid-month special';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return null; // Use default
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Custom Day Number Formatting
|
|
91
|
-
|
|
92
|
-
Format the day number display:
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
myHooks: DatepickerHooks = {
|
|
96
|
-
formatDayNumber: (date) => {
|
|
97
|
-
// Add suffix (1st, 2nd, 3rd, etc.)
|
|
98
|
-
const day = date.getDate();
|
|
99
|
-
const suffix = day === 1 || day === 21 || day === 31 ? 'st' :
|
|
100
|
-
day === 2 || day === 22 ? 'nd' :
|
|
101
|
-
day === 3 || day === 23 ? 'rd' : 'th';
|
|
102
|
-
return `${day}${suffix}`;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Validation Hooks
|
|
108
|
-
|
|
109
|
-
### Custom Date Validation
|
|
110
|
-
|
|
111
|
-
Add custom validation logic:
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
myHooks: DatepickerHooks = {
|
|
115
|
-
validateDate: (date, currentValue, mode) => {
|
|
116
|
-
// Prevent selecting dates in the past
|
|
117
|
-
const today = new Date();
|
|
118
|
-
today.setHours(0, 0, 0, 0);
|
|
119
|
-
if (date < today) {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Prevent selecting more than 5 dates in multiple mode
|
|
124
|
-
if (mode === 'multiple' && Array.isArray(currentValue)) {
|
|
125
|
-
if (currentValue.length >= 5) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Custom Range Validation
|
|
136
|
-
|
|
137
|
-
Validate date ranges:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
myHooks: DatepickerHooks = {
|
|
141
|
-
validateRange: (startDate, endDate) => {
|
|
142
|
-
// Ensure range is at least 3 days
|
|
143
|
-
const diffTime = endDate.getTime() - startDate.getTime();
|
|
144
|
-
const diffDays = diffTime / (1000 * 60 * 60 * 24);
|
|
145
|
-
|
|
146
|
-
if (diffDays < 3) {
|
|
147
|
-
return false; // Range too short
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Ensure range is not more than 30 days
|
|
151
|
-
if (diffDays > 30) {
|
|
152
|
-
return false; // Range too long
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return true;
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
getValidationError: (date) => {
|
|
159
|
-
const today = new Date();
|
|
160
|
-
today.setHours(0, 0, 0, 0);
|
|
161
|
-
if (date < today) {
|
|
162
|
-
return 'Cannot select past dates';
|
|
163
|
-
}
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Keyboard Shortcuts
|
|
170
|
-
|
|
171
|
-
### Custom Keyboard Shortcuts
|
|
172
|
-
|
|
173
|
-
Add custom keyboard shortcuts:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
myHooks: DatepickerHooks = {
|
|
177
|
-
handleShortcut: (event, context) => {
|
|
178
|
-
// Custom shortcut: Ctrl+1 for first day of month
|
|
179
|
-
if (event.ctrlKey && event.key === '1') {
|
|
180
|
-
const firstDay = new Date(context.currentDate);
|
|
181
|
-
firstDay.setDate(1);
|
|
182
|
-
// Navigate to first day
|
|
183
|
-
return true; // Handled
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Custom shortcut: Ctrl+L for last day of month
|
|
187
|
-
if (event.ctrlKey && event.key === 'l') {
|
|
188
|
-
const lastDay = new Date(context.currentDate);
|
|
189
|
-
lastDay.setMonth(lastDay.getMonth() + 1, 0);
|
|
190
|
-
// Navigate to last day
|
|
191
|
-
return true; // Handled
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return false; // Not handled, use default
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Using customShortcuts Input
|
|
200
|
-
|
|
201
|
-
Alternatively, use the `customShortcuts` input for simpler shortcuts:
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
import { KeyboardShortcutContext } from 'ngxsmk-datepicker';
|
|
205
|
-
|
|
206
|
-
@Component({
|
|
207
|
-
// ...
|
|
208
|
-
})
|
|
209
|
-
export class MyComponent {
|
|
210
|
-
shortcuts = {
|
|
211
|
-
'Ctrl+1': (context: KeyboardShortcutContext) => {
|
|
212
|
-
// Handle Ctrl+1
|
|
213
|
-
return true;
|
|
214
|
-
},
|
|
215
|
-
'Ctrl+L': (context: KeyboardShortcutContext) => {
|
|
216
|
-
// Handle Ctrl+L
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
```html
|
|
224
|
-
<ngxsmk-datepicker
|
|
225
|
-
[customShortcuts]="shortcuts"
|
|
226
|
-
mode="single">
|
|
227
|
-
</ngxsmk-datepicker>
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Date Formatting Hooks
|
|
231
|
-
|
|
232
|
-
### Custom Display Value Formatting
|
|
233
|
-
|
|
234
|
-
Format the input display value:
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
myHooks: DatepickerHooks = {
|
|
238
|
-
formatDisplayValue: (value, mode) => {
|
|
239
|
-
if (mode === 'single' && value instanceof Date) {
|
|
240
|
-
// Custom format: "Monday, January 15, 2024"
|
|
241
|
-
return value.toLocaleDateString('en-US', {
|
|
242
|
-
weekday: 'long',
|
|
243
|
-
year: 'numeric',
|
|
244
|
-
month: 'long',
|
|
245
|
-
day: 'numeric'
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (mode === 'range' && typeof value === 'object' && 'start' in value) {
|
|
250
|
-
const range = value as { start: Date; end: Date };
|
|
251
|
-
return `${range.start.toLocaleDateString()} → ${range.end.toLocaleDateString()}`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return ''; // Use default
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
formatAriaLabel: (date) => {
|
|
258
|
-
// Custom aria-label format
|
|
259
|
-
return `Select ${date.toLocaleDateString('en-US', {
|
|
260
|
-
weekday: 'long',
|
|
261
|
-
month: 'long',
|
|
262
|
-
day: 'numeric',
|
|
263
|
-
year: 'numeric'
|
|
264
|
-
})}`;
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Event Hooks
|
|
270
|
-
|
|
271
|
-
### Lifecycle Events
|
|
272
|
-
|
|
273
|
-
Hook into date selection lifecycle:
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
myHooks: DatepickerHooks = {
|
|
277
|
-
beforeDateSelect: (date, currentValue) => {
|
|
278
|
-
// Called before date is selected
|
|
279
|
-
// Return false to prevent selection
|
|
280
|
-
console.log('About to select:', date);
|
|
281
|
-
|
|
282
|
-
// Example: Prevent selection on weekends
|
|
283
|
-
const day = date.getDay();
|
|
284
|
-
if (day === 0 || day === 6) {
|
|
285
|
-
return false; // Prevent selection
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return true; // Allow selection
|
|
289
|
-
},
|
|
290
|
-
|
|
291
|
-
afterDateSelect: (date, newValue) => {
|
|
292
|
-
// Called after date is selected
|
|
293
|
-
console.log('Date selected:', date);
|
|
294
|
-
console.log('New value:', newValue);
|
|
295
|
-
|
|
296
|
-
// Perform side effects (API calls, analytics, etc.)
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
onCalendarOpen: () => {
|
|
300
|
-
console.log('Calendar opened');
|
|
301
|
-
// Track analytics, etc.
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
onCalendarClose: () => {
|
|
305
|
-
console.log('Calendar closed');
|
|
306
|
-
// Cleanup, etc.
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
## Complete Example
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
import { Component } from '@angular/core';
|
|
315
|
-
import { NgxsmkDatepickerComponent, DatepickerHooks } from 'ngxsmk-datepicker';
|
|
316
|
-
|
|
317
|
-
@Component({
|
|
318
|
-
selector: 'app-advanced',
|
|
319
|
-
standalone: true,
|
|
320
|
-
imports: [NgxsmkDatepickerComponent],
|
|
321
|
-
template: `
|
|
322
|
-
<ngxsmk-datepicker
|
|
323
|
-
[hooks]="advancedHooks"
|
|
324
|
-
[customShortcuts]="shortcuts"
|
|
325
|
-
mode="range">
|
|
326
|
-
</ngxsmk-datepicker>
|
|
327
|
-
`
|
|
328
|
-
})
|
|
329
|
-
export class AdvancedComponent {
|
|
330
|
-
advancedHooks: DatepickerHooks = {
|
|
331
|
-
// Custom classes
|
|
332
|
-
getDayCellClasses: (date, isSelected, isDisabled, isToday, isHoliday) => {
|
|
333
|
-
const classes: string[] = [];
|
|
334
|
-
if (date.getDay() === 0 || date.getDay() === 6) {
|
|
335
|
-
classes.push('weekend');
|
|
336
|
-
}
|
|
337
|
-
return classes;
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
// Custom validation
|
|
341
|
-
validateRange: (start, end) => {
|
|
342
|
-
const days = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
|
|
343
|
-
return days >= 3 && days <= 30;
|
|
344
|
-
},
|
|
345
|
-
|
|
346
|
-
// Custom formatting
|
|
347
|
-
formatDisplayValue: (value, mode) => {
|
|
348
|
-
if (mode === 'range' && typeof value === 'object' && 'start' in value) {
|
|
349
|
-
const r = value as { start: Date; end: Date };
|
|
350
|
-
return `${r.start.toLocaleDateString()} - ${r.end.toLocaleDateString()}`;
|
|
351
|
-
}
|
|
352
|
-
return '';
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
// Event hooks
|
|
356
|
-
beforeDateSelect: (date) => {
|
|
357
|
-
return date.getDay() !== 0 && date.getDay() !== 6; // No weekends
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
shortcuts = {
|
|
362
|
-
'Ctrl+Home': (context) => {
|
|
363
|
-
// Navigate to today
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
## Built-in Keyboard Shortcuts
|
|
371
|
-
|
|
372
|
-
The datepicker includes these built-in shortcuts:
|
|
373
|
-
|
|
374
|
-
| Key | Action |
|
|
375
|
-
|-----|--------|
|
|
376
|
-
| `←` `→` `↑` `↓` | Navigate dates |
|
|
377
|
-
| `Page Up` | Previous month |
|
|
378
|
-
| `Page Down` | Next month |
|
|
379
|
-
| `Shift + Page Up` | Previous year |
|
|
380
|
-
| `Shift + Page Down` | Next year |
|
|
381
|
-
| `Home` | First day of month |
|
|
382
|
-
| `End` | Last day of month |
|
|
383
|
-
| `Enter` / `Space` | Select focused date |
|
|
384
|
-
| `Escape` | Close calendar |
|
|
385
|
-
| `T` | Select today |
|
|
386
|
-
| `Y` | Select yesterday |
|
|
387
|
-
| `N` | Select tomorrow |
|
|
388
|
-
| `W` | Select next week |
|
|
389
|
-
|
|
390
|
-
Disable shortcuts:
|
|
391
|
-
```html
|
|
392
|
-
<ngxsmk-datepicker
|
|
393
|
-
[enableKeyboardShortcuts]="false"
|
|
394
|
-
mode="single">
|
|
395
|
-
</ngxsmk-datepicker>
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
## Best Practices
|
|
399
|
-
|
|
400
|
-
1. **Performance**: Keep hook functions lightweight and memoize expensive operations
|
|
401
|
-
2. **Accessibility**: Ensure custom formatting maintains accessibility
|
|
402
|
-
3. **Validation**: Provide clear error messages via `getValidationError`
|
|
403
|
-
4. **Consistency**: Use consistent formatting across all hooks
|
|
404
|
-
5. **Testing**: Test hooks thoroughly, especially validation logic
|
|
405
|
-
|
|
406
|
-
## Type Safety
|
|
407
|
-
|
|
408
|
-
All hooks are fully typed with TypeScript interfaces. Use the exported types for better IDE support:
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
import {
|
|
412
|
-
DatepickerHooks,
|
|
413
|
-
KeyboardShortcutContext,
|
|
414
|
-
KeyboardShortcutHelp
|
|
415
|
-
} from 'ngxsmk-datepicker';
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
1
|
+
# Extension Points and Hooks
|
|
2
|
+
|
|
3
|
+
**Last updated:** July 2, 2026 - **Current stable:** v2.4.0
|
|
4
|
+
|
|
5
|
+
ngxsmk-datepicker provides comprehensive extension points through the `hooks` input, allowing you to customize rendering, validation, keyboard shortcuts, formatting, and event handling.
|
|
6
|
+
|
|
7
|
+
> **📚 For a complete understanding of the plugin architecture, see the [Plugin Architecture Guide](./PLUGIN-ARCHITECTURE.md)** which covers architecture principles, plugin patterns, lifecycle, and advanced use cases.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
The `hooks` input accepts a `DatepickerHooks` object that implements various hook interfaces:
|
|
12
|
+
|
|
13
|
+
- **DayCellRenderHook**: Customize day cell rendering
|
|
14
|
+
- **ValidationHook**: Custom validation and range rules
|
|
15
|
+
- **KeyboardShortcutHook**: Custom keyboard shortcuts
|
|
16
|
+
- **DateFormatHook**: Custom date formatting
|
|
17
|
+
- **EventHook**: Event lifecycle hooks
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Component } from '@angular/core';
|
|
23
|
+
import { NgxsmkDatepickerComponent, DatepickerHooks } from 'ngxsmk-datepicker';
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'app-custom',
|
|
27
|
+
standalone: true,
|
|
28
|
+
imports: [NgxsmkDatepickerComponent],
|
|
29
|
+
template: `
|
|
30
|
+
<ngxsmk-datepicker
|
|
31
|
+
[hooks]="myHooks"
|
|
32
|
+
mode="single">
|
|
33
|
+
</ngxsmk-datepicker>
|
|
34
|
+
`
|
|
35
|
+
})
|
|
36
|
+
export class CustomComponent {
|
|
37
|
+
myHooks: DatepickerHooks = {
|
|
38
|
+
// Implement hook methods here
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Day Cell Rendering Hooks
|
|
44
|
+
|
|
45
|
+
### Custom CSS Classes
|
|
46
|
+
|
|
47
|
+
Add custom CSS classes to day cells:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
myHooks: DatepickerHooks = {
|
|
51
|
+
getDayCellClasses: (date, isSelected, isDisabled, isToday, isHoliday) => {
|
|
52
|
+
const classes: string[] = [];
|
|
53
|
+
|
|
54
|
+
// Add custom class for weekends
|
|
55
|
+
if (date.getDay() === 0 || date.getDay() === 6) {
|
|
56
|
+
classes.push('weekend-day');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add custom class for first of month
|
|
60
|
+
if (date.getDate() === 1) {
|
|
61
|
+
classes.push('first-of-month');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return classes;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Custom Tooltips
|
|
70
|
+
|
|
71
|
+
Customize tooltip text for day cells:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
myHooks: DatepickerHooks = {
|
|
75
|
+
getDayCellTooltip: (date, holidayLabel) => {
|
|
76
|
+
if (holidayLabel) {
|
|
77
|
+
return `Holiday: ${holidayLabel}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add custom tooltip for specific dates
|
|
81
|
+
if (date.getDate() === 15) {
|
|
82
|
+
return 'Mid-month special';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null; // Use default
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Custom Day Number Formatting
|
|
91
|
+
|
|
92
|
+
Format the day number display:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
myHooks: DatepickerHooks = {
|
|
96
|
+
formatDayNumber: (date) => {
|
|
97
|
+
// Add suffix (1st, 2nd, 3rd, etc.)
|
|
98
|
+
const day = date.getDate();
|
|
99
|
+
const suffix = day === 1 || day === 21 || day === 31 ? 'st' :
|
|
100
|
+
day === 2 || day === 22 ? 'nd' :
|
|
101
|
+
day === 3 || day === 23 ? 'rd' : 'th';
|
|
102
|
+
return `${day}${suffix}`;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Validation Hooks
|
|
108
|
+
|
|
109
|
+
### Custom Date Validation
|
|
110
|
+
|
|
111
|
+
Add custom validation logic:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
myHooks: DatepickerHooks = {
|
|
115
|
+
validateDate: (date, currentValue, mode) => {
|
|
116
|
+
// Prevent selecting dates in the past
|
|
117
|
+
const today = new Date();
|
|
118
|
+
today.setHours(0, 0, 0, 0);
|
|
119
|
+
if (date < today) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Prevent selecting more than 5 dates in multiple mode
|
|
124
|
+
if (mode === 'multiple' && Array.isArray(currentValue)) {
|
|
125
|
+
if (currentValue.length >= 5) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Custom Range Validation
|
|
136
|
+
|
|
137
|
+
Validate date ranges:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
myHooks: DatepickerHooks = {
|
|
141
|
+
validateRange: (startDate, endDate) => {
|
|
142
|
+
// Ensure range is at least 3 days
|
|
143
|
+
const diffTime = endDate.getTime() - startDate.getTime();
|
|
144
|
+
const diffDays = diffTime / (1000 * 60 * 60 * 24);
|
|
145
|
+
|
|
146
|
+
if (diffDays < 3) {
|
|
147
|
+
return false; // Range too short
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Ensure range is not more than 30 days
|
|
151
|
+
if (diffDays > 30) {
|
|
152
|
+
return false; // Range too long
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
getValidationError: (date) => {
|
|
159
|
+
const today = new Date();
|
|
160
|
+
today.setHours(0, 0, 0, 0);
|
|
161
|
+
if (date < today) {
|
|
162
|
+
return 'Cannot select past dates';
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Keyboard Shortcuts
|
|
170
|
+
|
|
171
|
+
### Custom Keyboard Shortcuts
|
|
172
|
+
|
|
173
|
+
Add custom keyboard shortcuts:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
myHooks: DatepickerHooks = {
|
|
177
|
+
handleShortcut: (event, context) => {
|
|
178
|
+
// Custom shortcut: Ctrl+1 for first day of month
|
|
179
|
+
if (event.ctrlKey && event.key === '1') {
|
|
180
|
+
const firstDay = new Date(context.currentDate);
|
|
181
|
+
firstDay.setDate(1);
|
|
182
|
+
// Navigate to first day
|
|
183
|
+
return true; // Handled
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Custom shortcut: Ctrl+L for last day of month
|
|
187
|
+
if (event.ctrlKey && event.key === 'l') {
|
|
188
|
+
const lastDay = new Date(context.currentDate);
|
|
189
|
+
lastDay.setMonth(lastDay.getMonth() + 1, 0);
|
|
190
|
+
// Navigate to last day
|
|
191
|
+
return true; // Handled
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return false; // Not handled, use default
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Using customShortcuts Input
|
|
200
|
+
|
|
201
|
+
Alternatively, use the `customShortcuts` input for simpler shortcuts:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { KeyboardShortcutContext } from 'ngxsmk-datepicker';
|
|
205
|
+
|
|
206
|
+
@Component({
|
|
207
|
+
// ...
|
|
208
|
+
})
|
|
209
|
+
export class MyComponent {
|
|
210
|
+
shortcuts = {
|
|
211
|
+
'Ctrl+1': (context: KeyboardShortcutContext) => {
|
|
212
|
+
// Handle Ctrl+1
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
'Ctrl+L': (context: KeyboardShortcutContext) => {
|
|
216
|
+
// Handle Ctrl+L
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<ngxsmk-datepicker
|
|
225
|
+
[customShortcuts]="shortcuts"
|
|
226
|
+
mode="single">
|
|
227
|
+
</ngxsmk-datepicker>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Date Formatting Hooks
|
|
231
|
+
|
|
232
|
+
### Custom Display Value Formatting
|
|
233
|
+
|
|
234
|
+
Format the input display value:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
myHooks: DatepickerHooks = {
|
|
238
|
+
formatDisplayValue: (value, mode) => {
|
|
239
|
+
if (mode === 'single' && value instanceof Date) {
|
|
240
|
+
// Custom format: "Monday, January 15, 2024"
|
|
241
|
+
return value.toLocaleDateString('en-US', {
|
|
242
|
+
weekday: 'long',
|
|
243
|
+
year: 'numeric',
|
|
244
|
+
month: 'long',
|
|
245
|
+
day: 'numeric'
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (mode === 'range' && typeof value === 'object' && 'start' in value) {
|
|
250
|
+
const range = value as { start: Date; end: Date };
|
|
251
|
+
return `${range.start.toLocaleDateString()} → ${range.end.toLocaleDateString()}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return ''; // Use default
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
formatAriaLabel: (date) => {
|
|
258
|
+
// Custom aria-label format
|
|
259
|
+
return `Select ${date.toLocaleDateString('en-US', {
|
|
260
|
+
weekday: 'long',
|
|
261
|
+
month: 'long',
|
|
262
|
+
day: 'numeric',
|
|
263
|
+
year: 'numeric'
|
|
264
|
+
})}`;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Event Hooks
|
|
270
|
+
|
|
271
|
+
### Lifecycle Events
|
|
272
|
+
|
|
273
|
+
Hook into date selection lifecycle:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
myHooks: DatepickerHooks = {
|
|
277
|
+
beforeDateSelect: (date, currentValue) => {
|
|
278
|
+
// Called before date is selected
|
|
279
|
+
// Return false to prevent selection
|
|
280
|
+
console.log('About to select:', date);
|
|
281
|
+
|
|
282
|
+
// Example: Prevent selection on weekends
|
|
283
|
+
const day = date.getDay();
|
|
284
|
+
if (day === 0 || day === 6) {
|
|
285
|
+
return false; // Prevent selection
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return true; // Allow selection
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
afterDateSelect: (date, newValue) => {
|
|
292
|
+
// Called after date is selected
|
|
293
|
+
console.log('Date selected:', date);
|
|
294
|
+
console.log('New value:', newValue);
|
|
295
|
+
|
|
296
|
+
// Perform side effects (API calls, analytics, etc.)
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
onCalendarOpen: () => {
|
|
300
|
+
console.log('Calendar opened');
|
|
301
|
+
// Track analytics, etc.
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
onCalendarClose: () => {
|
|
305
|
+
console.log('Calendar closed');
|
|
306
|
+
// Cleanup, etc.
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Complete Example
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { Component } from '@angular/core';
|
|
315
|
+
import { NgxsmkDatepickerComponent, DatepickerHooks } from 'ngxsmk-datepicker';
|
|
316
|
+
|
|
317
|
+
@Component({
|
|
318
|
+
selector: 'app-advanced',
|
|
319
|
+
standalone: true,
|
|
320
|
+
imports: [NgxsmkDatepickerComponent],
|
|
321
|
+
template: `
|
|
322
|
+
<ngxsmk-datepicker
|
|
323
|
+
[hooks]="advancedHooks"
|
|
324
|
+
[customShortcuts]="shortcuts"
|
|
325
|
+
mode="range">
|
|
326
|
+
</ngxsmk-datepicker>
|
|
327
|
+
`
|
|
328
|
+
})
|
|
329
|
+
export class AdvancedComponent {
|
|
330
|
+
advancedHooks: DatepickerHooks = {
|
|
331
|
+
// Custom classes
|
|
332
|
+
getDayCellClasses: (date, isSelected, isDisabled, isToday, isHoliday) => {
|
|
333
|
+
const classes: string[] = [];
|
|
334
|
+
if (date.getDay() === 0 || date.getDay() === 6) {
|
|
335
|
+
classes.push('weekend');
|
|
336
|
+
}
|
|
337
|
+
return classes;
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// Custom validation
|
|
341
|
+
validateRange: (start, end) => {
|
|
342
|
+
const days = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
|
|
343
|
+
return days >= 3 && days <= 30;
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// Custom formatting
|
|
347
|
+
formatDisplayValue: (value, mode) => {
|
|
348
|
+
if (mode === 'range' && typeof value === 'object' && 'start' in value) {
|
|
349
|
+
const r = value as { start: Date; end: Date };
|
|
350
|
+
return `${r.start.toLocaleDateString()} - ${r.end.toLocaleDateString()}`;
|
|
351
|
+
}
|
|
352
|
+
return '';
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
// Event hooks
|
|
356
|
+
beforeDateSelect: (date) => {
|
|
357
|
+
return date.getDay() !== 0 && date.getDay() !== 6; // No weekends
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
shortcuts = {
|
|
362
|
+
'Ctrl+Home': (context) => {
|
|
363
|
+
// Navigate to today
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Built-in Keyboard Shortcuts
|
|
371
|
+
|
|
372
|
+
The datepicker includes these built-in shortcuts:
|
|
373
|
+
|
|
374
|
+
| Key | Action |
|
|
375
|
+
|-----|--------|
|
|
376
|
+
| `←` `→` `↑` `↓` | Navigate dates |
|
|
377
|
+
| `Page Up` | Previous month |
|
|
378
|
+
| `Page Down` | Next month |
|
|
379
|
+
| `Shift + Page Up` | Previous year |
|
|
380
|
+
| `Shift + Page Down` | Next year |
|
|
381
|
+
| `Home` | First day of month |
|
|
382
|
+
| `End` | Last day of month |
|
|
383
|
+
| `Enter` / `Space` | Select focused date |
|
|
384
|
+
| `Escape` | Close calendar |
|
|
385
|
+
| `T` | Select today |
|
|
386
|
+
| `Y` | Select yesterday |
|
|
387
|
+
| `N` | Select tomorrow |
|
|
388
|
+
| `W` | Select next week |
|
|
389
|
+
|
|
390
|
+
Disable shortcuts:
|
|
391
|
+
```html
|
|
392
|
+
<ngxsmk-datepicker
|
|
393
|
+
[enableKeyboardShortcuts]="false"
|
|
394
|
+
mode="single">
|
|
395
|
+
</ngxsmk-datepicker>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Best Practices
|
|
399
|
+
|
|
400
|
+
1. **Performance**: Keep hook functions lightweight and memoize expensive operations
|
|
401
|
+
2. **Accessibility**: Ensure custom formatting maintains accessibility
|
|
402
|
+
3. **Validation**: Provide clear error messages via `getValidationError`
|
|
403
|
+
4. **Consistency**: Use consistent formatting across all hooks
|
|
404
|
+
5. **Testing**: Test hooks thoroughly, especially validation logic
|
|
405
|
+
|
|
406
|
+
## Type Safety
|
|
407
|
+
|
|
408
|
+
All hooks are fully typed with TypeScript interfaces. Use the exported types for better IDE support:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import {
|
|
412
|
+
DatepickerHooks,
|
|
413
|
+
KeyboardShortcutContext,
|
|
414
|
+
KeyboardShortcutHelp
|
|
415
|
+
} from 'ngxsmk-datepicker';
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
|