@ng-zen/cli 22.0.0-next.1 → 22.1.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/CHANGELOG.md +3 -7
- package/package.json +1 -1
- package/schematics/ui/files/alert/alert.ts +1 -2
- package/schematics/ui/files/avatar/avatar.ts +1 -2
- package/schematics/ui/files/button/button.ts +1 -2
- package/schematics/ui/files/checkbox/checkbox.stories.ts +59 -33
- package/schematics/ui/files/checkbox/checkbox.ts +22 -26
- package/schematics/ui/files/dialog/dialog.ts +1 -2
- package/schematics/ui/files/divider/divider.ts +1 -2
- package/schematics/ui/files/form-control/form-control.spec.ts +20 -5
- package/schematics/ui/files/form-control/form-control.stories.ts +58 -36
- package/schematics/ui/files/form-control/form-control.ts +55 -70
- package/schematics/ui/files/icon/icon.ts +1 -2
- package/schematics/ui/files/input/input.stories.ts +84 -34
- package/schematics/ui/files/input/input.ts +27 -24
- package/schematics/ui/files/popover/host/popover-host.ts +1 -2
- package/schematics/ui/files/radio/radio.registry.ts +2 -2
- package/schematics/ui/files/radio/radio.spec.ts +3 -72
- package/schematics/ui/files/radio/radio.stories.ts +60 -112
- package/schematics/ui/files/radio/radio.ts +21 -33
- package/schematics/ui/files/skeleton/skeleton.ts +1 -2
- package/schematics/ui/files/switch/switch.stories.ts +46 -30
- package/schematics/ui/files/switch/switch.ts +20 -20
- package/schematics/ui/files/textarea/textarea.ts +1 -2
- package/schematics/ui/index.js +17 -2
- package/schematics/ui/index.js.map +1 -1
- package/schematics/ui/index.ts +26 -4
- package/schematics/ui/schema.json +0 -1
|
@@ -1,43 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
afterNextRender,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
Component,
|
|
5
|
-
computed,
|
|
6
|
-
DestroyRef,
|
|
7
|
-
inject,
|
|
8
|
-
input,
|
|
9
|
-
model,
|
|
10
|
-
} from '@angular/core';
|
|
11
|
-
import { FormsModule } from '@angular/forms';
|
|
1
|
+
import { afterNextRender, Component, computed, DestroyRef, inject, input, model } from '@angular/core';
|
|
12
2
|
|
|
13
|
-
import { ZenFormControl
|
|
3
|
+
import { ZenFormControl } from '../form-control';
|
|
14
4
|
import { ZenRadioRegistry } from './radio.registry';
|
|
15
5
|
|
|
16
6
|
/**
|
|
17
|
-
* ZenRadio is a
|
|
18
|
-
* a consistent and customizable radio button style across the application.
|
|
19
|
-
* It supports Angular forms integration and provides two-way data binding
|
|
20
|
-
* for string values.
|
|
7
|
+
* ZenRadio is a radio button component backed by Signal Forms.
|
|
21
8
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
9
|
+
* Radio buttons with the same `name` form a group. The `value` is shared
|
|
10
|
+
* across the group — when one radio is selected, `onInput(value)` is called
|
|
11
|
+
* on every radio in the group via {@link ZenRadioRegistry}.
|
|
12
|
+
*
|
|
13
|
+
* Use together with a shared `[formField]` binding:
|
|
14
|
+
*
|
|
15
|
+
* ```html
|
|
16
|
+
* <zen-radio [formField]="form.color" name="color" option="red" />
|
|
17
|
+
* <zen-radio [formField]="form.color" name="color" option="green" />
|
|
18
|
+
* ```
|
|
25
19
|
*
|
|
26
20
|
* ### CSS Custom Properties
|
|
27
21
|
*
|
|
28
|
-
* You can customize the component using CSS custom properties:
|
|
29
22
|
*
|
|
30
23
|
* ```css
|
|
31
24
|
* :root {
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
25
|
+
* --zen-radio-size: 1rem;
|
|
26
|
+
* --zen-radio-border-radius: 50%;
|
|
27
|
+
* --zen-radio-appearance: hsl(0deg 0% 10%);
|
|
28
|
+
* --zen-radio-disabled-opacity: 0.6;
|
|
29
|
+
* --zen-radio-border: 1px solid hsl(0deg 0% 80%);
|
|
37
30
|
* }
|
|
38
31
|
* ```
|
|
39
32
|
*
|
|
40
|
-
* @
|
|
33
|
+
* @extends {ZenFormControl<string | null>}
|
|
41
34
|
*
|
|
42
35
|
* @author Konrad Stępień
|
|
43
36
|
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
@@ -49,6 +42,7 @@ import { ZenRadioRegistry } from './radio.registry';
|
|
|
49
42
|
<input
|
|
50
43
|
[attr.aria-checked]="checked()"
|
|
51
44
|
[attr.aria-disabled]="disabled()"
|
|
45
|
+
[attr.aria-invalid]="invalid() || null"
|
|
52
46
|
[checked]="checked()"
|
|
53
47
|
[disabled]="disabled()"
|
|
54
48
|
[name]="name()"
|
|
@@ -62,12 +56,6 @@ import { ZenRadioRegistry } from './radio.registry';
|
|
|
62
56
|
}
|
|
63
57
|
`,
|
|
64
58
|
styleUrls: ['./radio.scss'],
|
|
65
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
66
|
-
imports: [FormsModule],
|
|
67
|
-
providers: [ZenFormControlProvider(ZenRadio)],
|
|
68
|
-
host: {
|
|
69
|
-
'(blur)': 'onTouched()',
|
|
70
|
-
},
|
|
71
59
|
})
|
|
72
60
|
export class ZenRadio extends ZenFormControl<string | null> {
|
|
73
61
|
/**
|
|
@@ -80,7 +68,7 @@ export class ZenRadio extends ZenFormControl<string | null> {
|
|
|
80
68
|
* The name attribute for the radio button group.
|
|
81
69
|
* Radio buttons with the same name will be grouped together.
|
|
82
70
|
*/
|
|
83
|
-
readonly name = input.required<string>();
|
|
71
|
+
override readonly name = input.required<string>();
|
|
84
72
|
|
|
85
73
|
/**
|
|
86
74
|
* The value for this specific radio button.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ZenInput loader component that displays loading states.
|
|
@@ -31,6 +31,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
|
31
31
|
selector: 'zen-skeleton, zen-skeleton[rounded]',
|
|
32
32
|
template: ``,
|
|
33
33
|
styleUrl: './skeleton.scss',
|
|
34
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
35
34
|
})
|
|
36
35
|
export class ZenSkeleton {}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { NgComponentOutlet } from '@angular/common';
|
|
2
|
+
import { Component, signal } from '@angular/core';
|
|
3
|
+
import { form, FormField } from '@angular/forms/signals';
|
|
1
4
|
import { Meta, StoryObj } from '@storybook/angular';
|
|
2
5
|
|
|
6
|
+
import FormControlStories from '../form-control/form-control.stories';
|
|
3
7
|
import { ZenSwitch } from './switch';
|
|
4
8
|
|
|
5
9
|
type Options = ZenSwitch;
|
|
@@ -8,42 +12,18 @@ export default {
|
|
|
8
12
|
title: 'Ui/Switch',
|
|
9
13
|
component: ZenSwitch,
|
|
10
14
|
argTypes: {
|
|
15
|
+
...FormControlStories.argTypes,
|
|
11
16
|
value: {
|
|
12
|
-
control: 'boolean',
|
|
17
|
+
control: 'boolean' as const,
|
|
13
18
|
table: {
|
|
14
19
|
category: 'models',
|
|
15
|
-
type: {
|
|
16
|
-
summary: 'boolean',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
disabled: {
|
|
21
|
-
control: 'boolean',
|
|
22
|
-
table: {
|
|
23
|
-
category: 'models',
|
|
24
|
-
type: {
|
|
25
|
-
summary: 'boolean',
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
required: {
|
|
30
|
-
control: 'boolean',
|
|
31
|
-
table: {
|
|
32
|
-
category: 'inputs',
|
|
33
|
-
type: {
|
|
34
|
-
summary: 'boolean',
|
|
35
|
-
},
|
|
36
|
-
defaultValue: {
|
|
37
|
-
summary: 'false',
|
|
38
|
-
},
|
|
20
|
+
type: { summary: 'boolean' },
|
|
39
21
|
},
|
|
40
22
|
},
|
|
41
23
|
onInput: {
|
|
42
24
|
table: {
|
|
43
25
|
readonly: true,
|
|
44
|
-
type: {
|
|
45
|
-
summary: '(value: boolean) => void',
|
|
46
|
-
},
|
|
26
|
+
type: { summary: '(value: boolean) => void' },
|
|
47
27
|
},
|
|
48
28
|
},
|
|
49
29
|
onKeyDown: {
|
|
@@ -53,12 +33,48 @@ export default {
|
|
|
53
33
|
},
|
|
54
34
|
},
|
|
55
35
|
args: {
|
|
36
|
+
...FormControlStories.args,
|
|
56
37
|
value: false,
|
|
57
|
-
disabled: false,
|
|
58
|
-
required: false,
|
|
59
38
|
},
|
|
60
39
|
} satisfies Meta<Options>;
|
|
61
40
|
|
|
62
41
|
type Story = StoryObj<Options>;
|
|
63
42
|
|
|
64
43
|
export const Default: Story = {};
|
|
44
|
+
|
|
45
|
+
export const WithSignalForm: Story = {
|
|
46
|
+
render: () => ({
|
|
47
|
+
moduleMetadata: { imports: [NgComponentOutlet] },
|
|
48
|
+
props: { component: SwitchSignalFormComponent },
|
|
49
|
+
template: '<ng-container *ngComponentOutlet="component" />',
|
|
50
|
+
}),
|
|
51
|
+
parameters: {
|
|
52
|
+
docs: {
|
|
53
|
+
source: {
|
|
54
|
+
code: `
|
|
55
|
+
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
56
|
+
<zen-switch id="notifications" [formField]="form.notifications" />
|
|
57
|
+
<label for="notifications">
|
|
58
|
+
{{ form.notifications().value() ? 'Notifications on' : 'Notifications off' }}
|
|
59
|
+
</label>
|
|
60
|
+
</div>`,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
@Component({
|
|
67
|
+
standalone: true,
|
|
68
|
+
template: `
|
|
69
|
+
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
70
|
+
<zen-switch [formField]="form.notifications" id="notifications" />
|
|
71
|
+
<label for="notifications">
|
|
72
|
+
{{ form.notifications().value() ? 'Notifications on' : 'Notifications off' }}
|
|
73
|
+
</label>
|
|
74
|
+
</div>
|
|
75
|
+
`,
|
|
76
|
+
imports: [FormField, ZenSwitch],
|
|
77
|
+
})
|
|
78
|
+
class SwitchSignalFormComponent {
|
|
79
|
+
readonly form = form(signal({ notifications: false }));
|
|
80
|
+
}
|
|
@@ -1,34 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, model } from '@angular/core';
|
|
2
2
|
|
|
3
|
-
import { ZenFormControl
|
|
3
|
+
import { ZenFormControl } from '../form-control';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* It supports Angular forms integration and provides two-way data binding.
|
|
6
|
+
* ZenSwitch is a toggle switch component for boolean values backed by
|
|
7
|
+
* Signal Forms.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
9
|
+
* Connect it to a Signal Forms field with `[formField]`:
|
|
11
10
|
*
|
|
12
11
|
* ```html
|
|
13
|
-
* <zen-switch [
|
|
14
|
-
|
|
12
|
+
* <zen-switch [formField]="myForm.notifications" />
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Supports keyboard interaction: `Enter`, `Space`, `ArrowRight`, and
|
|
16
|
+
* `ArrowLeft` keys toggle or set the value.
|
|
15
17
|
*
|
|
16
18
|
* ### CSS Custom Properties
|
|
17
|
-
*
|
|
19
|
+
*
|
|
18
20
|
*
|
|
19
21
|
* ```css
|
|
20
22
|
* :root {
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* --zen-switch-thumb-size: 1rem;
|
|
24
|
+
* --zen-switch-height: 1.25rem;
|
|
25
|
+
* --zen-switch-width: 2rem;
|
|
26
|
+
* --zen-switch-apperance: hsl(0deg 0% 10%);
|
|
27
|
+
* --zen-switch-background: hsl(0deg 0% 80%);
|
|
26
28
|
* }
|
|
27
29
|
* ```
|
|
28
30
|
*
|
|
29
|
-
* @
|
|
31
|
+
* @extends {ZenFormControl<boolean>}
|
|
30
32
|
*
|
|
31
|
-
* @author Konrad
|
|
33
|
+
* @author Konrad Stepień
|
|
32
34
|
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
33
35
|
* @see [GitHub](https://github.com/kstepien3/ng-zen)
|
|
34
36
|
*/
|
|
@@ -41,20 +43,18 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
|
|
|
41
43
|
</span>
|
|
42
44
|
`,
|
|
43
45
|
styleUrl: './switch.scss',
|
|
44
|
-
|
|
45
|
-
providers: [ZenFormControlProvider(ZenSwitch)],
|
|
46
|
+
|
|
46
47
|
host: {
|
|
47
48
|
tabindex: '0',
|
|
48
49
|
role: 'switch',
|
|
49
50
|
'[attr.data-disabled]': 'disabled()',
|
|
50
51
|
'[attr.data-checked]': 'value()',
|
|
51
|
-
'(blur)': 'onTouched()',
|
|
52
52
|
'(click)': 'onInput(!this.value())',
|
|
53
53
|
'(keydown)': 'onKeyDown($event)',
|
|
54
54
|
},
|
|
55
55
|
})
|
|
56
56
|
export class ZenSwitch extends ZenFormControl<boolean> {
|
|
57
|
-
/** Holds the current
|
|
57
|
+
/** Holds the current switch value. */
|
|
58
58
|
readonly value = model(false);
|
|
59
59
|
|
|
60
60
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ZenTextarea is a reusable textarea component designed to provide
|
|
@@ -32,6 +32,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
|
32
32
|
<ng-content />
|
|
33
33
|
`,
|
|
34
34
|
styleUrls: ['./textarea.scss'],
|
|
35
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
36
35
|
})
|
|
37
36
|
export class ZenTextarea {}
|
package/schematics/ui/index.js
CHANGED
|
@@ -6,8 +6,21 @@ const schematics_1 = require("@angular-devkit/schematics");
|
|
|
6
6
|
const workspace_1 = require("@schematics/angular/utility/workspace");
|
|
7
7
|
const utils_1 = require("../../utils");
|
|
8
8
|
const DEFAULT_GENERATION_PATH = 'ui'; // src/app/ui
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const FORM_CONTROL_DEPS = new Set(['input', 'checkbox', 'switch', 'radio']);
|
|
10
|
+
function resolveFormControlDependency(tree, logger, ui, workingDirectory) {
|
|
11
|
+
if (!ui.some(u => FORM_CONTROL_DEPS.has(u)))
|
|
12
|
+
return;
|
|
13
|
+
const fcPath = (0, core_1.normalize)(`${workingDirectory}/form-control/form-control.ts`);
|
|
14
|
+
if (tree.exists(fcPath)) {
|
|
15
|
+
logger.info('ℹ form-control skipped — already exists');
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
ui.push('form-control');
|
|
19
|
+
logger.info('✔ form-control included (auto-included for form components)');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function uiGenerator({ ui: selected, project, ...options }) {
|
|
23
|
+
return async (tree, context) => {
|
|
11
24
|
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
12
25
|
const projectName = project || workspace.extensions['defaultProject'];
|
|
13
26
|
const projectObj = workspace.projects.get(projectName);
|
|
@@ -18,6 +31,8 @@ function uiGenerator({ ui, project, ...options }) {
|
|
|
18
31
|
options.path = ((0, workspace_1.buildDefaultPath)(projectObj) + '/' + DEFAULT_GENERATION_PATH);
|
|
19
32
|
}
|
|
20
33
|
const workingDirectory = (0, core_1.normalize)((0, core_1.join)(options.currentDirectory, options.path));
|
|
34
|
+
const ui = [...selected];
|
|
35
|
+
resolveFormControlDependency(tree, context.logger, ui, workingDirectory);
|
|
21
36
|
return (0, schematics_1.chain)([...(0, utils_1.applyFileTemplateUtil)(ui, { ...options, path: workingDirectory })]);
|
|
22
37
|
};
|
|
23
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/index.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/index.ts"],"names":[],"mappings":";;AA4BA,kCAqBC;AAjDD,+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,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;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 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 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
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
import { join, normalize, Path } from '@angular-devkit/core';
|
|
2
|
-
import { chain, Rule, Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { chain, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
3
3
|
import { buildDefaultPath, getWorkspace } from '@schematics/angular/utility/workspace';
|
|
4
4
|
|
|
5
5
|
import { applyFileTemplateUtil } from '../../utils';
|
|
6
|
-
import { Schema as UiOptions } from './schema';
|
|
6
|
+
import { Schema as UiOptions, UiType } from './schema';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_GENERATION_PATH = 'ui'; // src/app/ui
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const FORM_CONTROL_DEPS: ReadonlySet<UiType> = new Set(['input', 'checkbox', 'switch', 'radio']);
|
|
11
|
+
|
|
12
|
+
function resolveFormControlDependency(
|
|
13
|
+
tree: Tree,
|
|
14
|
+
logger: SchematicContext['logger'],
|
|
15
|
+
ui: UiType[],
|
|
16
|
+
workingDirectory: Path
|
|
17
|
+
): void {
|
|
18
|
+
if (!ui.some(u => FORM_CONTROL_DEPS.has(u))) return;
|
|
19
|
+
|
|
20
|
+
const fcPath = normalize(`${workingDirectory}/form-control/form-control.ts`);
|
|
21
|
+
if (tree.exists(fcPath)) {
|
|
22
|
+
logger.info('ℹ form-control skipped — already exists');
|
|
23
|
+
} else {
|
|
24
|
+
ui.push('form-control');
|
|
25
|
+
logger.info('✔ form-control included (auto-included for form components)');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function uiGenerator({ ui: selected, project, ...options }: UiOptions): Rule {
|
|
30
|
+
return async (tree: Tree, context: SchematicContext) => {
|
|
12
31
|
const workspace = await getWorkspace(tree);
|
|
13
32
|
const projectName = project || (workspace.extensions['defaultProject'] as string);
|
|
14
33
|
const projectObj = workspace.projects.get(projectName);
|
|
@@ -22,6 +41,9 @@ export function uiGenerator({ ui, project, ...options }: UiOptions): Rule {
|
|
|
22
41
|
}
|
|
23
42
|
|
|
24
43
|
const workingDirectory = normalize(join(options.currentDirectory, options.path));
|
|
44
|
+
const ui = [...selected];
|
|
45
|
+
|
|
46
|
+
resolveFormControlDependency(tree, context.logger, ui, workingDirectory);
|
|
25
47
|
|
|
26
48
|
return chain([...applyFileTemplateUtil(ui, { ...options, path: workingDirectory })]);
|
|
27
49
|
};
|