ng-primitives 0.91.2 → 0.93.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.
- package/a11y/index.d.ts +3 -1
- package/accordion/index.d.ts +8 -1
- package/autofill/index.d.ts +4 -0
- package/avatar/index.d.ts +11 -2
- package/breadcrumbs/index.d.ts +21 -7
- package/button/index.d.ts +3 -1
- package/checkbox/index.d.ts +3 -1
- package/fesm2022/ng-primitives-checkbox.mjs +7 -8
- package/fesm2022/ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/ng-primitives-file-upload.mjs +209 -202
- package/fesm2022/ng-primitives-file-upload.mjs.map +1 -1
- package/fesm2022/ng-primitives-focus-trap.mjs +107 -138
- package/fesm2022/ng-primitives-focus-trap.mjs.map +1 -1
- package/fesm2022/ng-primitives-form-field.mjs +323 -399
- package/fesm2022/ng-primitives-form-field.mjs.map +1 -1
- package/fesm2022/ng-primitives-interactions.mjs +57 -58
- package/fesm2022/ng-primitives-interactions.mjs.map +1 -1
- package/fesm2022/ng-primitives-listbox.mjs +2 -2
- package/fesm2022/ng-primitives-listbox.mjs.map +1 -1
- package/fesm2022/ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/ng-primitives-portal.mjs +5 -1
- package/fesm2022/ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/ng-primitives-slider.mjs +4 -5
- package/fesm2022/ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/ng-primitives-state.mjs +36 -8
- package/fesm2022/ng-primitives-state.mjs.map +1 -1
- package/fesm2022/ng-primitives-switch.mjs +4 -5
- package/fesm2022/ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/ng-primitives-tabs.mjs +194 -192
- package/fesm2022/ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/ng-primitives-toggle-group.mjs +4 -5
- package/fesm2022/ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/ng-primitives-toggle.mjs +4 -5
- package/fesm2022/ng-primitives-toggle.mjs.map +1 -1
- package/fesm2022/ng-primitives-tooltip.mjs +4 -4
- package/fesm2022/ng-primitives-tooltip.mjs.map +1 -1
- package/fesm2022/ng-primitives-utils.mjs +23 -14
- package/fesm2022/ng-primitives-utils.mjs.map +1 -1
- package/file-upload/index.d.ts +100 -52
- package/focus-trap/index.d.ts +33 -75
- package/form-field/index.d.ts +320 -123
- package/input/index.d.ts +6 -0
- package/interactions/index.d.ts +16 -16
- package/package.json +1 -1
- package/roving-focus/index.d.ts +6 -2
- package/schematics/ng-generate/templates/tabs/tabs.__fileSuffix@dasherize__.ts.template +2 -2
- package/slider/index.d.ts +14 -6
- package/state/index.d.ts +23 -8
- package/switch/index.d.ts +8 -4
- package/tabs/index.d.ts +333 -84
- package/textarea/index.d.ts +6 -0
- package/toggle/index.d.ts +5 -3
- package/toggle-group/index.d.ts +6 -2
- package/toolbar/index.d.ts +5 -0
- package/utils/index.d.ts +1 -1
|
@@ -1,75 +1,253 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import { uniqueId, onBooleanChange, controlStatus
|
|
4
|
-
import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
|
|
2
|
+
import { inject, Injector, signal, untracked, effect, input, Directive, computed, booleanAttribute, contentChild } from '@angular/core';
|
|
3
|
+
import { onChange, uniqueId, onBooleanChange, controlStatus } from 'ng-primitives/utils';
|
|
5
4
|
import { injectElementRef, explicitEffect } from 'ng-primitives/internal';
|
|
5
|
+
import { createPrimitive, dataBinding, onDestroy, attrBinding, listener } from 'ng-primitives/state';
|
|
6
6
|
import { NgControl } from '@angular/forms';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
8
|
+
const [NgpFormFieldStateToken, ngpFormField, injectFormFieldState, provideFormFieldState] = createPrimitive('NgpFormField', ({ ngControl }) => {
|
|
9
|
+
const element = injectElementRef();
|
|
10
|
+
const injector = inject(Injector);
|
|
11
|
+
// Store the form labels
|
|
12
|
+
const labels = signal([], ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
13
|
+
// Store the form descriptions
|
|
14
|
+
const descriptions = signal([], ...(ngDevMode ? [{ debugName: "descriptions" }] : []));
|
|
15
|
+
// Store the id of the associated form control
|
|
16
|
+
const formControl = signal(null, ...(ngDevMode ? [{ debugName: "formControl" }] : []));
|
|
17
|
+
// Store the validation error messages
|
|
18
|
+
const errors = signal([], ...(ngDevMode ? [{ debugName: "errors" }] : []));
|
|
19
|
+
// Form control state signals
|
|
20
|
+
const pristine = signal(null, ...(ngDevMode ? [{ debugName: "pristine" }] : []));
|
|
21
|
+
const touched = signal(null, ...(ngDevMode ? [{ debugName: "touched" }] : []));
|
|
22
|
+
const dirty = signal(null, ...(ngDevMode ? [{ debugName: "dirty" }] : []));
|
|
23
|
+
const valid = signal(null, ...(ngDevMode ? [{ debugName: "valid" }] : []));
|
|
24
|
+
const invalid = signal(null, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
|
|
25
|
+
const pending = signal(null, ...(ngDevMode ? [{ debugName: "pending" }] : []));
|
|
26
|
+
const disabled = signal(null, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
27
|
+
// Store the current status subscription
|
|
28
|
+
let subscription;
|
|
29
|
+
// Host bindings
|
|
30
|
+
dataBinding(element, 'data-invalid', invalid);
|
|
31
|
+
dataBinding(element, 'data-valid', valid);
|
|
32
|
+
dataBinding(element, 'data-touched', touched);
|
|
33
|
+
dataBinding(element, 'data-pristine', pristine);
|
|
34
|
+
dataBinding(element, 'data-dirty', dirty);
|
|
35
|
+
dataBinding(element, 'data-pending', pending);
|
|
36
|
+
dataBinding(element, 'data-disabled', disabled);
|
|
37
|
+
function updateStatus() {
|
|
38
|
+
const control = ngControl();
|
|
39
|
+
if (!control) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Wrap in try-catch to handle signal-forms interop controls where
|
|
43
|
+
// the `field` input may not be available yet (throws NG0950).
|
|
44
|
+
// Reading the signal still establishes a dependency, so the effect
|
|
45
|
+
// will re-run when the input becomes available.
|
|
46
|
+
try {
|
|
47
|
+
const controlPristine = control.pristine;
|
|
48
|
+
const controlTouched = control.touched;
|
|
49
|
+
const controlDirty = control.dirty;
|
|
50
|
+
const controlValid = control.valid;
|
|
51
|
+
const controlInvalid = control.invalid;
|
|
52
|
+
const controlPending = control.pending;
|
|
53
|
+
const controlDisabled = control.disabled;
|
|
54
|
+
const controlErrors = control.errors;
|
|
55
|
+
untracked(() => {
|
|
56
|
+
pristine.set(controlPristine);
|
|
57
|
+
touched.set(controlTouched);
|
|
58
|
+
dirty.set(controlDirty);
|
|
59
|
+
valid.set(controlValid);
|
|
60
|
+
invalid.set(controlInvalid);
|
|
61
|
+
pending.set(controlPending);
|
|
62
|
+
disabled.set(controlDisabled);
|
|
63
|
+
errors.set(controlErrors ? Object.keys(controlErrors) : []);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// NG0950: Required input not available yet. The effect will re-run
|
|
68
|
+
// when the signal input becomes available.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function setupSubscriptions(control) {
|
|
72
|
+
// Unsubscribe from the previous subscriptions.
|
|
73
|
+
subscription?.unsubscribe();
|
|
74
|
+
if (!control) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// For signal-forms interop controls, use an effect to reactively track status.
|
|
78
|
+
// For classic controls, also use an effect but additionally subscribe to events.
|
|
79
|
+
effect(() => {
|
|
80
|
+
updateStatus();
|
|
81
|
+
}, { injector });
|
|
82
|
+
// Classic controls also have an events observable we can subscribe to.
|
|
83
|
+
const underlyingControl = control?.control;
|
|
84
|
+
if (underlyingControl?.events) {
|
|
85
|
+
subscription = underlyingControl.events.subscribe(() => updateStatus());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Setup subscriptions when ngControl changes
|
|
89
|
+
onChange(ngControl, setupSubscriptions);
|
|
90
|
+
// Cleanup subscription on destroy
|
|
91
|
+
onDestroy(() => subscription?.unsubscribe());
|
|
92
|
+
// Methods
|
|
93
|
+
function setFormControl(id) {
|
|
94
|
+
formControl.set(id);
|
|
95
|
+
}
|
|
96
|
+
function addLabel(label) {
|
|
97
|
+
if (labels().includes(label)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
labels.update(currentLabels => [...currentLabels, label]);
|
|
101
|
+
}
|
|
102
|
+
function addDescription(description) {
|
|
103
|
+
if (descriptions().includes(description)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
descriptions.update(currentDescriptions => [...currentDescriptions, description]);
|
|
107
|
+
}
|
|
108
|
+
function removeFormControl() {
|
|
109
|
+
formControl.set(null);
|
|
110
|
+
}
|
|
111
|
+
function removeLabel(label) {
|
|
112
|
+
labels.update(currentLabels => currentLabels.filter(l => l !== label));
|
|
113
|
+
}
|
|
114
|
+
function removeDescription(description) {
|
|
115
|
+
descriptions.update(currentDescriptions => currentDescriptions.filter(d => d !== description));
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
labels,
|
|
119
|
+
descriptions,
|
|
120
|
+
formControl,
|
|
121
|
+
errors,
|
|
122
|
+
pristine,
|
|
123
|
+
touched,
|
|
124
|
+
dirty,
|
|
125
|
+
valid,
|
|
126
|
+
invalid,
|
|
127
|
+
pending,
|
|
128
|
+
disabled,
|
|
129
|
+
setFormControl,
|
|
130
|
+
addLabel,
|
|
131
|
+
addDescription,
|
|
132
|
+
removeFormControl,
|
|
133
|
+
removeLabel,
|
|
134
|
+
removeDescription,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const [NgpDescriptionStateToken, ngpDescription, injectDescriptionState, provideDescriptionState,] = createPrimitive('NgpDescription', ({ id = signal(uniqueId('ngp-description')) }) => {
|
|
139
|
+
const element = injectElementRef();
|
|
140
|
+
const formField = injectFormFieldState({ optional: true });
|
|
141
|
+
// Host bindings
|
|
142
|
+
attrBinding(element, 'id', id);
|
|
143
|
+
dataBinding(element, 'data-invalid', () => (formField()?.invalid() ? '' : null));
|
|
144
|
+
dataBinding(element, 'data-valid', () => (formField()?.valid() ? '' : null));
|
|
145
|
+
dataBinding(element, 'data-touched', () => (formField()?.touched() ? '' : null));
|
|
146
|
+
dataBinding(element, 'data-pristine', () => (formField()?.pristine() ? '' : null));
|
|
147
|
+
dataBinding(element, 'data-dirty', () => (formField()?.dirty() ? '' : null));
|
|
148
|
+
dataBinding(element, 'data-pending', () => (formField()?.pending() ? '' : null));
|
|
149
|
+
dataBinding(element, 'data-disabled', () => (formField()?.disabled() ? '' : null));
|
|
150
|
+
// Register with form field and cleanup on destroy
|
|
151
|
+
formField()?.addDescription(id());
|
|
152
|
+
onDestroy(() => formField()?.removeDescription(id()));
|
|
153
|
+
onChange(id, (newId, oldId) => {
|
|
154
|
+
if (oldId) {
|
|
155
|
+
formField()?.removeDescription(oldId);
|
|
156
|
+
}
|
|
157
|
+
formField()?.addDescription(newId);
|
|
158
|
+
});
|
|
159
|
+
return { id };
|
|
160
|
+
});
|
|
24
161
|
|
|
25
162
|
/**
|
|
26
163
|
* The `NgpDescription` directive is used to mark a description element within a form field. There may be multiple descriptions associated with a form control.
|
|
27
164
|
*/
|
|
28
165
|
class NgpDescription {
|
|
166
|
+
/**
|
|
167
|
+
* The description state.
|
|
168
|
+
*/
|
|
29
169
|
constructor() {
|
|
30
170
|
/**
|
|
31
171
|
* The id of the description. If not provided, a unique id will be generated.
|
|
32
172
|
*/
|
|
33
173
|
this.id = input(uniqueId('ngp-description'), ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
34
|
-
|
|
35
|
-
* Access the form field that the description is associated with.
|
|
36
|
-
*/
|
|
37
|
-
this.formField = injectFormFieldState({ optional: true });
|
|
38
|
-
effect(onCleanup => {
|
|
39
|
-
this.formField()?.addDescription(this.id());
|
|
40
|
-
onCleanup(() => this.formField()?.removeDescription(this.id()));
|
|
41
|
-
});
|
|
174
|
+
ngpDescription({ id: this.id });
|
|
42
175
|
}
|
|
43
176
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
44
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpDescription, isStandalone: true, selector: "[ngpDescription]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } },
|
|
177
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpDescription, isStandalone: true, selector: "[ngpDescription]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideDescriptionState()], exportAs: ["ngpDescription"], ngImport: i0 }); }
|
|
45
178
|
}
|
|
46
179
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpDescription, decorators: [{
|
|
47
180
|
type: Directive,
|
|
48
181
|
args: [{
|
|
49
182
|
selector: '[ngpDescription]',
|
|
50
183
|
exportAs: 'ngpDescription',
|
|
51
|
-
|
|
52
|
-
'[attr.id]': 'id()',
|
|
53
|
-
'[attr.data-invalid]': 'formField()?.invalid() ? "" : null',
|
|
54
|
-
'[attr.data-valid]': 'formField()?.valid() ? "" : null',
|
|
55
|
-
'[attr.data-touched]': 'formField()?.touched() ? "" : null',
|
|
56
|
-
'[attr.data-pristine]': 'formField()?.pristine() ? "" : null',
|
|
57
|
-
'[attr.data-dirty]': 'formField()?.dirty() ? "" : null',
|
|
58
|
-
'[attr.data-pending]': 'formField()?.pending() ? "" : null',
|
|
59
|
-
'[attr.data-disabled]': 'formField()?.disabled() ? "" : null',
|
|
60
|
-
},
|
|
184
|
+
providers: [provideDescriptionState()],
|
|
61
185
|
}]
|
|
62
186
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
63
187
|
|
|
188
|
+
const [NgpErrorStateToken, ngpError, injectErrorState, provideErrorState] = createPrimitive('NgpError', ({ id, validator = signal(null) }) => {
|
|
189
|
+
const element = injectElementRef();
|
|
190
|
+
const formField = injectFormFieldState({ optional: true });
|
|
191
|
+
// Determine if there is an error message
|
|
192
|
+
const hasError = computed(() => {
|
|
193
|
+
const errors = formField()?.errors() ?? [];
|
|
194
|
+
const validatorValue = validator();
|
|
195
|
+
return validatorValue ? errors?.includes(validatorValue) : errors?.length > 0;
|
|
196
|
+
}, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
|
|
197
|
+
// Determine whether the validator associated with this error is failing
|
|
198
|
+
const state = computed(() => (hasError() ? 'fail' : 'pass'), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
199
|
+
// Host bindings
|
|
200
|
+
attrBinding(element, 'id', id);
|
|
201
|
+
dataBinding(element, 'data-invalid', () => (formField()?.invalid() ? '' : null));
|
|
202
|
+
dataBinding(element, 'data-valid', () => (formField()?.valid() ? '' : null));
|
|
203
|
+
dataBinding(element, 'data-touched', () => (formField()?.touched() ? '' : null));
|
|
204
|
+
dataBinding(element, 'data-pristine', () => (formField()?.pristine() ? '' : null));
|
|
205
|
+
dataBinding(element, 'data-dirty', () => (formField()?.dirty() ? '' : null));
|
|
206
|
+
dataBinding(element, 'data-pending', () => (formField()?.pending() ? '' : null));
|
|
207
|
+
dataBinding(element, 'data-disabled', () => (formField()?.disabled() ? '' : null));
|
|
208
|
+
dataBinding(element, 'data-validator', state);
|
|
209
|
+
let currentId = id();
|
|
210
|
+
// Register/unregister with form field based on error state
|
|
211
|
+
function registerError() {
|
|
212
|
+
formField()?.addDescription(currentId);
|
|
213
|
+
}
|
|
214
|
+
function unregisterError() {
|
|
215
|
+
formField()?.removeDescription(currentId);
|
|
216
|
+
}
|
|
217
|
+
// Update error registration when hasError changes
|
|
218
|
+
onBooleanChange(hasError, registerError, unregisterError);
|
|
219
|
+
function updateIdRegistration(newId, oldId) {
|
|
220
|
+
if (oldId && hasError()) {
|
|
221
|
+
formField()?.removeDescription(oldId);
|
|
222
|
+
}
|
|
223
|
+
currentId = newId;
|
|
224
|
+
if (hasError()) {
|
|
225
|
+
formField()?.addDescription(newId);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Watch for id changes to update registration
|
|
229
|
+
explicitEffect([id], () => updateIdRegistration(id(), currentId));
|
|
230
|
+
// Cleanup on destroy
|
|
231
|
+
onDestroy(() => {
|
|
232
|
+
if (hasError()) {
|
|
233
|
+
formField()?.removeDescription(currentId);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
id,
|
|
238
|
+
hasError,
|
|
239
|
+
state,
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
|
|
64
243
|
/**
|
|
65
244
|
* The `NgpError` directive is used to mark an error message element within a form field. There may be multiple error messages associated with a form control.
|
|
66
245
|
*/
|
|
67
246
|
class NgpError {
|
|
247
|
+
/**
|
|
248
|
+
* The error state.
|
|
249
|
+
*/
|
|
68
250
|
constructor() {
|
|
69
|
-
/**
|
|
70
|
-
* Access the form field that the description is associated with.
|
|
71
|
-
*/
|
|
72
|
-
this.formField = injectFormFieldState({ optional: true });
|
|
73
251
|
/**
|
|
74
252
|
* The id of the error message. If not provided, a unique id will be generated.
|
|
75
253
|
*/
|
|
@@ -80,67 +258,55 @@ class NgpError {
|
|
|
80
258
|
this.validator = input(null, ...(ngDevMode ? [{ debugName: "validator", alias: 'ngpErrorValidator' }] : [{
|
|
81
259
|
alias: 'ngpErrorValidator',
|
|
82
260
|
}]));
|
|
83
|
-
|
|
84
|
-
* Determine if there is an error message.
|
|
85
|
-
*/
|
|
86
|
-
this.hasError = computed(() => {
|
|
87
|
-
const errors = this.formField()?.errors() ?? [];
|
|
88
|
-
const validator = this.validator();
|
|
89
|
-
return validator ? errors?.includes(validator) : errors?.length > 0;
|
|
90
|
-
}, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
|
|
91
|
-
/**
|
|
92
|
-
* Determine whether the validator associated with this error is failing.
|
|
93
|
-
*/
|
|
94
|
-
this.state = computed(() => (this.hasError() ? 'fail' : 'pass'), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
95
|
-
// add or remove the error message when the error state changes
|
|
96
|
-
onBooleanChange(this.hasError, () => this.formField()?.addDescription(this.id()), () => this.formField()?.removeDescription(this.id()));
|
|
97
|
-
}
|
|
98
|
-
ngOnChanges(changes) {
|
|
99
|
-
if ('id' in changes) {
|
|
100
|
-
this.formField()?.removeDescription(changes['id'].previousValue);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
ngOnDestroy() {
|
|
104
|
-
this.formField()?.removeDescription(this.id());
|
|
261
|
+
ngpError({ id: this.id, validator: this.validator });
|
|
105
262
|
}
|
|
106
263
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpError, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
107
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpError, isStandalone: true, selector: "[ngpError]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, validator: { classPropertyName: "validator", publicName: "ngpErrorValidator", isSignal: true, isRequired: false, transformFunction: null } },
|
|
264
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpError, isStandalone: true, selector: "[ngpError]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, validator: { classPropertyName: "validator", publicName: "ngpErrorValidator", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideErrorState()], exportAs: ["ngpError"], ngImport: i0 }); }
|
|
108
265
|
}
|
|
109
266
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpError, decorators: [{
|
|
110
267
|
type: Directive,
|
|
111
268
|
args: [{
|
|
112
269
|
selector: '[ngpError]',
|
|
113
270
|
exportAs: 'ngpError',
|
|
114
|
-
|
|
115
|
-
'[attr.id]': 'id()',
|
|
116
|
-
'[attr.data-invalid]': 'formField()?.invalid() ? "" : null',
|
|
117
|
-
'[attr.data-valid]': 'formField()?.valid() ? "" : null',
|
|
118
|
-
'[attr.data-touched]': 'formField()?.touched() ? "" : null',
|
|
119
|
-
'[attr.data-pristine]': 'formField()?.pristine() ? "" : null',
|
|
120
|
-
'[attr.data-dirty]': 'formField()?.dirty() ? "" : null',
|
|
121
|
-
'[attr.data-pending]': 'formField()?.pending() ? "" : null',
|
|
122
|
-
'[attr.data-disabled]': 'formField()?.disabled() ? "" : null',
|
|
123
|
-
'[attr.data-validator]': 'state()',
|
|
124
|
-
},
|
|
271
|
+
providers: [provideErrorState()],
|
|
125
272
|
}]
|
|
126
273
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], validator: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpErrorValidator", required: false }] }] } });
|
|
127
274
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
275
|
+
function ngpFormControl({ id, disabled = signal(false), }) {
|
|
276
|
+
const elementRef = injectElementRef();
|
|
277
|
+
// Access the form field that the form control is associated with.
|
|
278
|
+
const formField = injectFormFieldState({ optional: true });
|
|
279
|
+
// Access the form control status.
|
|
280
|
+
const status = controlStatus();
|
|
281
|
+
// Determine the aria-labelledby attribute value.
|
|
282
|
+
const ariaLabelledBy = computed(() => {
|
|
283
|
+
const labels = formField()?.labels() ?? [];
|
|
284
|
+
return labels.length > 0 ? labels.join(' ') : null;
|
|
285
|
+
}, ...(ngDevMode ? [{ debugName: "ariaLabelledBy" }] : []));
|
|
286
|
+
// Determine the aria-describedby attribute value.
|
|
287
|
+
const ariaDescribedBy = computed(() => {
|
|
288
|
+
const descriptions = formField()?.descriptions() ?? [];
|
|
289
|
+
return descriptions.length > 0 ? descriptions.join(' ') : null;
|
|
290
|
+
}, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
|
|
291
|
+
const supportsDisabledAttribute = 'disabled' in elementRef.nativeElement;
|
|
292
|
+
// Host bindings
|
|
293
|
+
attrBinding(elementRef, 'disabled', () => (supportsDisabledAttribute && disabled() ? '' : null));
|
|
294
|
+
explicitEffect([id], ([id], onCleanup) => {
|
|
295
|
+
formField()?.setFormControl(id);
|
|
296
|
+
onCleanup(() => formField()?.removeFormControl());
|
|
297
|
+
});
|
|
298
|
+
attrBinding(elementRef, 'id', id);
|
|
299
|
+
attrBinding(elementRef, 'aria-labelledby', ariaLabelledBy);
|
|
300
|
+
attrBinding(elementRef, 'aria-describedby', ariaDescribedBy);
|
|
301
|
+
dataBinding(elementRef, 'data-invalid', () => status().invalid);
|
|
302
|
+
dataBinding(elementRef, 'data-valid', () => status().valid);
|
|
303
|
+
dataBinding(elementRef, 'data-touched', () => status().touched);
|
|
304
|
+
dataBinding(elementRef, 'data-pristine', () => status().pristine);
|
|
305
|
+
dataBinding(elementRef, 'data-dirty', () => status().dirty);
|
|
306
|
+
dataBinding(elementRef, 'data-pending', () => status().pending);
|
|
307
|
+
dataBinding(elementRef, 'data-disabled', () => disabled() || status().disabled);
|
|
308
|
+
return computed(() => ({ ...status(), disabled: status().disabled || disabled() }));
|
|
309
|
+
}
|
|
144
310
|
|
|
145
311
|
/**
|
|
146
312
|
* Typically this primitive would be not be used directly, but instead a more specific form control primitive would be used (e.g. `ngpInput`). All of our form control primitives use `ngpFormControl` internally so they will have the same accessibility features as described below.
|
|
@@ -162,277 +328,38 @@ class NgpFormControl {
|
|
|
162
328
|
transform: booleanAttribute,
|
|
163
329
|
}]));
|
|
164
330
|
/**
|
|
165
|
-
* The
|
|
166
|
-
*/
|
|
167
|
-
this.elementRef = injectElementRef();
|
|
168
|
-
/**
|
|
169
|
-
* Whether the element supports the disabled attribute.
|
|
331
|
+
* The status of the form control.
|
|
170
332
|
*/
|
|
171
|
-
this.
|
|
172
|
-
/**
|
|
173
|
-
* The state of the form control.
|
|
174
|
-
*/
|
|
175
|
-
this.state = formControlState(this);
|
|
176
|
-
// Sync the form control state with the control state.
|
|
177
|
-
this.status = ngpFormControl({ id: this.state.id, disabled: this.state.disabled });
|
|
333
|
+
this.status = ngpFormControl({ id: this.id, disabled: this.disabled });
|
|
178
334
|
}
|
|
179
335
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpFormControl, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
180
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpFormControl, isStandalone: true, selector: "[ngpFormControl]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpFormControlDisabled", isSignal: true, isRequired: false, transformFunction: null } },
|
|
336
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpFormControl, isStandalone: true, selector: "[ngpFormControl]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpFormControlDisabled", isSignal: true, isRequired: false, transformFunction: null } }, exportAs: ["ngpFormControl"], ngImport: i0 }); }
|
|
181
337
|
}
|
|
182
338
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpFormControl, decorators: [{
|
|
183
339
|
type: Directive,
|
|
184
340
|
args: [{
|
|
185
341
|
selector: '[ngpFormControl]',
|
|
186
342
|
exportAs: 'ngpFormControl',
|
|
187
|
-
providers: [provideFormControlState()],
|
|
188
|
-
host: {
|
|
189
|
-
'[attr.disabled]': 'supportsDisabledAttribute && status().disabled ? "" : null',
|
|
190
|
-
},
|
|
191
343
|
}]
|
|
192
|
-
}],
|
|
193
|
-
function ngpFormControl({ id, disabled = signal(false), }) {
|
|
194
|
-
const element = injectElementRef().nativeElement;
|
|
195
|
-
// Access the form field that the form control is associated with.
|
|
196
|
-
const formField = injectFormFieldState({ optional: true });
|
|
197
|
-
// Access the form control status.
|
|
198
|
-
const status = controlStatus();
|
|
199
|
-
// Determine the aria-labelledby attribute value.
|
|
200
|
-
const ariaLabelledBy = computed(() => formField()?.labels().join(' '), ...(ngDevMode ? [{ debugName: "ariaLabelledBy" }] : []));
|
|
201
|
-
// Determine the aria-describedby attribute value.
|
|
202
|
-
const ariaDescribedBy = computed(() => formField()?.descriptions().join(' '), ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
|
|
203
|
-
explicitEffect([id], ([id], onCleanup) => {
|
|
204
|
-
formField()?.setFormControl(id);
|
|
205
|
-
onCleanup(() => formField()?.removeFormControl());
|
|
206
|
-
});
|
|
207
|
-
afterRenderEffect({
|
|
208
|
-
write: () => {
|
|
209
|
-
setAttribute(element, 'id', id());
|
|
210
|
-
setAttribute(element, 'aria-labelledby', ariaLabelledBy());
|
|
211
|
-
setAttribute(element, 'aria-describedby', ariaDescribedBy());
|
|
212
|
-
setStateAttribute(element, status().invalid, 'data-invalid');
|
|
213
|
-
setStateAttribute(element, status().valid, 'data-valid');
|
|
214
|
-
setStateAttribute(element, status().touched, 'data-touched');
|
|
215
|
-
setStateAttribute(element, status().pristine, 'data-pristine');
|
|
216
|
-
setStateAttribute(element, status().dirty, 'data-dirty');
|
|
217
|
-
setStateAttribute(element, status().pending, 'data-pending');
|
|
218
|
-
setStateAttribute(element, disabled() || status().disabled, 'data-disabled');
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
return computed(() => ({ ...status(), disabled: status().disabled || disabled() }));
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Sets the attribute on the element. If the value is not empty, the attribute is set to the value.
|
|
225
|
-
* If the value is empty, the attribute is removed.
|
|
226
|
-
* @param element The element to set the attribute on.
|
|
227
|
-
* @param attribute The attribute to set on the element.
|
|
228
|
-
* @param value The value to set on the attribute.
|
|
229
|
-
*/
|
|
230
|
-
function setAttribute(element, attribute, value) {
|
|
231
|
-
if (value && value.length > 0) {
|
|
232
|
-
element.setAttribute(attribute, value);
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
element.removeAttribute(attribute);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Sets the attribute on the element based on the state. If the state is true, the attribute
|
|
240
|
-
* is set to an empty string. If the state is false, the attribute is removed.
|
|
241
|
-
* @param element The element to set the attribute on.
|
|
242
|
-
* @param state The state to set the attribute based on.
|
|
243
|
-
* @param attribute The attribute to set on the element.
|
|
244
|
-
*/
|
|
245
|
-
function setStateAttribute(element, state, attribute) {
|
|
246
|
-
if (state) {
|
|
247
|
-
element.setAttribute(attribute, '');
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
element.removeAttribute(attribute);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
344
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpFormControlDisabled", required: false }] }] } });
|
|
253
345
|
|
|
254
346
|
/**
|
|
255
347
|
* The `NgpFormField` directive is a container for form field elements. Any labels, form controls, or descriptions should be placed within this directive.
|
|
256
348
|
*/
|
|
257
349
|
class NgpFormField {
|
|
350
|
+
/**
|
|
351
|
+
* The form field state.
|
|
352
|
+
*/
|
|
258
353
|
constructor() {
|
|
259
|
-
/**
|
|
260
|
-
* Store the form label.
|
|
261
|
-
* @internal
|
|
262
|
-
*/
|
|
263
|
-
this.labels = signal([], ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
264
|
-
/**
|
|
265
|
-
* Store the form descriptions.
|
|
266
|
-
* @internal
|
|
267
|
-
*/
|
|
268
|
-
this.descriptions = signal([], ...(ngDevMode ? [{ debugName: "descriptions" }] : []));
|
|
269
|
-
/**
|
|
270
|
-
* Store the id of the associated form control.
|
|
271
|
-
* @internal
|
|
272
|
-
*/
|
|
273
|
-
this.formControl = signal(null, ...(ngDevMode ? [{ debugName: "formControl" }] : []));
|
|
274
354
|
/**
|
|
275
355
|
* Find any NgControl within the form field.
|
|
276
356
|
* @internal
|
|
277
357
|
*/
|
|
278
358
|
this.ngControl = contentChild(NgControl, ...(ngDevMode ? [{ debugName: "ngControl" }] : []));
|
|
279
|
-
|
|
280
|
-
* Store the validation error messages.
|
|
281
|
-
* @internal
|
|
282
|
-
*/
|
|
283
|
-
this.errors = signal([], ...(ngDevMode ? [{ debugName: "errors" }] : []));
|
|
284
|
-
/**
|
|
285
|
-
* Whether the control is pristine.
|
|
286
|
-
* @internal
|
|
287
|
-
*/
|
|
288
|
-
this.pristine = signal(null, ...(ngDevMode ? [{ debugName: "pristine" }] : []));
|
|
289
|
-
/**
|
|
290
|
-
* Whether the control is touched.
|
|
291
|
-
* @internal
|
|
292
|
-
*/
|
|
293
|
-
this.touched = signal(null, ...(ngDevMode ? [{ debugName: "touched" }] : []));
|
|
294
|
-
/**
|
|
295
|
-
* Whether the control is dirty.
|
|
296
|
-
* @internal
|
|
297
|
-
*/
|
|
298
|
-
this.dirty = signal(null, ...(ngDevMode ? [{ debugName: "dirty" }] : []));
|
|
299
|
-
/**
|
|
300
|
-
* Whether the control is valid.
|
|
301
|
-
*/
|
|
302
|
-
this.valid = signal(null, ...(ngDevMode ? [{ debugName: "valid" }] : []));
|
|
303
|
-
/**
|
|
304
|
-
* Whether the control is invalid.
|
|
305
|
-
* @internal
|
|
306
|
-
*/
|
|
307
|
-
this.invalid = signal(null, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
|
|
308
|
-
/**
|
|
309
|
-
* Whether the control is pending.
|
|
310
|
-
* @internal
|
|
311
|
-
*/
|
|
312
|
-
this.pending = signal(null, ...(ngDevMode ? [{ debugName: "pending" }] : []));
|
|
313
|
-
/**
|
|
314
|
-
* Whether the control is disabled.
|
|
315
|
-
* @internal
|
|
316
|
-
*/
|
|
317
|
-
this.disabled = signal(null, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
318
|
-
/**
|
|
319
|
-
* Injector for creating effects outside the constructor.
|
|
320
|
-
*/
|
|
321
|
-
this.injector = inject(Injector);
|
|
322
|
-
/**
|
|
323
|
-
* The form field state.
|
|
324
|
-
*/
|
|
325
|
-
this.state = formFieldState(this);
|
|
326
|
-
// any time the ngControl changes, setup the subscriptions.
|
|
327
|
-
onChange(this.ngControl, this.setupSubscriptions.bind(this));
|
|
328
|
-
}
|
|
329
|
-
ngOnDestroy() {
|
|
330
|
-
this.subscription?.unsubscribe();
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Setup a listener for the form control status.
|
|
334
|
-
* @param control
|
|
335
|
-
*/
|
|
336
|
-
setupSubscriptions(control) {
|
|
337
|
-
// Unsubscribe from the previous subscriptions.
|
|
338
|
-
this.subscription?.unsubscribe();
|
|
339
|
-
if (!control) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
// For signal-forms interop controls, use an effect to reactively track status.
|
|
343
|
-
// For classic controls, also use an effect but additionally subscribe to events.
|
|
344
|
-
effect(() => {
|
|
345
|
-
this.updateStatus();
|
|
346
|
-
}, { injector: this.injector });
|
|
347
|
-
// Classic controls also have an events observable we can subscribe to.
|
|
348
|
-
const underlyingControl = control?.control;
|
|
349
|
-
if (underlyingControl?.events) {
|
|
350
|
-
this.subscription = underlyingControl.events.subscribe(() => this.updateStatus());
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
updateStatus() {
|
|
354
|
-
const control = this.ngControl();
|
|
355
|
-
if (!control) {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
// Wrap in try-catch to handle signal-forms interop controls where
|
|
359
|
-
// the `field` input may not be available yet (throws NG0950).
|
|
360
|
-
// Reading the signal still establishes a dependency, so the effect
|
|
361
|
-
// will re-run when the input becomes available.
|
|
362
|
-
try {
|
|
363
|
-
const pristine = control.pristine;
|
|
364
|
-
const touched = control.touched;
|
|
365
|
-
const dirty = control.dirty;
|
|
366
|
-
const valid = control.valid;
|
|
367
|
-
const invalid = control.invalid;
|
|
368
|
-
const pending = control.pending;
|
|
369
|
-
const disabled = control.disabled;
|
|
370
|
-
const errors = control.errors;
|
|
371
|
-
untracked(() => {
|
|
372
|
-
this.pristine.set(pristine);
|
|
373
|
-
this.touched.set(touched);
|
|
374
|
-
this.dirty.set(dirty);
|
|
375
|
-
this.valid.set(valid);
|
|
376
|
-
this.invalid.set(invalid);
|
|
377
|
-
this.pending.set(pending);
|
|
378
|
-
this.disabled.set(disabled);
|
|
379
|
-
this.errors.set(errors ? Object.keys(errors) : []);
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
catch {
|
|
383
|
-
// NG0950: Required input not available yet. The effect will re-run
|
|
384
|
-
// when the signal input becomes available.
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Register the id of the associated form control.
|
|
389
|
-
* @param id
|
|
390
|
-
* @internal
|
|
391
|
-
*/
|
|
392
|
-
setFormControl(id) {
|
|
393
|
-
this.formControl.set(id);
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* Register a label with the form field.
|
|
397
|
-
* @param label
|
|
398
|
-
* @internal
|
|
399
|
-
*/
|
|
400
|
-
addLabel(label) {
|
|
401
|
-
this.labels.update(labels => [...labels, label]);
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Register a description with the form field.
|
|
405
|
-
* @param description
|
|
406
|
-
* @internal
|
|
407
|
-
*/
|
|
408
|
-
addDescription(description) {
|
|
409
|
-
this.descriptions.update(descriptions => [...descriptions, description]);
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Remove the associated form control.
|
|
413
|
-
* @internal
|
|
414
|
-
*/
|
|
415
|
-
removeFormControl() {
|
|
416
|
-
this.formControl.set(null);
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Remove a label from the form field.
|
|
420
|
-
* @param label
|
|
421
|
-
* @internal
|
|
422
|
-
*/
|
|
423
|
-
removeLabel(label) {
|
|
424
|
-
this.labels.update(labels => labels.filter(l => l !== label));
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Remove a description from the form field.
|
|
428
|
-
* @param description
|
|
429
|
-
* @internal
|
|
430
|
-
*/
|
|
431
|
-
removeDescription(description) {
|
|
432
|
-
this.descriptions.update(descriptions => descriptions.filter(d => d !== description));
|
|
359
|
+
ngpFormField({ ngControl: this.ngControl });
|
|
433
360
|
}
|
|
434
361
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpFormField, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
435
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.9", type: NgpFormField, isStandalone: true, selector: "[ngpFormField]",
|
|
362
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.3.9", type: NgpFormField, isStandalone: true, selector: "[ngpFormField]", providers: [provideFormFieldState()], queries: [{ propertyName: "ngControl", first: true, predicate: NgControl, descendants: true, isSignal: true }], exportAs: ["ngpFormField"], ngImport: i0 }); }
|
|
436
363
|
}
|
|
437
364
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpFormField, decorators: [{
|
|
438
365
|
type: Directive,
|
|
@@ -440,59 +367,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
440
367
|
selector: '[ngpFormField]',
|
|
441
368
|
exportAs: 'ngpFormField',
|
|
442
369
|
providers: [provideFormFieldState()],
|
|
443
|
-
host: {
|
|
444
|
-
'[attr.data-invalid]': 'invalid() ? "" : null',
|
|
445
|
-
'[attr.data-valid]': 'valid() ? "" : null',
|
|
446
|
-
'[attr.data-touched]': 'touched() ? "" : null',
|
|
447
|
-
'[attr.data-pristine]': 'pristine() ? "" : null',
|
|
448
|
-
'[attr.data-dirty]': 'dirty() ? "" : null',
|
|
449
|
-
'[attr.data-pending]': 'pending() ? "" : null',
|
|
450
|
-
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
451
|
-
},
|
|
452
370
|
}]
|
|
453
371
|
}], ctorParameters: () => [], propDecorators: { ngControl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NgControl), { isSignal: true }] }] } });
|
|
454
372
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
* Access the element that the label is associated with.
|
|
474
|
-
*/
|
|
475
|
-
this.elementRef = inject(ElementRef);
|
|
476
|
-
/**
|
|
477
|
-
* Determine if the label is an HTML label element.
|
|
478
|
-
*/
|
|
479
|
-
this.isLabel = this.elementRef.nativeElement instanceof HTMLLabelElement;
|
|
480
|
-
effect(onCleanup => {
|
|
481
|
-
this.formField()?.addLabel(this.id());
|
|
482
|
-
onCleanup(() => this.formField()?.removeLabel(this.id()));
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
onClick(event) {
|
|
373
|
+
const [NgpLabelStateToken, ngpLabel, injectLabelState, provideLabelState] = createPrimitive('NgpLabel', ({ id }) => {
|
|
374
|
+
const element = injectElementRef();
|
|
375
|
+
const formField = injectFormFieldState({ optional: true });
|
|
376
|
+
// Derive the for attribute value if the label is an HTML label element
|
|
377
|
+
const htmlFor = computed(() => formField()?.formControl() ?? null, ...(ngDevMode ? [{ debugName: "htmlFor" }] : []));
|
|
378
|
+
// Determine if the label is an HTML label element
|
|
379
|
+
const isLabel = element.nativeElement instanceof HTMLLabelElement;
|
|
380
|
+
// Host bindings
|
|
381
|
+
attrBinding(element, 'id', id);
|
|
382
|
+
attrBinding(element, 'for', htmlFor);
|
|
383
|
+
dataBinding(element, 'data-invalid', () => (formField()?.invalid() ? '' : null));
|
|
384
|
+
dataBinding(element, 'data-valid', () => (formField()?.valid() ? '' : null));
|
|
385
|
+
dataBinding(element, 'data-touched', () => (formField()?.touched() ? '' : null));
|
|
386
|
+
dataBinding(element, 'data-pristine', () => (formField()?.pristine() ? '' : null));
|
|
387
|
+
dataBinding(element, 'data-dirty', () => (formField()?.dirty() ? '' : null));
|
|
388
|
+
dataBinding(element, 'data-pending', () => (formField()?.pending() ? '' : null));
|
|
389
|
+
dataBinding(element, 'data-disabled', () => (formField()?.disabled() ? '' : null));
|
|
390
|
+
function onClick(event) {
|
|
486
391
|
// by default a label will perform a click on the associated form control, however
|
|
487
392
|
// this only works if the associated form control is an input element which may not always
|
|
488
393
|
// be the case, so we prevent the default behavior and handle the click event ourselves.
|
|
489
394
|
// This was inspired by the HeadlessUI approach:
|
|
490
395
|
// https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/components/label/label.tsx#L58
|
|
491
|
-
if (
|
|
396
|
+
if (isLabel) {
|
|
492
397
|
event.preventDefault();
|
|
493
398
|
}
|
|
494
399
|
// to find the associated form control we can lookup via the known id
|
|
495
|
-
const targetId =
|
|
400
|
+
const targetId = htmlFor();
|
|
496
401
|
if (!targetId) {
|
|
497
402
|
return;
|
|
498
403
|
}
|
|
@@ -518,34 +423,53 @@ class NgpLabel {
|
|
|
518
423
|
// bound element is now focused.
|
|
519
424
|
target.focus({ preventScroll: true });
|
|
520
425
|
}
|
|
426
|
+
// Event listeners
|
|
427
|
+
listener(element, 'click', onClick);
|
|
428
|
+
// Register with form field and cleanup on destroy
|
|
429
|
+
formField()?.addLabel(id());
|
|
430
|
+
onDestroy(() => formField()?.removeLabel(id()));
|
|
431
|
+
// any time the id changes we need to update the registration with the form field
|
|
432
|
+
onChange(id, (newId, oldId) => {
|
|
433
|
+
if (oldId) {
|
|
434
|
+
formField()?.removeLabel(oldId);
|
|
435
|
+
}
|
|
436
|
+
formField()?.addLabel(newId);
|
|
437
|
+
});
|
|
438
|
+
return {
|
|
439
|
+
id,
|
|
440
|
+
htmlFor,
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* The `NgpLabel` directive is used to mark a label element within a form field. Preferably, there should use an HTML `<label>` element.
|
|
446
|
+
*/
|
|
447
|
+
class NgpLabel {
|
|
448
|
+
/**
|
|
449
|
+
* The label state.
|
|
450
|
+
*/
|
|
451
|
+
constructor() {
|
|
452
|
+
/**
|
|
453
|
+
* The id of the label. If not provided, a unique id will be generated.
|
|
454
|
+
*/
|
|
455
|
+
this.id = input(uniqueId('ngp-label'), ...(ngDevMode ? [{ debugName: "id" }] : []));
|
|
456
|
+
ngpLabel({ id: this.id });
|
|
457
|
+
}
|
|
521
458
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
522
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpLabel, isStandalone: true, selector: "[ngpLabel]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } },
|
|
459
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpLabel, isStandalone: true, selector: "[ngpLabel]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideLabelState()], exportAs: ["ngpLabel"], ngImport: i0 }); }
|
|
523
460
|
}
|
|
524
461
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpLabel, decorators: [{
|
|
525
462
|
type: Directive,
|
|
526
463
|
args: [{
|
|
527
464
|
selector: '[ngpLabel]',
|
|
528
465
|
exportAs: 'ngpLabel',
|
|
529
|
-
|
|
530
|
-
'[attr.id]': 'id()',
|
|
531
|
-
'[attr.for]': 'htmlFor()',
|
|
532
|
-
'[attr.data-invalid]': 'formField()?.invalid() ? "" : null',
|
|
533
|
-
'[attr.data-valid]': 'formField()?.valid() ? "" : null',
|
|
534
|
-
'[attr.data-touched]': 'formField()?.touched() ? "" : null',
|
|
535
|
-
'[attr.data-pristine]': 'formField()?.pristine() ? "" : null',
|
|
536
|
-
'[attr.data-dirty]': 'formField()?.dirty() ? "" : null',
|
|
537
|
-
'[attr.data-pending]': 'formField()?.pending() ? "" : null',
|
|
538
|
-
'[attr.data-disabled]': 'formField()?.disabled() ? "" : null',
|
|
539
|
-
},
|
|
466
|
+
providers: [provideLabelState()],
|
|
540
467
|
}]
|
|
541
|
-
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }]
|
|
542
|
-
type: HostListener,
|
|
543
|
-
args: ['click', ['$event']]
|
|
544
|
-
}] } });
|
|
468
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
545
469
|
|
|
546
470
|
/**
|
|
547
471
|
* Generated bundle index. Do not edit.
|
|
548
472
|
*/
|
|
549
473
|
|
|
550
|
-
export { NgpDescription, NgpError, NgpFormControl, NgpFormField, NgpLabel,
|
|
474
|
+
export { NgpDescription, NgpError, NgpFormControl, NgpFormField, NgpLabel, injectDescriptionState, injectErrorState, injectFormFieldState, injectLabelState, ngpDescription, ngpError, ngpFormControl, ngpFormField, ngpLabel, provideDescriptionState, provideErrorState, provideFormFieldState, provideLabelState };
|
|
551
475
|
//# sourceMappingURL=ng-primitives-form-field.mjs.map
|