@ng-zen/cli 22.0.0 β 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 +2 -16
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
## [22.0.
|
|
2
|
-
|
|
3
|
-
### β BREAKING CHANGES
|
|
4
|
-
|
|
5
|
-
* **deps:** clean up unused deps and update dependencies (#414)
|
|
6
|
-
|
|
7
|
-
### chore
|
|
8
|
-
|
|
9
|
-
* **deps:** clean up unused deps and update dependencies ([#414](https://github.com/kstepien3/ng-zen/issues/414)) ([04b8c7d](https://github.com/kstepien3/ng-zen/commit/04b8c7da59e70181147f105e95b6471234ff21f5))
|
|
1
|
+
## [22.1.0-next.1](https://github.com/kstepien3/ng-zen/compare/v22.0.0...v22.1.0-next.1) (2026-06-21)
|
|
10
2
|
|
|
11
3
|
### π New Features
|
|
12
4
|
|
|
13
|
-
* **
|
|
14
|
-
* **button:** migrate to dynamic CSS variables and refactor styles ([#411](https://github.com/kstepien3/ng-zen/issues/411)) ([e4464f7](https://github.com/kstepien3/ng-zen/commit/e4464f7580c91f0de30d9139177655915430fd43))
|
|
15
|
-
* **popover:** improve popover styles, stories, docs ([#404](https://github.com/kstepien3/ng-zen/issues/404)) ([6515443](https://github.com/kstepien3/ng-zen/commit/65154436cde09f77909638ef84e2213b04f2862c))
|
|
16
|
-
|
|
17
|
-
### π Documentation
|
|
18
|
-
|
|
19
|
-
* **README:** add DeepWiki link ([#409](https://github.com/kstepien3/ng-zen/issues/409)) ([7875347](https://github.com/kstepien3/ng-zen/commit/78753471c6e56036788c6371ce0f063ab06b524b))
|
|
5
|
+
* **ui:** migrate controls for signals and clean up ([#417](https://github.com/kstepien3/ng-zen/issues/417)) ([08824f8](https://github.com/kstepien3/ng-zen/commit/08824f85096745bcf001700dc415afe1a6cdb52e))
|
|
20
6
|
|
|
21
7
|
Development mode - changelog will be generated during build.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-zen/cli",
|
|
3
|
-
"version": "22.0.
|
|
3
|
+
"version": "22.1.0-next.1",
|
|
4
4
|
"description": "Angular UI components generator β Zen UI Kit CLI for schematics-based creation of customizable components like avatar, button, checkbox, divider, form-control, icon, input, skeleton, switch, textarea with Storybook demos.",
|
|
5
5
|
"license": "BSD-2-Clause",
|
|
6
6
|
"private": false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ZenAlert is a reusable alert component designed to provide
|
|
@@ -42,6 +42,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
|
42
42
|
</div>
|
|
43
43
|
`,
|
|
44
44
|
styleUrl: './alert.scss',
|
|
45
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
46
45
|
})
|
|
47
46
|
export class ZenAlert {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NgOptimizedImage } from '@angular/common';
|
|
2
|
-
import {
|
|
2
|
+
import { Component, input } from '@angular/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A component to display an avatar image.
|
|
@@ -46,7 +46,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
|
46
46
|
}
|
|
47
47
|
`,
|
|
48
48
|
styleUrl: './avatar.scss',
|
|
49
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
49
|
})
|
|
51
50
|
export class ZenAvatar {
|
|
52
51
|
/** Source path for the image which will be processed by Angular's image loader. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, input } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ZenButton is a reusable button component designed to provide
|
|
@@ -39,7 +39,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
|
39
39
|
<ng-content />
|
|
40
40
|
`,
|
|
41
41
|
styleUrl: './button.scss',
|
|
42
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
43
42
|
host: {
|
|
44
43
|
'[attr.type]': '"button"',
|
|
45
44
|
'[attr.data-size]': 'size()',
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { NgComponentOutlet } from '@angular/common';
|
|
2
|
+
import { Component, signal } from '@angular/core';
|
|
3
|
+
import { form, FormField, required } 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 { ZenCheckbox } from './checkbox';
|
|
4
8
|
|
|
5
9
|
type Options = ZenCheckbox;
|
|
@@ -8,53 +12,26 @@ export default {
|
|
|
8
12
|
title: 'Ui/Checkbox',
|
|
9
13
|
component: ZenCheckbox,
|
|
10
14
|
argTypes: {
|
|
15
|
+
...FormControlStories.argTypes,
|
|
11
16
|
value: {
|
|
12
17
|
table: {
|
|
13
18
|
category: 'models',
|
|
14
|
-
type: {
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
defaultValue: {
|
|
18
|
-
summary: 'false',
|
|
19
|
-
},
|
|
19
|
+
type: { summary: 'boolean | null' },
|
|
20
|
+
defaultValue: { summary: 'false' },
|
|
20
21
|
},
|
|
21
|
-
control: 'radio',
|
|
22
|
+
control: 'radio' as const,
|
|
22
23
|
options: [true, false, null],
|
|
23
24
|
},
|
|
24
|
-
disabled: {
|
|
25
|
-
control: 'boolean',
|
|
26
|
-
table: {
|
|
27
|
-
category: 'models',
|
|
28
|
-
type: {
|
|
29
|
-
summary: 'boolean',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
required: {
|
|
34
|
-
control: 'boolean',
|
|
35
|
-
table: {
|
|
36
|
-
category: 'inputs',
|
|
37
|
-
type: {
|
|
38
|
-
summary: 'boolean',
|
|
39
|
-
},
|
|
40
|
-
defaultValue: {
|
|
41
|
-
summary: 'false',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
25
|
onInput: {
|
|
46
26
|
table: {
|
|
47
27
|
readonly: true,
|
|
48
|
-
type: {
|
|
49
|
-
summary: '(value: boolean | null) => void',
|
|
50
|
-
},
|
|
28
|
+
type: { summary: '(value: boolean | null) => void' },
|
|
51
29
|
},
|
|
52
30
|
},
|
|
53
31
|
},
|
|
54
32
|
args: {
|
|
33
|
+
...FormControlStories.args,
|
|
55
34
|
value: false,
|
|
56
|
-
disabled: false,
|
|
57
|
-
required: false,
|
|
58
35
|
},
|
|
59
36
|
} satisfies Meta<Options>;
|
|
60
37
|
|
|
@@ -72,3 +49,52 @@ export const WithLabel: Story = {
|
|
|
72
49
|
`,
|
|
73
50
|
}),
|
|
74
51
|
};
|
|
52
|
+
|
|
53
|
+
export const WithSignalForm: Story = {
|
|
54
|
+
render: () => ({
|
|
55
|
+
moduleMetadata: { imports: [NgComponentOutlet] },
|
|
56
|
+
props: { component: CheckboxSignalFormComponent },
|
|
57
|
+
template: '<ng-container *ngComponentOutlet="component" />',
|
|
58
|
+
}),
|
|
59
|
+
parameters: {
|
|
60
|
+
docs: {
|
|
61
|
+
source: {
|
|
62
|
+
code: `
|
|
63
|
+
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
64
|
+
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
65
|
+
<zen-checkbox id="agree" [formField]="form.agree" />
|
|
66
|
+
<label for="agree">I agree to the terms</label>
|
|
67
|
+
</div>
|
|
68
|
+
@if (form.agree().invalid()) {
|
|
69
|
+
<p style="color: red; margin: 0; font-size: 0.875rem;">
|
|
70
|
+
{{ form.agree().errors()![0].message }}
|
|
71
|
+
</p>
|
|
72
|
+
}
|
|
73
|
+
</div>`,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
@Component({
|
|
80
|
+
standalone: true,
|
|
81
|
+
template: `
|
|
82
|
+
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
|
83
|
+
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
84
|
+
<zen-checkbox [formField]="form.agree" id="agree" />
|
|
85
|
+
<label for="agree">I agree to the terms</label>
|
|
86
|
+
</div>
|
|
87
|
+
@if (form.agree().invalid()) {
|
|
88
|
+
<p style="color: red; margin: 0; font-size: 0.875rem;">
|
|
89
|
+
{{ form.agree().errors()![0].message }}
|
|
90
|
+
</p>
|
|
91
|
+
}
|
|
92
|
+
</div>
|
|
93
|
+
`,
|
|
94
|
+
imports: [FormField, ZenCheckbox],
|
|
95
|
+
})
|
|
96
|
+
class CheckboxSignalFormComponent {
|
|
97
|
+
readonly form = form(signal({ agree: false }), s => {
|
|
98
|
+
required(s.agree, { message: 'You must agree to continue' });
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -1,34 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FormsModule } from '@angular/forms';
|
|
1
|
+
import { Component, effect, ElementRef, model, viewChild } from '@angular/core';
|
|
3
2
|
|
|
4
|
-
import { ZenFormControl
|
|
3
|
+
import { ZenFormControl } from '../form-control';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* ZenCheckbox is a
|
|
8
|
-
*
|
|
9
|
-
* It supports Angular forms integration and provides two-way data binding
|
|
10
|
-
* for boolean values.
|
|
6
|
+
* ZenCheckbox is a checkbox component supporting checked (`true`),
|
|
7
|
+
* unchecked (`false`), and indeterminate (`null`) states.
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* <zen-checkbox [
|
|
9
|
+
* Connect it to a Signal Forms field with `[formField]`:
|
|
10
|
+
*
|
|
11
|
+
* ```html
|
|
12
|
+
* <zen-checkbox [formField]="myForm.agree" />
|
|
13
|
+
* <label for="agree">I agree to the terms</label>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* When `value` is `null` the native checkbox is set to indeterminate via
|
|
17
|
+
* the `indeterminate` DOM property.
|
|
16
18
|
*
|
|
17
19
|
* ### CSS Custom Properties
|
|
18
20
|
*
|
|
19
|
-
* You can customize the component using CSS custom properties:
|
|
20
21
|
*
|
|
21
22
|
* ```css
|
|
22
23
|
* :root {
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* --zen-checkbox-size: 1rem;
|
|
25
|
+
* --zen-checkbox-border-radius: 0.375rem;
|
|
26
|
+
* --zen-checkbox-appearance: hsl(0deg 0% 10%);
|
|
27
|
+
* --zen-checkbox-disabled-opacity: 0.6;
|
|
28
|
+
* --zen-checkbox-border: 1px solid hsl(0deg 0% 80%);
|
|
28
29
|
* }
|
|
29
30
|
* ```
|
|
30
31
|
*
|
|
31
|
-
* @
|
|
32
|
+
* @extends {ZenFormControl<boolean | null>}
|
|
32
33
|
*
|
|
33
34
|
* @author Konrad StΔpieΕ
|
|
34
35
|
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
@@ -39,9 +40,10 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
|
|
|
39
40
|
template: `
|
|
40
41
|
<input
|
|
41
42
|
[attr.aria-disabled]="disabled()"
|
|
43
|
+
[attr.aria-invalid]="invalid() || null"
|
|
44
|
+
[checked]="value()"
|
|
42
45
|
[disabled]="disabled()"
|
|
43
|
-
|
|
44
|
-
(ngModelChange)="onInput($event)"
|
|
46
|
+
(change)="onInput(inputElement.checked)"
|
|
45
47
|
#inputElement
|
|
46
48
|
type="checkbox"
|
|
47
49
|
/>
|
|
@@ -53,12 +55,6 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
|
|
|
53
55
|
<!-- @else { β } -->
|
|
54
56
|
`,
|
|
55
57
|
styleUrls: ['./checkbox.scss'],
|
|
56
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
57
|
-
imports: [FormsModule],
|
|
58
|
-
providers: [ZenFormControlProvider(ZenCheckbox)],
|
|
59
|
-
host: {
|
|
60
|
-
'(blur)': 'onTouched()',
|
|
61
|
-
},
|
|
62
58
|
})
|
|
63
59
|
export class ZenCheckbox extends ZenFormControl<boolean | null> {
|
|
64
60
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, effect, ElementRef, inject, input, model, untracked } from '@angular/core';
|
|
2
2
|
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
3
3
|
|
|
4
4
|
import { ZenIcon } from '../icon';
|
|
@@ -55,7 +55,6 @@ type DialogSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
|
55
55
|
</div>
|
|
56
56
|
`,
|
|
57
57
|
styleUrl: './dialog.scss',
|
|
58
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
59
58
|
imports: [ZenIcon],
|
|
60
59
|
host: {
|
|
61
60
|
'[attr.aria-label]': 'header()',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, input } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ZenDivider is a reusable divider component that provides a simple way to
|
|
@@ -36,7 +36,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
|
36
36
|
selector: `zen-divider, zen-divider[vertical],`,
|
|
37
37
|
template: '<ng-content/>',
|
|
38
38
|
styleUrl: './divider.scss',
|
|
39
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
40
39
|
host: {
|
|
41
40
|
'[class]': '"zen-align-"+align()',
|
|
42
41
|
},
|
|
@@ -2,27 +2,42 @@ import { Component, model, provideZonelessChangeDetection } from '@angular/core'
|
|
|
2
2
|
import { TestBed } from '@angular/core/testing';
|
|
3
3
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import { ZenFormControl
|
|
5
|
+
import { ZenFormControl } from './form-control';
|
|
6
6
|
|
|
7
7
|
@Component({
|
|
8
8
|
template: '...',
|
|
9
|
-
providers: [ZenFormControlProvider(FormControl)],
|
|
10
9
|
})
|
|
11
|
-
class
|
|
10
|
+
class TestFormControl extends ZenFormControl<string> {
|
|
12
11
|
readonly value = model<string>('');
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
describe('FormControl', () => {
|
|
16
15
|
beforeEach(async () => {
|
|
17
16
|
await TestBed.configureTestingModule({
|
|
18
|
-
imports: [
|
|
17
|
+
imports: [TestFormControl],
|
|
19
18
|
providers: [provideZonelessChangeDetection()],
|
|
20
19
|
}).compileComponents();
|
|
21
20
|
});
|
|
22
21
|
|
|
23
22
|
it('should create', () => {
|
|
24
|
-
const fixture = TestBed.createComponent(
|
|
23
|
+
const fixture = TestBed.createComponent(TestFormControl);
|
|
25
24
|
const component = fixture.componentInstance;
|
|
26
25
|
expect(component).toBeTruthy();
|
|
27
26
|
});
|
|
27
|
+
|
|
28
|
+
it('should have FormUiControl properties', () => {
|
|
29
|
+
const fixture = TestBed.createComponent(TestFormControl);
|
|
30
|
+
const component = fixture.componentInstance;
|
|
31
|
+
expect(component.disabled()).toBe(false);
|
|
32
|
+
expect(component.required()).toBe(false);
|
|
33
|
+
expect(component.touched()).toBe(false);
|
|
34
|
+
expect(component.dirty()).toBe(false);
|
|
35
|
+
expect(component.invalid()).toBe(false);
|
|
36
|
+
expect(component.pending()).toBe(false);
|
|
37
|
+
expect(component.hidden()).toBe(false);
|
|
38
|
+
expect(component.readonly()).toBe(false);
|
|
39
|
+
expect(component.name()).toBe('');
|
|
40
|
+
expect(component.errors()).toEqual([]);
|
|
41
|
+
expect(component.disabledReasons()).toEqual([]);
|
|
42
|
+
});
|
|
28
43
|
});
|
|
@@ -16,59 +16,81 @@ export default {
|
|
|
16
16
|
story => `<div style="display: flex; flex-direction: column; gap: 2rem; align-items: center">${story}</div>`
|
|
17
17
|
),
|
|
18
18
|
],
|
|
19
|
-
args: {
|
|
20
|
-
value: '',
|
|
21
|
-
disabled: false,
|
|
22
|
-
required: false,
|
|
23
|
-
},
|
|
24
19
|
argTypes: {
|
|
25
|
-
value: {
|
|
26
|
-
control: false,
|
|
27
|
-
table: {
|
|
28
|
-
category: 'models',
|
|
29
|
-
readonly: true,
|
|
30
|
-
type: {
|
|
31
|
-
summary: 'T',
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
20
|
disabled: {
|
|
36
|
-
control: 'boolean',
|
|
37
|
-
table: {
|
|
38
|
-
category: 'models',
|
|
39
|
-
type: {
|
|
40
|
-
summary: 'boolean',
|
|
41
|
-
},
|
|
42
|
-
readonly: true,
|
|
43
|
-
},
|
|
21
|
+
control: 'boolean' as const,
|
|
22
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } },
|
|
44
23
|
},
|
|
45
24
|
required: {
|
|
46
|
-
control: 'boolean',
|
|
25
|
+
control: 'boolean' as const,
|
|
26
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } },
|
|
27
|
+
},
|
|
28
|
+
touched: {
|
|
29
|
+
control: false as const,
|
|
30
|
+
table: { category: 'models', type: { summary: 'boolean' }, readonly: true },
|
|
31
|
+
},
|
|
32
|
+
dirty: {
|
|
33
|
+
control: false as const,
|
|
34
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, readonly: true },
|
|
35
|
+
},
|
|
36
|
+
invalid: {
|
|
37
|
+
control: false as const,
|
|
38
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, readonly: true },
|
|
39
|
+
},
|
|
40
|
+
pending: {
|
|
41
|
+
control: false as const,
|
|
42
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, readonly: true },
|
|
43
|
+
},
|
|
44
|
+
hidden: {
|
|
45
|
+
control: 'boolean' as const,
|
|
46
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } },
|
|
47
|
+
},
|
|
48
|
+
readonly: {
|
|
49
|
+
control: 'boolean' as const,
|
|
50
|
+
table: { category: 'inputs', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } },
|
|
51
|
+
},
|
|
52
|
+
name: {
|
|
53
|
+
control: 'text' as const,
|
|
54
|
+
table: { category: 'models', type: { summary: 'string' } },
|
|
55
|
+
},
|
|
56
|
+
errors: {
|
|
57
|
+
control: false as const,
|
|
58
|
+
table: { category: 'models', type: { summary: 'ValidationError[]' }, readonly: true },
|
|
59
|
+
},
|
|
60
|
+
disabledReasons: {
|
|
61
|
+
control: false as const,
|
|
62
|
+
table: { category: 'models', type: { summary: 'DisabledReason[]' }, readonly: true },
|
|
63
|
+
},
|
|
64
|
+
value: {
|
|
65
|
+
control: false as const,
|
|
47
66
|
table: {
|
|
48
|
-
category: '
|
|
49
|
-
type: {
|
|
50
|
-
summary: 'boolean',
|
|
51
|
-
},
|
|
52
|
-
defaultValue: {
|
|
53
|
-
summary: 'false',
|
|
54
|
-
},
|
|
67
|
+
category: 'models',
|
|
55
68
|
readonly: true,
|
|
69
|
+
type: { summary: 'T' },
|
|
56
70
|
},
|
|
57
71
|
},
|
|
58
72
|
onInput: {
|
|
59
73
|
table: {
|
|
60
74
|
readonly: true,
|
|
61
|
-
type: {
|
|
62
|
-
summary: '(value: T) => void',
|
|
63
|
-
},
|
|
75
|
+
type: { summary: '(value: T) => void' },
|
|
64
76
|
},
|
|
65
77
|
},
|
|
66
78
|
},
|
|
79
|
+
args: {
|
|
80
|
+
disabled: false,
|
|
81
|
+
required: false,
|
|
82
|
+
touched: false,
|
|
83
|
+
dirty: false,
|
|
84
|
+
invalid: false,
|
|
85
|
+
pending: false,
|
|
86
|
+
hidden: false,
|
|
87
|
+
readonly: false,
|
|
88
|
+
name: '',
|
|
89
|
+
value: '',
|
|
90
|
+
},
|
|
67
91
|
parameters: {
|
|
68
92
|
docs: {
|
|
69
93
|
canvas: {
|
|
70
|
-
// This will remove the "show code" button
|
|
71
|
-
// https://storybook.js.org/docs/api/doc-blocks/doc-block-canvas#sourcestate
|
|
72
94
|
sourceState: 'none',
|
|
73
95
|
},
|
|
74
96
|
},
|
|
@@ -1,100 +1,85 @@
|
|
|
1
|
-
import { booleanAttribute, Directive,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
-
export const ZenFormControlProvider = <T extends ZenFormControl<any>>(component: Type<T>) => ({
|
|
6
|
-
provide: NG_VALUE_ACCESSOR,
|
|
7
|
-
useExisting: forwardRef(() => component),
|
|
8
|
-
multi: true,
|
|
9
|
-
});
|
|
1
|
+
import { booleanAttribute, Directive, input, ModelSignal, output } from '@angular/core';
|
|
2
|
+
import type { DisabledReason, ValidationError, WithOptionalFieldTree } from '@angular/forms/signals';
|
|
3
|
+
import { FormValueControl } from '@angular/forms/signals';
|
|
10
4
|
|
|
11
5
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
6
|
+
* Abstract base class for form controls that integrate with Signal Forms
|
|
7
|
+
* via the {@link FormValueControl} interface.
|
|
8
|
+
*
|
|
9
|
+
* Provides all {@link FormUiControl} state inputs (`disabled`, `required`,
|
|
10
|
+
* `touched`, `errors`, etc.) and the `touch` output used by the
|
|
11
|
+
* `[formField]` directive.
|
|
15
12
|
*
|
|
16
|
-
* Subclasses must provide an implementation
|
|
13
|
+
* Subclasses must provide an implementation of the `value` model signal
|
|
14
|
+
* with the appropriate value type.
|
|
15
|
+
*
|
|
16
|
+
* Decorated with `@Directive` only to declare the host event binding
|
|
17
|
+
* `(blur) -> touch.emit()`.
|
|
17
18
|
*
|
|
18
19
|
* @example
|
|
19
20
|
*
|
|
20
21
|
* ```ts
|
|
21
22
|
* @Component({
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* selector: 'my-input',
|
|
24
|
+
* template: `<input [value]="value()" (input)="onInput($event.target.value)" />`,
|
|
24
25
|
* })
|
|
25
|
-
* export class
|
|
26
|
-
* readonly value = model<string>('')
|
|
26
|
+
* export class MyInput extends ZenFormControl<string> {
|
|
27
|
+
* readonly value = model<string>('');
|
|
27
28
|
* }
|
|
28
29
|
* ```
|
|
29
30
|
*
|
|
30
|
-
* @implements {
|
|
31
|
+
* @implements {FormValueControl}
|
|
31
32
|
*
|
|
32
33
|
* @author Konrad StΔpieΕ
|
|
33
34
|
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
34
35
|
* @see [GitHub](https://github.com/kstepien3/ng-zen)
|
|
35
36
|
*/
|
|
36
|
-
@Directive({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
@Directive({
|
|
38
|
+
host: {
|
|
39
|
+
'(blur)': 'touch.emit()',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
export abstract class ZenFormControl<Value> implements FormValueControl<Value> {
|
|
43
|
+
/** The underlying value of the control. Subclasses must provide their own implementation using `model()`. */
|
|
42
44
|
abstract readonly value: ModelSignal<Value>;
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
/** Validation errors for the field. Auto-bound by the Signal Forms directive. */
|
|
47
|
+
readonly errors = input<readonly ValidationError.WithOptionalFieldTree[]>([]);
|
|
48
|
+
/** Whether the control is disabled. Auto-bound by the Signal Forms directive. */
|
|
49
|
+
readonly disabled = input(false);
|
|
50
|
+
/** Reasons why the field is disabled. Auto-bound by the Signal Forms directive. */
|
|
51
|
+
readonly disabledReasons = input<readonly WithOptionalFieldTree<DisabledReason>[]>([]);
|
|
52
|
+
/** Whether the field is read-only. Auto-bound by the Signal Forms directive. */
|
|
53
|
+
readonly readonly = input<boolean>(false);
|
|
54
|
+
/** Whether the field is hidden from view. Auto-bound by the Signal Forms directive. */
|
|
55
|
+
readonly hidden = input<boolean>(false);
|
|
56
|
+
/** Whether the field has validation errors. Auto-bound by the Signal Forms directive. */
|
|
57
|
+
readonly invalid = input<boolean>(false);
|
|
58
|
+
/** Whether async validation is in progress. Auto-bound by the Signal Forms directive. */
|
|
59
|
+
readonly pending = input<boolean>(false);
|
|
60
|
+
/** Whether the user has focused and then blurred the field. */
|
|
61
|
+
readonly touched = input<boolean>(false);
|
|
62
|
+
/** Whether the field value has been modified. Auto-bound by the Signal Forms directive. */
|
|
63
|
+
readonly dirty = input(false);
|
|
64
|
+
/** Field name in the form. Auto-bound by the Signal Forms directive. */
|
|
65
|
+
readonly name = input('');
|
|
66
|
+
/** Whether the field is required. Auto-bound by the Signal Forms directive from schema validators. */
|
|
45
67
|
readonly required = input(false, { transform: booleanAttribute });
|
|
68
|
+
/** Emitted when the field is blurred. The {@link FormField} directive listens to this output to mark the field as touched. */
|
|
69
|
+
readonly touch = output<void>();
|
|
46
70
|
|
|
47
|
-
//
|
|
48
|
-
|
|
71
|
+
// @ignore
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
|
73
|
+
focus(_options?: FocusOptions): void {}
|
|
49
74
|
|
|
75
|
+
// @ignore
|
|
50
76
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
51
|
-
|
|
77
|
+
reset(): void {}
|
|
52
78
|
|
|
53
|
-
/**
|
|
54
|
-
* Should be called by the subclass when the control's value changes
|
|
55
|
-
* as a result of user interaction. This method updates the control's value
|
|
56
|
-
* and notifies Angular of the change.
|
|
57
|
-
*/
|
|
79
|
+
/** Should be called by the subclass when the control's value changes as a result of user interaction. */
|
|
58
80
|
onInput(value: Value): void {
|
|
59
81
|
if (this.disabled()) return;
|
|
60
82
|
|
|
61
83
|
this.value.set(value);
|
|
62
|
-
this.onChange(this.value());
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Writes a new value to the element.
|
|
67
|
-
* @internal For internal use by Angular forms.
|
|
68
|
-
* @ignore
|
|
69
|
-
*/
|
|
70
|
-
writeValue(value: Value): void {
|
|
71
|
-
this.value.set(value);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Registers a callback function that is called when the control's value changes in the UI.
|
|
76
|
-
* @internal For internal use by Angular forms.
|
|
77
|
-
* @ignore
|
|
78
|
-
*/
|
|
79
|
-
registerOnChange(fn: (value: Value) => void): void {
|
|
80
|
-
this.onChange = fn;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Registers a callback function that is called by the forms API on component touch.
|
|
85
|
-
* @internal For internal use by Angular forms.
|
|
86
|
-
* @ignore
|
|
87
|
-
*/
|
|
88
|
-
registerOnTouched(fn: () => void): void {
|
|
89
|
-
this.onTouched = fn;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* This function is called by the forms API when the control status changes to or from "DISABLED".
|
|
94
|
-
* @internal For internal use by Angular forms.
|
|
95
|
-
* @ignore
|
|
96
|
-
*/
|
|
97
|
-
setDisabledState(isDisabled: boolean): void {
|
|
98
|
-
this.disabled.set(isDisabled);
|
|
99
84
|
}
|
|
100
85
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { booleanAttribute,
|
|
1
|
+
import { booleanAttribute, Component, computed, inject, input } from '@angular/core';
|
|
2
2
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
3
3
|
|
|
4
4
|
/** SVG icon data structure from HugeIcons library - an array of [tagName, attributes] tuples. */
|
|
@@ -43,7 +43,6 @@ export type IconSvgObject = readonly (readonly [string, Record<string, string |
|
|
|
43
43
|
></svg>
|
|
44
44
|
`,
|
|
45
45
|
styles: [':host { display: inline-flex; }'],
|
|
46
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
47
46
|
})
|
|
48
47
|
export class ZenIcon {
|
|
49
48
|
/** SVG object imported from HugeIcons library. */
|