@spartan-ng/cli 0.0.1-alpha.566 → 0.0.1-alpha.567

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.
Files changed (73) hide show
  1. package/package.json +1 -1
  2. package/src/generators/healthcheck/generator.js +6 -2
  3. package/src/generators/healthcheck/generator.js.map +1 -1
  4. package/src/generators/healthcheck/healthchecks/hlm-menu.d.ts +2 -0
  5. package/src/generators/healthcheck/healthchecks/hlm-menu.js +37 -0
  6. package/src/generators/healthcheck/healthchecks/hlm-menu.js.map +1 -0
  7. package/src/generators/healthcheck/healthchecks.d.ts +2 -1
  8. package/src/generators/healthcheck/healthchecks.js.map +1 -1
  9. package/src/generators/healthcheck/utils/runner.d.ts +1 -1
  10. package/src/generators/healthcheck/utils/runner.js +2 -2
  11. package/src/generators/healthcheck/utils/runner.js.map +1 -1
  12. package/src/generators/migrate-menu/compat.d.ts +2 -0
  13. package/src/generators/migrate-menu/compat.js +6 -0
  14. package/src/generators/migrate-menu/compat.js.map +1 -0
  15. package/src/generators/migrate-menu/generator.d.ts +5 -0
  16. package/src/generators/migrate-menu/generator.js +138 -0
  17. package/src/generators/migrate-menu/generator.js.map +1 -0
  18. package/src/generators/migrate-menu/schema.d.ts +4 -0
  19. package/src/generators/migrate-menu/schema.json +19 -0
  20. package/src/generators/migrate-module-imports/import-map.js +10 -10
  21. package/src/generators/migrate-module-imports/import-map.js.map +1 -1
  22. package/src/generators/ui/generator.js +1 -30
  23. package/src/generators/ui/generator.js.map +1 -1
  24. package/src/generators/ui/libs/context-menu/files/index.ts.template +6 -0
  25. package/src/generators/ui/libs/context-menu/files/lib/hlm-context-menu-token.ts.template +22 -0
  26. package/src/generators/ui/libs/context-menu/files/lib/hlm-context-menu-trigger.ts.template +54 -0
  27. package/src/generators/ui/libs/context-menu/generator.js +9 -0
  28. package/src/generators/ui/libs/context-menu/generator.js.map +1 -0
  29. package/src/generators/ui/libs/dropdown-menu/files/index.ts.template +45 -0
  30. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-item-check.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-checkbox-indicator.ts.template} +5 -7
  31. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-checkbox.ts.template +36 -0
  32. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-group.ts.template +17 -0
  33. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-item-sub-indicator.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-item-sub-indicator.ts.template} +4 -5
  34. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-item.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-item.ts.template} +19 -14
  35. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-label.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-label.ts.template} +10 -13
  36. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-item-radio-indicator.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-radio-indicator.ts.template} +5 -7
  37. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-radio.ts.template +36 -0
  38. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-separator.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-separator.ts.template} +5 -6
  39. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-shortcut.ts.template → dropdown-menu/files/lib/hlm-dropdown-menu-shortcut.ts.template} +5 -8
  40. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-sub.ts.template +63 -0
  41. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-token.ts.template +22 -0
  42. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu-trigger.ts.template +46 -0
  43. package/src/generators/ui/libs/dropdown-menu/files/lib/hlm-dropdown-menu.ts.template +67 -0
  44. package/src/generators/ui/libs/dropdown-menu/generator.d.ts +3 -0
  45. package/src/generators/ui/libs/dropdown-menu/generator.js +9 -0
  46. package/src/generators/ui/libs/dropdown-menu/generator.js.map +1 -0
  47. package/src/generators/ui/libs/menubar/files/index.ts.template +8 -0
  48. package/src/generators/ui/libs/menubar/files/lib/hlm-menubar-token.ts.template +22 -0
  49. package/src/generators/ui/libs/menubar/files/lib/hlm-menubar-trigger.ts.template +66 -0
  50. package/src/generators/ui/libs/{menu/files/lib/hlm-menu-bar.ts.template → menubar/files/lib/hlm-menubar.ts.template} +7 -8
  51. package/src/generators/ui/libs/menubar/generator.d.ts +3 -0
  52. package/src/generators/ui/libs/{menu → menubar}/generator.js +1 -1
  53. package/src/generators/ui/libs/menubar/generator.js.map +1 -0
  54. package/src/generators/ui/primitive-deps.js +3 -3
  55. package/src/generators/ui/primitive-deps.js.map +1 -1
  56. package/src/generators/ui/primitives.d.ts +1 -1
  57. package/src/generators/ui/supported-ui-libraries.json +61 -42
  58. package/src/utils/config.d.ts +1 -0
  59. package/src/utils/config.js +11 -0
  60. package/src/utils/config.js.map +1 -1
  61. package/src/utils/import-alias.d.ts +1 -0
  62. package/src/utils/import-alias.js +7 -0
  63. package/src/utils/import-alias.js.map +1 -0
  64. package/src/generators/ui/libs/menu/files/index.ts.template +0 -49
  65. package/src/generators/ui/libs/menu/files/lib/hlm-menu-bar-item.ts.template +0 -21
  66. package/src/generators/ui/libs/menu/files/lib/hlm-menu-group.ts.template +0 -15
  67. package/src/generators/ui/libs/menu/files/lib/hlm-menu-item-checkbox.ts.template +0 -27
  68. package/src/generators/ui/libs/menu/files/lib/hlm-menu-item-icon.ts.template +0 -16
  69. package/src/generators/ui/libs/menu/files/lib/hlm-menu-item-radio.ts.template +0 -27
  70. package/src/generators/ui/libs/menu/files/lib/hlm-menu.ts.template +0 -39
  71. package/src/generators/ui/libs/menu/files/lib/hlm-sub-menu.ts.template +0 -25
  72. package/src/generators/ui/libs/menu/generator.js.map +0 -1
  73. /package/src/generators/ui/libs/{menu → context-menu}/generator.d.ts +0 -0
@@ -0,0 +1,45 @@
1
+ import { HlmDropdownMenu } from './lib/hlm-dropdown-menu';
2
+ import { HlmDropdownMenuCheckbox } from './lib/hlm-dropdown-menu-checkbox';
3
+ import { HlmDropdownMenuCheckboxIndicator } from './lib/hlm-dropdown-menu-checkbox-indicator';
4
+ import { HlmDropdownMenuGroup } from './lib/hlm-dropdown-menu-group';
5
+ import { HlmDropdownMenuItem } from './lib/hlm-dropdown-menu-item';
6
+ import { HlmDropdownMenuItemSubIndicator } from './lib/hlm-dropdown-menu-item-sub-indicator';
7
+ import { HlmDropdownMenuLabel } from './lib/hlm-dropdown-menu-label';
8
+ import { HlmDropdownMenuRadio } from './lib/hlm-dropdown-menu-radio';
9
+ import { HlmDropdownMenuRadioIndicator } from './lib/hlm-dropdown-menu-radio-indicator';
10
+ import { HlmDropdownMenuSeparator } from './lib/hlm-dropdown-menu-separator';
11
+ import { HlmDropdownMenuShortcut } from './lib/hlm-dropdown-menu-shortcut';
12
+ import { HlmDropdownMenuSub } from './lib/hlm-dropdown-menu-sub';
13
+
14
+ import { HlmDropdownMenuTrigger } from './lib/hlm-dropdown-menu-trigger';
15
+
16
+ export * from './lib/hlm-dropdown-menu';
17
+ export * from './lib/hlm-dropdown-menu-checkbox';
18
+ export * from './lib/hlm-dropdown-menu-checkbox-indicator';
19
+ export * from './lib/hlm-dropdown-menu-group';
20
+ export * from './lib/hlm-dropdown-menu-item';
21
+ export * from './lib/hlm-dropdown-menu-item-sub-indicator';
22
+ export * from './lib/hlm-dropdown-menu-label';
23
+ export * from './lib/hlm-dropdown-menu-radio';
24
+ export * from './lib/hlm-dropdown-menu-radio-indicator';
25
+ export * from './lib/hlm-dropdown-menu-separator';
26
+ export * from './lib/hlm-dropdown-menu-shortcut';
27
+ export * from './lib/hlm-dropdown-menu-sub';
28
+ export * from './lib/hlm-dropdown-menu-token';
29
+ export * from './lib/hlm-dropdown-menu-trigger';
30
+
31
+ export const HlmDropdownMenuImports = [
32
+ HlmDropdownMenu,
33
+ HlmDropdownMenuCheckbox,
34
+ HlmDropdownMenuCheckboxIndicator,
35
+ HlmDropdownMenuGroup,
36
+ HlmDropdownMenuItem,
37
+ HlmDropdownMenuItemSubIndicator,
38
+ HlmDropdownMenuLabel,
39
+ HlmDropdownMenuRadio,
40
+ HlmDropdownMenuRadioIndicator,
41
+ HlmDropdownMenuSeparator,
42
+ HlmDropdownMenuShortcut,
43
+ HlmDropdownMenuSub,
44
+ HlmDropdownMenuTrigger,
45
+ ] as const;
@@ -1,28 +1,26 @@
1
1
  import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
2
  import { NgIcon, provideIcons } from '@ng-icons/core';
3
3
  import { lucideCheck } from '@ng-icons/lucide';
4
- import { HlmIcon } from '<%- importAlias %>/icon';
5
4
  import { hlm } from '<%- importAlias %>/utils';
6
5
  import type { ClassValue } from 'clsx';
7
6
 
8
7
  @Component({
9
- selector: 'hlm-menu-item-check',
10
- imports: [NgIcon, HlmIcon],
8
+ selector: 'hlm-dropdown-menu-checkbox-indicator',
9
+ imports: [NgIcon],
11
10
  providers: [provideIcons({ lucideCheck })],
12
11
  changeDetection: ChangeDetectionStrategy.OnPush,
13
12
  host: {
14
13
  '[class]': '_computedClass()',
15
14
  },
16
15
  template: `
17
- <!-- Using 1rem for size to mimick h-4 w-4 -->
18
- <ng-icon hlm size="1rem" name="lucideCheck" />
16
+ <ng-icon class="text-base" name="lucideCheck" />
19
17
  `,
20
18
  })
21
- export class HlmMenuItemCheck {
19
+ export class HlmDropdownMenuCheckboxIndicator {
22
20
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
23
21
  protected readonly _computedClass = computed(() =>
24
22
  hlm(
25
- 'absolute left-2 flex h-3.5 w-3.5 items-center justify-center opacity-0 group-[.checked]:opacity-100',
23
+ 'pointer-events-none absolute left-2 flex size-3.5 items-center justify-center opacity-0 group-data-[checked]:opacity-100',
26
24
  this.userClass(),
27
25
  ),
28
26
  );
@@ -0,0 +1,36 @@
1
+ import { type BooleanInput } from '@angular/cdk/coercion';
2
+ import { CdkMenuItemCheckbox } from '@angular/cdk/menu';
3
+ import { Directive, booleanAttribute, computed, inject, input } from '@angular/core';
4
+ import { hlm } from '<%- importAlias %>/utils';
5
+ import type { ClassValue } from 'clsx';
6
+
7
+ @Directive({
8
+ selector: '[hlmDropdownMenuCheckbox]',
9
+ hostDirectives: [
10
+ {
11
+ directive: CdkMenuItemCheckbox,
12
+ inputs: ['cdkMenuItemDisabled: disabled', 'cdkMenuItemChecked: checked'],
13
+ outputs: ['cdkMenuItemTriggered: triggered'],
14
+ },
15
+ ],
16
+ host: {
17
+ 'data-slot': 'dropdown-menu-checkbox-item',
18
+ '[attr.data-disabled]': 'disabled() ? "" : null',
19
+ '[attr.data-checked]': 'checked() ? "" : null',
20
+ '[class]': '_computedClass()',
21
+ },
22
+ })
23
+ export class HlmDropdownMenuCheckbox {
24
+ private readonly _cdkMenuItem = inject(CdkMenuItemCheckbox);
25
+
26
+ public readonly userClass = input<ClassValue>('', { alias: 'class' });
27
+ protected readonly _computedClass = computed(() =>
28
+ hlm(
29
+ 'hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground group relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
30
+ this.userClass(),
31
+ ),
32
+ );
33
+
34
+ public readonly checked = input<boolean, BooleanInput>(this._cdkMenuItem.checked, { transform: booleanAttribute });
35
+ public readonly disabled = input<boolean, BooleanInput>(this._cdkMenuItem.disabled, { transform: booleanAttribute });
36
+ }
@@ -0,0 +1,17 @@
1
+ import { CdkMenuGroup } from '@angular/cdk/menu';
2
+ import { computed, Directive, input } from '@angular/core';
3
+ import { hlm } from '<%- importAlias %>/utils';
4
+ import type { ClassValue } from 'clsx';
5
+
6
+ @Directive({
7
+ selector: '[hlmDropdownMenuGroup],hlm-dropdown-menu-group',
8
+ hostDirectives: [CdkMenuGroup],
9
+ host: {
10
+ 'data-slot': 'dropdown-menu-group',
11
+ '[class]': '_computedClass()',
12
+ },
13
+ })
14
+ export class HlmDropdownMenuGroup {
15
+ public readonly userClass = input<ClassValue>('', { alias: 'class' });
16
+ protected readonly _computedClass = computed(() => hlm('block', this.userClass()));
17
+ }
@@ -1,23 +1,22 @@
1
1
  import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
2
  import { NgIcon, provideIcons } from '@ng-icons/core';
3
3
  import { lucideChevronRight } from '@ng-icons/lucide';
4
- import { HlmIcon } from '<%- importAlias %>/icon';
5
4
  import { hlm } from '<%- importAlias %>/utils';
6
5
  import type { ClassValue } from 'clsx';
7
6
 
8
7
  @Component({
9
- selector: 'hlm-menu-item-sub-indicator',
10
- imports: [NgIcon, HlmIcon],
8
+ selector: 'hlm-dropdown-menu-item-sub-indicator',
9
+ imports: [NgIcon],
11
10
  providers: [provideIcons({ lucideChevronRight })],
12
11
  changeDetection: ChangeDetectionStrategy.OnPush,
13
12
  host: {
14
13
  '[class]': '_computedClass()',
15
14
  },
16
15
  template: `
17
- <ng-icon hlm size="sm" name="lucideChevronRight" class="text-popover-foreground" />
16
+ <ng-icon name="lucideChevronRight" class="text-base" />
18
17
  `,
19
18
  })
20
- export class HlmMenuItemSubIndicator {
19
+ export class HlmDropdownMenuItemSubIndicator {
21
20
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
22
21
  protected readonly _computedClass = computed(() => hlm('ml-auto size-4', this.userClass()));
23
22
  }
@@ -1,36 +1,41 @@
1
- import type { BooleanInput } from '@angular/cdk/coercion';
1
+ import { type BooleanInput } from '@angular/cdk/coercion';
2
+ import { CdkMenuItem } from '@angular/cdk/menu';
2
3
  import { booleanAttribute, computed, Directive, input } from '@angular/core';
3
- import { BrnMenuItem } from '@spartan-ng/brain/menu';
4
4
  import { hlm } from '<%- importAlias %>/utils';
5
5
  import type { ClassValue } from 'clsx';
6
6
 
7
7
  @Directive({
8
- selector: '[hlmMenuItem]',
8
+ selector: 'button[hlmDropdownMenuItem]',
9
9
  hostDirectives: [
10
10
  {
11
- directive: BrnMenuItem,
12
- inputs: ['disabled: disabled'],
13
- outputs: ['triggered: triggered'],
11
+ directive: CdkMenuItem,
12
+ inputs: ['cdkMenuItemDisabled: disabled'],
13
+ outputs: ['cdkMenuItemTriggered: triggered'],
14
14
  },
15
15
  ],
16
16
  host: {
17
+ 'data-slot': 'dropdown-menu-item',
17
18
  '[class]': '_computedClass()',
19
+ '[disabled]': 'disabled() || null',
20
+ '[attr.data-disabled]': 'disabled() ? "" : null',
18
21
  '[attr.data-variant]': 'variant()',
19
22
  '[attr.data-inset]': 'inset() ? "" : null',
20
23
  },
21
24
  })
22
- export class HlmMenuItem {
23
- public readonly variant = input<'default' | 'destructive'>('default');
24
-
25
- public readonly inset = input<boolean, BooleanInput>(false, {
26
- transform: booleanAttribute,
27
- });
28
-
25
+ export class HlmDropdownMenuItem {
29
26
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
30
27
  protected readonly _computedClass = computed(() =>
31
28
  hlm(
32
- `hover:bg-accent focus-visible:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[ng-icon]:!text-destructive [&_ng-icon:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_ng-icon]:pointer-events-none [&_ng-icon]:shrink-0`,
29
+ "hover:bg-accent focus-visible:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[ng-icon]:!text-destructive [&_ng-icon:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_ng-icon]:pointer-events-none [&_ng-icon]:shrink-0 [&_svg:not([class*='text-'])]:text-base",
33
30
  this.userClass(),
34
31
  ),
35
32
  );
33
+
34
+ public readonly disabled = input<boolean, BooleanInput>(false, { transform: booleanAttribute });
35
+
36
+ public readonly variant = input<'default' | 'destructive'>('default');
37
+
38
+ public readonly inset = input<boolean, BooleanInput>(false, {
39
+ transform: booleanAttribute,
40
+ });
36
41
  }
@@ -1,26 +1,23 @@
1
- import type { BooleanInput } from '@angular/cdk/coercion';
2
- import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
1
+ import { type BooleanInput } from '@angular/cdk/coercion';
2
+ import { booleanAttribute, computed, Directive, input } from '@angular/core';
3
3
  import { hlm } from '<%- importAlias %>/utils';
4
4
  import type { ClassValue } from 'clsx';
5
5
 
6
- @Component({
7
- selector: 'hlm-menu-label',
8
- changeDetection: ChangeDetectionStrategy.OnPush,
6
+ @Directive({
7
+ selector: '[hlmDropdownMenuLabel],hlm-dropdown-menu-label',
9
8
  host: {
9
+ 'data-slot': 'dropdown-menu-label',
10
10
  '[class]': '_computedClass()',
11
11
  '[attr.data-inset]': 'inset() ? "" : null',
12
12
  },
13
- template: `
14
- <ng-content />
15
- `,
16
13
  })
17
- export class HlmMenuLabel {
18
- public readonly inset = input<boolean, BooleanInput>(false, {
19
- transform: booleanAttribute,
20
- });
21
-
14
+ export class HlmDropdownMenuLabel {
22
15
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
23
16
  protected readonly _computedClass = computed(() =>
24
17
  hlm('block px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', this.userClass()),
25
18
  );
19
+
20
+ public readonly inset = input<boolean, BooleanInput>(false, {
21
+ transform: booleanAttribute,
22
+ });
26
23
  }
@@ -1,28 +1,26 @@
1
1
  import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
2
  import { NgIcon, provideIcons } from '@ng-icons/core';
3
3
  import { lucideCircle } from '@ng-icons/lucide';
4
- import { HlmIcon } from '<%- importAlias %>/icon';
5
4
  import { hlm } from '<%- importAlias %>/utils';
6
5
  import type { ClassValue } from 'clsx';
7
6
 
8
7
  @Component({
9
- selector: 'hlm-menu-item-radio',
10
- imports: [NgIcon, HlmIcon],
8
+ selector: 'hlm-dropdown-menu-radio-indicator',
9
+ imports: [NgIcon],
11
10
  providers: [provideIcons({ lucideCircle })],
12
11
  changeDetection: ChangeDetectionStrategy.OnPush,
13
12
  host: {
14
13
  '[class]': '_computedClass()',
15
14
  },
16
15
  template: `
17
- <!-- Using 0.5rem for size to mimick h-2 w-2 -->
18
- <ng-icon hlm size="0.5rem" class="*:*:fill-current" name="lucideCircle" />
16
+ <ng-icon name="lucideCircle" class="text-[0.5rem] *:[svg]:fill-current" />
19
17
  `,
20
18
  })
21
- export class HlmMenuItemRadioIndicator {
19
+ export class HlmDropdownMenuRadioIndicator {
22
20
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
23
21
  protected readonly _computedClass = computed(() =>
24
22
  hlm(
25
- 'absolute left-2 flex h-3.5 w-3.5 items-center justify-center opacity-0 group-[.checked]:opacity-100',
23
+ 'pointer-events-none absolute left-2 flex size-3.5 items-center justify-center opacity-0 group-data-[checked]:opacity-100',
26
24
  this.userClass(),
27
25
  ),
28
26
  );
@@ -0,0 +1,36 @@
1
+ import { type BooleanInput } from '@angular/cdk/coercion';
2
+ import { CdkMenuItemRadio } from '@angular/cdk/menu';
3
+ import { Directive, booleanAttribute, computed, inject, input } from '@angular/core';
4
+ import { hlm } from '<%- importAlias %>/utils';
5
+ import type { ClassValue } from 'clsx';
6
+
7
+ @Directive({
8
+ selector: '[hlmDropdownMenuRadio]',
9
+ hostDirectives: [
10
+ {
11
+ directive: CdkMenuItemRadio,
12
+ inputs: ['cdkMenuItemDisabled: disabled', 'cdkMenuItemChecked: checked'],
13
+ outputs: ['cdkMenuItemTriggered: triggered'],
14
+ },
15
+ ],
16
+ host: {
17
+ 'data-slot': 'dropdown-menu-radio-item',
18
+ '[attr.data-disabled]': 'disabled() ? "" : null',
19
+ '[attr.data-checked]': 'checked() ? "" : null',
20
+ '[class]': '_computedClass()',
21
+ },
22
+ })
23
+ export class HlmDropdownMenuRadio {
24
+ private readonly _cdkMenuItem = inject(CdkMenuItemRadio);
25
+
26
+ public readonly userClass = input<ClassValue>('', { alias: 'class' });
27
+ protected readonly _computedClass = computed(() =>
28
+ hlm(
29
+ 'hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground group relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
30
+ this.userClass(),
31
+ ),
32
+ );
33
+
34
+ public readonly checked = input<boolean, BooleanInput>(this._cdkMenuItem.checked, { transform: booleanAttribute });
35
+ public readonly disabled = input<boolean, BooleanInput>(this._cdkMenuItem.disabled, { transform: booleanAttribute });
36
+ }
@@ -1,16 +1,15 @@
1
- import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
1
+ import { computed, Directive, input } from '@angular/core';
2
2
  import { hlm } from '<%- importAlias %>/utils';
3
3
  import type { ClassValue } from 'clsx';
4
4
 
5
- @Component({
6
- selector: 'hlm-menu-separator',
7
- changeDetection: ChangeDetectionStrategy.OnPush,
5
+ @Directive({
6
+ selector: '[hlmDropdownMenuSeparator],hlm-dropdown-menu-separator',
8
7
  host: {
8
+ 'data-slot': 'dropdown-menu-separator',
9
9
  '[class]': '_computedClass()',
10
10
  },
11
- template: '',
12
11
  })
13
- export class HlmMenuSeparator {
12
+ export class HlmDropdownMenuSeparator {
14
13
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
15
14
  protected readonly _computedClass = computed(() => hlm('bg-border -mx-1 my-1 block h-px', this.userClass()));
16
15
  }
@@ -1,18 +1,15 @@
1
- import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
1
+ import { computed, Directive, input } from '@angular/core';
2
2
  import { hlm } from '<%- importAlias %>/utils';
3
3
  import type { ClassValue } from 'clsx';
4
4
 
5
- @Component({
6
- selector: 'hlm-menu-shortcut',
7
- changeDetection: ChangeDetectionStrategy.OnPush,
5
+ @Directive({
6
+ selector: '[hlmDropdownMenuShortcut],hlm-dropdown-menu-shortcut',
8
7
  host: {
8
+ 'data-slot': 'dropdown-menu-shortcut',
9
9
  '[class]': '_computedClass()',
10
10
  },
11
- template: `
12
- <ng-content />
13
- `,
14
11
  })
15
- export class HlmMenuShortcut {
12
+ export class HlmDropdownMenuShortcut {
16
13
  public readonly userClass = input<ClassValue>('', { alias: 'class' });
17
14
  protected readonly _computedClass = computed(() =>
18
15
  hlm('text-muted-foreground ml-auto text-xs tracking-widest', this.userClass()),
@@ -0,0 +1,63 @@
1
+ import { CdkMenu } from '@angular/cdk/menu';
2
+ import { computed, Directive, inject, input, signal } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { hlm } from '<%- importAlias %>/utils';
5
+ import type { ClassValue } from 'clsx';
6
+
7
+ @Directive({
8
+ selector: '[hlmDropdownMenuSub],hlm-dropdown-menu-sub',
9
+ hostDirectives: [CdkMenu],
10
+ host: {
11
+ 'data-slot': 'dropdown-menu-sub',
12
+ '[attr.data-state]': '_state()',
13
+ '[attr.data-side]': '_side()',
14
+ '[class]': '_computedClass()',
15
+ },
16
+ })
17
+ export class HlmDropdownMenuSub {
18
+ private readonly _host = inject(CdkMenu);
19
+
20
+ protected readonly _state = signal('open');
21
+ protected readonly _side = signal('top');
22
+
23
+ public readonly userClass = input<ClassValue>('', { alias: 'class' });
24
+ protected readonly _computedClass = computed(() =>
25
+ hlm(
26
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-top overflow-hidden rounded-md border p-1 shadow-lg',
27
+ this.userClass(),
28
+ ),
29
+ );
30
+
31
+ constructor() {
32
+ this.setSideWithDarkMagic();
33
+ // this is a best effort, but does not seem to work currently
34
+ // TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
35
+ this._host.closed.pipe(takeUntilDestroyed()).subscribe(() => this._state.set('closed'));
36
+ }
37
+
38
+ private setSideWithDarkMagic() {
39
+ /**
40
+ * This is an ugly workaround to at least figure out the correct side of where a submenu
41
+ * will appear and set the attribute to the host accordingly
42
+ *
43
+ * First of all we take advantage of the menu stack not being aware of the root
44
+ * object immediately after it is added. This code executes before the root element is added,
45
+ * which means the stack is still empty and the peek method returns undefined.
46
+ */
47
+ const isRoot = this._host.menuStack.peek() === undefined;
48
+ setTimeout(() => {
49
+ // our menu trigger directive leaves the last position used for use immediately after opening
50
+ // we can access it here and determine the correct side.
51
+ // eslint-disable-next-line
52
+ const ps = (this._host as any)._parentTrigger._spartanLastPosition;
53
+ if (!ps) {
54
+ // if we have no last position we default to the most likely option
55
+ // I hate that we have to do this and hope we can revisit soon and improve
56
+ this._side.set(isRoot ? 'top' : 'left');
57
+ return;
58
+ }
59
+ const side = isRoot ? ps.originY : ps.originX === 'end' ? 'right' : 'left';
60
+ this._side.set(side);
61
+ });
62
+ }
63
+ }
@@ -0,0 +1,22 @@
1
+ import { InjectionToken, type ValueProvider, inject } from '@angular/core';
2
+ import { type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
3
+
4
+ export interface HlmDropdownMenuConfig {
5
+ align: MenuAlign;
6
+ side: MenuSide;
7
+ }
8
+
9
+ const defaultConfig: HlmDropdownMenuConfig = {
10
+ align: 'start',
11
+ side: 'bottom',
12
+ };
13
+
14
+ const HlmDropdownMenuConfigToken = new InjectionToken<HlmDropdownMenuConfig>('HlmDropdownMenuConfig');
15
+
16
+ export function provideHlmDropdownMenuConfig(config: Partial<HlmDropdownMenuConfig>): ValueProvider {
17
+ return { provide: HlmDropdownMenuConfigToken, useValue: { ...defaultConfig, ...config } };
18
+ }
19
+
20
+ export function injectHlmDropdownMenuConfig(): HlmDropdownMenuConfig {
21
+ return inject(HlmDropdownMenuConfigToken, { optional: true }) ?? defaultConfig;
22
+ }
@@ -0,0 +1,46 @@
1
+ import { CdkMenuTrigger } from '@angular/cdk/menu';
2
+ import { computed, Directive, effect, inject, input } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { createMenuPosition, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
5
+ import { injectHlmDropdownMenuConfig } from './hlm-dropdown-menu-token';
6
+
7
+ @Directive({
8
+ selector: '[hlmDropdownMenuTrigger]',
9
+ hostDirectives: [
10
+ {
11
+ directive: CdkMenuTrigger,
12
+ inputs: ['cdkMenuTriggerFor: hlmDropdownMenuTrigger', 'cdkMenuTriggerData: hlmDropdownMenuTriggerData'],
13
+ outputs: ['cdkMenuOpened: hlmDropdownMenuOpened', 'cdkMenuClosed: hlmDropdownMenuClosed'],
14
+ },
15
+ ],
16
+ host: {
17
+ 'data-slot': 'dropdown-menu-trigger',
18
+ },
19
+ })
20
+ export class HlmDropdownMenuTrigger {
21
+ private readonly _cdkTrigger = inject(CdkMenuTrigger, { host: true });
22
+ private readonly _config = injectHlmDropdownMenuConfig();
23
+
24
+ public readonly align = input<MenuAlign>(this._config.align);
25
+ public readonly side = input<MenuSide>(this._config.side);
26
+
27
+ private readonly _menuPosition = computed(() => createMenuPosition(this.align(), this.side()));
28
+
29
+ constructor() {
30
+ // once the trigger opens we wait until the next tick and then grab the last position
31
+ // used to position the menu. we store this in our trigger which the brnMenu directive has
32
+ // access to through DI
33
+ this._cdkTrigger.opened.pipe(takeUntilDestroyed()).subscribe(() =>
34
+ setTimeout(
35
+ () =>
36
+ // eslint-disable-next-line
37
+ ((this._cdkTrigger as any)._spartanLastPosition = // eslint-disable-next-line
38
+ (this._cdkTrigger as any).overlayRef._positionStrategy._lastPosition),
39
+ ),
40
+ );
41
+
42
+ effect(() => {
43
+ this._cdkTrigger.menuPosition = this._menuPosition();
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,67 @@
1
+ import { type NumberInput } from '@angular/cdk/coercion';
2
+ import { CdkMenu } from '@angular/cdk/menu';
3
+ import { computed, Directive, inject, input, numberAttribute, signal } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { hlm } from '<%- importAlias %>/utils';
6
+ import type { ClassValue } from 'clsx';
7
+
8
+ @Directive({
9
+ selector: '[hlmDropdownMenu],hlm-dropdown-menu',
10
+ hostDirectives: [CdkMenu],
11
+ host: {
12
+ 'data-slot': 'dropdown-menu',
13
+ '[attr.data-state]': '_state()',
14
+ '[attr.data-side]': '_side()',
15
+ '[class]': '_computedClass()',
16
+ '[style.--side-offset]': 'sideOffset()',
17
+ },
18
+ })
19
+ export class HlmDropdownMenu {
20
+ private readonly _host = inject(CdkMenu);
21
+
22
+ protected readonly _state = signal('open');
23
+ protected readonly _side = signal('top');
24
+
25
+ public readonly userClass = input<ClassValue>('', { alias: 'class' });
26
+ protected readonly _computedClass = computed(() =>
27
+ hlm(
28
+ 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 my-[--spacing(var(--side-offset))] min-w-[8rem] origin-top overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
29
+ this.userClass(),
30
+ ),
31
+ );
32
+
33
+ public readonly sideOffset = input<number, NumberInput>(1, { transform: numberAttribute });
34
+
35
+ constructor() {
36
+ this.setSideWithDarkMagic();
37
+ // this is a best effort, but does not seem to work currently
38
+ // TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
39
+ this._host.closed.pipe(takeUntilDestroyed()).subscribe(() => this._state.set('closed'));
40
+ }
41
+
42
+ private setSideWithDarkMagic() {
43
+ /**
44
+ * This is an ugly workaround to at least figure out the correct side of where a submenu
45
+ * will appear and set the attribute to the host accordingly
46
+ *
47
+ * First of all we take advantage of the menu stack not being aware of the root
48
+ * object immediately after it is added. This code executes before the root element is added,
49
+ * which means the stack is still empty and the peek method returns undefined.
50
+ */
51
+ const isRoot = this._host.menuStack.peek() === undefined;
52
+ setTimeout(() => {
53
+ // our menu trigger directive leaves the last position used for use immediately after opening
54
+ // we can access it here and determine the correct side.
55
+ // eslint-disable-next-line
56
+ const ps = (this._host as any)._parentTrigger._spartanLastPosition;
57
+ if (!ps) {
58
+ // if we have no last position we default to the most likely option
59
+ // I hate that we have to do this and hope we can revisit soon and improve
60
+ this._side.set(isRoot ? 'top' : 'left');
61
+ return;
62
+ }
63
+ const side = isRoot ? ps.originY : ps.originX === 'end' ? 'right' : 'left';
64
+ this._side.set(side);
65
+ });
66
+ }
67
+ }
@@ -0,0 +1,3 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ import type { HlmBaseGeneratorSchema } from '../../../base/schema';
3
+ export declare function generator(tree: Tree, options: HlmBaseGeneratorSchema): Promise<import("@nx/devkit").GeneratorCallback>;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generator = generator;
4
+ const tslib_1 = require("tslib");
5
+ const generator_1 = tslib_1.__importDefault(require("../../../base/generator"));
6
+ async function generator(tree, options) {
7
+ return await (0, generator_1.default)(tree, { ...options, name: 'dropdown-menu' });
8
+ }
9
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../../../libs/cli/src/generators/ui/libs/dropdown-menu/generator.ts"],"names":[],"mappings":";;AAIA,8BAEC;;AALD,gFAAuD;AAGhD,KAAK,UAAU,SAAS,CAAC,IAAU,EAAE,OAA+B;IAC1E,OAAO,MAAM,IAAA,mBAAgB,EAAC,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { HlmMenubar } from './lib/hlm-menubar';
2
+ import { HlmMenubarTrigger } from './lib/hlm-menubar-trigger';
3
+
4
+ export * from './lib/hlm-menubar';
5
+ export * from './lib/hlm-menubar-token';
6
+ export * from './lib/hlm-menubar-trigger';
7
+
8
+ export const HlmMenubarImports = [HlmMenubar, HlmMenubarTrigger] as const;
@@ -0,0 +1,22 @@
1
+ import { InjectionToken, type ValueProvider, inject } from '@angular/core';
2
+ import { type MenuAlign, type MenuSide } from '@spartan-ng/brain/core';
3
+
4
+ export interface HlmMenubarConfig {
5
+ align: MenuAlign;
6
+ side: MenuSide;
7
+ }
8
+
9
+ const defaultConfig: HlmMenubarConfig = {
10
+ align: 'start',
11
+ side: 'bottom',
12
+ };
13
+
14
+ const HlmMenubarConfigToken = new InjectionToken<HlmMenubarConfig>('HlmMenubarConfig');
15
+
16
+ export function provideHlmMenubarConfig(config: Partial<HlmMenubarConfig>): ValueProvider {
17
+ return { provide: HlmMenubarConfigToken, useValue: { ...defaultConfig, ...config } };
18
+ }
19
+
20
+ export function injectHlmMenubarConfig(): HlmMenubarConfig {
21
+ return inject(HlmMenubarConfigToken, { optional: true }) ?? defaultConfig;
22
+ }