@spartan-ng/cli 0.0.1-alpha.720 → 1.0.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 +3 -3
- package/package.json +3 -2
- package/src/generators/base/generator.js +1 -2
- package/src/generators/base/generator.js.map +1 -1
- package/src/generators/base/lib/initialize-angular-library.d.ts +1 -1
- package/src/generators/base/lib/initialize-angular-library.js.map +1 -1
- package/src/generators/base/lib/styles/transform-style-map.d.ts +2 -0
- package/src/generators/base/lib/styles/transform-style-map.js +5 -4
- package/src/generators/base/lib/styles/transform-style-map.js.map +1 -1
- package/src/generators/base/schema.d.ts +2 -0
- package/src/generators/migrate-helm-libraries/generator.js +2 -2
- package/src/generators/migrate-helm-libraries/generator.js.map +1 -1
- package/src/generators/migrate-helm-libraries/schema.d.ts +2 -0
- package/src/generators/migrate-hlm/generator.js +1 -1
- package/src/generators/migrate-hlm/generator.js.map +1 -1
- package/src/generators/ui/generator.d.ts +2 -0
- package/src/generators/ui/generator.js +7 -4
- package/src/generators/ui/generator.js.map +1 -1
- package/src/generators/ui/libs/autocomplete/files/lib/hlm-autocomplete-input.ts.template +37 -33
- package/src/generators/ui/libs/breadcrumb/files/lib/hlm-breadcrumb-ellipsis.ts.template +11 -17
- package/src/generators/ui/libs/calendar/files/lib/hlm-calendar-multi.ts.template +1 -1
- package/src/generators/ui/libs/calendar/files/lib/hlm-calendar-range.ts.template +1 -1
- package/src/generators/ui/libs/calendar/files/lib/hlm-calendar.ts.template +1 -1
- package/src/generators/ui/libs/combobox/files/lib/hlm-combobox-input.ts.template +47 -42
- package/src/generators/ui/libs/context-menu/files/lib/hlm-context-menu-trigger.ts.template +8 -14
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-anchor.ts.template +4 -2
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-input.ts.template +49 -37
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-multi.ts.template +6 -6
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker-trigger.ts.template +1 -1
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-picker.ts.template +6 -6
- package/src/generators/ui/libs/date-picker/files/lib/hlm-date-range-picker.ts.template +9 -6
- package/src/generators/ui/libs/dropdown-menu/files/index.ts.template +1 -0
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-checkbox.ts.template +2 -0
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-focus-on-hover.ts.template +35 -0
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-item.ts.template +2 -0
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-radio.ts.template +2 -0
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-sub-trigger.ts.template +8 -11
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-sub.ts.template +11 -25
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-trigger.ts.template +7 -14
- package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu.ts.template +11 -25
- package/src/generators/ui/libs/input-otp/files/lib/hlm-input-otp-fake-caret.ts.template +7 -4
- package/src/generators/ui/libs/menubar/files/lib/hlm-menubar-trigger.ts.template +8 -14
- package/src/generators/ui/libs/pagination/files/lib/hlm-numbered-pagination-query-params.ts.template +65 -62
- package/src/generators/ui/libs/popover/files/lib/hlm-popover-content.ts.template +1 -4
- package/src/generators/ui/libs/sheet/files/lib/hlm-sheet-overlay.ts.template +1 -6
- package/src/generators/ui/libs/sidebar/files/lib/hlm-sidebar.service.ts.template +1 -1
- package/src/generators/ui/libs/tooltip/files/lib/hlm-tooltip.ts.template +1 -1
- package/src/generators/ui/style-luma.css +9 -9
- package/src/generators/ui/style-lyra.css +7 -7
- package/src/generators/ui/style-maia.css +9 -9
- package/src/generators/ui/style-mira.css +9 -9
- package/src/generators/ui/style-nova.css +40 -10
- package/src/generators/ui/style-vega.css +457 -311
- package/src/generators/ui/supported-ui-libraries.json +44 -46
- package/src/index.d.ts +2 -2
- package/src/index.js +4 -3
- package/src/index.js.map +1 -1
- package/src/utils/config.d.ts +9 -2
- package/src/utils/config.js +28 -4
- package/src/utils/config.js.map +1 -1
- package/src/utils/supported-styles.d.ts +2 -0
- package/src/utils/supported-styles.js +7 -0
- package/src/utils/supported-styles.js.map +1 -0
- package/src/registry/index.d.ts +0 -1
- package/src/registry/index.js +0 -5
- package/src/registry/index.js.map +0 -1
- package/src/registry/schema.d.ts +0 -2180
- package/src/registry/schema.js +0 -175
- package/src/registry/schema.js.map +0 -1
|
@@ -2,58 +2,59 @@ import { BooleanInput } from '@angular/cdk/coercion';
|
|
|
2
2
|
import { booleanAttribute, ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
3
3
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
4
4
|
import { lucideChevronDown, lucideX } from '@ng-icons/lucide';
|
|
5
|
-
import { BrnComboboxImports, BrnComboboxPopoverTrigger } from '@spartan-ng/brain/combobox';
|
|
6
|
-
import { HlmInputGroupImports } from '<%- importAlias %>/input-group';
|
|
5
|
+
import { BrnComboboxAnchor, BrnComboboxImports, BrnComboboxPopoverTrigger } from '@spartan-ng/brain/combobox';
|
|
6
|
+
import { HlmInputGroup, HlmInputGroupImports } from '<%- importAlias %>/input-group';
|
|
7
|
+
import { classes } from '<%- importAlias %>/utils';
|
|
7
8
|
|
|
8
9
|
@Component({
|
|
9
10
|
selector: 'hlm-combobox-input',
|
|
10
11
|
imports: [HlmInputGroupImports, NgIcon, BrnComboboxImports, BrnComboboxPopoverTrigger],
|
|
11
12
|
providers: [provideIcons({ lucideChevronDown, lucideX })],
|
|
12
13
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14
|
+
hostDirectives: [BrnComboboxAnchor, HlmInputGroup],
|
|
13
15
|
template: `
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
<input
|
|
17
|
+
brnComboboxInput
|
|
18
|
+
#comboboxInput="brnComboboxInput"
|
|
19
|
+
brnComboboxPopoverTrigger
|
|
20
|
+
[closeOnTriggerClick]="false"
|
|
21
|
+
hlmInputGroupInput
|
|
22
|
+
[id]="inputId()"
|
|
23
|
+
[placeholder]="placeholder()"
|
|
24
|
+
[forceInvalid]="forceInvalid()"
|
|
25
|
+
[aria-invalid]="ariaInvalidOverride()"
|
|
26
|
+
/>
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
<hlm-input-group-addon align="inline-end">
|
|
29
|
+
@if (showTrigger()) {
|
|
30
|
+
<button
|
|
31
|
+
brnComboboxPopoverTrigger
|
|
32
|
+
hlmInputGroupButton
|
|
33
|
+
data-slot="input-group-button"
|
|
34
|
+
[disabled]="comboboxInput.disabled()"
|
|
35
|
+
size="icon-xs"
|
|
36
|
+
variant="ghost"
|
|
37
|
+
class="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
|
|
38
|
+
>
|
|
39
|
+
<ng-icon name="lucideChevronDown" />
|
|
40
|
+
</button>
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
@if (showClear()) {
|
|
44
|
+
<button
|
|
45
|
+
*brnComboboxClear
|
|
46
|
+
hlmInputGroupButton
|
|
47
|
+
data-slot="combobox-clear"
|
|
48
|
+
[disabled]="comboboxInput.disabled()"
|
|
49
|
+
size="icon-xs"
|
|
50
|
+
variant="ghost"
|
|
51
|
+
>
|
|
52
|
+
<ng-icon name="lucideX" />
|
|
53
|
+
</button>
|
|
54
|
+
}
|
|
55
|
+
</hlm-input-group-addon>
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
</hlm-input-group>
|
|
57
|
+
<ng-content />
|
|
57
58
|
`,
|
|
58
59
|
})
|
|
59
60
|
export class HlmComboboxInput {
|
|
@@ -71,4 +72,8 @@ export class HlmComboboxInput {
|
|
|
71
72
|
transform: (v: BooleanInput) => (v === '' || v === undefined ? undefined : booleanAttribute(v)),
|
|
72
73
|
alias: 'aria-invalid',
|
|
73
74
|
});
|
|
75
|
+
|
|
76
|
+
constructor() {
|
|
77
|
+
classes(() => 'w-auto');
|
|
78
|
+
}
|
|
74
79
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type BooleanInput } from '@angular/cdk/coercion';
|
|
2
2
|
import { CdkContextMenuTrigger } from '@angular/cdk/menu';
|
|
3
|
-
import { booleanAttribute, computed, Directive, effect, inject, input } from '@angular/core';
|
|
4
|
-
import {
|
|
5
|
-
import { createMenuPosition, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
3
|
+
import { booleanAttribute, computed, Directive, effect, forwardRef, inject, input } from '@angular/core';
|
|
4
|
+
import { createMenuPosition, MENU_SIDE, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
6
5
|
import { classes } from '<%- importAlias %>/utils';
|
|
7
6
|
import { injectHlmContextMenuConfig } from './hlm-context-menu-token';
|
|
8
7
|
|
|
9
8
|
@Directive({
|
|
10
9
|
selector: '[hlmContextMenuTrigger]',
|
|
10
|
+
providers: [{ provide: MENU_SIDE, useExisting: forwardRef(() => HlmContextMenuTrigger) }],
|
|
11
11
|
hostDirectives: [
|
|
12
12
|
{
|
|
13
13
|
directive: CdkContextMenuTrigger,
|
|
@@ -36,17 +36,11 @@ export class HlmContextMenuTrigger {
|
|
|
36
36
|
private readonly _menuPosition = computed(() => createMenuPosition(this.align(), this.side()));
|
|
37
37
|
|
|
38
38
|
constructor() {
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
() =>
|
|
45
|
-
// eslint-disable-next-line
|
|
46
|
-
((this._cdkTrigger as any)._spartanLastPosition = // eslint-disable-next-line
|
|
47
|
-
(this._cdkTrigger as any).overlayRef._positionStrategy._lastPosition),
|
|
48
|
-
),
|
|
49
|
-
);
|
|
39
|
+
// CDK sets transform-origin on the menu content (a shared hlm-dropdown-menu) from the resolved
|
|
40
|
+
// position; the content reads it to animate from the anchored corner and to derive its data-side.
|
|
41
|
+
// Cast tolerates @angular/cdk < 21.2 (we still support >=21.0), where the property is absent and
|
|
42
|
+
// the assignment is a harmless no-op.
|
|
43
|
+
(this._cdkTrigger as { transformOriginSelector?: string }).transformOriginSelector = '[data-slot="dropdown-menu"]';
|
|
50
44
|
|
|
51
45
|
effect(() => {
|
|
52
46
|
this._cdkTrigger.menuPosition = this._menuPosition();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directive, effect, ElementRef, inject, input } from '@angular/core';
|
|
1
|
+
import { Directive, effect, ElementRef, inject, input, linkedSignal } from '@angular/core';
|
|
2
2
|
import { BrnOverlay } from '@spartan-ng/brain/overlay';
|
|
3
3
|
import { BrnPopover } from '@spartan-ng/brain/popover';
|
|
4
4
|
|
|
@@ -7,10 +7,12 @@ export class HlmDatePickerAnchor {
|
|
|
7
7
|
private readonly _host = inject(ElementRef, { host: true });
|
|
8
8
|
private readonly _brnOverlay = inject(BrnOverlay, { optional: true });
|
|
9
9
|
|
|
10
|
-
public readonly
|
|
10
|
+
public readonly hlmDatePickerAnchorForInput = input<BrnPopover | undefined>(undefined, {
|
|
11
11
|
alias: 'hlmDatePickerAnchorFor',
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
public readonly hlmDatePickerAnchorFor = linkedSignal(this.hlmDatePickerAnchorForInput);
|
|
15
|
+
|
|
14
16
|
constructor() {
|
|
15
17
|
effect(() => {
|
|
16
18
|
this.hlmDatePickerAnchorFor()?.setOrigin(this._host.nativeElement);
|
|
@@ -1,61 +1,69 @@
|
|
|
1
1
|
import { BooleanInput } from '@angular/cdk/coercion';
|
|
2
|
-
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';
|
|
3
13
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
4
14
|
import { lucideCalendar, lucideX } from '@ng-icons/lucide';
|
|
5
|
-
import {
|
|
6
|
-
import { HlmInputGroupImports } from '<%- importAlias %>/input-group';
|
|
7
|
-
import { HlmDatePickerAnchor } from './hlm-date-picker-anchor';
|
|
15
|
+
import { HlmInputGroup, HlmInputGroupImports } from '<%- importAlias %>/input-group';
|
|
8
16
|
import { HlmDatePickerTriggerBase, provideHlmDatePickerTrigger } from './hlm-date-picker-trigger.token';
|
|
9
17
|
import { injectHlmDatePicker, injectHlmDatePickerConfig } from './hlm-date-picker.token';
|
|
10
18
|
|
|
11
19
|
@Component({
|
|
12
20
|
selector: 'hlm-date-picker-input',
|
|
13
|
-
imports: [HlmInputGroupImports,
|
|
21
|
+
imports: [HlmInputGroupImports, NgIcon],
|
|
14
22
|
providers: [provideIcons({ lucideCalendar, lucideX }), provideHlmDatePickerTrigger(HlmDatePickerInput)],
|
|
15
23
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
24
|
+
hostDirectives: [HlmInputGroup],
|
|
16
25
|
template: `
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@if (_showClearButton()) {
|
|
33
|
-
<button
|
|
34
|
-
hlmInputGroupButton
|
|
35
|
-
size="icon-xs"
|
|
36
|
-
variant="ghost"
|
|
37
|
-
[attr.aria-label]="clearAriaLabel()"
|
|
38
|
-
(click)="_clear()"
|
|
39
|
-
[disabled]="_disabled()"
|
|
40
|
-
>
|
|
41
|
-
<ng-icon name="lucideX" />
|
|
42
|
-
</button>
|
|
43
|
-
}
|
|
26
|
+
<input
|
|
27
|
+
hlmInputGroupInput
|
|
28
|
+
[value]="_inputValue()"
|
|
29
|
+
[id]="inputId()"
|
|
30
|
+
[placeholder]="placeholder()"
|
|
31
|
+
[disabled]="_disabled()"
|
|
32
|
+
[forceInvalid]="forceInvalid()"
|
|
33
|
+
(click)="_handleClick()"
|
|
34
|
+
(keydown.arrowDown)="_open()"
|
|
35
|
+
(keydown.enter)="_handleEnter($event)"
|
|
36
|
+
(input)="_handleInputChange($event)"
|
|
37
|
+
(blur)="_commitDate()"
|
|
38
|
+
/>
|
|
39
|
+
<hlm-input-group-addon align="inline-end">
|
|
40
|
+
@if (_showClearButton()) {
|
|
44
41
|
<button
|
|
45
42
|
hlmInputGroupButton
|
|
46
43
|
size="icon-xs"
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
variant="ghost"
|
|
45
|
+
[attr.aria-label]="clearAriaLabel()"
|
|
46
|
+
(click)="_clear()"
|
|
49
47
|
[disabled]="_disabled()"
|
|
50
48
|
>
|
|
51
|
-
<ng-icon name="
|
|
49
|
+
<ng-icon name="lucideX" />
|
|
52
50
|
</button>
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
}
|
|
52
|
+
<button
|
|
53
|
+
hlmInputGroupButton
|
|
54
|
+
size="icon-xs"
|
|
55
|
+
[attr.aria-label]="calendarAriaLabel()"
|
|
56
|
+
(click)="_popover().open()"
|
|
57
|
+
[disabled]="_disabled()"
|
|
58
|
+
>
|
|
59
|
+
<ng-icon name="lucideCalendar" />
|
|
60
|
+
</button>
|
|
61
|
+
</hlm-input-group-addon>
|
|
55
62
|
`,
|
|
56
63
|
})
|
|
57
64
|
export class HlmDatePickerInput<T> implements HlmDatePickerTriggerBase {
|
|
58
65
|
private static _nextId = 0;
|
|
66
|
+
private readonly _host = inject(ElementRef);
|
|
59
67
|
private readonly _datePicker = injectHlmDatePicker<T>();
|
|
60
68
|
private readonly _config = injectHlmDatePickerConfig<T>();
|
|
61
69
|
|
|
@@ -129,6 +137,10 @@ export class HlmDatePickerInput<T> implements HlmDatePickerTriggerBase {
|
|
|
129
137
|
},
|
|
130
138
|
});
|
|
131
139
|
|
|
140
|
+
constructor() {
|
|
141
|
+
effect(() => this._popover()?.setOrigin(this._host.nativeElement));
|
|
142
|
+
}
|
|
143
|
+
|
|
132
144
|
protected _handleInputChange(event: Event) {
|
|
133
145
|
const text = (event.target as HTMLInputElement).value;
|
|
134
146
|
this._inputValue.set(text);
|
|
@@ -42,12 +42,7 @@ export const HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR = {
|
|
|
42
42
|
hostDirectives: [BrnFieldControl],
|
|
43
43
|
host: { class: 'block' },
|
|
44
44
|
template: `
|
|
45
|
-
<hlm-popover
|
|
46
|
-
sideOffset="5"
|
|
47
|
-
[state]="_popoverState()"
|
|
48
|
-
(stateChanged)="_popoverState.set($event)"
|
|
49
|
-
(closed)="_onTouched?.()"
|
|
50
|
-
>
|
|
45
|
+
<hlm-popover sideOffset="5" [state]="_popoverState()" (stateChanged)="_onStateChange($event)">
|
|
51
46
|
<ng-content />
|
|
52
47
|
|
|
53
48
|
<hlm-popover-content class="w-fit p-0" *hlmPopoverPortal="let ctx">
|
|
@@ -136,6 +131,11 @@ export class HlmDatePickerMulti<T> implements HlmDatePickerBase<T>, ControlValue
|
|
|
136
131
|
protected _onChange?: ChangeFn<T[]>;
|
|
137
132
|
protected _onTouched?: TouchFn;
|
|
138
133
|
|
|
134
|
+
protected _onStateChange(state: BrnOverlayState) {
|
|
135
|
+
this._popoverState.set(state);
|
|
136
|
+
if (state === 'closed') this._onTouched?.();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
139
|
protected _handleChange(value: T[] | undefined) {
|
|
140
140
|
if (value === undefined) return;
|
|
141
141
|
|
|
@@ -63,7 +63,7 @@ export class HlmDatePickerTrigger implements HlmDatePickerTriggerBase {
|
|
|
63
63
|
|
|
64
64
|
public readonly userClass = input<ClassValue>('', { alias: 'class' });
|
|
65
65
|
protected readonly _computedClass = computed(() =>
|
|
66
|
-
hlm('spartan-date-picker-trigger
|
|
66
|
+
hlm('spartan-date-picker-trigger justify-between', this.userClass()),
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
protected readonly _isPlaceholder = computed(() => !this._datePicker.hasDate());
|
|
@@ -36,12 +36,7 @@ export const HLM_DATE_PICKER_VALUE_ACCESSOR = {
|
|
|
36
36
|
hostDirectives: [BrnFieldControl],
|
|
37
37
|
host: { class: 'block' },
|
|
38
38
|
template: `
|
|
39
|
-
<hlm-popover
|
|
40
|
-
sideOffset="5"
|
|
41
|
-
[state]="_popoverState()"
|
|
42
|
-
(stateChanged)="_popoverState.set($event)"
|
|
43
|
-
(closed)="_onTouched?.()"
|
|
44
|
-
>
|
|
39
|
+
<hlm-popover sideOffset="5" [state]="_popoverState()" (stateChanged)="_onStateChange($event)">
|
|
45
40
|
<ng-content />
|
|
46
41
|
|
|
47
42
|
<hlm-popover-content class="w-fit p-0" *hlmPopoverPortal="let ctx">
|
|
@@ -122,6 +117,11 @@ export class HlmDatePicker<T> implements HlmDatePickerBase<T>, ControlValueAcces
|
|
|
122
117
|
protected _onChange?: ChangeFn<T>;
|
|
123
118
|
protected _onTouched?: TouchFn;
|
|
124
119
|
|
|
120
|
+
protected _onStateChange(state: BrnOverlayState) {
|
|
121
|
+
this._popoverState.set(state);
|
|
122
|
+
if (state === 'closed') this._onTouched?.();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
125
|
protected _handleChange(value: T | undefined) {
|
|
126
126
|
if (this._disabled()) return;
|
|
127
127
|
this.updateDate(value);
|
|
@@ -42,12 +42,7 @@ export const HLM_DATE_RANGE_PICKER_VALUE_ACCESSOR = {
|
|
|
42
42
|
hostDirectives: [BrnFieldControl],
|
|
43
43
|
host: { class: 'block' },
|
|
44
44
|
template: `
|
|
45
|
-
<hlm-popover
|
|
46
|
-
sideOffset="5"
|
|
47
|
-
[state]="_popoverState()"
|
|
48
|
-
(stateChanged)="_popoverState.set($event)"
|
|
49
|
-
(closed)="_onClose(); _onTouched?.()"
|
|
50
|
-
>
|
|
45
|
+
<hlm-popover sideOffset="5" [state]="_popoverState()" (stateChanged)="_onStateChange($event)">
|
|
51
46
|
<ng-content />
|
|
52
47
|
|
|
53
48
|
<hlm-popover-content class="w-fit p-0" *hlmPopoverPortal="let ctx">
|
|
@@ -130,6 +125,14 @@ export class HlmDateRangePicker<T> implements HlmDatePickerBase<T>, ControlValue
|
|
|
130
125
|
protected _onChange?: ChangeFn<[T, T] | null>;
|
|
131
126
|
protected _onTouched?: TouchFn;
|
|
132
127
|
|
|
128
|
+
protected _onStateChange(state: BrnOverlayState) {
|
|
129
|
+
this._popoverState.set(state);
|
|
130
|
+
if (state === 'closed') {
|
|
131
|
+
this._onClose();
|
|
132
|
+
this._onTouched?.();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
133
136
|
protected _handleStartDayChange(value: T | undefined) {
|
|
134
137
|
this._start.set(value);
|
|
135
138
|
}
|
|
@@ -17,6 +17,7 @@ import { HlmDropdownMenuTrigger } from './lib/hlm-dropdown-menu-trigger';
|
|
|
17
17
|
export * from './lib/hlm-dropdown-menu';
|
|
18
18
|
export * from './lib/hlm-dropdown-menu-checkbox';
|
|
19
19
|
export * from './lib/hlm-dropdown-menu-checkbox-indicator';
|
|
20
|
+
export * from './lib/hlm-dropdown-menu-focus-on-hover';
|
|
20
21
|
export * from './lib/hlm-dropdown-menu-group';
|
|
21
22
|
export * from './lib/hlm-dropdown-menu-item';
|
|
22
23
|
export * from './lib/hlm-dropdown-menu-item-sub-indicator';
|
package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-checkbox.ts.template
CHANGED
|
@@ -2,6 +2,7 @@ import { type BooleanInput } from '@angular/cdk/coercion';
|
|
|
2
2
|
import { CdkMenuItem, CdkMenuItemCheckbox, CdkMenuItemSelectable } from '@angular/cdk/menu';
|
|
3
3
|
import { Directive, booleanAttribute, inject, input } from '@angular/core';
|
|
4
4
|
import { classes } from '<%- importAlias %>/utils';
|
|
5
|
+
import { HlmDropdownMenuFocusOnHover } from './hlm-dropdown-menu-focus-on-hover';
|
|
5
6
|
|
|
6
7
|
/** @internal. Use HlmDropdownMenuCheckbox instead. */
|
|
7
8
|
@Directive({
|
|
@@ -28,6 +29,7 @@ export class HlmDropdownMenuCheckboxCdk extends CdkMenuItemCheckbox {
|
|
|
28
29
|
inputs: ['cdkMenuItemDisabled: disabled', 'cdkMenuItemChecked: checked', 'keepOpen'],
|
|
29
30
|
outputs: ['cdkMenuItemTriggered: triggered'],
|
|
30
31
|
},
|
|
32
|
+
HlmDropdownMenuFocusOnHover,
|
|
31
33
|
],
|
|
32
34
|
host: {
|
|
33
35
|
'data-slot': 'dropdown-menu-checkbox-item',
|
package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-focus-on-hover.ts.template
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { InputModalityDetector } from '@angular/cdk/a11y';
|
|
2
|
+
import { CdkMenu, CdkMenuItem } from '@angular/cdk/menu';
|
|
3
|
+
import { Directive, inject } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
* Moves DOM focus to the hovered menu item. CDK menus only move focus with the keyboard, so on a pointer
|
|
8
|
+
* the highlight would otherwise stay on the last keyboard-focused item (leaving two rows highlighted) and,
|
|
9
|
+
* when a submenu closes, focus would fall to <body>, the menu stack would report no focus, and the whole
|
|
10
|
+
* dropdown would collapse. Following the pointer with focus (Radix/shadcn behaviour) keeps a single
|
|
11
|
+
* highlight and keeps focus inside the menu stack. setActiveMenuItem also syncs the key manager so keyboard
|
|
12
|
+
* navigation continues from the hovered item.
|
|
13
|
+
*
|
|
14
|
+
* Applied as a host directive on every dropdown item type (item, checkbox, radio, sub-trigger).
|
|
15
|
+
*/
|
|
16
|
+
@Directive({
|
|
17
|
+
selector: '[hlmDropdownMenuFocusOnHover]',
|
|
18
|
+
host: {
|
|
19
|
+
'(mouseenter)': '_focusOnHover()',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
export class HlmDropdownMenuFocusOnHover {
|
|
23
|
+
private readonly _cdkMenuItem = inject(CdkMenuItem, { self: true });
|
|
24
|
+
private readonly _parentMenu = inject(CdkMenu, { optional: true });
|
|
25
|
+
private readonly _inputModality = inject(InputModalityDetector);
|
|
26
|
+
|
|
27
|
+
protected _focusOnHover(): void {
|
|
28
|
+
// Only skip synthetic hovers from touch taps; every real hover (mouse, or keyboard-then-hover,
|
|
29
|
+
// which leaves the modality as 'keyboard') should move focus to keep a single highlight.
|
|
30
|
+
if (this._inputModality.mostRecentModality === 'touch' || this._cdkMenuItem.disabled) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this._parentMenu?.setActiveMenuItem(this._cdkMenuItem);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -2,6 +2,7 @@ import { type BooleanInput } from '@angular/cdk/coercion';
|
|
|
2
2
|
import { CdkMenuItem } from '@angular/cdk/menu';
|
|
3
3
|
import { booleanAttribute, Directive, HOST_TAG_NAME, inject, input } from '@angular/core';
|
|
4
4
|
import { classes } from '<%- importAlias %>/utils';
|
|
5
|
+
import { HlmDropdownMenuFocusOnHover } from './hlm-dropdown-menu-focus-on-hover';
|
|
5
6
|
|
|
6
7
|
@Directive({
|
|
7
8
|
selector: '[hlmDropdownMenuItem],hlm-dropdown-menu-item',
|
|
@@ -11,6 +12,7 @@ import { classes } from '<%- importAlias %>/utils';
|
|
|
11
12
|
inputs: ['cdkMenuItemDisabled: disabled'],
|
|
12
13
|
outputs: ['cdkMenuItemTriggered: triggered'],
|
|
13
14
|
},
|
|
15
|
+
HlmDropdownMenuFocusOnHover,
|
|
14
16
|
],
|
|
15
17
|
host: {
|
|
16
18
|
'data-slot': 'dropdown-menu-item',
|
|
@@ -2,6 +2,7 @@ import { type BooleanInput } from '@angular/cdk/coercion';
|
|
|
2
2
|
import { CdkMenuItem, CdkMenuItemRadio, CdkMenuItemSelectable } from '@angular/cdk/menu';
|
|
3
3
|
import { Directive, booleanAttribute, inject, input } from '@angular/core';
|
|
4
4
|
import { classes } from '<%- importAlias %>/utils';
|
|
5
|
+
import { HlmDropdownMenuFocusOnHover } from './hlm-dropdown-menu-focus-on-hover';
|
|
5
6
|
|
|
6
7
|
/** @internal. Use HlmDropdownMenuRadio instead. */
|
|
7
8
|
@Directive({
|
|
@@ -28,6 +29,7 @@ export class HlmDropdownMenuRadioCdk extends CdkMenuItemRadio {
|
|
|
28
29
|
inputs: ['cdkMenuItemDisabled: disabled', 'cdkMenuItemChecked: checked', 'keepOpen'],
|
|
29
30
|
outputs: ['cdkMenuItemTriggered: triggered'],
|
|
30
31
|
},
|
|
32
|
+
HlmDropdownMenuFocusOnHover,
|
|
31
33
|
],
|
|
32
34
|
host: {
|
|
33
35
|
'data-slot': 'dropdown-menu-radio-item',
|
package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-sub-trigger.ts.template
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CdkMenuTrigger } from '@angular/cdk/menu';
|
|
2
|
-
import { computed, Directive, effect, inject, input } from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import { createMenuPosition, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
2
|
+
import { computed, Directive, effect, forwardRef, inject, input } from '@angular/core';
|
|
3
|
+
import { createMenuPosition, MENU_SIDE, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
5
4
|
import { classes } from '<%- importAlias %>/utils';
|
|
6
5
|
import { injectHlmDropdownMenuConfig } from './hlm-dropdown-menu-token';
|
|
7
6
|
|
|
8
7
|
@Directive({
|
|
9
8
|
selector: '[hlmDropdownMenuSubTrigger]',
|
|
9
|
+
providers: [{ provide: MENU_SIDE, useExisting: forwardRef(() => HlmDropdownMenuSubTrigger) }],
|
|
10
10
|
hostDirectives: [
|
|
11
11
|
{
|
|
12
12
|
directive: CdkMenuTrigger,
|
|
@@ -26,14 +26,11 @@ export class HlmDropdownMenuSubTrigger {
|
|
|
26
26
|
private readonly _menuPosition = computed(() => createMenuPosition(this.align(), this.side()));
|
|
27
27
|
|
|
28
28
|
constructor() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
(this._cdkTrigger as any).overlayRef._positionStrategy._lastPosition),
|
|
35
|
-
),
|
|
36
|
-
);
|
|
29
|
+
// CDK sets transform-origin on the submenu content from the resolved position; the content reads it
|
|
30
|
+
// to animate from the anchored corner and to derive its data-side. Cast tolerates @angular/cdk < 21.2
|
|
31
|
+
// (we still support >=21.0), where the property is absent and the assignment is a harmless no-op.
|
|
32
|
+
(this._cdkTrigger as { transformOriginSelector?: string }).transformOriginSelector =
|
|
33
|
+
'[data-slot="dropdown-menu-sub"]';
|
|
37
34
|
|
|
38
35
|
effect(() => {
|
|
39
36
|
this._cdkTrigger.menuPosition = this._menuPosition();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CdkMenu } from '@angular/cdk/menu';
|
|
2
|
-
import { Directive, inject, signal } from '@angular/core';
|
|
2
|
+
import { Directive, ElementRef, inject, signal } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { deriveMenuSideFromTransformOrigin, MENU_SIDE, type MenuSide } from '@spartan-ng/brain/core';
|
|
4
5
|
import { classes } from '<%- importAlias %>/utils';
|
|
5
6
|
|
|
6
7
|
@Directive({
|
|
@@ -14,12 +15,15 @@ import { classes } from '<%- importAlias %>/utils';
|
|
|
14
15
|
})
|
|
15
16
|
export class HlmDropdownMenuSub {
|
|
16
17
|
private readonly _host = inject(CdkMenu);
|
|
18
|
+
private readonly _elementRef = inject(ElementRef<HTMLElement>);
|
|
19
|
+
// The sub-trigger provides its configured side; CDK parents this content's injector under it.
|
|
20
|
+
private readonly _menuSide = inject(MENU_SIDE, { optional: true });
|
|
17
21
|
|
|
18
22
|
protected readonly _state = signal('open');
|
|
19
|
-
protected readonly _side = signal('
|
|
23
|
+
protected readonly _side = signal<MenuSide>(this._menuSide?.side() ?? 'right');
|
|
20
24
|
|
|
21
25
|
constructor() {
|
|
22
|
-
this.
|
|
26
|
+
this.setSideFromTransformOrigin();
|
|
23
27
|
// this is a best effort, but does not seem to work currently
|
|
24
28
|
// TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
|
|
25
29
|
this._host.closed.pipe(takeUntilDestroyed()).subscribe(() => this._state.set('closed'));
|
|
@@ -27,29 +31,11 @@ export class HlmDropdownMenuSub {
|
|
|
27
31
|
classes(() => 'spartan-dropdown-menu-sub-content w-auto');
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* will appear and set the attribute to the host accordingly
|
|
34
|
-
*
|
|
35
|
-
* First of all we take advantage of the menu stack not being aware of the root
|
|
36
|
-
* object immediately after it is added. This code executes before the root element is added,
|
|
37
|
-
* which means the stack is still empty and the peek method returns undefined.
|
|
38
|
-
*/
|
|
39
|
-
const isRoot = this._host.menuStack.peek() === undefined;
|
|
34
|
+
private setSideFromTransformOrigin() {
|
|
35
|
+
const side = this._menuSide?.side() ?? 'right';
|
|
36
|
+
// CDK sets transform-origin on this element synchronously on attach; read it next tick and derive side
|
|
40
37
|
setTimeout(() => {
|
|
41
|
-
|
|
42
|
-
// we can access it here and determine the correct side.
|
|
43
|
-
// eslint-disable-next-line
|
|
44
|
-
const ps = (this._host as any)._parentTrigger._spartanLastPosition;
|
|
45
|
-
if (!ps) {
|
|
46
|
-
// if we have no last position we default to the most likely option
|
|
47
|
-
// I hate that we have to do this and hope we can revisit soon and improve
|
|
48
|
-
this._side.set(isRoot ? 'top' : 'left');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const side = isRoot ? ps.originY : ps.originX === 'end' ? 'right' : 'left';
|
|
52
|
-
this._side.set(side);
|
|
38
|
+
this._side.set(deriveMenuSideFromTransformOrigin(this._elementRef.nativeElement.style.transformOrigin, side));
|
|
53
39
|
});
|
|
54
40
|
}
|
|
55
41
|
}
|
package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-trigger.ts.template
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CdkMenuTrigger } from '@angular/cdk/menu';
|
|
2
|
-
import { computed, Directive, effect, inject, input } from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import { createMenuPosition, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
2
|
+
import { computed, Directive, effect, forwardRef, inject, input } from '@angular/core';
|
|
3
|
+
import { createMenuPosition, MENU_SIDE, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
|
|
5
4
|
import { injectHlmDropdownMenuConfig } from './hlm-dropdown-menu-token';
|
|
6
5
|
|
|
7
6
|
@Directive({
|
|
8
7
|
selector: '[hlmDropdownMenuTrigger]',
|
|
8
|
+
providers: [{ provide: MENU_SIDE, useExisting: forwardRef(() => HlmDropdownMenuTrigger) }],
|
|
9
9
|
hostDirectives: [
|
|
10
10
|
{
|
|
11
11
|
directive: CdkMenuTrigger,
|
|
@@ -25,17 +25,10 @@ export class HlmDropdownMenuTrigger {
|
|
|
25
25
|
private readonly _menuPosition = computed(() => createMenuPosition(this.align(), this.side()));
|
|
26
26
|
|
|
27
27
|
constructor() {
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
this._cdkTrigger
|
|
32
|
-
setTimeout(
|
|
33
|
-
() =>
|
|
34
|
-
// eslint-disable-next-line
|
|
35
|
-
((this._cdkTrigger as any)._spartanLastPosition = // eslint-disable-next-line
|
|
36
|
-
(this._cdkTrigger as any).overlayRef._positionStrategy._lastPosition),
|
|
37
|
-
),
|
|
38
|
-
);
|
|
28
|
+
// CDK sets transform-origin on the menu content from the resolved position; the content reads it to
|
|
29
|
+
// animate from the anchored corner and to derive its data-side. Cast tolerates @angular/cdk < 21.2
|
|
30
|
+
// (we still support >=21.0), where the property is absent and the assignment is a harmless no-op.
|
|
31
|
+
(this._cdkTrigger as { transformOriginSelector?: string }).transformOriginSelector = '[data-slot="dropdown-menu"]';
|
|
39
32
|
|
|
40
33
|
effect(() => {
|
|
41
34
|
this._cdkTrigger.menuPosition = this._menuPosition();
|