ngxsmk-datepicker 2.0.3 → 2.0.5
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 +25 -2
- package/docs/API.md +4 -2
- package/docs/signal-forms.md +9 -2
- package/fesm2022/ngxsmk-datepicker.mjs +32 -2
- package/package.json +1 -1
- package/types/ngxsmk-datepicker.d.ts +2 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**npm i ngxsmk-datepicker**
|
|
9
9
|
|
|
10
|
-
> **Stable Version**: `2.0.
|
|
10
|
+
> **Stable Version**: `2.0.5` is the current stable release. For production use, install the latest version from npm.
|
|
11
11
|
>
|
|
12
12
|
> ⚠️ **Warning**: Version `1.9.26` contains broken styles. If you are using `1.9.26`, please upgrade to `1.9.28` or downgrade to `1.9.25` immediately.
|
|
13
13
|
|
|
@@ -33,6 +33,7 @@ Built with Angular Signals for optimal performance and a clean, declarative API.
|
|
|
33
33
|
## **✨ Features**
|
|
34
34
|
|
|
35
35
|
* **Multiple Selection Modes**: Supports `single`, `range`, and `multiple` date selection.
|
|
36
|
+
* **Smart Range Reselection**: Clicking the start date again after selecting a complete range clears only the end date, allowing quick range adjustments without clearing the entire selection.
|
|
36
37
|
* **Inline and Popover Display**: Can be rendered inline or as a popover with automatic mode detection.
|
|
37
38
|
* **Light and Dark Themes**: Includes built-in support for light and dark modes.
|
|
38
39
|
* **Holiday Marking**: Automatically mark and disable holidays using a custom `HolidayProvider`.
|
|
@@ -127,7 +128,7 @@ For details, see [CONTRIBUTING.md](https://github.com/NGXSMK/ngxsmk-datepicker/b
|
|
|
127
128
|
|
|
128
129
|
Install the package using npm:
|
|
129
130
|
|
|
130
|
-
npm install ngxsmk-datepicker@2.0.
|
|
131
|
+
npm install ngxsmk-datepicker@2.0.5
|
|
131
132
|
|
|
132
133
|
## **Usage**
|
|
133
134
|
|
|
@@ -488,6 +489,26 @@ export class PlainFormComponent {
|
|
|
488
489
|
</form>
|
|
489
490
|
```
|
|
490
491
|
|
|
492
|
+
### **Form Validation**
|
|
493
|
+
|
|
494
|
+
By default, the datepicker input is `readonly` to prevent invalid date strings and force selection via the calendar. However, **browsers do not validate `readonly` fields** during native form submission.
|
|
495
|
+
|
|
496
|
+
**Behavior:**
|
|
497
|
+
- Native browser validation (e.g., blocking submit on `required` fields) will **NOT** trigger on the datepicker by default.
|
|
498
|
+
- Custom validation (e.g., Angular validators) works normally but often only shows errors after the control is "touched".
|
|
499
|
+
|
|
500
|
+
**Solutions:**
|
|
501
|
+
|
|
502
|
+
1. **Enable Typing (Recommended for Native Validation):**
|
|
503
|
+
Set `[allowTyping]="true"` to make the input standard editable field. This enables native browser validation tooltips and submit-blocking.
|
|
504
|
+
```html
|
|
505
|
+
<ngxsmk-datepicker [allowTyping]="true" required ...></ngxsmk-datepicker>
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
2. **Custom Validation Logic:**
|
|
509
|
+
If you prefer the readonly behavior, ensure your form submission handler explicitly checks `form.invalid` before proceeding, as the browser won't stop the submit button click.
|
|
510
|
+
|
|
511
|
+
|
|
491
512
|
## **⚙️ API Reference**
|
|
492
513
|
|
|
493
514
|
### **Inputs**
|
|
@@ -506,6 +527,8 @@ export class PlainFormComponent {
|
|
|
506
527
|
| minuteInterval | number | 1 | Interval for minute dropdown options. |
|
|
507
528
|
| showTime | boolean | false | Enables the hour/minute/AM/PM selection section. |
|
|
508
529
|
| timeOnly | boolean | false | Display time picker only (no calendar). Automatically enables `showTime`. Perfect for time selection scenarios. |
|
|
530
|
+
| allowTyping | boolean | false | Enable manual typing in the input field. Required for native validation. | `[allowTyping]="true"` |
|
|
531
|
+
| displayFormat | string | null | Custom date format string (e.g., 'MM/DD/YYYY'). | `displayFormat="DD.MM.YYYY"` |
|
|
509
532
|
| showCalendarButton | boolean | false | Show/hide the calendar icon button. When `false`, users can still open calendar by clicking the input field. |
|
|
510
533
|
| value | DatepickerValue | null | Programmatic value setting. Set the datepicker value from code (useful for server-side API data). |
|
|
511
534
|
| startAt | DateInput | null | The date to initially center the calendar view on. |
|
package/docs/API.md
CHANGED
|
@@ -55,6 +55,8 @@ import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
|
55
55
|
| `inline` | `boolean \| 'always' \| 'auto'` | `false` | Stable | Inline display mode | `[inline]="true"` or `inline="auto"` |
|
|
56
56
|
| `showTime` | `boolean` | `false` | Stable | Show time selection | `[showTime]="true"` |
|
|
57
57
|
| `timeOnly` | `boolean` | `false` | Stable | Display time picker only (no calendar). Automatically enables `showTime`. | `[timeOnly]="true"` |
|
|
58
|
+
| `allowTyping` | `boolean` | `false` | Stable | Enable manual typing in the input field. Required for native validation. | `[allowTyping]="true"` |
|
|
59
|
+
| `displayFormat` | `string` | `null` | Stable | Custom date format string (e.g., 'MM/DD/YYYY'). | `displayFormat="DD.MM.YYYY"` |
|
|
58
60
|
| `showCalendarButton` | `boolean` | `true` | Stable | Show/hide the calendar icon button. When `false`, users can still open calendar by clicking the input field. | `[showCalendarButton]="false"` |
|
|
59
61
|
| `minuteInterval` | `number` | `1` | Stable | Minute selection interval | `[minuteInterval]="15"` |
|
|
60
62
|
| `showRanges` | `boolean` | `true` | Stable | Show predefined ranges (range mode) | `[showRanges]="true"` |
|
|
@@ -2180,7 +2182,7 @@ type DateInput =
|
|
|
2180
2182
|
```
|
|
2181
2183
|
|
|
2182
2184
|
|
|
2183
|
-
### SignalFormField (v2.0.
|
|
2185
|
+
### SignalFormField (v2.0.5+)
|
|
2184
2186
|
|
|
2185
2187
|
**Status**: Stable
|
|
2186
2188
|
|
|
@@ -2190,7 +2192,7 @@ Type representing a signal-based form field.
|
|
|
2190
2192
|
type SignalFormField = any; // Compatible with Angular 21 FieldTree
|
|
2191
2193
|
```
|
|
2192
2194
|
|
|
2193
|
-
### SignalFormFieldConfig (v2.0.
|
|
2195
|
+
### SignalFormFieldConfig (v2.0.5+)
|
|
2194
2196
|
|
|
2195
2197
|
**Status**: Stable
|
|
2196
2198
|
|
package/docs/signal-forms.md
CHANGED
|
@@ -113,7 +113,7 @@ export class TwoWayComponent {
|
|
|
113
113
|
}
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
### Signal Field Resolution (v2.0.
|
|
116
|
+
### Signal Field Resolution (v2.0.5+)
|
|
117
117
|
|
|
118
118
|
The datepicker includes a robust resolution mechanism for signal-based fields. It can handle:
|
|
119
119
|
- **Direct Signals**: A signal that contains the field configuration.
|
|
@@ -136,7 +136,7 @@ const config: SignalFormFieldConfig = {
|
|
|
136
136
|
};
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
**TypeScript Compatibility (v2.0.
|
|
139
|
+
**TypeScript Compatibility (v2.0.5+):**
|
|
140
140
|
|
|
141
141
|
The datepicker is fully compatible with Angular 21+ `FieldTree<string | Date | null, string>` structure. The types accept:
|
|
142
142
|
- `WritableSignal<Date | null>` for date values
|
|
@@ -321,6 +321,13 @@ export class ValidatedFormComponent {
|
|
|
321
321
|
}
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
+
### Note on Native Validation
|
|
325
|
+
By default, the datepicker input is `readonly`. Browsers do not validate `readonly` fields. To enable native browser validation (e.g., blocking submit on empty required fields), set `[allowTyping]="true"`.
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<ngxsmk-datepicker [field]="myForm.date" [allowTyping]="true" required ...></ngxsmk-datepicker>
|
|
329
|
+
```
|
|
330
|
+
|
|
324
331
|
## Date Range Forms
|
|
325
332
|
|
|
326
333
|
For date range selection:
|
|
@@ -2134,6 +2134,20 @@ class FieldSyncService {
|
|
|
2134
2134
|
getLastKnownValue() {
|
|
2135
2135
|
return this._lastKnownFieldValue;
|
|
2136
2136
|
}
|
|
2137
|
+
markAsTouched(fieldInput) {
|
|
2138
|
+
const field = this.resolveField(fieldInput);
|
|
2139
|
+
if (!field || typeof field !== 'object') {
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
try {
|
|
2143
|
+
if (typeof field.markAsTouched === 'function') {
|
|
2144
|
+
field.markAsTouched();
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
catch {
|
|
2148
|
+
// Ignore errors when marking as touched
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2137
2151
|
cleanup() {
|
|
2138
2152
|
if (this._fieldEffectRef) {
|
|
2139
2153
|
this._fieldEffectRef.destroy();
|
|
@@ -5473,6 +5487,9 @@ class NgxsmkDatepickerComponent {
|
|
|
5473
5487
|
this.valueChange.emit(normalizedVal);
|
|
5474
5488
|
this.onChange(normalizedVal);
|
|
5475
5489
|
this.onTouched();
|
|
5490
|
+
if (this._field) {
|
|
5491
|
+
this.fieldSyncService.markAsTouched(this._field);
|
|
5492
|
+
}
|
|
5476
5493
|
if (!this.isInlineMode && val !== null && !this.timeOnly) {
|
|
5477
5494
|
if (this.mode === 'single' || (this.mode === 'range' && this.startDate && this.endDate)) {
|
|
5478
5495
|
this.isCalendarOpen = false;
|
|
@@ -6741,6 +6758,9 @@ class NgxsmkDatepickerComponent {
|
|
|
6741
6758
|
this._focused = false;
|
|
6742
6759
|
this.stateChanges.next();
|
|
6743
6760
|
this.onTouched();
|
|
6761
|
+
if (this._field) {
|
|
6762
|
+
this.fieldSyncService.markAsTouched(this._field);
|
|
6763
|
+
}
|
|
6744
6764
|
}
|
|
6745
6765
|
if (!this.allowTyping)
|
|
6746
6766
|
return;
|
|
@@ -7213,7 +7233,17 @@ class NgxsmkDatepickerComponent {
|
|
|
7213
7233
|
}
|
|
7214
7234
|
const dayTime = getStartOfDay(day).getTime();
|
|
7215
7235
|
const startTime = this.startDate ? getStartOfDay(this.startDate).getTime() : null;
|
|
7216
|
-
if
|
|
7236
|
+
// Check if clicking on start date when both start and end dates exist
|
|
7237
|
+
if (this.startDate && this.endDate && dayTime === startTime) {
|
|
7238
|
+
// Clear the end date, keep the start date
|
|
7239
|
+
this.endDate = null;
|
|
7240
|
+
this.hoveredDate = null;
|
|
7241
|
+
this._invalidateMemoCache();
|
|
7242
|
+
// Emit the partial range (start only, no end)
|
|
7243
|
+
this.emitValue({ start: this.startDate, end: null });
|
|
7244
|
+
this.scheduleChangeDetection();
|
|
7245
|
+
}
|
|
7246
|
+
else if (!this.startDate || (this.startDate && this.endDate)) {
|
|
7217
7247
|
this.startDate = this.applyTimeIfNeeded(day);
|
|
7218
7248
|
this.endDate = null;
|
|
7219
7249
|
this.hoveredDate = null;
|
|
@@ -7229,7 +7259,7 @@ class NgxsmkDatepickerComponent {
|
|
|
7229
7259
|
this.scheduleChangeDetection();
|
|
7230
7260
|
}
|
|
7231
7261
|
else if (dayTime === startTime) {
|
|
7232
|
-
// No action needed when dayTime equals startTime
|
|
7262
|
+
// No action needed when dayTime equals startTime (start date clicked again, no end date selected yet)
|
|
7233
7263
|
}
|
|
7234
7264
|
else {
|
|
7235
7265
|
const potentialEndDate = this.applyTimeIfNeeded(day);
|
package/package.json
CHANGED
|
@@ -184,6 +184,7 @@ type SignalFormFieldConfig = {
|
|
|
184
184
|
setValue?: (value: DatepickerValue | string) => void;
|
|
185
185
|
updateValue?: (updater: () => DatepickerValue | string) => void;
|
|
186
186
|
markAsDirty?: () => void;
|
|
187
|
+
markAsTouched?: () => void;
|
|
187
188
|
};
|
|
188
189
|
type SignalFormField = any;
|
|
189
190
|
interface FieldSyncCallbacks {
|
|
@@ -212,6 +213,7 @@ declare class FieldSyncService {
|
|
|
212
213
|
syncFieldValue(fieldInput: SignalFormField | Signal<SignalFormField> | (() => unknown) | unknown, callbacks: FieldSyncCallbacks): boolean;
|
|
213
214
|
updateFieldFromInternal(value: DatepickerValue, fieldInput: SignalFormField | Signal<SignalFormField> | (() => unknown) | unknown): void;
|
|
214
215
|
getLastKnownValue(): DatepickerValue | undefined;
|
|
216
|
+
markAsTouched(fieldInput: SignalFormField | Signal<SignalFormField> | (() => unknown) | unknown): void;
|
|
215
217
|
cleanup(): void;
|
|
216
218
|
static ɵfac: i0.ɵɵFactoryDeclaration<FieldSyncService, never>;
|
|
217
219
|
static ɵprov: i0.ɵɵInjectableDeclaration<FieldSyncService>;
|