@egose/shadcn-theme-ng 0.5.0 → 0.5.2
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/form-checkbox/README.md +1 -1
- package/form-checkbox/fesm2022/form-checkbox.mjs +38 -10
- package/form-checkbox/types/form-checkbox.d.ts +16 -1
- package/form-field-simple/fesm2022/form-field-simple.mjs +24 -7
- package/form-field-simple/types/form-field-simple.d.ts +7 -0
- package/package.json +1 -1
package/form-checkbox/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Selector (from source): `eg-form-checkbox` (standalone component).
|
|
|
94
94
|
| `id` | `string \| undefined` | — | Fallback id (second priority). |
|
|
95
95
|
| `name` | `string \| undefined` | — | Checkbox `name` attribute (defaults to `controlName`). |
|
|
96
96
|
| `checked` | `boolean` | `false` | Initial checked state passed to `hlm-checkbox`. |
|
|
97
|
-
| `required` | `boolean` | `false` | Shows a red `*` and sets checkbox `required
|
|
97
|
+
| `required` | `boolean` | `false` | Shows a red `*` and sets checkbox `required` (boolean-coerced, so bare `required` works). Presentation only — it does not validate by itself; add `Validators.requiredTrue` to the `FormControl` or the control is never invalid and no error is shown. |
|
|
98
98
|
| `disabled` | `boolean` | `false` | Locks interaction via checkbox `wrapperDisabled` (does not write to the form control). |
|
|
99
99
|
| `class` | `ClassValue` | `''` | Extra host classes. |
|
|
100
100
|
| `checkboxClass` | `string` | `''` | Extra classes for `hlm-checkbox`. |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, signal, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, DestroyRef, signal, input, computed, booleanAttribute, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
3
4
|
import * as i1 from '@angular/forms';
|
|
4
5
|
import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
|
|
5
6
|
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
@@ -18,6 +19,7 @@ function injectEgFormCheckboxConfig() {
|
|
|
18
19
|
|
|
19
20
|
class EgFormCheckbox {
|
|
20
21
|
formGroupDirective = inject(FormGroupDirective);
|
|
22
|
+
_destroyRef = inject(DestroyRef);
|
|
21
23
|
_errorMessages = injectEgFormErrorMessages();
|
|
22
24
|
generatedId = inject(HlmFormIdGenerator).generate('eg-form-checkbox');
|
|
23
25
|
_config = injectEgFormCheckboxConfig();
|
|
@@ -34,6 +36,14 @@ class EgFormCheckbox {
|
|
|
34
36
|
...(ngDevMode ? [{ debugName: "_status" }] : /* istanbul ignore next */ []));
|
|
35
37
|
_submitted = signal(false, /* @ts-ignore */
|
|
36
38
|
...(ngDevMode ? [{ debugName: "_submitted" }] : /* istanbul ignore next */ []));
|
|
39
|
+
/**
|
|
40
|
+
* `touched`/`dirty` are plain flags with no observable: a stepper surfacing
|
|
41
|
+
* errors via `markAllAsTouched()` (no status change, no submit) would
|
|
42
|
+
* otherwise stay invisible under OnPush. Polled in `ngDoCheck` so the
|
|
43
|
+
* computed below recomputes on interaction changes too.
|
|
44
|
+
*/
|
|
45
|
+
_interacted = signal(false, /* @ts-ignore */
|
|
46
|
+
...(ngDevMode ? [{ debugName: "_interacted" }] : /* istanbul ignore next */ []));
|
|
37
47
|
_trackedControl = null;
|
|
38
48
|
_controlSub = null;
|
|
39
49
|
// Classes
|
|
@@ -64,8 +74,13 @@ class EgFormCheckbox {
|
|
|
64
74
|
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
65
75
|
checked = input(false, /* @ts-ignore */
|
|
66
76
|
...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Shows the red `*` marker and sets the native `required` on the checkbox.
|
|
79
|
+
* Note: this does not add validation by itself — pair it with
|
|
80
|
+
* `Validators.requiredTrue` on the `FormControl`, otherwise the control is
|
|
81
|
+
* never invalid and no error is displayed.
|
|
82
|
+
*/
|
|
83
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
69
84
|
disabled = input(false, /* @ts-ignore */
|
|
70
85
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
71
86
|
effectiveId = computed(() => this.controlId() || this.id() || this.generatedId, /* @ts-ignore */
|
|
@@ -100,10 +115,9 @@ class EgFormCheckbox {
|
|
|
100
115
|
showError = computed(() => {
|
|
101
116
|
this._status();
|
|
102
117
|
this._submitted();
|
|
118
|
+
this._interacted();
|
|
103
119
|
const control = this.formGroupDirective.form.get(this.controlName());
|
|
104
|
-
return
|
|
105
|
-
!!control?.invalid &&
|
|
106
|
-
(control.dirty || control.touched || this._submitted()));
|
|
120
|
+
return !!this.resolvedError() && !!control?.invalid && (this._interacted() || this._submitted());
|
|
107
121
|
}, /* @ts-ignore */
|
|
108
122
|
...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
109
123
|
describedBy() {
|
|
@@ -111,6 +125,16 @@ class EgFormCheckbox {
|
|
|
111
125
|
}
|
|
112
126
|
ngAfterContentInit() {
|
|
113
127
|
this._trackControl();
|
|
128
|
+
// `ngSubmit` emits synchronously during event dispatch, before change
|
|
129
|
+
// detection traverses. Flipping the signal here (instead of only polling
|
|
130
|
+
// `submitted` in `ngDoCheck`) marks this OnPush view dirty up-front, so
|
|
131
|
+
// the error renders in the same pass even when no `@Input()` changed and
|
|
132
|
+
// the control itself never became touched/dirty. `ngDoCheck` below stays
|
|
133
|
+
// as the fallback (e.g. `resetForm()` clears `submitted` silently).
|
|
134
|
+
this.formGroupDirective.ngSubmit.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => {
|
|
135
|
+
if (!this._submitted())
|
|
136
|
+
this._submitted.set(true);
|
|
137
|
+
});
|
|
114
138
|
}
|
|
115
139
|
ngDoCheck() {
|
|
116
140
|
const submitted = !!this.formGroupDirective?.submitted;
|
|
@@ -119,11 +143,14 @@ class EgFormCheckbox {
|
|
|
119
143
|
const control = this.formGroupDirective.form.get(this.controlName()) ?? null;
|
|
120
144
|
if (control !== this._trackedControl) {
|
|
121
145
|
this._trackControl();
|
|
146
|
+
return;
|
|
122
147
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
if (control) {
|
|
149
|
+
if (this._status() !== control.status)
|
|
150
|
+
this._status.set(control.status);
|
|
151
|
+
const interacted = control.touched || control.dirty;
|
|
152
|
+
if (this._interacted() !== interacted)
|
|
153
|
+
this._interacted.set(interacted);
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
ngOnDestroy() {
|
|
@@ -136,6 +163,7 @@ class EgFormCheckbox {
|
|
|
136
163
|
const control = this.formGroupDirective.form.get(this.controlName()) ?? null;
|
|
137
164
|
this._trackedControl = control;
|
|
138
165
|
this._status.set(control?.status ?? null);
|
|
166
|
+
this._interacted.set(!!control && (control.touched || control.dirty));
|
|
139
167
|
if (control) {
|
|
140
168
|
this._controlSub = control.statusChanges.subscribe((status) => {
|
|
141
169
|
if (this._status() !== status)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
2
|
import { DoCheck, AfterContentInit, OnDestroy, ValueProvider } from '@angular/core';
|
|
3
|
+
import { BooleanInput } from '@angular/cdk/coercion';
|
|
3
4
|
import { ClassValue } from 'clsx';
|
|
4
5
|
|
|
5
6
|
declare class EgFormCheckbox implements DoCheck, AfterContentInit, OnDestroy {
|
|
6
7
|
private readonly formGroupDirective;
|
|
8
|
+
private readonly _destroyRef;
|
|
7
9
|
private readonly _errorMessages;
|
|
8
10
|
private readonly generatedId;
|
|
9
11
|
private readonly _config;
|
|
@@ -18,6 +20,13 @@ declare class EgFormCheckbox implements DoCheck, AfterContentInit, OnDestroy {
|
|
|
18
20
|
*/
|
|
19
21
|
private readonly _status;
|
|
20
22
|
private readonly _submitted;
|
|
23
|
+
/**
|
|
24
|
+
* `touched`/`dirty` are plain flags with no observable: a stepper surfacing
|
|
25
|
+
* errors via `markAllAsTouched()` (no status change, no submit) would
|
|
26
|
+
* otherwise stay invisible under OnPush. Polled in `ngDoCheck` so the
|
|
27
|
+
* computed below recomputes on interaction changes too.
|
|
28
|
+
*/
|
|
29
|
+
private readonly _interacted;
|
|
21
30
|
private _trackedControl;
|
|
22
31
|
private _controlSub;
|
|
23
32
|
readonly userClass: _angular_core.InputSignal<ClassValue>;
|
|
@@ -36,7 +45,13 @@ declare class EgFormCheckbox implements DoCheck, AfterContentInit, OnDestroy {
|
|
|
36
45
|
id: _angular_core.InputSignal<string | undefined>;
|
|
37
46
|
name: _angular_core.InputSignal<string | undefined>;
|
|
38
47
|
checked: _angular_core.InputSignal<boolean>;
|
|
39
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Shows the red `*` marker and sets the native `required` on the checkbox.
|
|
50
|
+
* Note: this does not add validation by itself — pair it with
|
|
51
|
+
* `Validators.requiredTrue` on the `FormControl`, otherwise the control is
|
|
52
|
+
* never invalid and no error is displayed.
|
|
53
|
+
*/
|
|
54
|
+
required: _angular_core.InputSignalWithTransform<boolean, BooleanInput>;
|
|
40
55
|
disabled: _angular_core.InputSignal<boolean>;
|
|
41
56
|
readonly effectiveId: _angular_core.Signal<string>;
|
|
42
57
|
readonly errorId: _angular_core.Signal<string>;
|
|
@@ -21,6 +21,14 @@ class EgFormField {
|
|
|
21
21
|
*/
|
|
22
22
|
submittedSignal = signal(false, /* @ts-ignore */
|
|
23
23
|
...(ngDevMode ? [{ debugName: "submittedSignal" }] : /* istanbul ignore next */ []));
|
|
24
|
+
/**
|
|
25
|
+
* `touched`/`dirty` are plain flags with no observable: surfacing errors via
|
|
26
|
+
* `markAllAsTouched()` (e.g. a stepper blocking `Next` without changing the
|
|
27
|
+
* control status) would otherwise stay invisible under OnPush. Polled in
|
|
28
|
+
* `ngDoCheck` so `hasError` recomputes on interaction changes too.
|
|
29
|
+
*/
|
|
30
|
+
interactedSignal = signal(false, /* @ts-ignore */
|
|
31
|
+
...(ngDevMode ? [{ debugName: "interactedSignal" }] : /* istanbul ignore next */ []));
|
|
24
32
|
sub = new Subscription();
|
|
25
33
|
ngAfterContentInit() {
|
|
26
34
|
const ctrlDir = this.control();
|
|
@@ -29,32 +37,41 @@ class EgFormField {
|
|
|
29
37
|
this.statusSignal.set(status);
|
|
30
38
|
}));
|
|
31
39
|
this.statusSignal.set(ctrlDir.control.status);
|
|
40
|
+
this.interactedSignal.set(ctrlDir.control.touched || ctrlDir.control.dirty);
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
43
|
ngDoCheck() {
|
|
35
44
|
const submitted = !!this.formGroupDirective?.submitted;
|
|
36
45
|
if (this.submittedSignal() !== submitted)
|
|
37
46
|
this.submittedSignal.set(submitted);
|
|
47
|
+
// NOTE: `control` itself may still be unset on the first pass (this hook
|
|
48
|
+
// runs before the content child's setup), so optional-chain everything.
|
|
49
|
+
const ctrl = this.control()?.control ?? null;
|
|
50
|
+
if (ctrl) {
|
|
51
|
+
const interacted = ctrl.touched || ctrl.dirty;
|
|
52
|
+
if (this.interactedSignal() !== interacted)
|
|
53
|
+
this.interactedSignal.set(interacted);
|
|
54
|
+
}
|
|
38
55
|
}
|
|
39
56
|
ngOnDestroy() {
|
|
40
57
|
this.sub.unsubscribe();
|
|
41
58
|
}
|
|
42
59
|
hasError = computed(() => {
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
60
|
+
const ctrl = this.control()?.control ?? null;
|
|
61
|
+
if (!ctrl)
|
|
45
62
|
return false;
|
|
46
63
|
this.statusSignal();
|
|
47
64
|
this.submittedSignal();
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
this.interactedSignal();
|
|
66
|
+
return !!ctrl.errors && (this.interactedSignal() || this.submittedSignal());
|
|
50
67
|
}, /* @ts-ignore */
|
|
51
68
|
...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
|
|
52
69
|
firstErrorKey = computed(() => {
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
70
|
+
const ctrl = this.control()?.control ?? null;
|
|
71
|
+
if (!ctrl?.errors)
|
|
55
72
|
return null;
|
|
56
73
|
this.statusSignal();
|
|
57
|
-
return Object.keys(
|
|
74
|
+
return Object.keys(ctrl.errors)[0] ?? null;
|
|
58
75
|
}, /* @ts-ignore */
|
|
59
76
|
...(ngDevMode ? [{ debugName: "firstErrorKey" }] : /* istanbul ignore next */ []));
|
|
60
77
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -16,6 +16,13 @@ declare class EgFormField implements AfterContentInit, DoCheck, OnDestroy {
|
|
|
16
16
|
* even when the control was never touched/dirty.
|
|
17
17
|
*/
|
|
18
18
|
private submittedSignal;
|
|
19
|
+
/**
|
|
20
|
+
* `touched`/`dirty` are plain flags with no observable: surfacing errors via
|
|
21
|
+
* `markAllAsTouched()` (e.g. a stepper blocking `Next` without changing the
|
|
22
|
+
* control status) would otherwise stay invisible under OnPush. Polled in
|
|
23
|
+
* `ngDoCheck` so `hasError` recomputes on interaction changes too.
|
|
24
|
+
*/
|
|
25
|
+
private interactedSignal;
|
|
19
26
|
private sub;
|
|
20
27
|
ngAfterContentInit(): void;
|
|
21
28
|
ngDoCheck(): void;
|