@spartan-ng/cli 1.0.4 → 1.1.0-alpha.1
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/package.json +1 -1
- package/src/generators/base/generator.js +9 -3
- package/src/generators/base/generator.js.map +1 -1
- package/src/generators/base/lib/build-dependency-array.js +1 -1
- package/src/generators/base/lib/build-dependency-array.js.map +1 -1
- package/src/generators/base/lib/package-json-dependencies.d.ts +7 -0
- package/src/generators/base/lib/package-json-dependencies.js +17 -0
- package/src/generators/base/lib/package-json-dependencies.js.map +1 -0
- package/src/generators/base/lib/styles/transform-style-map.js +15 -5
- package/src/generators/base/lib/styles/transform-style-map.js.map +1 -1
- package/src/generators/healthcheck/generator.js +2 -0
- package/src/generators/healthcheck/generator.js.map +1 -1
- package/src/generators/healthcheck/healthchecks/scaffold-integrity.d.ts +2 -0
- package/src/generators/healthcheck/healthchecks/scaffold-integrity.js +40 -0
- package/src/generators/healthcheck/healthchecks/scaffold-integrity.js.map +1 -0
- package/src/generators/init/generator.js +3 -3
- package/src/generators/init/generator.js.map +1 -1
- package/src/generators/ui/libs/calendar/files/index.ts.template +3 -1
- package/src/generators/ui/libs/calendar/files/lib/hlm-month-year-calendar.ts.template +115 -0
- package/src/generators/ui/libs/date-picker/files/index.ts.template +13 -1
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-multi-input.ts.template +85 -0
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-input.ts.template +23 -124
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-multi.token.ts.template +57 -0
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-multi.ts.template +25 -5
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-trigger.ts.template +8 -5
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker.token.ts.template +14 -36
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker.ts.template +18 -15
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-range-input.ts.template +86 -0
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-range-picker.token.ts.template +36 -1
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-range-picker.ts.template +38 -7
- package/src/generators/ui/libs/date-picker/files/lib/hlm-month-year-input.ts.template +86 -0
- package/src/generators/ui/libs/date-picker/files/lib/hlm-month-year-picker.token.ts.template +85 -0
- package/src/generators/ui/libs/date-picker/files/lib/hlm-month-year-picker.ts.template +185 -0
- package/src/generators/ui/libs/dialog/files/lib/hlm-dialog.service.ts.template +16 -3
- package/src/generators/ui/libs/sidebar/files/lib/hlm-sidebar-trigger.ts.template +1 -1
- package/src/generators/ui/primitive-deps.js +5 -5
- package/src/generators/ui/primitive-deps.js.map +1 -1
- package/src/generators/ui/supported-ui-libraries.json +44 -43
- package/src/utils/version-utils.d.ts +1 -0
- package/src/utils/version-utils.js +13 -0
- package/src/utils/version-utils.js.map +1 -1
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-trigger.token.ts.template +0 -11
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
booleanAttribute,
|
|
4
|
-
ChangeDetectionStrategy,
|
|
5
|
-
Component,
|
|
6
|
-
computed,
|
|
7
|
-
effect,
|
|
8
|
-
ElementRef,
|
|
9
|
-
inject,
|
|
10
|
-
input,
|
|
11
|
-
linkedSignal,
|
|
12
|
-
} from '@angular/core';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
13
2
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
14
3
|
import { lucideCalendar, lucideX } from '@ng-icons/lucide';
|
|
4
|
+
import {
|
|
5
|
+
BrnDateInput,
|
|
6
|
+
type BrnDatePickerTriggerBase,
|
|
7
|
+
provideBrnDatePickerTrigger,
|
|
8
|
+
} from '@spartan-ng/brain/date-picker';
|
|
15
9
|
import { HlmInputGroup, HlmInputGroupImports } from '<%- importAlias %>/input-group';
|
|
16
|
-
import {
|
|
17
|
-
import { injectHlmDatePicker, injectHlmDatePickerConfig } from './hlm-date-picker.token';
|
|
10
|
+
import { injectHlmDatePickerConfig } from './hlm-date-picker.token';
|
|
18
11
|
|
|
19
12
|
@Component({
|
|
20
13
|
selector: 'hlm-date-picker-input',
|
|
21
14
|
imports: [HlmInputGroupImports, NgIcon],
|
|
22
|
-
providers: [provideIcons({ lucideCalendar, lucideX }),
|
|
15
|
+
providers: [provideIcons({ lucideCalendar, lucideX }), provideBrnDatePickerTrigger(HlmDatePickerInput)],
|
|
23
16
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
24
17
|
hostDirectives: [HlmInputGroup],
|
|
25
18
|
template: `
|
|
26
19
|
<input
|
|
20
|
+
#input
|
|
27
21
|
hlmInputGroupInput
|
|
28
22
|
[value]="_inputValue()"
|
|
29
23
|
[id]="inputId()"
|
|
@@ -34,7 +28,8 @@ import { injectHlmDatePicker, injectHlmDatePickerConfig } from './hlm-date-picke
|
|
|
34
28
|
(keydown.arrowDown)="_open()"
|
|
35
29
|
(keydown.enter)="_handleEnter($event)"
|
|
36
30
|
(input)="_handleInputChange($event)"
|
|
37
|
-
(
|
|
31
|
+
(focus)="_handleFocus()"
|
|
32
|
+
(blur)="_handleBlur()"
|
|
38
33
|
/>
|
|
39
34
|
<hlm-input-group-addon align="inline-end">
|
|
40
35
|
@if (_showClearButton()) {
|
|
@@ -61,127 +56,31 @@ import { injectHlmDatePicker, injectHlmDatePickerConfig } from './hlm-date-picke
|
|
|
61
56
|
</hlm-input-group-addon>
|
|
62
57
|
`,
|
|
63
58
|
})
|
|
64
|
-
export class HlmDatePickerInput<T> implements
|
|
65
|
-
private static _nextId = 0;
|
|
66
|
-
private readonly _host = inject(ElementRef);
|
|
67
|
-
private readonly _datePicker = injectHlmDatePicker<T>();
|
|
59
|
+
export class HlmDatePickerInput<T> extends BrnDateInput<T> implements BrnDatePickerTriggerBase {
|
|
68
60
|
private readonly _config = injectHlmDatePickerConfig<T>();
|
|
69
61
|
|
|
70
|
-
protected readonly _popover = this._datePicker.popover;
|
|
71
|
-
protected readonly _disabled = this._datePicker.disabledState;
|
|
72
|
-
|
|
73
|
-
public readonly inputId = input(`hlm-date-picker-input-${HlmDatePickerInput._nextId++}`);
|
|
74
|
-
|
|
75
|
-
public readonly placeholder = input('');
|
|
76
|
-
|
|
77
|
-
public readonly inputValue = input<string>('');
|
|
78
|
-
|
|
79
62
|
/**
|
|
80
|
-
* Parses input text into a date value. Return `
|
|
63
|
+
* Parses input text into a date value. Return `null` for invalid
|
|
81
64
|
* input - the picker's date is cleared while the text is preserved so
|
|
82
65
|
* the user can fix it.
|
|
83
66
|
*
|
|
84
67
|
* Defaults to `parseDate` from `HlmDatePickerConfig`.
|
|
85
68
|
*/
|
|
86
|
-
public readonly parseDate = input<(value: string) => T |
|
|
87
|
-
|
|
88
|
-
public readonly forceInvalid = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
|
|
89
|
-
|
|
90
|
-
/** Show a clear button that resets the input and picker date. Hidden when empty. */
|
|
91
|
-
public readonly showClear = input<boolean, BooleanInput>(true, { transform: booleanAttribute });
|
|
92
|
-
|
|
93
|
-
/** Open the popover on input click. */
|
|
94
|
-
public readonly openOnClick = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
|
|
95
|
-
|
|
96
|
-
/** Accessible label for the clear button. */
|
|
97
|
-
public readonly clearAriaLabel = input<string>('Clear date');
|
|
98
|
-
|
|
99
|
-
/** Accessible label for the calendar trigger button. */
|
|
100
|
-
public readonly calendarAriaLabel = input<string>('Open calendar');
|
|
101
|
-
|
|
102
|
-
/** @internal Id used by the trigger contract for labeling. */
|
|
103
|
-
public readonly triggerId = this.inputId;
|
|
69
|
+
public readonly parseDate = input<(value: string) => T | null>(this._config.parseDate);
|
|
104
70
|
|
|
105
71
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
72
|
+
* Formats the current date into the input/edit format shown while the
|
|
73
|
+
* input is focused. On blur the picker's display format is restored.
|
|
74
|
+
*
|
|
75
|
+
* Defaults to `formatInputDate` from `HlmDatePickerConfig`.
|
|
109
76
|
*/
|
|
110
|
-
|
|
111
|
-
source: () => ({
|
|
112
|
-
formatted: this._datePicker.formattedDate(),
|
|
113
|
-
inputValue: this.inputValue(),
|
|
114
|
-
}),
|
|
115
|
-
computation: (source, previous) => {
|
|
116
|
-
// First render: prefer formatted, fall back to inputValue.
|
|
117
|
-
if (previous === undefined) {
|
|
118
|
-
return source.formatted ?? source.inputValue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Picker's formatted date changed - snap to canonical format.
|
|
122
|
-
if (source.formatted !== previous.source.formatted) {
|
|
123
|
-
if (source.formatted !== undefined) {
|
|
124
|
-
return source.formatted;
|
|
125
|
-
}
|
|
126
|
-
// Cleared externally vs. user has invalid text in flight: only
|
|
127
|
-
// mirror the clear when the displayed text was in sync.
|
|
128
|
-
return previous.value === previous.source.formatted ? '' : previous.value;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Parent updated inputValue - reflect it.
|
|
132
|
-
if (source.inputValue !== previous.source.inputValue) {
|
|
133
|
-
return source.inputValue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return previous.value;
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
constructor() {
|
|
141
|
-
effect(() => this._popover()?.setOrigin(this._host.nativeElement));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
protected _handleInputChange(event: Event) {
|
|
145
|
-
const text = (event.target as HTMLInputElement).value;
|
|
146
|
-
this._inputValue.set(text);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
protected readonly _showClearButton = computed(() => this.showClear() && this._inputValue().length > 0);
|
|
150
|
-
|
|
151
|
-
protected _clear() {
|
|
152
|
-
this._inputValue.set('');
|
|
153
|
-
this._datePicker.updateDate?.(undefined);
|
|
154
|
-
this._datePicker.touched?.();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
protected _handleEnter(event: Event) {
|
|
158
|
-
event.preventDefault();
|
|
159
|
-
this._commitDate();
|
|
160
|
-
this._popover().close();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
protected _commitDate() {
|
|
164
|
-
const value = this._inputValue();
|
|
165
|
-
|
|
166
|
-
if (!value) {
|
|
167
|
-
this._datePicker.updateDate?.(undefined);
|
|
168
|
-
this._datePicker.touched?.();
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Invalid parse: clear the picker date, keep the text so the user can fix it.
|
|
173
|
-
const parsed = this.parseDate()(value);
|
|
174
|
-
this._datePicker.updateDate?.(parsed ?? undefined);
|
|
175
|
-
this._datePicker.touched?.();
|
|
176
|
-
}
|
|
77
|
+
public readonly formatInputDate = input<(date: T) => string>(this._config.formatInputDate);
|
|
177
78
|
|
|
178
|
-
protected
|
|
179
|
-
this.
|
|
79
|
+
protected parseValue(value: string): T | null {
|
|
80
|
+
return this.parseDate()(value);
|
|
180
81
|
}
|
|
181
82
|
|
|
182
|
-
protected
|
|
183
|
-
|
|
184
|
-
this._open();
|
|
185
|
-
}
|
|
83
|
+
protected formatInputValue(value: T): string {
|
|
84
|
+
return this.formatInputDate()(value);
|
|
186
85
|
}
|
|
187
86
|
}
|
package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-multi.token.ts.template
CHANGED
|
@@ -14,6 +14,15 @@ export interface HlmDatePickerMultiConfig<T> {
|
|
|
14
14
|
*/
|
|
15
15
|
formatDates: (dates: T[]) => string;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Defines how the dates should be displayed while the input is focused,
|
|
19
|
+
* i.e. the format the user is expected to type in.
|
|
20
|
+
*
|
|
21
|
+
* @param dates
|
|
22
|
+
* @returns formatted dates in the input/edit format
|
|
23
|
+
*/
|
|
24
|
+
formatInputDates: (dates: T[]) => string;
|
|
25
|
+
|
|
17
26
|
/**
|
|
18
27
|
* Defines how the date should be transformed before saving to model/form.
|
|
19
28
|
*
|
|
@@ -21,13 +30,61 @@ export interface HlmDatePickerMultiConfig<T> {
|
|
|
21
30
|
* @returns transformed date
|
|
22
31
|
*/
|
|
23
32
|
transformDates: (dates: T[]) => T[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse a user-entered string into a date.
|
|
36
|
+
*
|
|
37
|
+
* @param value the raw string from the input
|
|
38
|
+
* @returns the parsed date, or `null` when the value can't be parsed
|
|
39
|
+
*/
|
|
40
|
+
parseDate: (value: string) => T[] | null;
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
function getDefaultConfig<T>(): HlmDatePickerMultiConfig<T> {
|
|
27
44
|
return {
|
|
28
45
|
formatDates: (dates) => dates.map((date) => (date instanceof Date ? date.toDateString() : `${date}`)).join(', '),
|
|
46
|
+
formatInputDates: (dates) =>
|
|
47
|
+
dates
|
|
48
|
+
.map((date) => {
|
|
49
|
+
if (!(date instanceof Date)) return `${date}`;
|
|
50
|
+
|
|
51
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
52
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
53
|
+
const year = date.getFullYear();
|
|
54
|
+
|
|
55
|
+
return `${day}/${month}/${year}`;
|
|
56
|
+
})
|
|
57
|
+
.join(', '),
|
|
29
58
|
transformDates: (dates) => dates,
|
|
30
59
|
autoCloseOnMaxSelection: false,
|
|
60
|
+
parseDate: (value) => {
|
|
61
|
+
if (typeof value !== 'string') return null;
|
|
62
|
+
|
|
63
|
+
const parts = value.split(',').map((v) => v.trim());
|
|
64
|
+
const result: Date[] = [];
|
|
65
|
+
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
const match = part.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
|
68
|
+
if (!match) return null;
|
|
69
|
+
|
|
70
|
+
const day = Number(match[1]);
|
|
71
|
+
const month = Number(match[2]);
|
|
72
|
+
const year = Number(match[3]);
|
|
73
|
+
|
|
74
|
+
if (month < 1 || month > 12) return null;
|
|
75
|
+
if (day < 1 || day > 31) return null;
|
|
76
|
+
|
|
77
|
+
const date = new Date(year, month - 1, day);
|
|
78
|
+
|
|
79
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
result.push(date);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result as T[];
|
|
87
|
+
},
|
|
31
88
|
};
|
|
32
89
|
}
|
|
33
90
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
viewChild,
|
|
15
15
|
} from '@angular/core';
|
|
16
16
|
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
17
|
+
import { type BrnDatePickerBase, BrnDatePickerTriggerToken, provideBrnDatePicker } from '@spartan-ng/brain/date-picker';
|
|
17
18
|
import { BrnFieldControl, provideBrnLabelable } from '@spartan-ng/brain/field';
|
|
18
19
|
import type { ChangeFn, TouchFn } from '@spartan-ng/brain/forms';
|
|
19
20
|
import type { BrnOverlayState } from '@spartan-ng/brain/overlay';
|
|
@@ -21,8 +22,6 @@ import { BrnPopover } from '@spartan-ng/brain/popover';
|
|
|
21
22
|
import { HlmCalendarMulti } from '<%- importAlias %>/calendar';
|
|
22
23
|
import { HlmPopoverImports } from '<%- importAlias %>/popover';
|
|
23
24
|
import { injectHlmDatePickerMultiConfig } from './hlm-date-picker-multi.token';
|
|
24
|
-
import { HlmDatePickerTriggerToken } from './hlm-date-picker-trigger.token';
|
|
25
|
-
import { HlmDatePickerBase, provideHlmDatePicker } from './hlm-date-picker.token';
|
|
26
25
|
|
|
27
26
|
export const HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR = {
|
|
28
27
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -35,7 +34,7 @@ export const HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR = {
|
|
|
35
34
|
imports: [HlmPopoverImports, HlmCalendarMulti],
|
|
36
35
|
providers: [
|
|
37
36
|
HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR,
|
|
38
|
-
|
|
37
|
+
provideBrnDatePicker(HlmDatePickerMulti),
|
|
39
38
|
provideBrnLabelable(HlmDatePickerMulti),
|
|
40
39
|
],
|
|
41
40
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -63,12 +62,12 @@ export const HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR = {
|
|
|
63
62
|
</hlm-popover>
|
|
64
63
|
`,
|
|
65
64
|
})
|
|
66
|
-
export class HlmDatePickerMulti<T> implements
|
|
65
|
+
export class HlmDatePickerMulti<T> implements BrnDatePickerBase<T[]>, ControlValueAccessor {
|
|
67
66
|
private readonly _config = injectHlmDatePickerMultiConfig<T>();
|
|
68
67
|
|
|
69
68
|
public readonly popover = viewChild.required(BrnPopover);
|
|
70
69
|
|
|
71
|
-
private readonly _trigger = contentChild(
|
|
70
|
+
private readonly _trigger = contentChild(BrnDatePickerTriggerToken);
|
|
72
71
|
|
|
73
72
|
/** Show dropdowns to navigate between months or years. */
|
|
74
73
|
public readonly captionLayout = input<'dropdown' | 'label' | 'dropdown-months' | 'dropdown-years'>('label');
|
|
@@ -128,6 +127,9 @@ export class HlmDatePickerMulti<T> implements HlmDatePickerBase<T>, ControlValue
|
|
|
128
127
|
|
|
129
128
|
public readonly hasDate = computed(() => !!this._mutableDate()?.length);
|
|
130
129
|
|
|
130
|
+
/** @internal The current raw value, used by inputs to reformat on focus. */
|
|
131
|
+
public readonly value = computed(() => this._mutableDate() ?? null);
|
|
132
|
+
|
|
131
133
|
protected _onChange?: ChangeFn<T[]>;
|
|
132
134
|
protected _onTouched?: TouchFn;
|
|
133
135
|
|
|
@@ -151,6 +153,24 @@ export class HlmDatePickerMulti<T> implements HlmDatePickerBase<T>, ControlValue
|
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Commit dates to the picker. Updates the internal model, notifies form
|
|
158
|
+
* controls, and emits `dateChange`. Intended to be called from a text input
|
|
159
|
+
* that parses user-entered values. Pass `null` to clear the selection.
|
|
160
|
+
*/
|
|
161
|
+
public updateDate(value: T[] | null) {
|
|
162
|
+
if (this._disabled()) return;
|
|
163
|
+
const transformedDate = value ? this.transformDates()(value) : undefined;
|
|
164
|
+
|
|
165
|
+
this._mutableDate.set(transformedDate);
|
|
166
|
+
this._onChange?.(transformedDate ?? []);
|
|
167
|
+
this.dateChange.emit(transformedDate ?? []);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public touched(): void {
|
|
171
|
+
this._onTouched?.();
|
|
172
|
+
}
|
|
173
|
+
|
|
154
174
|
/** CONTROL VALUE ACCESSOR */
|
|
155
175
|
public writeValue(value: T[] | null): void {
|
|
156
176
|
this._mutableDate.set(value ? this.transformDates()(value) : undefined);
|
|
@@ -2,18 +2,21 @@ import { BooleanInput } from '@angular/cdk/coercion';
|
|
|
2
2
|
import { booleanAttribute, ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
|
|
3
3
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
4
4
|
import { lucideChevronDown } from '@ng-icons/lucide';
|
|
5
|
+
import {
|
|
6
|
+
type BrnDatePickerTriggerBase,
|
|
7
|
+
injectBrnDatePicker,
|
|
8
|
+
provideBrnDatePickerTrigger,
|
|
9
|
+
} from '@spartan-ng/brain/date-picker';
|
|
5
10
|
import { BrnFieldControl, BrnFieldControlDescribedBy } from '@spartan-ng/brain/field';
|
|
6
11
|
import { ButtonVariants, HlmButtonImports } from '<%- importAlias %>/button';
|
|
7
12
|
import { HlmPopoverTrigger } from '<%- importAlias %>/popover';
|
|
8
13
|
import { hlm } from '<%- importAlias %>/utils';
|
|
9
14
|
import { ClassValue } from 'clsx';
|
|
10
|
-
import { HlmDatePickerTriggerBase, provideHlmDatePickerTrigger } from './hlm-date-picker-trigger.token';
|
|
11
|
-
import { injectHlmDatePicker } from './hlm-date-picker.token';
|
|
12
15
|
|
|
13
16
|
@Component({
|
|
14
17
|
selector: 'hlm-date-picker-trigger',
|
|
15
18
|
imports: [HlmButtonImports, HlmPopoverTrigger, NgIcon, BrnFieldControlDescribedBy],
|
|
16
|
-
providers: [provideIcons({ lucideChevronDown }),
|
|
19
|
+
providers: [provideIcons({ lucideChevronDown }), provideBrnDatePickerTrigger(HlmDatePickerTrigger)],
|
|
17
20
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
18
21
|
host: { 'data-slot': 'date-picker-trigger' },
|
|
19
22
|
template: `
|
|
@@ -48,11 +51,11 @@ import { injectHlmDatePicker } from './hlm-date-picker.token';
|
|
|
48
51
|
</button>
|
|
49
52
|
`,
|
|
50
53
|
})
|
|
51
|
-
export class HlmDatePickerTrigger implements
|
|
54
|
+
export class HlmDatePickerTrigger implements BrnDatePickerTriggerBase {
|
|
52
55
|
private static _nextId = 0;
|
|
53
56
|
|
|
54
57
|
private readonly _fieldControl = inject(BrnFieldControl, { optional: true });
|
|
55
|
-
private readonly _datePicker =
|
|
58
|
+
private readonly _datePicker = injectBrnDatePicker();
|
|
56
59
|
|
|
57
60
|
private readonly _invalid = this._fieldControl?.invalid;
|
|
58
61
|
protected readonly _spartanInvalid = computed(() => this.forceInvalid() || this._fieldControl?.spartanInvalid());
|
|
@@ -1,36 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ExistingProvider,
|
|
3
|
-
inject,
|
|
4
|
-
InjectionToken,
|
|
5
|
-
type Signal,
|
|
6
|
-
type Type,
|
|
7
|
-
type ValueProvider,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import type { BrnPopover } from '@spartan-ng/brain/popover';
|
|
10
|
-
|
|
11
|
-
export interface HlmDatePickerBase<T> {
|
|
12
|
-
popover: Signal<BrnPopover>;
|
|
13
|
-
disabledState: Signal<boolean>;
|
|
14
|
-
formattedDate: Signal<string | undefined>;
|
|
15
|
-
hasDate: Signal<boolean>;
|
|
16
|
-
/** Commit a date to the picker (e.g. from a parsed input). Pass `undefined` to clear. Optional. */
|
|
17
|
-
updateDate?(value: T | undefined): void;
|
|
18
|
-
// used for ControlValueAccessor
|
|
19
|
-
touched?(): void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const HlmDatePickerToken = new InjectionToken<HlmDatePickerBase<unknown>>('HlmDatePickerToken');
|
|
23
|
-
|
|
24
|
-
export function provideHlmDatePicker(instance: Type<HlmDatePickerBase<unknown>>): ExistingProvider {
|
|
25
|
-
return { provide: HlmDatePickerToken, useExisting: instance };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Inject the date picker component.
|
|
30
|
-
*/
|
|
31
|
-
export function injectHlmDatePicker<T>(): HlmDatePickerBase<T> {
|
|
32
|
-
return inject(HlmDatePickerToken) as HlmDatePickerBase<T>;
|
|
33
|
-
}
|
|
1
|
+
import { inject, InjectionToken, type ValueProvider } from '@angular/core';
|
|
34
2
|
|
|
35
3
|
export interface HlmDatePickerConfig<T> {
|
|
36
4
|
/**
|
|
@@ -46,6 +14,15 @@ export interface HlmDatePickerConfig<T> {
|
|
|
46
14
|
*/
|
|
47
15
|
formatDate: (date: T) => string;
|
|
48
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Defines how the date should be displayed while the input is focused,
|
|
19
|
+
* i.e. the format the user is expected to type in.
|
|
20
|
+
*
|
|
21
|
+
* @param date
|
|
22
|
+
* @returns formatted date in the input/edit format
|
|
23
|
+
*/
|
|
24
|
+
formatInputDate: (date: T) => string;
|
|
25
|
+
|
|
49
26
|
/**
|
|
50
27
|
* Defines how the date should be transformed before saving to model/form.
|
|
51
28
|
*
|
|
@@ -58,18 +35,19 @@ export interface HlmDatePickerConfig<T> {
|
|
|
58
35
|
* Parse a user-entered string into a date.
|
|
59
36
|
*
|
|
60
37
|
* @param value the raw string from the input
|
|
61
|
-
* @returns the parsed date, or `
|
|
38
|
+
* @returns the parsed date, or `null` when the value can't be parsed
|
|
62
39
|
*/
|
|
63
|
-
parseDate: (value: string) => T |
|
|
40
|
+
parseDate: (value: string) => T | null;
|
|
64
41
|
}
|
|
65
42
|
|
|
66
43
|
function getDefaultConfig<T>(): HlmDatePickerConfig<T> {
|
|
67
44
|
return {
|
|
68
45
|
formatDate: (date) => (date instanceof Date ? date.toDateString() : `${date}`),
|
|
46
|
+
formatInputDate: (date) => (date instanceof Date ? date.toDateString() : `${date}`),
|
|
69
47
|
transformDate: (date) => date,
|
|
70
48
|
parseDate: (value) => {
|
|
71
49
|
const date = new Date(value);
|
|
72
|
-
return isNaN(date.getTime()) ?
|
|
50
|
+
return isNaN(date.getTime()) ? null : (date as T);
|
|
73
51
|
},
|
|
74
52
|
autoCloseOnSelect: false,
|
|
75
53
|
};
|
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
viewChild,
|
|
14
14
|
} from '@angular/core';
|
|
15
15
|
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
16
|
+
import { type BrnDatePickerBase, BrnDatePickerTriggerToken, provideBrnDatePicker } from '@spartan-ng/brain/date-picker';
|
|
16
17
|
import { BrnFieldControl, provideBrnLabelable } from '@spartan-ng/brain/field';
|
|
17
18
|
import type { ChangeFn, TouchFn } from '@spartan-ng/brain/forms';
|
|
18
19
|
import type { BrnOverlayState } from '@spartan-ng/brain/overlay';
|
|
19
20
|
import { BrnPopover } from '@spartan-ng/brain/popover';
|
|
20
21
|
import { HlmCalendar } from '<%- importAlias %>/calendar';
|
|
21
22
|
import { HlmPopoverImports } from '<%- importAlias %>/popover';
|
|
22
|
-
import {
|
|
23
|
-
import { HlmDatePickerBase, injectHlmDatePickerConfig, provideHlmDatePicker } from './hlm-date-picker.token';
|
|
23
|
+
import { injectHlmDatePickerConfig } from './hlm-date-picker.token';
|
|
24
24
|
|
|
25
25
|
export const HLM_DATE_PICKER_VALUE_ACCESSOR = {
|
|
26
26
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -31,7 +31,7 @@ export const HLM_DATE_PICKER_VALUE_ACCESSOR = {
|
|
|
31
31
|
@Component({
|
|
32
32
|
selector: 'hlm-date-picker',
|
|
33
33
|
imports: [HlmPopoverImports, HlmCalendar],
|
|
34
|
-
providers: [HLM_DATE_PICKER_VALUE_ACCESSOR,
|
|
34
|
+
providers: [HLM_DATE_PICKER_VALUE_ACCESSOR, provideBrnDatePicker(HlmDatePicker), provideBrnLabelable(HlmDatePicker)],
|
|
35
35
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
36
36
|
hostDirectives: [BrnFieldControl],
|
|
37
37
|
host: { class: 'block' },
|
|
@@ -56,12 +56,12 @@ export const HLM_DATE_PICKER_VALUE_ACCESSOR = {
|
|
|
56
56
|
</hlm-popover>
|
|
57
57
|
`,
|
|
58
58
|
})
|
|
59
|
-
export class HlmDatePicker<T> implements
|
|
59
|
+
export class HlmDatePicker<T> implements BrnDatePickerBase<T>, ControlValueAccessor {
|
|
60
60
|
private readonly _config = injectHlmDatePickerConfig<T>();
|
|
61
61
|
|
|
62
62
|
public readonly popover = viewChild.required(BrnPopover);
|
|
63
63
|
|
|
64
|
-
private readonly _trigger = contentChild(
|
|
64
|
+
private readonly _trigger = contentChild(BrnDatePickerTriggerToken);
|
|
65
65
|
|
|
66
66
|
/** Show dropdowns to navigate between months or years. */
|
|
67
67
|
public readonly captionLayout = input<'dropdown' | 'label' | 'dropdown-months' | 'dropdown-years'>('label');
|
|
@@ -108,13 +108,16 @@ export class HlmDatePicker<T> implements HlmDatePickerBase<T>, ControlValueAcces
|
|
|
108
108
|
return date ? this.formatDate()(date) : undefined;
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
public readonly dateChange = output<T>();
|
|
111
|
+
public readonly dateChange = output<T | null>();
|
|
112
112
|
|
|
113
113
|
public readonly labelableId = computed(() => this._trigger()?.triggerId());
|
|
114
114
|
|
|
115
115
|
public readonly hasDate = computed(() => !!this._mutableDate());
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
/** @internal The current raw value, used by inputs to reformat on focus. */
|
|
118
|
+
public readonly value = computed(() => this._mutableDate() ?? null);
|
|
119
|
+
|
|
120
|
+
protected _onChange?: ChangeFn<T | null>;
|
|
118
121
|
protected _onTouched?: TouchFn;
|
|
119
122
|
|
|
120
123
|
protected _onStateChange(state: BrnOverlayState) {
|
|
@@ -124,7 +127,7 @@ export class HlmDatePicker<T> implements HlmDatePickerBase<T>, ControlValueAcces
|
|
|
124
127
|
|
|
125
128
|
protected _handleChange(value: T | undefined) {
|
|
126
129
|
if (this._disabled()) return;
|
|
127
|
-
this.updateDate(value);
|
|
130
|
+
this.updateDate(value ?? null);
|
|
128
131
|
|
|
129
132
|
if (this.autoCloseOnSelect()) {
|
|
130
133
|
this._popoverState.set('closed');
|
|
@@ -137,13 +140,13 @@ export class HlmDatePicker<T> implements HlmDatePickerBase<T>, ControlValueAcces
|
|
|
137
140
|
* close the popover - it's intended to be called from a text input that
|
|
138
141
|
* is parsing user-entered values while typing.
|
|
139
142
|
*/
|
|
140
|
-
public updateDate(value: T |
|
|
143
|
+
public updateDate(value: T | null) {
|
|
141
144
|
if (this._disabled()) return;
|
|
142
|
-
const transformedDate = value
|
|
145
|
+
const transformedDate = value != null ? this.transformDate()(value) : undefined;
|
|
143
146
|
|
|
144
147
|
this._mutableDate.set(transformedDate);
|
|
145
|
-
this._onChange?.(transformedDate
|
|
146
|
-
this.dateChange.emit(transformedDate
|
|
148
|
+
this._onChange?.(transformedDate ?? null);
|
|
149
|
+
this.dateChange.emit(transformedDate ?? null);
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
/** CONTROL VALUE ACCESSOR */
|
|
@@ -151,7 +154,7 @@ export class HlmDatePicker<T> implements HlmDatePickerBase<T>, ControlValueAcces
|
|
|
151
154
|
this._mutableDate.set(value ? this.transformDate()(value) : undefined);
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
public registerOnChange(fn: ChangeFn<T>): void {
|
|
157
|
+
public registerOnChange(fn: ChangeFn<T | null>): void {
|
|
155
158
|
this._onChange = fn;
|
|
156
159
|
}
|
|
157
160
|
|
|
@@ -177,7 +180,7 @@ export class HlmDatePicker<T> implements HlmDatePickerBase<T>, ControlValueAcces
|
|
|
177
180
|
|
|
178
181
|
public reset() {
|
|
179
182
|
this._mutableDate.set(undefined);
|
|
180
|
-
this._onChange?.(
|
|
181
|
-
this.dateChange.emit(
|
|
183
|
+
this._onChange?.(null);
|
|
184
|
+
this.dateChange.emit(null);
|
|
182
185
|
}
|
|
183
186
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
2
|
+
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
3
|
+
import { lucideCalendar, lucideX } from '@ng-icons/lucide';
|
|
4
|
+
import {
|
|
5
|
+
BrnDateInput,
|
|
6
|
+
type BrnDatePickerTriggerBase,
|
|
7
|
+
provideBrnDatePickerTrigger,
|
|
8
|
+
} from '@spartan-ng/brain/date-picker';
|
|
9
|
+
import { HlmInputGroup, HlmInputGroupImports } from '<%- importAlias %>/input-group';
|
|
10
|
+
import { injectHlmDateRangePickerConfig } from './hlm-date-range-picker.token';
|
|
11
|
+
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'hlm-date-range-input',
|
|
14
|
+
imports: [HlmInputGroupImports, NgIcon],
|
|
15
|
+
providers: [provideIcons({ lucideCalendar, lucideX }), provideBrnDatePickerTrigger(HlmDateRangeInput)],
|
|
16
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
17
|
+
hostDirectives: [HlmInputGroup],
|
|
18
|
+
template: `
|
|
19
|
+
<input
|
|
20
|
+
#input
|
|
21
|
+
hlmInputGroupInput
|
|
22
|
+
[value]="_inputValue()"
|
|
23
|
+
[id]="inputId()"
|
|
24
|
+
[placeholder]="placeholder()"
|
|
25
|
+
[disabled]="_disabled()"
|
|
26
|
+
[forceInvalid]="forceInvalid()"
|
|
27
|
+
(click)="_handleClick()"
|
|
28
|
+
(keydown.arrowDown)="_open()"
|
|
29
|
+
(keydown.enter)="_handleEnter($event)"
|
|
30
|
+
(input)="_handleInputChange($event)"
|
|
31
|
+
(focus)="_handleFocus()"
|
|
32
|
+
(blur)="_handleBlur()"
|
|
33
|
+
/>
|
|
34
|
+
<hlm-input-group-addon align="inline-end">
|
|
35
|
+
@if (_showClearButton()) {
|
|
36
|
+
<button
|
|
37
|
+
hlmInputGroupButton
|
|
38
|
+
size="icon-xs"
|
|
39
|
+
variant="ghost"
|
|
40
|
+
[attr.aria-label]="clearAriaLabel()"
|
|
41
|
+
(click)="_clear()"
|
|
42
|
+
[disabled]="_disabled()"
|
|
43
|
+
>
|
|
44
|
+
<ng-icon name="lucideX" />
|
|
45
|
+
</button>
|
|
46
|
+
}
|
|
47
|
+
<button
|
|
48
|
+
hlmInputGroupButton
|
|
49
|
+
size="icon-xs"
|
|
50
|
+
[attr.aria-label]="calendarAriaLabel()"
|
|
51
|
+
(click)="_popover().open()"
|
|
52
|
+
[disabled]="_disabled()"
|
|
53
|
+
>
|
|
54
|
+
<ng-icon name="lucideCalendar" />
|
|
55
|
+
</button>
|
|
56
|
+
</hlm-input-group-addon>
|
|
57
|
+
`,
|
|
58
|
+
})
|
|
59
|
+
export class HlmDateRangeInput<T> extends BrnDateInput<[T, T]> implements BrnDatePickerTriggerBase {
|
|
60
|
+
private readonly _config = injectHlmDateRangePickerConfig<T>();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses input text into a date range. Return `null` for invalid
|
|
64
|
+
* input - the picker's range is cleared while the text is preserved so
|
|
65
|
+
* the user can fix it.
|
|
66
|
+
*
|
|
67
|
+
* Defaults to `parseDate` from `HlmDateRangePickerConfig`.
|
|
68
|
+
*/
|
|
69
|
+
public readonly parseDate = input<(value: string) => [T, T] | null>(this._config.parseDate);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Formats the current range into the input/edit format shown while the
|
|
73
|
+
* input is focused. On blur the picker's display format is restored.
|
|
74
|
+
*
|
|
75
|
+
* Defaults to `formatInputDates` from `HlmDateRangePickerConfig`.
|
|
76
|
+
*/
|
|
77
|
+
public readonly formatInputDates = input<(dates: [T | null, T | null]) => string>(this._config.formatInputDates);
|
|
78
|
+
|
|
79
|
+
protected override parseValue(value: string): [T, T] | null {
|
|
80
|
+
return this.parseDate()(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected override formatInputValue(value: [T, T]): string {
|
|
84
|
+
return this.formatInputDates()(value);
|
|
85
|
+
}
|
|
86
|
+
}
|