@ng-zen/cli 22.4.1 → 22.5.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -3
- package/eslint-config/index.js +130 -0
- package/package.json +14 -1
- package/schematics/ng-add/eslint-config.js +87 -0
- package/schematics/ng-add/eslint-config.js.map +1 -0
- package/schematics/ng-add/eslint-config.ts +98 -0
- package/schematics/ng-add/icon-library/icon-library.js +2 -1
- package/schematics/ng-add/icon-library/icon-library.js.map +1 -1
- package/schematics/ng-add/icon-library/icon-library.ts +2 -1
- package/schematics/ng-add/index.js +14 -2
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/index.ts +14 -3
- package/schematics/ng-add/schema.js.map +1 -1
- package/schematics/ng-add/schema.json +5 -0
- package/schematics/ng-add/schema.ts +1 -0
- package/schematics/ui/files/avatar/avatar.ts +1 -1
- package/schematics/ui/files/checkbox/checkbox.stories.ts +1 -1
- package/schematics/ui/files/checkbox/checkbox.ts +2 -2
- package/schematics/ui/files/dialog/dialog.service.stories.ts +3 -3
- package/schematics/ui/files/dialog/dialog.service.ts +2 -2
- package/schematics/ui/files/dialog/dialog.spec.ts +6 -6
- package/schematics/ui/files/dialog/dialog.ts +1 -1
- package/schematics/ui/files/form-control/form-control.ts +5 -5
- package/schematics/ui/files/icon/icon.ts +5 -5
- package/schematics/ui/files/input/input.stories.ts +4 -4
- package/schematics/ui/files/input/input.ts +1 -1
- package/schematics/ui/files/pin/index.ts +3 -0
- package/schematics/ui/files/pin/pin-item.spec.ts +151 -0
- package/schematics/ui/files/pin/pin-item.ts +115 -0
- package/schematics/ui/files/pin/pin.spec.ts +153 -0
- package/schematics/ui/files/pin/pin.stories.ts +164 -0
- package/schematics/ui/files/pin/pin.ts +49 -0
- package/schematics/ui/files/pin/pin.types.ts +15 -0
- package/schematics/ui/files/popover/popover.spec.ts +10 -7
- package/schematics/ui/files/popover/popover.stories.ts +1 -1
- package/schematics/ui/files/radio/radio.stories.ts +3 -3
- package/schematics/ui/files/radio/radio.ts +3 -3
- package/schematics/ui/files/switch/switch.stories.ts +1 -1
- package/schematics/ui/index.js +2 -4
- package/schematics/ui/index.js.map +1 -1
- package/schematics/ui/index.ts +2 -4
- package/schematics/ui/schema.js.map +1 -1
- package/schematics/ui/schema.json +1 -0
- package/schematics/ui/schema.ts +1 -0
- package/utils/apply-file-template.util.js.map +1 -1
- package/utils/apply-file-template.util.ts +3 -3
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { BellIcon, ChatIcon, RotateLeft02Icon } from '@hugeicons/core-free-icons';
|
|
2
|
+
import { Args, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
|
|
3
|
+
|
|
4
|
+
import { ZenAvatar } from '../avatar';
|
|
5
|
+
import { ZenBadge } from '../badge';
|
|
6
|
+
import { ZenButton } from '../button';
|
|
7
|
+
import { ZenIcon } from '../icon';
|
|
8
|
+
import { ZenPin } from './pin';
|
|
9
|
+
import type { PinPosition } from './pin.types';
|
|
10
|
+
import { ZenPinItem } from './pin-item';
|
|
11
|
+
|
|
12
|
+
interface WithBadgeArgs extends Args {
|
|
13
|
+
position: PinPosition;
|
|
14
|
+
offsetX: number;
|
|
15
|
+
offsetY: number;
|
|
16
|
+
flushed: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Story = StoryObj<ZenPin>;
|
|
20
|
+
|
|
21
|
+
const html = String.raw;
|
|
22
|
+
|
|
23
|
+
const meta: Meta = {
|
|
24
|
+
title: 'Ui/Pin',
|
|
25
|
+
component: ZenPin,
|
|
26
|
+
tags: ['autodocs'],
|
|
27
|
+
decorators: [
|
|
28
|
+
moduleMetadata({
|
|
29
|
+
imports: [ZenPin, ZenPinItem, ZenBadge, ZenButton, ZenIcon, ZenAvatar],
|
|
30
|
+
}),
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
export const WithBadge: StoryObj<WithBadgeArgs> = {
|
|
37
|
+
argTypes: {
|
|
38
|
+
offsetX: {
|
|
39
|
+
name: 'offsetX (px)',
|
|
40
|
+
description: 'Horizontal offset in pixels.',
|
|
41
|
+
control: { type: 'range', min: -50, max: 50, step: 1 },
|
|
42
|
+
table: {
|
|
43
|
+
category: 'story parameters',
|
|
44
|
+
type: { summary: 'number' },
|
|
45
|
+
defaultValue: { summary: '0' },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
offsetY: {
|
|
49
|
+
name: 'offsetY (px)',
|
|
50
|
+
description: 'Vertical offset in pixels.',
|
|
51
|
+
control: { type: 'range', min: -50, max: 50, step: 1 },
|
|
52
|
+
table: {
|
|
53
|
+
category: 'story parameters',
|
|
54
|
+
type: { summary: 'number' },
|
|
55
|
+
defaultValue: { summary: '0' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
flushed: {
|
|
59
|
+
description: 'Whether the pin sits flush at the edge without overlapping the anchor.',
|
|
60
|
+
control: { type: 'boolean' },
|
|
61
|
+
table: {
|
|
62
|
+
category: 'story parameters',
|
|
63
|
+
type: { summary: 'boolean' },
|
|
64
|
+
defaultValue: { summary: 'false' },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
position: {
|
|
68
|
+
name: 'zenPinPosition',
|
|
69
|
+
description: 'Position of the pin relative to the anchor element.',
|
|
70
|
+
control: 'select',
|
|
71
|
+
options: [
|
|
72
|
+
'top',
|
|
73
|
+
'bottom',
|
|
74
|
+
'center',
|
|
75
|
+
'left',
|
|
76
|
+
'right',
|
|
77
|
+
'top left',
|
|
78
|
+
'top right',
|
|
79
|
+
'top center',
|
|
80
|
+
'bottom left',
|
|
81
|
+
'bottom right',
|
|
82
|
+
'bottom center',
|
|
83
|
+
'center left',
|
|
84
|
+
'center right',
|
|
85
|
+
] satisfies PinPosition[],
|
|
86
|
+
table: {
|
|
87
|
+
category: 'inputs',
|
|
88
|
+
type: { summary: 'PinPosition' },
|
|
89
|
+
defaultValue: { summary: 'top right' },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
args: {
|
|
94
|
+
position: 'top right',
|
|
95
|
+
offsetX: 0,
|
|
96
|
+
offsetY: 0,
|
|
97
|
+
flushed: false,
|
|
98
|
+
},
|
|
99
|
+
render: args => {
|
|
100
|
+
return {
|
|
101
|
+
props: {
|
|
102
|
+
position: args.position,
|
|
103
|
+
flushed: args.flushed,
|
|
104
|
+
offsetValue: `${args.offsetY}px ${args.offsetX}px`,
|
|
105
|
+
},
|
|
106
|
+
template: html`
|
|
107
|
+
<div style="margin: 4rem;">
|
|
108
|
+
<button zen-button zenPin>
|
|
109
|
+
Notifications
|
|
110
|
+
<zen-badge
|
|
111
|
+
zenPinItem
|
|
112
|
+
color="danger"
|
|
113
|
+
variant="solid"
|
|
114
|
+
[zenPinPosition]="position"
|
|
115
|
+
[zenPinOffset]="offsetValue"
|
|
116
|
+
[zenPinFlush]="flushed"
|
|
117
|
+
>
|
|
118
|
+
3
|
|
119
|
+
</zen-badge>
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
`,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const WithIcons: Story = {
|
|
128
|
+
render: () => ({
|
|
129
|
+
props: { BellIcon, ChatIcon, RotateLeft02Icon },
|
|
130
|
+
template: html`
|
|
131
|
+
<div style="margin: 4rem; display: flex; gap: 1rem">
|
|
132
|
+
<zen-avatar zenPin src="https://github.com/kstepien3.png">
|
|
133
|
+
<zen-icon zenPinItem [icon]="ChatIcon" [strokeWidth]="2" [size]="18" />
|
|
134
|
+
</zen-avatar>
|
|
135
|
+
|
|
136
|
+
<zen-avatar src="https://github.com/kstepien3.png" zenPin>
|
|
137
|
+
<zen-icon
|
|
138
|
+
zenPinItem
|
|
139
|
+
zenPinPosition="bottom right"
|
|
140
|
+
zenPinFlush
|
|
141
|
+
zenPinOffset="-5px"
|
|
142
|
+
[icon]="RotateLeft02Icon"
|
|
143
|
+
[strokeWidth]="2"
|
|
144
|
+
[size]="24"
|
|
145
|
+
/>
|
|
146
|
+
</zen-avatar>
|
|
147
|
+
</div>
|
|
148
|
+
`,
|
|
149
|
+
}),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const WithImage: Story = {
|
|
153
|
+
render: () => ({
|
|
154
|
+
template: html`
|
|
155
|
+
<div style="margin: 4rem; display: flex; justify-content: center;">
|
|
156
|
+
<div zenPin style="display: flex">
|
|
157
|
+
<zen-badge zenPinItem color="success" zenPinPosition="top right">Random photo</zen-badge>
|
|
158
|
+
<zen-badge zenPinItem color="info" zenPinPosition="bottom right">Multiple pins</zen-badge>
|
|
159
|
+
<img src="https://picsum.photos/536/354" alt="photo" style="border-radius: 0.5rem;" />
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
`,
|
|
163
|
+
}),
|
|
164
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Directive, ElementRef, inject, Renderer2 } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ZenPin is an anchor-positioned overlay directive. It sets up the host element
|
|
5
|
+
* as a CSS anchor. Use `zenPinItem` on child elements to pin them as overlays.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
*
|
|
9
|
+
* ```html
|
|
10
|
+
* <!-- Pin a badge to a host element -->
|
|
11
|
+
* <button zen-button zenPin>
|
|
12
|
+
* Notifications
|
|
13
|
+
* <zen-badge zenPinItem zenPinPosition="top right" [zenPinOffset]="5px">3</zen-badge>
|
|
14
|
+
* </button>
|
|
15
|
+
*
|
|
16
|
+
* <!-- Multiple pins on the same host -->
|
|
17
|
+
* <button zen-button zenPin>
|
|
18
|
+
* Notifications
|
|
19
|
+
* <zen-badge zenPinItem zenPinPosition="top right" zenPinOverlap>3</zen-badge>
|
|
20
|
+
* <zen-icon zenPinItem zenPinPosition="bottom right" [icon]="bellIcon" [size]="16" />
|
|
21
|
+
* </button>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @author Konrad Stępień
|
|
25
|
+
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
26
|
+
* @see [GitHub](https://github.com/kstepien3/ng-zen)
|
|
27
|
+
* @see [CSS Anchor Positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning)
|
|
28
|
+
*/
|
|
29
|
+
@Directive({
|
|
30
|
+
selector: '[zenPin]',
|
|
31
|
+
})
|
|
32
|
+
export class ZenPin {
|
|
33
|
+
/** @ignore Counter used to generate unique anchor names. */
|
|
34
|
+
static pinId = 0;
|
|
35
|
+
|
|
36
|
+
/** @ignore The native host element used as the CSS anchor. */
|
|
37
|
+
readonly hostEl: HTMLElement;
|
|
38
|
+
|
|
39
|
+
/** @ignore Generated CSS anchor name referenced by ZenPinItem. */
|
|
40
|
+
readonly anchorName: string;
|
|
41
|
+
|
|
42
|
+
constructor() {
|
|
43
|
+
const el = inject(ElementRef);
|
|
44
|
+
const renderer = inject(Renderer2);
|
|
45
|
+
this.hostEl = el.nativeElement;
|
|
46
|
+
this.anchorName = `--zen-pin-${ZenPin.pinId++}`;
|
|
47
|
+
renderer.setStyle(this.hostEl, 'anchor-name', this.anchorName);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Vertical axis position relative to the host element. */
|
|
2
|
+
export type PinVerticalPosition = 'top' | 'bottom' | 'center';
|
|
3
|
+
|
|
4
|
+
/** Horizontal axis position relative to the host element. */
|
|
5
|
+
export type PinHorizontalPosition = 'left' | 'right' | 'center';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Position of the pinned overlay relative to its anchor.
|
|
9
|
+
* Accepts one or two values (e.g. `'top'`, `'left'`, `'top right'`, `'center left'`).
|
|
10
|
+
* Uses CSS `position-area` under the hood.
|
|
11
|
+
*/
|
|
12
|
+
export type PinPosition =
|
|
13
|
+
| `${PinVerticalPosition} ${PinHorizontalPosition}`
|
|
14
|
+
| PinVerticalPosition
|
|
15
|
+
| PinHorizontalPosition;
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import { Component, provideZonelessChangeDetection, TemplateRef,
|
|
1
|
+
import { Component, provideZonelessChangeDetection, TemplateRef, viewChild } from '@angular/core';
|
|
2
2
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
|
+
import { ZenButton } from '../button';
|
|
5
6
|
import { ZenPopover } from './popover';
|
|
6
7
|
|
|
7
8
|
@Component({
|
|
8
9
|
template: `
|
|
9
|
-
<button
|
|
10
|
+
<button id="trigger-btn" type="button" zen-button zenPopoverPlacement="bottom" [zenPopover]="content">
|
|
11
|
+
Trigger
|
|
12
|
+
</button>
|
|
10
13
|
|
|
11
14
|
<ng-template #content>Popover content</ng-template>
|
|
12
15
|
`,
|
|
13
16
|
standalone: true,
|
|
14
|
-
imports: [ZenPopover],
|
|
17
|
+
imports: [ZenPopover, ZenButton],
|
|
15
18
|
})
|
|
16
19
|
class TemplateBottomComponent {
|
|
17
|
-
|
|
20
|
+
readonly content = viewChild.required<TemplateRef<unknown>>('content');
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
@Component({
|
|
21
24
|
template: `
|
|
22
|
-
<button
|
|
25
|
+
<button id="trigger-btn" type="button" zenPopoverPlacement="left" [zenPopover]="content">Trigger</button>
|
|
23
26
|
|
|
24
27
|
<ng-template #content>Popover content</ng-template>
|
|
25
28
|
`,
|
|
@@ -27,12 +30,12 @@ class TemplateBottomComponent {
|
|
|
27
30
|
imports: [ZenPopover],
|
|
28
31
|
})
|
|
29
32
|
class TemplateLeftComponent {
|
|
30
|
-
|
|
33
|
+
readonly content = viewChild.required<TemplateRef<unknown>>('content');
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
@Component({
|
|
34
37
|
template: `
|
|
35
|
-
<button [zenPopover]="'String content'"
|
|
38
|
+
<button id="trigger-btn" type="button" [zenPopover]="'String content'">Trigger</button>
|
|
36
39
|
`,
|
|
37
40
|
standalone: true,
|
|
38
41
|
imports: [ZenPopover],
|
|
@@ -69,7 +69,7 @@ export const WithTemplate: Story = {
|
|
|
69
69
|
render: ({ placement, content }) => ({
|
|
70
70
|
template: `
|
|
71
71
|
<ng-template #popoverContent>
|
|
72
|
-
<i>${content}</i>
|
|
72
|
+
<i>${content as string}</i>
|
|
73
73
|
</ng-template>
|
|
74
74
|
<button zen-btn [zenPopover]="popoverContent" [zenPopoverPlacement]="'${placement}'">
|
|
75
75
|
Template
|
|
@@ -95,17 +95,17 @@ export const WithSignalForm: Story = {
|
|
|
95
95
|
<div style="display: flex; flex-direction: column; gap: 0.5rem">
|
|
96
96
|
<!-- eslint-disable-next-line @angular-eslint/template/label-has-associated-control -->
|
|
97
97
|
<label style="display: flex; align-items: center; gap: 0.25rem">
|
|
98
|
-
<zen-radio [formField]="form.color"
|
|
98
|
+
<zen-radio option="red" [formField]="form.color" />
|
|
99
99
|
Red
|
|
100
100
|
</label>
|
|
101
101
|
<!-- eslint-disable-next-line @angular-eslint/template/label-has-associated-control -->
|
|
102
102
|
<label style="display: flex; align-items: center; gap: 0.25rem">
|
|
103
|
-
<zen-radio [formField]="form.color"
|
|
103
|
+
<zen-radio option="green" [formField]="form.color" />
|
|
104
104
|
Green
|
|
105
105
|
</label>
|
|
106
106
|
<!-- eslint-disable-next-line @angular-eslint/template/label-has-associated-control -->
|
|
107
107
|
<label style="display: flex; align-items: center; gap: 0.25rem">
|
|
108
|
-
<zen-radio [formField]="form.color"
|
|
108
|
+
<zen-radio option="blue" [formField]="form.color" />
|
|
109
109
|
Blue
|
|
110
110
|
</label>
|
|
111
111
|
</div>
|
|
@@ -40,6 +40,8 @@ import { ZenRadioRegistry } from './radio.registry';
|
|
|
40
40
|
selector: 'zen-radio',
|
|
41
41
|
template: `
|
|
42
42
|
<input
|
|
43
|
+
#inputElement
|
|
44
|
+
type="radio"
|
|
43
45
|
[attr.aria-checked]="checked()"
|
|
44
46
|
[attr.aria-disabled]="disabled()"
|
|
45
47
|
[attr.aria-invalid]="invalid() || null"
|
|
@@ -48,8 +50,6 @@ import { ZenRadioRegistry } from './radio.registry';
|
|
|
48
50
|
[name]="name()"
|
|
49
51
|
[value]="option()"
|
|
50
52
|
(change)="onRadioChange()"
|
|
51
|
-
#inputElement
|
|
52
|
-
type="radio"
|
|
53
53
|
/>
|
|
54
54
|
@if (checked()) {
|
|
55
55
|
<span class="radio-dot"></span>
|
|
@@ -78,7 +78,7 @@ export class ZenRadio extends ZenFormControl<string | null> {
|
|
|
78
78
|
/**
|
|
79
79
|
* Determines if this radio button is checked based on the group value.
|
|
80
80
|
*/
|
|
81
|
-
protected checked = computed<boolean>(() => this.value() === this.option());
|
|
81
|
+
protected readonly checked = computed<boolean>(() => this.value() === this.option());
|
|
82
82
|
/**
|
|
83
83
|
* Handles radio button selection using native change event.
|
|
84
84
|
*/
|
|
@@ -67,7 +67,7 @@ export const WithSignalForm: Story = {
|
|
|
67
67
|
standalone: true,
|
|
68
68
|
template: `
|
|
69
69
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
70
|
-
<zen-switch [formField]="form.notifications"
|
|
70
|
+
<zen-switch id="notifications" [formField]="form.notifications" />
|
|
71
71
|
<label for="notifications">
|
|
72
72
|
{{ form.notifications().value() ? 'Notifications on' : 'Notifications off' }}
|
|
73
73
|
</label>
|
package/schematics/ui/index.js
CHANGED
|
@@ -22,14 +22,12 @@ function resolveFormControlDependency(tree, logger, ui, workingDirectory) {
|
|
|
22
22
|
function uiGenerator({ ui: selected, project, ...options }) {
|
|
23
23
|
return async (tree, context) => {
|
|
24
24
|
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
25
|
-
const projectName = project
|
|
25
|
+
const projectName = project ?? workspace.extensions['defaultProject'];
|
|
26
26
|
const projectObj = workspace.projects.get(projectName);
|
|
27
27
|
if (!projectObj) {
|
|
28
28
|
throw new Error(`Project "${projectName}" not found in workspace.`);
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
options.path = ((0, workspace_1.buildDefaultPath)(projectObj) + '/' + DEFAULT_GENERATION_PATH);
|
|
32
|
-
}
|
|
30
|
+
options.path ??= ((0, workspace_1.buildDefaultPath)(projectObj) + '/' + DEFAULT_GENERATION_PATH);
|
|
33
31
|
const workingDirectory = (0, core_1.normalize)((0, core_1.join)(options.currentDirectory, options.path));
|
|
34
32
|
const ui = [...selected];
|
|
35
33
|
resolveFormControlDependency(tree, context.logger, ui, workingDirectory);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/index.ts"],"names":[],"mappings":";;AA4BA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/index.ts"],"names":[],"mappings":";;AA4BA,kCAmBC;AA/CD,+CAA6D;AAC7D,2DAAiF;AACjF,qEAAuF;AAEvF,uCAAoD;AAGpD,MAAM,uBAAuB,GAAG,IAAI,CAAC,CAAC,aAAa;AAEnD,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjG,SAAS,4BAA4B,CACnC,IAAU,EACV,MAAkC,EAClC,EAAY,EACZ,gBAAsB;IAEtB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO;IAEpD,MAAM,MAAM,GAAG,IAAA,gBAAS,EAAC,GAAG,gBAAgB,+BAA+B,CAAC,CAAC;IAC7E,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAa;IAC1E,OAAO,KAAK,EAAE,IAAU,EAAE,OAAyB,EAAE,EAAE;QACrD,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,OAAO,CAAC,IAAI,KAAK,CAAC,IAAA,4BAAgB,EAAC,UAAU,CAAC,GAAG,GAAG,GAAG,uBAAuB,CAAS,CAAC;QAExF,MAAM,gBAAgB,GAAG,IAAA,gBAAS,EAAC,IAAA,WAAI,EAAC,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACjF,MAAM,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;QAEzB,4BAA4B,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAEzE,OAAO,IAAA,kBAAK,EAAC,CAAC,GAAG,IAAA,6BAAqB,EAAC,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { join, normalize, Path } from '@angular-devkit/core';\nimport { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';\n\nimport { applyFileTemplateUtil } from '../../utils';\nimport { Schema as UiOptions, UiType } from './schema';\n\nconst DEFAULT_GENERATION_PATH = 'ui'; // src/app/ui\n\nconst FORM_CONTROL_DEPS: ReadonlySet<UiType> = new Set(['input', 'checkbox', 'switch', 'radio']);\n\nfunction resolveFormControlDependency(\n tree: Tree,\n logger: SchematicContext['logger'],\n ui: UiType[],\n workingDirectory: Path\n): void {\n if (!ui.some(u => FORM_CONTROL_DEPS.has(u))) return;\n\n const fcPath = normalize(`${workingDirectory}/form-control/form-control.ts`);\n if (tree.exists(fcPath)) {\n logger.info('ℹ form-control skipped — already exists');\n } else {\n ui.push('form-control');\n logger.info('✔ form-control included (auto-included for form components)');\n }\n}\n\nexport function uiGenerator({ ui: selected, project, ...options }: UiOptions): Rule {\n return async (tree: Tree, context: SchematicContext) => {\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 options.path ??= (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;\n\n const workingDirectory = normalize(join(options.currentDirectory, options.path));\n const ui = [...selected];\n\n resolveFormControlDependency(tree, context.logger, ui, workingDirectory);\n\n return chain([...applyFileTemplateUtil(ui, { ...options, path: workingDirectory })]);\n };\n}\n"]}
|
package/schematics/ui/index.ts
CHANGED
|
@@ -29,16 +29,14 @@ function resolveFormControlDependency(
|
|
|
29
29
|
export function uiGenerator({ ui: selected, project, ...options }: UiOptions): Rule {
|
|
30
30
|
return async (tree: Tree, context: SchematicContext) => {
|
|
31
31
|
const workspace = await getWorkspace(tree);
|
|
32
|
-
const projectName = project
|
|
32
|
+
const projectName = project ?? (workspace.extensions['defaultProject'] as string);
|
|
33
33
|
const projectObj = workspace.projects.get(projectName);
|
|
34
34
|
|
|
35
35
|
if (!projectObj) {
|
|
36
36
|
throw new Error(`Project "${projectName}" not found in workspace.`);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
options.path = (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;
|
|
41
|
-
}
|
|
39
|
+
options.path ??= (buildDefaultPath(projectObj) + '/' + DEFAULT_GENERATION_PATH) as Path;
|
|
42
40
|
|
|
43
41
|
const workingDirectory = normalize(join(options.currentDirectory, options.path));
|
|
44
42
|
const ui = [...selected];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/schema.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\nexport type UiType =\n | 'alert'\n | 'avatar'\n | 'badge'\n | 'button'\n | 'card'\n | 'checkbox'\n | 'dialog'\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"]}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/schema.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\nexport type UiType =\n | 'alert'\n | 'avatar'\n | 'badge'\n | 'button'\n | 'card'\n | 'checkbox'\n | 'dialog'\n | 'divider'\n | 'form-control'\n | 'icon'\n | 'input'\n | 'pin'\n | 'popover'\n | 'radio'\n | 'skeleton'\n | 'switch'\n | 'textarea';\n\nexport interface Schema extends GeneratorSchemaBase {\n ui: UiType[];\n}\n"]}
|
package/schematics/ui/schema.ts
CHANGED
|
@@ -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,
|
|
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,2DAAsH;AAGtH,qEAAiE;AAEjE,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,EAAU,EAAE,CAAC,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;AACjF,MAAM,cAAc,GAAG,CAAC,OAAgB,EAAQ,EAAE,CAAC,IAAA,mBAAM,EAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;AAEpH,SAAgB,qBAAqB,CAAC,OAAiB,EAAE,MAA2B;IAClF,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, Source, url } from '@angular-devkit/schematics';\n\nimport { GeneratorSchemaBase, UiType } from '../schematics/ui/schema';\nimport { selectedElements } from '../services/selected-elements';\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[]): Source => apply(url(`./templates`), rules);\nconst includeStories = (include: boolean): Rule => filter(filePath => include || !filePath.endsWith('.stories.ts'));\n\nexport function applyFileTemplateUtil(folders: UiType[], 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"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalize, strings } from '@angular-devkit/core';
|
|
2
|
-
import { apply, applyTemplates, chain, filter, mergeWith, move, Rule, url } from '@angular-devkit/schematics';
|
|
2
|
+
import { apply, applyTemplates, chain, filter, mergeWith, move, Rule, Source, url } from '@angular-devkit/schematics';
|
|
3
3
|
|
|
4
4
|
import { GeneratorSchemaBase, UiType } from '../schematics/ui/schema';
|
|
5
5
|
import { selectedElements } from '../services/selected-elements';
|
|
@@ -13,8 +13,8 @@ const createTemplateRules = (folder: string, path: string): Rule[] => [
|
|
|
13
13
|
move(normalize(`${path}/${folder}`)),
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
const getTemplates = (rules: Rule[]) => apply(url(`./templates`), rules);
|
|
17
|
-
const includeStories = (include: boolean) => filter(filePath => include || !filePath.endsWith('.stories.ts'));
|
|
16
|
+
const getTemplates = (rules: Rule[]): Source => apply(url(`./templates`), rules);
|
|
17
|
+
const includeStories = (include: boolean): Rule => filter(filePath => include || !filePath.endsWith('.stories.ts'));
|
|
18
18
|
|
|
19
19
|
export function applyFileTemplateUtil(folders: UiType[], config: GeneratorSchemaBase): Rule[] {
|
|
20
20
|
selectedElements.push(...folders);
|