@ng-zen/cli 21.0.0 → 21.1.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +27 -25
  3. package/package.json +15 -12
  4. package/schematics/collection.json +5 -5
  5. package/schematics/dependency-manager/index.js +1 -1
  6. package/schematics/dependency-manager/index.js.map +1 -1
  7. package/schematics/dependency-manager/index.ts +1 -1
  8. package/schematics/dependency-manager/utils/get-dependencies.js.map +1 -1
  9. package/schematics/dependency-manager/utils/get-dependencies.ts +2 -2
  10. package/schematics/{components → ui}/files/alert/alert.stories.ts +1 -1
  11. package/schematics/{components → ui}/files/avatar/avatar.stories.ts +3 -3
  12. package/schematics/{components → ui}/files/button/button.stories.ts +1 -1
  13. package/schematics/{components → ui}/files/checkbox/checkbox.stories.ts +1 -1
  14. package/schematics/{components → ui}/files/divider/divider.stories.ts +2 -2
  15. package/schematics/{components → ui}/files/form-control/form-control.stories.ts +1 -1
  16. package/schematics/{components → ui}/files/icon/icon.stories.ts +1 -1
  17. package/schematics/{components → ui}/files/input/input.stories.ts +1 -1
  18. package/schematics/ui/files/popover/host/popover-host.scss +76 -0
  19. package/schematics/ui/files/popover/host/popover-host.spec.ts +20 -0
  20. package/schematics/ui/files/popover/host/popover-host.ts +38 -0
  21. package/schematics/ui/files/popover/index.ts +1 -0
  22. package/schematics/ui/files/popover/popover-positions.type.ts +13 -0
  23. package/schematics/ui/files/popover/popover.spec.ts +171 -0
  24. package/schematics/ui/files/popover/popover.stories.ts +66 -0
  25. package/schematics/ui/files/popover/popover.ts +130 -0
  26. package/schematics/{components → ui}/files/radio/radio.stories.ts +1 -1
  27. package/schematics/{components → ui}/files/skeleton/skeleton.stories.ts +1 -1
  28. package/schematics/{components → ui}/files/switch/switch.stories.ts +1 -1
  29. package/schematics/{components → ui}/files/textarea/textarea.stories.ts +1 -1
  30. package/schematics/ui/index.js +27 -0
  31. package/schematics/ui/index.js.map +1 -0
  32. package/schematics/ui/index.ts +31 -0
  33. package/schematics/ui/schema.js.map +1 -0
  34. package/schematics/{components → ui}/schema.json +15 -6
  35. package/schematics/{components → ui}/schema.ts +3 -2
  36. package/schematics/{components → ui}/templates/README.md.template +3 -3
  37. package/services/selected-elements.js.map +1 -1
  38. package/services/selected-elements.ts +2 -2
  39. package/types/files-config.js.map +1 -1
  40. package/types/files-config.ts +2 -2
  41. package/types/generator-schema-base.js.map +1 -1
  42. package/types/generator-schema-base.ts +2 -1
  43. package/types/schematics-folder.js.map +1 -1
  44. package/types/schematics-folder.ts +2 -2
  45. package/utils/apply-file-template.util.js.map +1 -1
  46. package/utils/apply-file-template.util.ts +1 -1
  47. package/schematics/components/index.js +0 -16
  48. package/schematics/components/index.js.map +0 -1
  49. package/schematics/components/index.ts +0 -16
  50. package/schematics/components/schema.js.map +0 -1
  51. /package/schematics/{components → ui}/files/alert/alert.scss +0 -0
  52. /package/schematics/{components → ui}/files/alert/alert.spec.ts +0 -0
  53. /package/schematics/{components → ui}/files/alert/alert.ts +0 -0
  54. /package/schematics/{components → ui}/files/alert/index.ts +0 -0
  55. /package/schematics/{components → ui}/files/avatar/avatar.scss +0 -0
  56. /package/schematics/{components → ui}/files/avatar/avatar.spec.ts +0 -0
  57. /package/schematics/{components → ui}/files/avatar/avatar.ts +0 -0
  58. /package/schematics/{components → ui}/files/avatar/index.ts +0 -0
  59. /package/schematics/{components → ui}/files/button/button.scss +0 -0
  60. /package/schematics/{components → ui}/files/button/button.spec.ts +0 -0
  61. /package/schematics/{components → ui}/files/button/button.ts +0 -0
  62. /package/schematics/{components → ui}/files/button/index.ts +0 -0
  63. /package/schematics/{components → ui}/files/checkbox/checkbox.scss +0 -0
  64. /package/schematics/{components → ui}/files/checkbox/checkbox.spec.ts +0 -0
  65. /package/schematics/{components → ui}/files/checkbox/checkbox.ts +0 -0
  66. /package/schematics/{components → ui}/files/checkbox/index.ts +0 -0
  67. /package/schematics/{components → ui}/files/divider/divider.scss +0 -0
  68. /package/schematics/{components → ui}/files/divider/divider.spec.ts +0 -0
  69. /package/schematics/{components → ui}/files/divider/divider.ts +0 -0
  70. /package/schematics/{components → ui}/files/divider/index.ts +0 -0
  71. /package/schematics/{components → ui}/files/form-control/form-control.spec.ts +0 -0
  72. /package/schematics/{components → ui}/files/form-control/form-control.ts +0 -0
  73. /package/schematics/{components → ui}/files/form-control/index.ts +0 -0
  74. /package/schematics/{components → ui}/files/icon/icon.spec.ts +0 -0
  75. /package/schematics/{components → ui}/files/icon/icon.ts +0 -0
  76. /package/schematics/{components → ui}/files/icon/index.ts +0 -0
  77. /package/schematics/{components → ui}/files/input/index.ts +0 -0
  78. /package/schematics/{components → ui}/files/input/input.scss +0 -0
  79. /package/schematics/{components → ui}/files/input/input.spec.ts +0 -0
  80. /package/schematics/{components → ui}/files/input/input.ts +0 -0
  81. /package/schematics/{components → ui}/files/radio/index.ts +0 -0
  82. /package/schematics/{components → ui}/files/radio/radio.registry.ts +0 -0
  83. /package/schematics/{components → ui}/files/radio/radio.scss +0 -0
  84. /package/schematics/{components → ui}/files/radio/radio.spec.ts +0 -0
  85. /package/schematics/{components → ui}/files/radio/radio.ts +0 -0
  86. /package/schematics/{components → ui}/files/skeleton/index.ts +0 -0
  87. /package/schematics/{components → ui}/files/skeleton/skeleton.scss +0 -0
  88. /package/schematics/{components → ui}/files/skeleton/skeleton.spec.ts +0 -0
  89. /package/schematics/{components → ui}/files/skeleton/skeleton.ts +0 -0
  90. /package/schematics/{components → ui}/files/switch/index.ts +0 -0
  91. /package/schematics/{components → ui}/files/switch/switch.scss +0 -0
  92. /package/schematics/{components → ui}/files/switch/switch.spec.ts +0 -0
  93. /package/schematics/{components → ui}/files/switch/switch.ts +0 -0
  94. /package/schematics/{components → ui}/files/textarea/index.ts +0 -0
  95. /package/schematics/{components → ui}/files/textarea/textarea.scss +0 -0
  96. /package/schematics/{components → ui}/files/textarea/textarea.spec.ts +0 -0
  97. /package/schematics/{components → ui}/files/textarea/textarea.ts +0 -0
  98. /package/schematics/{components → ui}/schema.js +0 -0
@@ -0,0 +1,171 @@
1
+ import { Component, provideZonelessChangeDetection, TemplateRef, ViewChild } from '@angular/core';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { ZenPopover } from './popover';
6
+
7
+ @Component({
8
+ template: `
9
+ <button [zenPopover]="content" id="trigger-btn" zenPopoverPlacement="bottom">Trigger</button>
10
+
11
+ <ng-template #content>Popover content</ng-template>
12
+ `,
13
+ standalone: true,
14
+ imports: [ZenPopover],
15
+ })
16
+ class TemplateBottomComponent {
17
+ @ViewChild('content', { static: true }) content!: TemplateRef<unknown>;
18
+ }
19
+
20
+ @Component({
21
+ template: `
22
+ <button [zenPopover]="content" id="trigger-btn" zenPopoverPlacement="left">Trigger</button>
23
+
24
+ <ng-template #content>Popover content</ng-template>
25
+ `,
26
+ standalone: true,
27
+ imports: [ZenPopover],
28
+ })
29
+ class TemplateLeftComponent {
30
+ @ViewChild('content', { static: true }) content!: TemplateRef<unknown>;
31
+ }
32
+
33
+ @Component({
34
+ template: `
35
+ <button [zenPopover]="'String content'" id="trigger-btn">Trigger</button>
36
+ `,
37
+ standalone: true,
38
+ imports: [ZenPopover],
39
+ })
40
+ class StringContentComponent {}
41
+
42
+ function getPopoverHostEl(fixture: ComponentFixture<unknown>): HTMLElement | null {
43
+ return fixture.nativeElement.querySelector('[popover="auto"]') as HTMLElement | null;
44
+ }
45
+
46
+ describe('ZenPopover', () => {
47
+ beforeEach(() => {
48
+ HTMLElement.prototype.showPopover = vi.fn();
49
+ });
50
+
51
+ describe('TemplateRef content', () => {
52
+ beforeEach(async () => {
53
+ await TestBed.configureTestingModule({
54
+ imports: [TemplateBottomComponent],
55
+ providers: [provideZonelessChangeDetection()],
56
+ }).compileComponents();
57
+ });
58
+
59
+ it('does not create another host on second click while open', () => {
60
+ const fixture = TestBed.createComponent(TemplateBottomComponent);
61
+ fixture.detectChanges();
62
+
63
+ const trigger = fixture.nativeElement.querySelector('#trigger-btn') as HTMLButtonElement;
64
+
65
+ trigger.click();
66
+ fixture.detectChanges();
67
+
68
+ const host1 = getPopoverHostEl(fixture)!;
69
+ expect(host1).toBeTruthy();
70
+
71
+ trigger.click();
72
+ fixture.detectChanges();
73
+
74
+ expect(fixture.nativeElement.querySelectorAll('[popover="auto"]').length).toBe(1);
75
+ expect(getPopoverHostEl(fixture)).toBe(host1);
76
+ });
77
+
78
+ it('creates host on click and calls showPopover()', () => {
79
+ const fixture = TestBed.createComponent(TemplateBottomComponent);
80
+ fixture.detectChanges();
81
+
82
+ fixture.nativeElement.querySelector('#trigger-btn').click();
83
+ fixture.detectChanges();
84
+
85
+ const host = getPopoverHostEl(fixture)!;
86
+ expect(host.showPopover).toHaveBeenCalledTimes(1);
87
+ });
88
+
89
+ it('renders TemplateRef content into host', () => {
90
+ const fixture = TestBed.createComponent(TemplateBottomComponent);
91
+ fixture.detectChanges();
92
+
93
+ fixture.nativeElement.querySelector('#trigger-btn').click();
94
+ fixture.detectChanges();
95
+
96
+ expect(getPopoverHostEl(fixture)!.textContent).toContain('Popover content');
97
+ });
98
+
99
+ it('passes placement to host as data-placement (bottom)', () => {
100
+ const fixture = TestBed.createComponent(TemplateBottomComponent);
101
+ fixture.detectChanges();
102
+
103
+ fixture.nativeElement.querySelector('#trigger-btn').click();
104
+ fixture.detectChanges();
105
+
106
+ expect(getPopoverHostEl(fixture)!.getAttribute('data-placement')).toBe('bottom');
107
+ });
108
+
109
+ it('sets anchor styles on trigger and host', () => {
110
+ const fixture = TestBed.createComponent(TemplateBottomComponent);
111
+ fixture.detectChanges();
112
+
113
+ const trigger = fixture.nativeElement.querySelector('#trigger-btn') as HTMLButtonElement;
114
+ trigger.click();
115
+ fixture.detectChanges();
116
+
117
+ const host = getPopoverHostEl(fixture)!;
118
+
119
+ expect(trigger.style.getPropertyValue('anchor-name')).toMatch(/^--anchor-/);
120
+ expect(host.style.getPropertyValue('position-anchor')).toBe(trigger.style.getPropertyValue('anchor-name'));
121
+ });
122
+
123
+ it('destroys host when beforetoggle closes', () => {
124
+ const fixture = TestBed.createComponent(TemplateBottomComponent);
125
+ fixture.detectChanges();
126
+
127
+ fixture.nativeElement.querySelector('#trigger-btn').click();
128
+ fixture.detectChanges();
129
+
130
+ const host = getPopoverHostEl(fixture)!;
131
+
132
+ const ev = new Event('beforetoggle');
133
+ Object.defineProperty(ev, 'newState', { value: 'closed' });
134
+
135
+ host.dispatchEvent(ev);
136
+ fixture.detectChanges();
137
+
138
+ expect(getPopoverHostEl(fixture)).toBeNull();
139
+ });
140
+ });
141
+
142
+ it('reflects placement as data-placement (left)', async () => {
143
+ await TestBed.configureTestingModule({
144
+ imports: [TemplateLeftComponent],
145
+ providers: [provideZonelessChangeDetection()],
146
+ }).compileComponents();
147
+
148
+ const fixture = TestBed.createComponent(TemplateLeftComponent);
149
+ fixture.detectChanges();
150
+
151
+ fixture.nativeElement.querySelector('#trigger-btn').click();
152
+ fixture.detectChanges();
153
+
154
+ expect(getPopoverHostEl(fixture)!.getAttribute('data-placement')).toBe('left');
155
+ });
156
+
157
+ it('renders string content as text node in host', async () => {
158
+ await TestBed.configureTestingModule({
159
+ imports: [StringContentComponent],
160
+ providers: [provideZonelessChangeDetection()],
161
+ }).compileComponents();
162
+
163
+ const fixture = TestBed.createComponent(StringContentComponent);
164
+ fixture.detectChanges();
165
+
166
+ fixture.nativeElement.querySelector('#trigger-btn').click();
167
+ fixture.detectChanges();
168
+
169
+ expect(getPopoverHostEl(fixture)!.textContent).toContain('String content');
170
+ });
171
+ });
@@ -0,0 +1,66 @@
1
+ import { argsToTemplate, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
2
+
3
+ import { ZenButton } from '../button';
4
+ import { ZenPopover } from './popover';
5
+ import type { PopoverPlacement } from './popover-positions.type';
6
+
7
+ type Story = StoryObj<ZenPopover>;
8
+
9
+ const meta = {
10
+ title: 'Ui/Popover',
11
+ component: ZenPopover,
12
+ decorators: [
13
+ moduleMetadata({
14
+ imports: [ZenButton, ZenPopover],
15
+ }),
16
+ ],
17
+ argTypes: {
18
+ placement: {
19
+ name: 'zenPopoverPlacement',
20
+ control: 'select',
21
+ options: [
22
+ 'top',
23
+ 'top-start',
24
+ 'top-end',
25
+ 'bottom',
26
+ 'bottom-start',
27
+ 'bottom-end',
28
+ 'left',
29
+ 'left-start',
30
+ 'left-end',
31
+ 'right',
32
+ 'right-start',
33
+ 'right-end',
34
+ ] satisfies PopoverPlacement[],
35
+ table: {
36
+ category: 'inputs',
37
+ type: {
38
+ summary: 'string',
39
+ },
40
+ defaultValue: {
41
+ summary: 'top' satisfies PopoverPlacement,
42
+ },
43
+ },
44
+ },
45
+ id: { control: 'text', table: { defaultValue: { summary: 'zen-popover-*X*' } } },
46
+ content: { name: 'zenPopover', control: 'text' },
47
+ },
48
+ args: {
49
+ placement: 'top',
50
+ id: 'zen-popover-story',
51
+ content: 'This is a popover',
52
+ },
53
+ } satisfies Meta<ZenPopover>;
54
+
55
+ export default meta;
56
+
57
+ export const Default: Story = {
58
+ render: ({ content, placement, ...args }) => ({
59
+ props: args,
60
+ template: `
61
+ <button zen-btn [zenPopover]="'${content as string}'" [zenPopoverPlacement]="'${placement}'" ${argsToTemplate(args)}>
62
+ Toggle
63
+ </button>
64
+ `,
65
+ }),
66
+ };
@@ -0,0 +1,130 @@
1
+ import {
2
+ ComponentRef,
3
+ Directive,
4
+ effect,
5
+ ElementRef,
6
+ EmbeddedViewRef,
7
+ inject,
8
+ input,
9
+ Renderer2,
10
+ TemplateRef,
11
+ ViewContainerRef,
12
+ } from '@angular/core';
13
+
14
+ import { ZenPopoverHost } from './host/popover-host';
15
+ import { PopoverPlacement } from './popover-positions.type';
16
+
17
+ /**
18
+ * ZenPopover is a reusable popover directive built on the native Popover API.
19
+ * It provides a consistent, themeable popover surface with CSS-based positioning,
20
+ * animations, and multiple trigger modes.
21
+ *
22
+ * ### Limitations (arrow)
23
+ * ZenPopover may use CSS fallback positioning (e.g. via `position-try-fallbacks`) to keep the
24
+ * popover inside the viewport. Today CSS doesn’t provide a stable, cross-browser way to detect
25
+ * which fallback placement was ultimately chosen, so an “arrow” implemented with `::before/::after`
26
+ * can’t be reliably synchronized with the final placement without extra JavaScript.
27
+ * A CSS-only solution becomes possible with Anchored Container Queries (e.g. `container-type: anchored`
28
+ * and `@container anchored(fallback: ...)`) where supported.
29
+ *
30
+ * @see https://developer.chrome.com/blog/anchored-container-queries
31
+ *
32
+ * ### CSS Custom Properties
33
+ * You can customize the popover using CSS custom properties (see component styles/docs).
34
+ *
35
+ * @author Konrad Stępień
36
+ * @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
37
+ * @see [GitHub](https://github.com/kstepien3/ng-zen)
38
+ * @see [MDN Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)
39
+ */
40
+
41
+ type HostProperties = keyof ZenPopoverHost;
42
+
43
+ @Directive({
44
+ selector: '[zenPopover]',
45
+ standalone: true,
46
+ host: {
47
+ '(click)': 'togglePopover()',
48
+ },
49
+ })
50
+ export class ZenPopover {
51
+ private static uniqueId = 0;
52
+
53
+ /** Content to display inside the popover. Can be a `string` or a `TemplateRef`.*/
54
+ readonly content = input.required<TemplateRef<unknown> | string>({ alias: 'zenPopover' });
55
+ /** Placement of the popover relative to the trigger element. Defaults to `top`.*/
56
+ readonly placement = input<PopoverPlacement>('top', { alias: 'zenPopoverPlacement' });
57
+ /** The HTML id attribute is used to specify a unique id for an HTML element.*/
58
+ readonly id = input<string>(`zen-popover-${ZenPopover.uniqueId++}`);
59
+
60
+ private hostRef: ComponentRef<ZenPopoverHost> | null = null;
61
+ private viewRef: EmbeddedViewRef<unknown> | null = null;
62
+
63
+ private unlistenToggle: (() => void) | null = null;
64
+
65
+ private readonly anchorName = `--anchor-${this.id()}`;
66
+
67
+ private readonly vcr = inject(ViewContainerRef);
68
+ private readonly renderer = inject(Renderer2);
69
+ private readonly triggerEl = inject(ElementRef).nativeElement;
70
+
71
+ constructor() {
72
+ effect(onCleanup => {
73
+ onCleanup(() => {
74
+ this.destroyPopover();
75
+ });
76
+ });
77
+ }
78
+
79
+ protected togglePopover(): void {
80
+ if (this.hostRef) {
81
+ return;
82
+ }
83
+
84
+ const popoverEl = this.createHost();
85
+ this.renderContent(popoverEl);
86
+ this.unlistenToggle = this.renderer.listen(popoverEl, 'beforetoggle', (event: ToggleEvent) => {
87
+ if (event.newState === 'closed') {
88
+ this.destroyPopover();
89
+ }
90
+ });
91
+
92
+ popoverEl.showPopover();
93
+ }
94
+
95
+ private createHost(): HTMLElement {
96
+ this.renderer.setStyle(this.triggerEl, 'anchor-name', this.anchorName);
97
+
98
+ this.hostRef = this.vcr.createComponent(ZenPopoverHost);
99
+ this.hostRef.setInput('id' satisfies HostProperties, this.id());
100
+ this.hostRef.setInput('placement' satisfies HostProperties, this.placement());
101
+
102
+ const popoverEl = this.hostRef.location.nativeElement as HTMLElement;
103
+ this.renderer.setStyle(popoverEl, 'position-anchor', this.anchorName);
104
+
105
+ return popoverEl;
106
+ }
107
+
108
+ private renderContent(popoverEl: HTMLElement) {
109
+ const contentValue = this.content();
110
+ if (contentValue instanceof TemplateRef) {
111
+ this.viewRef = contentValue.createEmbeddedView({});
112
+ this.renderer.appendChild(popoverEl, this.viewRef.rootNodes[0]);
113
+ } else {
114
+ this.renderer.appendChild(popoverEl, this.renderer.createText(contentValue));
115
+ }
116
+ }
117
+
118
+ private destroyPopover() {
119
+ if (this.unlistenToggle) {
120
+ this.unlistenToggle();
121
+ this.unlistenToggle = null;
122
+ }
123
+
124
+ this.viewRef?.destroy();
125
+ this.hostRef?.destroy();
126
+ this.viewRef = null;
127
+ this.hostRef = null;
128
+ this.vcr.clear();
129
+ }
130
+ }
@@ -10,7 +10,7 @@ interface StoryParams {
10
10
  type Options = ZenRadio & StoryParams;
11
11
 
12
12
  export default {
13
- title: 'Components/Radio',
13
+ title: 'Ui/Radio',
14
14
  component: ZenRadio,
15
15
  argTypes: {
16
16
  value: {
@@ -11,7 +11,7 @@ interface StoryParams {
11
11
  type Options = ZenSkeleton & StoryParams;
12
12
 
13
13
  export default {
14
- title: 'Components/Skeleton',
14
+ title: 'Ui/Skeleton',
15
15
  component: ZenSkeleton,
16
16
  argTypes: {
17
17
  rounded: {
@@ -5,7 +5,7 @@ import { ZenSwitch } from './switch';
5
5
  type Options = ZenSwitch;
6
6
 
7
7
  export default {
8
- title: 'Components/Switch',
8
+ title: 'Ui/Switch',
9
9
  component: ZenSwitch,
10
10
  argTypes: {
11
11
  value: {
@@ -12,7 +12,7 @@ interface StoryParams {
12
12
  type Options = ZenTextarea & StoryParams;
13
13
 
14
14
  export default {
15
- title: 'Components/Textarea',
15
+ title: 'Ui/Textarea',
16
16
  component: ZenTextarea,
17
17
  args: {
18
18
  content: '',
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uiGenerator = uiGenerator;
4
+ const core_1 = require("@angular-devkit/core");
5
+ const schematics_1 = require("@angular-devkit/schematics");
6
+ const workspace_1 = require("@schematics/angular/utility/workspace");
7
+ const utils_1 = require("../../utils");
8
+ const DEFAULT_GENERATION_PATH = 'ui'; // src/app/ui
9
+ function uiGenerator({ ui, project, ...options }) {
10
+ return async (tree) => {
11
+ const workspace = await (0, workspace_1.getWorkspace)(tree);
12
+ const projectName = project || workspace.extensions['defaultProject'];
13
+ const projectObj = workspace.projects.get(projectName);
14
+ if (!projectObj) {
15
+ throw new Error(`Project "${projectName}" not found in workspace.`);
16
+ }
17
+ if (options.path === undefined) {
18
+ options.path = ((0, workspace_1.buildDefaultPath)(projectObj) + '/' + DEFAULT_GENERATION_PATH);
19
+ }
20
+ const workingDirectory = (0, core_1.normalize)((0, core_1.join)(options.currentDirectory, options.path));
21
+ return (0, schematics_1.chain)([
22
+ ...(0, utils_1.applyFileTemplateUtil)(ui, { ...options, path: workingDirectory }),
23
+ (0, schematics_1.schematic)('dependency-manager', {}),
24
+ ]);
25
+ };
26
+ }
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/index.ts"],"names":[],"mappings":";;AASA,kCAqBC;AA9BD,+CAA6D;AAC7D,2DAA0E;AAC1E,qEAAuF;AAEvF,uCAAoD;AAGpD,MAAM,uBAAuB,GAAG,IAAI,CAAC,CAAC,aAAa;AAEnD,SAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,EAAa;IAChE,OAAO,KAAK,EAAE,IAAU,EAAE,EAAE;QAC1B,MAAM,SAAS,GAAG,MAAM,IAAA,wBAAY,EAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,WAAW,GAAG,OAAO,IAAK,SAAS,CAAC,UAAU,CAAC,gBAAgB,CAAY,CAAC;QAClF,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEvD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,YAAY,WAAW,2BAA2B,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,IAAA,4BAAgB,EAAC,UAAU,CAAC,GAAG,GAAG,GAAG,uBAAuB,CAAS,CAAC;QACxF,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAA,gBAAS,EAAC,IAAA,WAAI,EAAC,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAEjF,OAAO,IAAA,kBAAK,EAAC;YACX,GAAG,IAAA,6BAAqB,EAAC,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;YACpE,IAAA,sBAAS,EAAC,oBAAoB,EAAE,EAAE,CAAC;SACpC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { join, normalize, Path } from '@angular-devkit/core';\nimport { chain, Rule, schematic, Tree } from '@angular-devkit/schematics';\nimport { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';\n\nimport { applyFileTemplateUtil } from '../../utils';\nimport { Schema as UiOptions } from './schema';\n\nconst DEFAULT_GENERATION_PATH = 'ui'; // src/app/ui\n\nexport function uiGenerator({ ui, project, ...options }: UiOptions): Rule {\n return async (tree: Tree) => {\n const workspace = await getWorkspace(tree);\n const projectName = project || (workspace.extensions['defaultProject'] as string);\n const projectObj = workspace.projects.get(projectName);\n\n if (!projectObj) {\n throw new Error(`Project \"${projectName}\" not found in workspace.`);\n }\n\n if (options.path === undefined) {\n options.path = (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;\n }\n\n const workingDirectory = normalize(join(options.currentDirectory, options.path));\n\n return chain([\n ...applyFileTemplateUtil(ui, { ...options, path: workingDirectory }),\n schematic('dependency-manager', {}),\n ]);\n };\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { join, normalize, Path } from '@angular-devkit/core';
2
+ import { chain, Rule, schematic, Tree } from '@angular-devkit/schematics';
3
+ import { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';
4
+
5
+ import { applyFileTemplateUtil } from '../../utils';
6
+ import { Schema as UiOptions } from './schema';
7
+
8
+ const DEFAULT_GENERATION_PATH = 'ui'; // src/app/ui
9
+
10
+ export function uiGenerator({ ui, project, ...options }: UiOptions): Rule {
11
+ return async (tree: Tree) => {
12
+ const workspace = await getWorkspace(tree);
13
+ const projectName = project || (workspace.extensions['defaultProject'] as string);
14
+ const projectObj = workspace.projects.get(projectName);
15
+
16
+ if (!projectObj) {
17
+ throw new Error(`Project "${projectName}" not found in workspace.`);
18
+ }
19
+
20
+ if (options.path === undefined) {
21
+ options.path = (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;
22
+ }
23
+
24
+ const workingDirectory = normalize(join(options.currentDirectory, options.path));
25
+
26
+ return chain([
27
+ ...applyFileTemplateUtil(ui, { ...options, path: workingDirectory }),
28
+ schematic('dependency-manager', {}),
29
+ ]);
30
+ };
31
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/schema.ts"],"names":[],"mappings":"","sourcesContent":["import { GeneratorSchemaBase } from '../../types';\n\nexport type UiType =\n | 'alert'\n | 'avatar'\n | 'button'\n | 'checkbox'\n | 'divider'\n | 'form-control'\n | 'icon'\n | 'input'\n | 'popover'\n | 'radio'\n | 'skeleton'\n | 'switch'\n | 'textarea';\n\nexport interface Schema extends GeneratorSchemaBase {\n ui: UiType[];\n}\n"]}
@@ -16,15 +16,23 @@
16
16
  "readOnly": true
17
17
  },
18
18
  "path": {
19
- "description": "Relative subpath to append to rootPath",
19
+ "description": "The path at which to create the component file, relative to the current workspace. If not provided, components will be generated in the 'ui' folder within the project's source directory (e.g., src/app/ui).",
20
20
  "type": "string",
21
+ "format": "path",
21
22
  "$default": {
22
23
  "$source": "argv",
23
24
  "index": 0
24
25
  }
25
26
  },
26
- "components": {
27
- "description": "Select components to generate",
27
+ "project": {
28
+ "description": "The name of the project",
29
+ "type": "string",
30
+ "$default": {
31
+ "$source": "projectName"
32
+ }
33
+ },
34
+ "ui": {
35
+ "description": "Select elements to generate",
28
36
  "type": "array",
29
37
  "items": {
30
38
  "type": "string",
@@ -37,6 +45,7 @@
37
45
  "form-control",
38
46
  "icon",
39
47
  "input",
48
+ "popover",
40
49
  "radio",
41
50
  "skeleton",
42
51
  "switch",
@@ -45,13 +54,13 @@
45
54
  },
46
55
  "multiselect": true,
47
56
  "minItems": 1,
48
- "x-prompt": "Which component(s) should be generated?"
57
+ "x-prompt": "Which ui elements should be generated?"
49
58
  },
50
59
  "stories": {
51
- "description": "Include component's stories for better documentation and testing",
60
+ "description": "Include stories for better documentation and testing",
52
61
  "type": "boolean",
53
62
  "default": false
54
63
  }
55
64
  },
56
- "required": ["components"]
65
+ "required": ["ui"]
57
66
  }
@@ -1,6 +1,6 @@
1
1
  import { GeneratorSchemaBase } from '../../types';
2
2
 
3
- export type ComponentType =
3
+ export type UiType =
4
4
  | 'alert'
5
5
  | 'avatar'
6
6
  | 'button'
@@ -9,11 +9,12 @@ export type ComponentType =
9
9
  | 'form-control'
10
10
  | 'icon'
11
11
  | 'input'
12
+ | 'popover'
12
13
  | 'radio'
13
14
  | 'skeleton'
14
15
  | 'switch'
15
16
  | 'textarea';
16
17
 
17
18
  export interface Schema extends GeneratorSchemaBase {
18
- components: ComponentType[];
19
+ ui: UiType[];
19
20
  }
@@ -8,14 +8,14 @@ Generated by `@ng-zen/cli` on <%= localeDate %>
8
8
 
9
9
  <%= classify(name) %> component was generated by
10
10
  ```bash
11
- ng generate @ng-zen/cli:component --components <%= name %>
11
+ ng generate @ng-zen/cli:ui --ui <%= name %>
12
12
  ```
13
13
 
14
14
  This component is part of a larger set of UI components aimed at enhancing and streamlining the development process in Angular applications.
15
15
 
16
16
  More information you can find here
17
17
  ```bash
18
- ng generate @ng-zen/cli:component --help
18
+ ng generate @ng-zen/cli:ui --help
19
19
  ```
20
20
 
21
21
  ## Installation
@@ -27,7 +27,7 @@ ng add @ng-zen/cli
27
27
 
28
28
  then you can generate components via
29
29
  ```bash
30
- ng generate @ng-zen/cli:component
30
+ ng generate @ng-zen/cli:ui
31
31
  ```
32
32
 
33
33
 
@@ -1 +1 @@
1
- {"version":3,"file":"selected-elements.js","sourceRoot":"","sources":["../../../../src/services/selected-elements.ts"],"names":[],"mappings":";;;AAEA,qFAAqF;AACxE,QAAA,gBAAgB,GAAoB,EAAE,CAAC","sourcesContent":["import { ComponentType } from '../schematics/components/schema';\n\n// add selected elements to this array, then values are available in other schematics\nexport const selectedElements: ComponentType[] = [];\n"]}
1
+ {"version":3,"file":"selected-elements.js","sourceRoot":"","sources":["../../../../src/services/selected-elements.ts"],"names":[],"mappings":";;;AAEA,qFAAqF;AACxE,QAAA,gBAAgB,GAAa,EAAE,CAAC","sourcesContent":["import { UiType } from '../schematics/ui/schema';\n\n// add selected elements to this array, then values are available in other schematics\nexport const selectedElements: UiType[] = [];\n"]}
@@ -1,4 +1,4 @@
1
- import { ComponentType } from '../schematics/components/schema';
1
+ import { UiType } from '../schematics/ui/schema';
2
2
 
3
3
  // add selected elements to this array, then values are available in other schematics
4
- export const selectedElements: ComponentType[] = [];
4
+ export const selectedElements: UiType[] = [];
@@ -1 +1 @@
1
- {"version":3,"file":"files-config.js","sourceRoot":"","sources":["../../../../src/types/files-config.ts"],"names":[],"mappings":"","sourcesContent":["import { NodeDependencyType } from '@schematics/angular/utility/dependencies';\n\nimport { ComponentType } from '../schematics/components/schema';\n\ntype Kind = Record<NodeDependencyType, Record<string, string>>;\n\nexport type FilesConfig = Record<ComponentType, Partial<Kind>>;\n"]}
1
+ {"version":3,"file":"files-config.js","sourceRoot":"","sources":["../../../../src/types/files-config.ts"],"names":[],"mappings":"","sourcesContent":["import { NodeDependencyType } from '@schematics/angular/utility/dependencies';\n\nimport { UiType } from '../schematics/ui/schema';\n\ntype Kind = Record<NodeDependencyType, Record<string, string>>;\n\nexport type FilesConfig = Record<UiType, Partial<Kind>>;\n"]}
@@ -1,7 +1,7 @@
1
1
  import { NodeDependencyType } from '@schematics/angular/utility/dependencies';
2
2
 
3
- import { ComponentType } from '../schematics/components/schema';
3
+ import { UiType } from '../schematics/ui/schema';
4
4
 
5
5
  type Kind = Record<NodeDependencyType, Record<string, string>>;
6
6
 
7
- export type FilesConfig = Record<ComponentType, Partial<Kind>>;
7
+ export type FilesConfig = Record<UiType, Partial<Kind>>;
@@ -1 +1 @@
1
- {"version":3,"file":"generator-schema-base.js","sourceRoot":"","sources":["../../../../src/types/generator-schema-base.ts"],"names":[],"mappings":"","sourcesContent":["import { Path } from '@angular-devkit/core';\n\nexport interface GeneratorSchemaBase {\n currentDirectory: Path;\n path: Path;\n stories: boolean;\n}\n"]}
1
+ {"version":3,"file":"generator-schema-base.js","sourceRoot":"","sources":["../../../../src/types/generator-schema-base.ts"],"names":[],"mappings":"","sourcesContent":["import { Path } from '@angular-devkit/core';\n\nexport interface GeneratorSchemaBase {\n currentDirectory: Path;\n path?: Path;\n stories: boolean;\n project?: string;\n}\n"]}
@@ -2,6 +2,7 @@ import { Path } from '@angular-devkit/core';
2
2
 
3
3
  export interface GeneratorSchemaBase {
4
4
  currentDirectory: Path;
5
- path: Path;
5
+ path?: Path;
6
6
  stories: boolean;
7
+ project?: string;
7
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schematics-folder.js","sourceRoot":"","sources":["../../../../src/types/schematics-folder.ts"],"names":[],"mappings":"","sourcesContent":["import { ComponentType } from '../schematics/components/schema';\n\nexport type SchematicsFolder = ComponentType;\n"]}
1
+ {"version":3,"file":"schematics-folder.js","sourceRoot":"","sources":["../../../../src/types/schematics-folder.ts"],"names":[],"mappings":"","sourcesContent":["import { UiType } from '../schematics/ui/schema';\n\nexport type SchematicsFolder = UiType;\n"]}
@@ -1,3 +1,3 @@
1
- import { ComponentType } from '../schematics/components/schema';
1
+ import { UiType } from '../schematics/ui/schema';
2
2
 
3
- export type SchematicsFolder = ComponentType;
3
+ export type SchematicsFolder = UiType;
@@ -1 +1 @@
1
- {"version":3,"file":"apply-file-template.util.js","sourceRoot":"","sources":["../../../../src/utils/apply-file-template.util.ts"],"names":[],"mappings":";;AAkBA,sDASC;AA3BD,+CAA0D;AAC1D,2DAA8G;AAE9G,qEAAiE;AAGjE,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAAC;IACpE,IAAA,2BAAc,EAAC;QACb,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE;QACvC,GAAG,cAAO;KACX,CAAC;IACF,IAAA,iBAAI,EAAC,IAAA,gBAAS,EAAC,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;AACzE,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,IAAA,mBAAM,EAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AAE9G,SAAgB,qBAAqB,CAAC,OAA2B,EAAE,MAA2B;IAC5F,oCAAgB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAEvD,MAAM,YAAY,GAAG,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,WAAW,MAAM,EAAE,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;QAEjG,OAAO,IAAA,kBAAK,EAAC,CAAC,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAS,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { normalize, strings } from '@angular-devkit/core';\nimport { apply, applyTemplates, chain, filter, mergeWith, move, Rule, url } from '@angular-devkit/schematics';\n\nimport { selectedElements } from '../services/selected-elements';\nimport { GeneratorSchemaBase, SchematicsFolder } from '../types';\n\nconst createTemplateRules = (folder: string, path: string): Rule[] => [\n applyTemplates({\n name: folder,\n localeDate: new Date().toLocaleString(),\n ...strings,\n }),\n move(normalize(`${path}/${folder}`)),\n];\n\nconst getTemplates = (rules: Rule[]) => apply(url(`./templates`), rules);\nconst includeStories = (include: boolean) => filter(filePath => include || !filePath.endsWith('.stories.ts'));\n\nexport function applyFileTemplateUtil(folders: SchematicsFolder[], config: GeneratorSchemaBase): Rule[] {\n selectedElements.push(...folders);\n return folders.map(folder => {\n const RULES = createTemplateRules(folder, config.path);\n\n const folderSource = apply(url(`./files/${folder}`), [includeStories(config.stories), ...RULES]);\n\n return chain([folderSource, getTemplates(RULES)].map(mergeWith));\n });\n}\n"]}
1
+ {"version":3,"file":"apply-file-template.util.js","sourceRoot":"","sources":["../../../../src/utils/apply-file-template.util.ts"],"names":[],"mappings":";;AAkBA,sDASC;AA3BD,+CAA0D;AAC1D,2DAA8G;AAE9G,qEAAiE;AAGjE,MAAM,mBAAmB,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAAC;IACpE,IAAA,2BAAc,EAAC;QACb,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE;QACvC,GAAG,cAAO;KACX,CAAC;IACF,IAAA,iBAAI,EAAC,IAAA,gBAAS,EAAC,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;AACzE,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,IAAA,mBAAM,EAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AAE9G,SAAgB,qBAAqB,CAAC,OAA2B,EAAE,MAA2B;IAC5F,oCAAgB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAc,CAAC,CAAC;QAEjE,MAAM,YAAY,GAAG,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,WAAW,MAAM,EAAE,CAAC,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;QAEjG,OAAO,IAAA,kBAAK,EAAC,CAAC,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAS,CAAC,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { normalize, strings } from '@angular-devkit/core';\nimport { apply, applyTemplates, chain, filter, mergeWith, move, Rule, url } from '@angular-devkit/schematics';\n\nimport { selectedElements } from '../services/selected-elements';\nimport { GeneratorSchemaBase, SchematicsFolder } from '../types';\n\nconst createTemplateRules = (folder: string, path: string): Rule[] => [\n applyTemplates({\n name: folder,\n localeDate: new Date().toLocaleString(),\n ...strings,\n }),\n move(normalize(`${path}/${folder}`)),\n];\n\nconst getTemplates = (rules: Rule[]) => apply(url(`./templates`), rules);\nconst includeStories = (include: boolean) => filter(filePath => include || !filePath.endsWith('.stories.ts'));\n\nexport function applyFileTemplateUtil(folders: SchematicsFolder[], config: GeneratorSchemaBase): Rule[] {\n selectedElements.push(...folders);\n return folders.map(folder => {\n const RULES = createTemplateRules(folder, config.path as string);\n\n const folderSource = apply(url(`./files/${folder}`), [includeStories(config.stories), ...RULES]);\n\n return chain([folderSource, getTemplates(RULES)].map(mergeWith));\n });\n}\n"]}
@@ -19,7 +19,7 @@ const includeStories = (include: boolean) => filter(filePath => include || !file
19
19
  export function applyFileTemplateUtil(folders: SchematicsFolder[], config: GeneratorSchemaBase): Rule[] {
20
20
  selectedElements.push(...folders);
21
21
  return folders.map(folder => {
22
- const RULES = createTemplateRules(folder, config.path);
22
+ const RULES = createTemplateRules(folder, config.path as string);
23
23
 
24
24
  const folderSource = apply(url(`./files/${folder}`), [includeStories(config.stories), ...RULES]);
25
25