forty-cdk 0.2.0 → 0.3.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/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, Directive, ElementRef, DestroyRef, input, booleanAttribute } from '@angular/core';
|
|
3
|
+
import { FOR_FIELD_CONTEXT, IdGenerator, FOR_FIELDSET_CONTEXT, adoptHostId, injectFieldWiring, createDefaults } from 'forty-cdk/core';
|
|
4
|
+
export { FOR_FIELD_CONTEXT, injectFieldWiring } from 'forty-cdk/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the surrounding `ForField` context, throwing if the consumer
|
|
8
|
+
* placed the piece outside a `[forField]` element.
|
|
9
|
+
*/
|
|
10
|
+
function injectFieldContext(piece) {
|
|
11
|
+
const ctx = inject(FOR_FIELD_CONTEXT, { optional: true });
|
|
12
|
+
if (!ctx) {
|
|
13
|
+
throw new Error(`[forty-cdk/field] ${piece} must be used inside a [forField] element.`);
|
|
14
|
+
}
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Headless form-field container that wires accessible labelling, description,
|
|
20
|
+
* and error association for a single control. It renders nothing and imposes no
|
|
21
|
+
* layout: its only job is to connect a `[forLabel]`, `[forFieldDescription]`,
|
|
22
|
+
* and `[forFieldError]` to the control via `id` / `aria-labelledby` /
|
|
23
|
+
* `aria-describedby` / `aria-errormessage`, and to reflect the control's
|
|
24
|
+
* validation state as `data-*` hooks for styling.
|
|
25
|
+
*
|
|
26
|
+
* Any forty-cdk form control (every `FormValueControl` / `FormCheckboxControl`
|
|
27
|
+
* primitive) auto-associates when wrapped — no extra markup. A plain native
|
|
28
|
+
* `<input>` opts in with `[forFieldControl]`.
|
|
29
|
+
*
|
|
30
|
+
* Host reflects `data-invalid` / `data-disabled` / `data-required` /
|
|
31
|
+
* `data-touched` from the registered control.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```html
|
|
35
|
+
* <div forField>
|
|
36
|
+
* <label forLabel>Notifications</label>
|
|
37
|
+
* <button forSwitch [formField]="settings.notify"></button>
|
|
38
|
+
* <p forFieldDescription>We'll only email you about security.</p>
|
|
39
|
+
* @if (settings.notify().invalid()) {
|
|
40
|
+
* <p forFieldError #err="forFieldError">{{ err.messages().join(', ') }}</p>
|
|
41
|
+
* }
|
|
42
|
+
* </div>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
class ForField {
|
|
46
|
+
#idGen = inject(IdGenerator);
|
|
47
|
+
#fieldset = inject(FOR_FIELDSET_CONTEXT, { optional: true });
|
|
48
|
+
#controlId = signal(this.#idGen.next('for-field-control'), /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "#controlId" }] : /* istanbul ignore next */ []));
|
|
50
|
+
#control = signal(null, /* @ts-ignore */
|
|
51
|
+
...(ngDevMode ? [{ debugName: "#control" }] : /* istanbul ignore next */ []));
|
|
52
|
+
#hasLabel = signal(false, /* @ts-ignore */
|
|
53
|
+
...(ngDevMode ? [{ debugName: "#hasLabel" }] : /* istanbul ignore next */ []));
|
|
54
|
+
#hasDescription = signal(false, /* @ts-ignore */
|
|
55
|
+
...(ngDevMode ? [{ debugName: "#hasDescription" }] : /* istanbul ignore next */ []));
|
|
56
|
+
#hasError = signal(false, /* @ts-ignore */
|
|
57
|
+
...(ngDevMode ? [{ debugName: "#hasError" }] : /* istanbul ignore next */ []));
|
|
58
|
+
/**
|
|
59
|
+
* The element the field actually targets for `id` / `aria-*` association and
|
|
60
|
+
* focus: the control's nominated `labelledElement` (Select trigger / Combobox
|
|
61
|
+
* input) when present, else its host (Listbox, native `<input>`).
|
|
62
|
+
*/
|
|
63
|
+
#targetEl = computed(() => {
|
|
64
|
+
const control = this.#control();
|
|
65
|
+
if (!control) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return control.labelledElement?.() ?? control.host;
|
|
69
|
+
}, /* @ts-ignore */
|
|
70
|
+
...(ngDevMode ? [{ debugName: "#targetEl" }] : /* istanbul ignore next */ []));
|
|
71
|
+
/**
|
|
72
|
+
* Id assigned to the control; a label's `for` points here. Adopts a
|
|
73
|
+
* nominated control's own id (`labelledElementId`) when present so the label
|
|
74
|
+
* and the primitive's internal `aria-labelledby` resolve to the same
|
|
75
|
+
* focusable element; otherwise the field's owned/host-adopted id.
|
|
76
|
+
*/
|
|
77
|
+
controlId = computed(() => this.#control()?.labelledElementId?.() ?? this.#controlId(), /* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
|
|
79
|
+
/** Id of the label element. */
|
|
80
|
+
labelId = signal(this.#idGen.next('for-field-label'), /* @ts-ignore */
|
|
81
|
+
...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
82
|
+
/** Id of the description element. */
|
|
83
|
+
descriptionId = signal(this.#idGen.next('for-field-description'), /* @ts-ignore */
|
|
84
|
+
...(ngDevMode ? [{ debugName: "descriptionId" }] : /* istanbul ignore next */ []));
|
|
85
|
+
/** Id of the error element. */
|
|
86
|
+
errorId = signal(this.#idGen.next('for-field-error'), /* @ts-ignore */
|
|
87
|
+
...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
|
|
88
|
+
/** The currently registered control handle, or null. */
|
|
89
|
+
control = this.#control.asReadonly();
|
|
90
|
+
/** Whether the registered control is currently invalid. */
|
|
91
|
+
invalid = computed(() => this.#control()?.invalid?.() ?? false, /* @ts-ignore */
|
|
92
|
+
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
93
|
+
/** Whether the registered control is required. */
|
|
94
|
+
required = computed(() => this.#control()?.required?.() ?? false, /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/**
|
|
97
|
+
* Whether the field is disabled: the registered control's own disabled state
|
|
98
|
+
* OR'd with a surrounding `[forFieldset]`'s `disabled` (so a disabled group
|
|
99
|
+
* reaches custom-role controls a native `<fieldset disabled>` can't).
|
|
100
|
+
*/
|
|
101
|
+
disabled = computed(() => (this.#control()?.disabled?.() ?? false) || (this.#fieldset?.disabled() ?? false), /* @ts-ignore */
|
|
102
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
103
|
+
/** Whether the registered control has been touched. */
|
|
104
|
+
touched = computed(() => this.#control()?.touched?.() ?? false, /* @ts-ignore */
|
|
105
|
+
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
106
|
+
/** Resolved `aria-labelledby` for the control (label id, or null). */
|
|
107
|
+
labelledBy = computed(() => (this.#hasLabel() ? this.labelId() : null), /* @ts-ignore */
|
|
108
|
+
...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
109
|
+
/**
|
|
110
|
+
* Resolved `aria-describedby`: the description id always, plus the error id
|
|
111
|
+
* when the control is invalid. The error id is included here (in addition to
|
|
112
|
+
* `aria-errormessage`) because assistive-tech support for `aria-errormessage`
|
|
113
|
+
* is uneven; doubling it up is the widely-used robust pattern.
|
|
114
|
+
*/
|
|
115
|
+
describedBy = computed(() => {
|
|
116
|
+
const ids = [];
|
|
117
|
+
if (this.#hasDescription()) {
|
|
118
|
+
ids.push(this.descriptionId());
|
|
119
|
+
}
|
|
120
|
+
if (this.#hasError() && this.invalid()) {
|
|
121
|
+
ids.push(this.errorId());
|
|
122
|
+
}
|
|
123
|
+
return ids.length > 0 ? ids.join(' ') : null;
|
|
124
|
+
}, /* @ts-ignore */
|
|
125
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
126
|
+
/** Resolved `aria-errormessage`: the error id when present and invalid, else null. */
|
|
127
|
+
errorMessageId = computed(() => this.#hasError() && this.invalid() ? this.errorId() : null, /* @ts-ignore */
|
|
128
|
+
...(ngDevMode ? [{ debugName: "errorMessageId" }] : /* istanbul ignore next */ []));
|
|
129
|
+
/** Register the control whose state the field reflects. */
|
|
130
|
+
registerControl(handle) {
|
|
131
|
+
this.#control.set(handle);
|
|
132
|
+
// Adopt a consumer-set id on the host (the host-is-the-control case). When
|
|
133
|
+
// the control nominates a distinct labelled element it carries its own
|
|
134
|
+
// `labelledElementId`, which `controlId` prefers — so a wrapper-host id, if
|
|
135
|
+
// any, is harmlessly ignored there.
|
|
136
|
+
adoptHostId(handle.host, this.#controlId);
|
|
137
|
+
}
|
|
138
|
+
/** Remove a previously registered control. */
|
|
139
|
+
unregisterControl(handle) {
|
|
140
|
+
if (this.#control() === handle) {
|
|
141
|
+
this.#control.set(null);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Mark the label slot present; returns an unregister callback. The field
|
|
146
|
+
* targets a single label per slot — its `labelId` / `descriptionId` /
|
|
147
|
+
* `errorId` are single ids, not id lists — so one `[forLabel]`,
|
|
148
|
+
* `[forFieldDescription]`, and `[forFieldError]` per field is the supported
|
|
149
|
+
* shape. Presence is a boolean, not a count.
|
|
150
|
+
*/
|
|
151
|
+
registerLabel() {
|
|
152
|
+
this.#hasLabel.set(true);
|
|
153
|
+
return () => this.#hasLabel.set(false);
|
|
154
|
+
}
|
|
155
|
+
/** Mark the description slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */
|
|
156
|
+
registerDescription() {
|
|
157
|
+
this.#hasDescription.set(true);
|
|
158
|
+
return () => this.#hasDescription.set(false);
|
|
159
|
+
}
|
|
160
|
+
/** Mark the error slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */
|
|
161
|
+
registerError() {
|
|
162
|
+
this.#hasError.set(true);
|
|
163
|
+
return () => this.#hasError.set(false);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Forward a click to the control's focusable element and focus it — matching
|
|
167
|
+
* the activation a native `<label for>` triggers via the browser, so a
|
|
168
|
+
* non-`<label>` `[forLabel]` toggles a custom-role control instead of only
|
|
169
|
+
* focusing it.
|
|
170
|
+
*/
|
|
171
|
+
clickControl() {
|
|
172
|
+
const target = this.#targetEl();
|
|
173
|
+
if (!target) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
target.click();
|
|
177
|
+
target.focus();
|
|
178
|
+
}
|
|
179
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForField, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
180
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForField, isStandalone: true, selector: "[forField]", host: { properties: { "attr.data-invalid": "invalid() ? \"\" : null", "attr.data-disabled": "disabled() ? \"\" : null", "attr.data-required": "required() ? \"\" : null", "attr.data-touched": "touched() ? \"\" : null" } }, providers: [{ provide: FOR_FIELD_CONTEXT, useExisting: ForField }], exportAs: ["forField"], ngImport: i0 });
|
|
181
|
+
}
|
|
182
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForField, decorators: [{
|
|
183
|
+
type: Directive,
|
|
184
|
+
args: [{
|
|
185
|
+
selector: '[forField]',
|
|
186
|
+
exportAs: 'forField',
|
|
187
|
+
host: {
|
|
188
|
+
'[attr.data-invalid]': 'invalid() ? "" : null',
|
|
189
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
190
|
+
'[attr.data-required]': 'required() ? "" : null',
|
|
191
|
+
'[attr.data-touched]': 'touched() ? "" : null',
|
|
192
|
+
},
|
|
193
|
+
providers: [{ provide: FOR_FIELD_CONTEXT, useExisting: ForField }],
|
|
194
|
+
}]
|
|
195
|
+
}] });
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Accessible label for a form control. Inside a `[forField]` it auto-wires:
|
|
199
|
+
* it adopts the field's `labelId` and registers itself so the control gains
|
|
200
|
+
* `aria-labelledby`.
|
|
201
|
+
*
|
|
202
|
+
* Click-to-activate is consistent across both host shapes: a native `<label>`
|
|
203
|
+
* emits `for` and the browser forwards the click to the control (toggling a
|
|
204
|
+
* checkbox / switch, activating a button); a non-`<label>` host has no native
|
|
205
|
+
* `for` forwarding, so the directive forwards the click itself — clicking the
|
|
206
|
+
* label activates the control either way, not just focuses it. A click that
|
|
207
|
+
* originated on the control (when the control is nested inside the label host)
|
|
208
|
+
* is not re-forwarded, so a label-wrapping layout toggles once, matching native
|
|
209
|
+
* `<label>` semantics.
|
|
210
|
+
*
|
|
211
|
+
* Usable standalone outside a field — there it is an inert
|
|
212
|
+
* marker and the consumer wires native `for` themselves.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```html
|
|
216
|
+
* <div forField>
|
|
217
|
+
* <label forLabel>Email</label>
|
|
218
|
+
* <input forFieldControl type="email" />
|
|
219
|
+
* </div>
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
class ForLabel {
|
|
223
|
+
ctx = inject(FOR_FIELD_CONTEXT, { optional: true });
|
|
224
|
+
#host = inject(ElementRef);
|
|
225
|
+
/** The label's id when inside a field, else null. */
|
|
226
|
+
labelId = computed(() => this.ctx?.labelId() ?? null, /* @ts-ignore */
|
|
227
|
+
...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
228
|
+
/** `for` is only meaningful on a native `<label>`; null otherwise. */
|
|
229
|
+
forAttr = computed(() => {
|
|
230
|
+
if (!this.ctx || this.#host.nativeElement.tagName !== 'LABEL') {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return this.ctx.controlId();
|
|
234
|
+
}, /* @ts-ignore */
|
|
235
|
+
...(ngDevMode ? [{ debugName: "forAttr" }] : /* istanbul ignore next */ []));
|
|
236
|
+
constructor() {
|
|
237
|
+
const ctx = this.ctx;
|
|
238
|
+
if (ctx) {
|
|
239
|
+
const unregister = ctx.registerLabel();
|
|
240
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
onClick(event) {
|
|
244
|
+
if (!this.ctx || this.#host.nativeElement.tagName === 'LABEL') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// When the control is nested inside the label host (label wraps control),
|
|
248
|
+
// a click on the control already activates it natively; forwarding again
|
|
249
|
+
// would double-toggle. Mirror native `<label>` semantics and bail when the
|
|
250
|
+
// click originated from the control (or anything inside it).
|
|
251
|
+
const control = this.ctx.control();
|
|
252
|
+
const controlEl = control?.labelledElement?.() ?? control?.host ?? null;
|
|
253
|
+
const target = event.target;
|
|
254
|
+
if (controlEl && target && controlEl.contains(target)) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.ctx.clickControl();
|
|
258
|
+
}
|
|
259
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
260
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForLabel, isStandalone: true, selector: "[forLabel]", host: { listeners: { "click": "onClick($event)" }, properties: { "attr.id": "labelId()", "attr.for": "forAttr()" } }, exportAs: ["forLabel"], ngImport: i0 });
|
|
261
|
+
}
|
|
262
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForLabel, decorators: [{
|
|
263
|
+
type: Directive,
|
|
264
|
+
args: [{
|
|
265
|
+
selector: '[forLabel]',
|
|
266
|
+
exportAs: 'forLabel',
|
|
267
|
+
host: {
|
|
268
|
+
'[attr.id]': 'labelId()',
|
|
269
|
+
'[attr.for]': 'forAttr()',
|
|
270
|
+
'(click)': 'onClick($event)',
|
|
271
|
+
},
|
|
272
|
+
}]
|
|
273
|
+
}], ctorParameters: () => [] });
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Supplementary description / hint for a form control. Adopts the field's
|
|
277
|
+
* `descriptionId` and registers itself so the control gains
|
|
278
|
+
* `aria-describedby`. Apply on a `<p>` / `<span>` inside a `[forField]`.
|
|
279
|
+
*/
|
|
280
|
+
class ForFieldDescription {
|
|
281
|
+
ctx = injectFieldContext('ForFieldDescription');
|
|
282
|
+
constructor() {
|
|
283
|
+
const unregister = this.ctx.registerDescription();
|
|
284
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
285
|
+
}
|
|
286
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
287
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForFieldDescription, isStandalone: true, selector: "[forFieldDescription]", host: { properties: { "attr.id": "ctx.descriptionId()" } }, exportAs: ["forFieldDescription"], ngImport: i0 });
|
|
288
|
+
}
|
|
289
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldDescription, decorators: [{
|
|
290
|
+
type: Directive,
|
|
291
|
+
args: [{
|
|
292
|
+
selector: '[forFieldDescription]',
|
|
293
|
+
exportAs: 'forFieldDescription',
|
|
294
|
+
host: {
|
|
295
|
+
'[attr.id]': 'ctx.descriptionId()',
|
|
296
|
+
},
|
|
297
|
+
}]
|
|
298
|
+
}], ctorParameters: () => [] });
|
|
299
|
+
|
|
300
|
+
function extractMessage(error) {
|
|
301
|
+
if ('message' in error && typeof error.message === 'string' && error.message !== '') {
|
|
302
|
+
return error.message;
|
|
303
|
+
}
|
|
304
|
+
return error.kind;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Error region for a form control. Adopts the field's `errorId`, registers
|
|
308
|
+
* itself so the control gains `aria-errormessage` (and the id is folded into
|
|
309
|
+
* `aria-describedby`) while invalid, and **reads the control's Signal Forms
|
|
310
|
+
* validation errors automatically** — the consumer renders `messages()` (or
|
|
311
|
+
* the raw `errors()`) without any manual plumbing.
|
|
312
|
+
*
|
|
313
|
+
* Carries `role="alert"`. Drive its presence with `@if (err.shown())` (or the
|
|
314
|
+
* field's own `invalid()`); when unmounted the control's `aria-errormessage`
|
|
315
|
+
* drops automatically.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```html
|
|
319
|
+
* @if (err.shown()) {
|
|
320
|
+
* <p forFieldError #err="forFieldError">{{ err.messages().join(', ') }}</p>
|
|
321
|
+
* }
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
class ForFieldError {
|
|
325
|
+
ctx = injectFieldContext('ForFieldError');
|
|
326
|
+
/** The control's current validation errors (Signal Forms). */
|
|
327
|
+
errors = computed(() => this.ctx.control()?.errors?.() ?? [], /* @ts-ignore */
|
|
328
|
+
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
329
|
+
/** Human-readable messages derived from {@link errors}. */
|
|
330
|
+
messages = computed(() => this.errors().map((error) => extractMessage(error)), /* @ts-ignore */
|
|
331
|
+
...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
332
|
+
/** Whether the control currently has at least one error. */
|
|
333
|
+
hasErrors = computed(() => this.errors().length > 0, /* @ts-ignore */
|
|
334
|
+
...(ngDevMode ? [{ debugName: "hasErrors" }] : /* istanbul ignore next */ []));
|
|
335
|
+
/** Whether the error region should be shown (control invalid and has errors). */
|
|
336
|
+
shown = computed(() => this.ctx.invalid() && this.hasErrors(), /* @ts-ignore */
|
|
337
|
+
...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
|
|
338
|
+
constructor() {
|
|
339
|
+
const unregister = this.ctx.registerError();
|
|
340
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
341
|
+
}
|
|
342
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldError, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
343
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForFieldError, isStandalone: true, selector: "[forFieldError]", host: { attributes: { "role": "alert" }, properties: { "attr.id": "ctx.errorId()" } }, exportAs: ["forFieldError"], ngImport: i0 });
|
|
344
|
+
}
|
|
345
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldError, decorators: [{
|
|
346
|
+
type: Directive,
|
|
347
|
+
args: [{
|
|
348
|
+
selector: '[forFieldError]',
|
|
349
|
+
exportAs: 'forFieldError',
|
|
350
|
+
host: {
|
|
351
|
+
'[attr.id]': 'ctx.errorId()',
|
|
352
|
+
role: 'alert',
|
|
353
|
+
},
|
|
354
|
+
}]
|
|
355
|
+
}], ctorParameters: () => [] });
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Opt-in marker that wires a non-forty-cdk control — a native `<input>`,
|
|
359
|
+
* `<textarea>`, `<select>`, or any custom element — into its surrounding
|
|
360
|
+
* `[forField]`. forty-cdk form primitives (which extend the shared form base)
|
|
361
|
+
* auto-wire and do **not** need this; it exists for plain native controls that
|
|
362
|
+
* have no primitive of their own.
|
|
363
|
+
*
|
|
364
|
+
* Validation state is consumer-driven via the `invalid` / `required` /
|
|
365
|
+
* `disabled` / `touched` inputs (native controls have no Signal Forms errors
|
|
366
|
+
* to surface). Reflects `aria-invalid` on the host.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```html
|
|
370
|
+
* <div forField>
|
|
371
|
+
* <label forLabel>Email</label>
|
|
372
|
+
* <input forFieldControl type="email" [invalid]="emailInvalid()" />
|
|
373
|
+
* </div>
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
class ForFieldControl {
|
|
377
|
+
/** Marks the control invalid — drives the field's error region and `aria-invalid`. */
|
|
378
|
+
invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
379
|
+
/** Marks the control required — reflected by the field as `data-required`. */
|
|
380
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
381
|
+
/** Marks the control disabled — reflected by the field as `data-disabled`. */
|
|
382
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
383
|
+
/** Marks the control touched — reflected by the field as `data-touched`. */
|
|
384
|
+
touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
385
|
+
constructor() {
|
|
386
|
+
injectFieldWiring({
|
|
387
|
+
invalid: this.invalid,
|
|
388
|
+
required: this.required,
|
|
389
|
+
disabled: this.disabled,
|
|
390
|
+
touched: this.touched,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
394
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForFieldControl, isStandalone: true, selector: "[forFieldControl]", inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-invalid": "invalid() ? \"true\" : null" } }, exportAs: ["forFieldControl"], ngImport: i0 });
|
|
395
|
+
}
|
|
396
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldControl, decorators: [{
|
|
397
|
+
type: Directive,
|
|
398
|
+
args: [{
|
|
399
|
+
selector: '[forFieldControl]',
|
|
400
|
+
exportAs: 'forFieldControl',
|
|
401
|
+
host: {
|
|
402
|
+
'[attr.aria-invalid]': 'invalid() ? "true" : null',
|
|
403
|
+
},
|
|
404
|
+
}]
|
|
405
|
+
}], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }] } });
|
|
406
|
+
|
|
407
|
+
const FALLBACK = {};
|
|
408
|
+
const { token, provideDefaults } = createDefaults('FOR_FIELD_DEFAULTS', FALLBACK);
|
|
409
|
+
/** Token holding the resolved field defaults for the current scope. */
|
|
410
|
+
const FOR_FIELD_DEFAULTS = token;
|
|
411
|
+
/**
|
|
412
|
+
* Configures forty-cdk field defaults for this injector scope. Partial
|
|
413
|
+
* overrides inherit unspecified keys from the parent scope (or library
|
|
414
|
+
* defaults at the root).
|
|
415
|
+
*/
|
|
416
|
+
function provideForFieldDefaults(defaults = {}) {
|
|
417
|
+
return provideDefaults(defaults);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Generated bundle index. Do not edit.
|
|
422
|
+
*/
|
|
423
|
+
|
|
424
|
+
export { FOR_FIELD_DEFAULTS, ForField, ForFieldControl, ForFieldDescription, ForFieldError, ForLabel, provideForFieldDefaults };
|
|
425
|
+
//# sourceMappingURL=forty-cdk-field.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forty-cdk-field.mjs","sources":["../../../projects/forty-cdk/field/src/field-context.ts","../../../projects/forty-cdk/field/src/field.ts","../../../projects/forty-cdk/field/src/label.ts","../../../projects/forty-cdk/field/src/field-description.ts","../../../projects/forty-cdk/field/src/field-error.ts","../../../projects/forty-cdk/field/src/field-control.ts","../../../projects/forty-cdk/field/src/field-defaults.ts","../../../projects/forty-cdk/field/src/forty-cdk-field.ts"],"sourcesContent":["import { inject } from '@angular/core';\n\nimport { FOR_FIELD_CONTEXT, type ForFieldContext } from 'forty-cdk/core';\n\nexport {\n FOR_FIELD_CONTEXT,\n injectFieldWiring,\n type ForFieldContext,\n type FieldControlHandle,\n} from 'forty-cdk/core';\n\n/**\n * Resolves the surrounding `ForField` context, throwing if the consumer\n * placed the piece outside a `[forField]` element.\n */\nexport function injectFieldContext(piece: string): ForFieldContext {\n const ctx = inject(FOR_FIELD_CONTEXT, { optional: true });\n if (!ctx) {\n throw new Error(`[forty-cdk/field] ${piece} must be used inside a [forField] element.`);\n }\n return ctx;\n}\n","import { computed, Directive, inject, signal } from '@angular/core';\n\nimport { adoptHostId, IdGenerator, FOR_FIELDSET_CONTEXT } from 'forty-cdk/core';\nimport { FOR_FIELD_CONTEXT, type FieldControlHandle, type ForFieldContext } from './field-context';\n\n/**\n * Headless form-field container that wires accessible labelling, description,\n * and error association for a single control. It renders nothing and imposes no\n * layout: its only job is to connect a `[forLabel]`, `[forFieldDescription]`,\n * and `[forFieldError]` to the control via `id` / `aria-labelledby` /\n * `aria-describedby` / `aria-errormessage`, and to reflect the control's\n * validation state as `data-*` hooks for styling.\n *\n * Any forty-cdk form control (every `FormValueControl` / `FormCheckboxControl`\n * primitive) auto-associates when wrapped — no extra markup. A plain native\n * `<input>` opts in with `[forFieldControl]`.\n *\n * Host reflects `data-invalid` / `data-disabled` / `data-required` /\n * `data-touched` from the registered control.\n *\n * @example\n * ```html\n * <div forField>\n * <label forLabel>Notifications</label>\n * <button forSwitch [formField]=\"settings.notify\"></button>\n * <p forFieldDescription>We'll only email you about security.</p>\n * @if (settings.notify().invalid()) {\n * <p forFieldError #err=\"forFieldError\">{{ err.messages().join(', ') }}</p>\n * }\n * </div>\n * ```\n */\n@Directive({\n selector: '[forField]',\n exportAs: 'forField',\n host: {\n '[attr.data-invalid]': 'invalid() ? \"\" : null',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n '[attr.data-required]': 'required() ? \"\" : null',\n '[attr.data-touched]': 'touched() ? \"\" : null',\n },\n providers: [{ provide: FOR_FIELD_CONTEXT, useExisting: ForField }],\n})\nexport class ForField implements ForFieldContext {\n readonly #idGen = inject(IdGenerator);\n readonly #fieldset = inject(FOR_FIELDSET_CONTEXT, { optional: true });\n\n readonly #controlId = signal(this.#idGen.next('for-field-control'));\n readonly #control = signal<FieldControlHandle | null>(null);\n readonly #hasLabel = signal(false);\n readonly #hasDescription = signal(false);\n readonly #hasError = signal(false);\n\n /**\n * The element the field actually targets for `id` / `aria-*` association and\n * focus: the control's nominated `labelledElement` (Select trigger / Combobox\n * input) when present, else its host (Listbox, native `<input>`).\n */\n readonly #targetEl = computed<HTMLElement | null>(() => {\n const control = this.#control();\n if (!control) {\n return null;\n }\n return control.labelledElement?.() ?? control.host;\n });\n\n /**\n * Id assigned to the control; a label's `for` points here. Adopts a\n * nominated control's own id (`labelledElementId`) when present so the label\n * and the primitive's internal `aria-labelledby` resolve to the same\n * focusable element; otherwise the field's owned/host-adopted id.\n */\n readonly controlId = computed(() => this.#control()?.labelledElementId?.() ?? this.#controlId());\n /** Id of the label element. */\n readonly labelId = signal(this.#idGen.next('for-field-label'));\n /** Id of the description element. */\n readonly descriptionId = signal(this.#idGen.next('for-field-description'));\n /** Id of the error element. */\n readonly errorId = signal(this.#idGen.next('for-field-error'));\n /** The currently registered control handle, or null. */\n readonly control = this.#control.asReadonly();\n\n /** Whether the registered control is currently invalid. */\n readonly invalid = computed(() => this.#control()?.invalid?.() ?? false);\n /** Whether the registered control is required. */\n readonly required = computed(() => this.#control()?.required?.() ?? false);\n /**\n * Whether the field is disabled: the registered control's own disabled state\n * OR'd with a surrounding `[forFieldset]`'s `disabled` (so a disabled group\n * reaches custom-role controls a native `<fieldset disabled>` can't).\n */\n readonly disabled = computed(\n () => (this.#control()?.disabled?.() ?? false) || (this.#fieldset?.disabled() ?? false),\n );\n /** Whether the registered control has been touched. */\n readonly touched = computed(() => this.#control()?.touched?.() ?? false);\n\n /** Resolved `aria-labelledby` for the control (label id, or null). */\n readonly labelledBy = computed(() => (this.#hasLabel() ? this.labelId() : null));\n\n /**\n * Resolved `aria-describedby`: the description id always, plus the error id\n * when the control is invalid. The error id is included here (in addition to\n * `aria-errormessage`) because assistive-tech support for `aria-errormessage`\n * is uneven; doubling it up is the widely-used robust pattern.\n */\n readonly describedBy = computed(() => {\n const ids: string[] = [];\n if (this.#hasDescription()) {\n ids.push(this.descriptionId());\n }\n if (this.#hasError() && this.invalid()) {\n ids.push(this.errorId());\n }\n return ids.length > 0 ? ids.join(' ') : null;\n });\n\n /** Resolved `aria-errormessage`: the error id when present and invalid, else null. */\n readonly errorMessageId = computed(() =>\n this.#hasError() && this.invalid() ? this.errorId() : null,\n );\n\n /** Register the control whose state the field reflects. */\n registerControl(handle: FieldControlHandle): void {\n this.#control.set(handle);\n // Adopt a consumer-set id on the host (the host-is-the-control case). When\n // the control nominates a distinct labelled element it carries its own\n // `labelledElementId`, which `controlId` prefers — so a wrapper-host id, if\n // any, is harmlessly ignored there.\n adoptHostId(handle.host, this.#controlId);\n }\n\n /** Remove a previously registered control. */\n unregisterControl(handle: FieldControlHandle): void {\n if (this.#control() === handle) {\n this.#control.set(null);\n }\n }\n\n /**\n * Mark the label slot present; returns an unregister callback. The field\n * targets a single label per slot — its `labelId` / `descriptionId` /\n * `errorId` are single ids, not id lists — so one `[forLabel]`,\n * `[forFieldDescription]`, and `[forFieldError]` per field is the supported\n * shape. Presence is a boolean, not a count.\n */\n registerLabel(): () => void {\n this.#hasLabel.set(true);\n return () => this.#hasLabel.set(false);\n }\n\n /** Mark the description slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */\n registerDescription(): () => void {\n this.#hasDescription.set(true);\n return () => this.#hasDescription.set(false);\n }\n\n /** Mark the error slot present; returns an unregister callback. See {@link registerLabel} for the single-instance-per-slot contract. */\n registerError(): () => void {\n this.#hasError.set(true);\n return () => this.#hasError.set(false);\n }\n\n /**\n * Forward a click to the control's focusable element and focus it — matching\n * the activation a native `<label for>` triggers via the browser, so a\n * non-`<label>` `[forLabel]` toggles a custom-role control instead of only\n * focusing it.\n */\n clickControl(): void {\n const target = this.#targetEl();\n if (!target) {\n return;\n }\n target.click();\n target.focus();\n }\n}\n","import { computed, DestroyRef, Directive, ElementRef, inject } from '@angular/core';\n\nimport { FOR_FIELD_CONTEXT } from './field-context';\n\n/**\n * Accessible label for a form control. Inside a `[forField]` it auto-wires:\n * it adopts the field's `labelId` and registers itself so the control gains\n * `aria-labelledby`.\n *\n * Click-to-activate is consistent across both host shapes: a native `<label>`\n * emits `for` and the browser forwards the click to the control (toggling a\n * checkbox / switch, activating a button); a non-`<label>` host has no native\n * `for` forwarding, so the directive forwards the click itself — clicking the\n * label activates the control either way, not just focuses it. A click that\n * originated on the control (when the control is nested inside the label host)\n * is not re-forwarded, so a label-wrapping layout toggles once, matching native\n * `<label>` semantics.\n *\n * Usable standalone outside a field — there it is an inert\n * marker and the consumer wires native `for` themselves.\n *\n * @example\n * ```html\n * <div forField>\n * <label forLabel>Email</label>\n * <input forFieldControl type=\"email\" />\n * </div>\n * ```\n */\n@Directive({\n selector: '[forLabel]',\n exportAs: 'forLabel',\n host: {\n '[attr.id]': 'labelId()',\n '[attr.for]': 'forAttr()',\n '(click)': 'onClick($event)',\n },\n})\nexport class ForLabel {\n protected readonly ctx = inject(FOR_FIELD_CONTEXT, { optional: true });\n readonly #host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** The label's id when inside a field, else null. */\n protected readonly labelId = computed(() => this.ctx?.labelId() ?? null);\n\n /** `for` is only meaningful on a native `<label>`; null otherwise. */\n protected readonly forAttr = computed(() => {\n if (!this.ctx || this.#host.nativeElement.tagName !== 'LABEL') {\n return null;\n }\n return this.ctx.controlId();\n });\n\n constructor() {\n const ctx = this.ctx;\n if (ctx) {\n const unregister = ctx.registerLabel();\n inject(DestroyRef).onDestroy(unregister);\n }\n }\n\n protected onClick(event: MouseEvent): void {\n if (!this.ctx || this.#host.nativeElement.tagName === 'LABEL') {\n return;\n }\n // When the control is nested inside the label host (label wraps control),\n // a click on the control already activates it natively; forwarding again\n // would double-toggle. Mirror native `<label>` semantics and bail when the\n // click originated from the control (or anything inside it).\n const control = this.ctx.control();\n const controlEl = control?.labelledElement?.() ?? control?.host ?? null;\n const target = event.target as Node | null;\n if (controlEl && target && controlEl.contains(target)) {\n return;\n }\n this.ctx.clickControl();\n }\n}\n","import { DestroyRef, Directive, inject } from '@angular/core';\n\nimport { injectFieldContext } from './field-context';\n\n/**\n * Supplementary description / hint for a form control. Adopts the field's\n * `descriptionId` and registers itself so the control gains\n * `aria-describedby`. Apply on a `<p>` / `<span>` inside a `[forField]`.\n */\n@Directive({\n selector: '[forFieldDescription]',\n exportAs: 'forFieldDescription',\n host: {\n '[attr.id]': 'ctx.descriptionId()',\n },\n})\nexport class ForFieldDescription {\n protected readonly ctx = injectFieldContext('ForFieldDescription');\n\n constructor() {\n const unregister = this.ctx.registerDescription();\n inject(DestroyRef).onDestroy(unregister);\n }\n}\n","import { computed, DestroyRef, Directive, inject } from '@angular/core';\nimport type { ValidationError } from '@angular/forms/signals';\n\nimport { injectFieldContext } from './field-context';\n\nfunction extractMessage(error: ValidationError.WithOptionalFieldTree): string {\n if ('message' in error && typeof error.message === 'string' && error.message !== '') {\n return error.message;\n }\n return error.kind;\n}\n\n/**\n * Error region for a form control. Adopts the field's `errorId`, registers\n * itself so the control gains `aria-errormessage` (and the id is folded into\n * `aria-describedby`) while invalid, and **reads the control's Signal Forms\n * validation errors automatically** — the consumer renders `messages()` (or\n * the raw `errors()`) without any manual plumbing.\n *\n * Carries `role=\"alert\"`. Drive its presence with `@if (err.shown())` (or the\n * field's own `invalid()`); when unmounted the control's `aria-errormessage`\n * drops automatically.\n *\n * @example\n * ```html\n * @if (err.shown()) {\n * <p forFieldError #err=\"forFieldError\">{{ err.messages().join(', ') }}</p>\n * }\n * ```\n */\n@Directive({\n selector: '[forFieldError]',\n exportAs: 'forFieldError',\n host: {\n '[attr.id]': 'ctx.errorId()',\n role: 'alert',\n },\n})\nexport class ForFieldError {\n protected readonly ctx = injectFieldContext('ForFieldError');\n\n /** The control's current validation errors (Signal Forms). */\n readonly errors = computed(() => this.ctx.control()?.errors?.() ?? []);\n\n /** Human-readable messages derived from {@link errors}. */\n readonly messages = computed<readonly string[]>(() =>\n this.errors().map((error) => extractMessage(error)),\n );\n\n /** Whether the control currently has at least one error. */\n readonly hasErrors = computed(() => this.errors().length > 0);\n\n /** Whether the error region should be shown (control invalid and has errors). */\n readonly shown = computed(() => this.ctx.invalid() && this.hasErrors());\n\n constructor() {\n const unregister = this.ctx.registerError();\n inject(DestroyRef).onDestroy(unregister);\n }\n}\n","import { booleanAttribute, Directive, input } from '@angular/core';\n\nimport { injectFieldWiring } from './field-context';\n\n/**\n * Opt-in marker that wires a non-forty-cdk control — a native `<input>`,\n * `<textarea>`, `<select>`, or any custom element — into its surrounding\n * `[forField]`. forty-cdk form primitives (which extend the shared form base)\n * auto-wire and do **not** need this; it exists for plain native controls that\n * have no primitive of their own.\n *\n * Validation state is consumer-driven via the `invalid` / `required` /\n * `disabled` / `touched` inputs (native controls have no Signal Forms errors\n * to surface). Reflects `aria-invalid` on the host.\n *\n * @example\n * ```html\n * <div forField>\n * <label forLabel>Email</label>\n * <input forFieldControl type=\"email\" [invalid]=\"emailInvalid()\" />\n * </div>\n * ```\n */\n@Directive({\n selector: '[forFieldControl]',\n exportAs: 'forFieldControl',\n host: {\n '[attr.aria-invalid]': 'invalid() ? \"true\" : null',\n },\n})\nexport class ForFieldControl {\n /** Marks the control invalid — drives the field's error region and `aria-invalid`. */\n readonly invalid = input(false, { transform: booleanAttribute });\n /** Marks the control required — reflected by the field as `data-required`. */\n readonly required = input(false, { transform: booleanAttribute });\n /** Marks the control disabled — reflected by the field as `data-disabled`. */\n readonly disabled = input(false, { transform: booleanAttribute });\n /** Marks the control touched — reflected by the field as `data-touched`. */\n readonly touched = input(false, { transform: booleanAttribute });\n\n constructor() {\n injectFieldWiring({\n invalid: this.invalid,\n required: this.required,\n disabled: this.disabled,\n touched: this.touched,\n });\n }\n}\n","import { type Provider } from '@angular/core';\n\nimport { createDefaults } from 'forty-cdk/core';\n\n/**\n * Defaults inherited by descendant fields in the surrounding injector scope.\n * Configure with `provideForFieldDefaults`. The shape is a stub today —\n * present so future per-scope tuning can land without churning the public\n * surface.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface ForFieldDefaults {}\n\nconst FALLBACK: ForFieldDefaults = {};\n\nconst { token, provideDefaults } = createDefaults<ForFieldDefaults>('FOR_FIELD_DEFAULTS', FALLBACK);\n\n/** Token holding the resolved field defaults for the current scope. */\nexport const FOR_FIELD_DEFAULTS = token;\n\n/**\n * Configures forty-cdk field defaults for this injector scope. Partial\n * overrides inherit unspecified keys from the parent scope (or library\n * defaults at the root).\n */\nexport function provideForFieldDefaults(defaults: Partial<ForFieldDefaults> = {}): Provider[] {\n return provideDefaults(defaults);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, signal, input, booleanAttribute, computed, Directive, DestroyRef } from '@angular/core';
|
|
3
|
+
import { IdGenerator, FOR_FIELDSET_CONTEXT, reflectDisabled, createDefaults } from 'forty-cdk/core';
|
|
4
|
+
export { FOR_FIELDSET_CONTEXT } from 'forty-cdk/core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Headless grouping container that gives a set of related fields a shared
|
|
8
|
+
* accessible name — the styleless counterpart to a native `<fieldset>` +
|
|
9
|
+
* `<legend>`, and the grouping companion to `ForField`.
|
|
10
|
+
*
|
|
11
|
+
* On a native `<fieldset>` it relies on the implicit grouping and emits no
|
|
12
|
+
* role; on any other element it emits `role="group"` + `aria-labelledby`
|
|
13
|
+
* pointing at the `[forFieldsetLegend]` (same host-tag detection idiom as
|
|
14
|
+
* `ForLabel`'s `<label>` check).
|
|
15
|
+
*
|
|
16
|
+
* The `disabled` input reflects `data-disabled` and is provided via context so
|
|
17
|
+
* descendant `ForField` controls OR it into their own disabled state — native
|
|
18
|
+
* `<fieldset disabled>` does not reach custom-role controls like `forSwitch`.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```html
|
|
22
|
+
* <fieldset forFieldset [disabled]="locked()">
|
|
23
|
+
* <legend forFieldsetLegend>Shipping address</legend>
|
|
24
|
+
* <div forField>
|
|
25
|
+
* <label forLabel>Street</label>
|
|
26
|
+
* <input forFieldControl />
|
|
27
|
+
* </div>
|
|
28
|
+
* </fieldset>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
class ForFieldset {
|
|
32
|
+
#idGen = inject(IdGenerator);
|
|
33
|
+
#host = inject(ElementRef);
|
|
34
|
+
#parent = inject(FOR_FIELDSET_CONTEXT, { optional: true, skipSelf: true });
|
|
35
|
+
#legends = signal(0, /* @ts-ignore */
|
|
36
|
+
...(ngDevMode ? [{ debugName: "#legends" }] : /* istanbul ignore next */ []));
|
|
37
|
+
/**
|
|
38
|
+
* Whether the group is disabled, as bound by the consumer via `[disabled]`.
|
|
39
|
+
* The host reflection and descendant controls read the composed
|
|
40
|
+
* {@link disabled} signal below, which also folds in an enclosing disabled
|
|
41
|
+
* `[forFieldset]`.
|
|
42
|
+
*/
|
|
43
|
+
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), transform: booleanAttribute, alias: 'disabled' });
|
|
44
|
+
/**
|
|
45
|
+
* Effective disabled: the group's own `[disabled]` OR'd with an enclosing
|
|
46
|
+
* disabled `[forFieldset]`. Reflects `data-disabled`, emits the native
|
|
47
|
+
* `disabled` attribute on a `<fieldset>` (or `aria-disabled` elsewhere), and
|
|
48
|
+
* propagates to descendant controls via context — so a disabled fieldset
|
|
49
|
+
* reaches custom-role controls (`forSwitch`, `forCheckbox`, …) a native
|
|
50
|
+
* `<fieldset disabled>` can't, and nesting composes like native fieldsets
|
|
51
|
+
* (a disabled outer group cannot be re-enabled by an inner one).
|
|
52
|
+
*/
|
|
53
|
+
disabled = computed(() => this.disabledInput() || (this.#parent?.disabled() ?? false), /* @ts-ignore */
|
|
54
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
55
|
+
/** Id of the legend element; the group's `aria-labelledby` resolves here. */
|
|
56
|
+
legendId = signal(this.#idGen.next('for-fieldset-legend'), /* @ts-ignore */
|
|
57
|
+
...(ngDevMode ? [{ debugName: "legendId" }] : /* istanbul ignore next */ []));
|
|
58
|
+
/** Whether the host element is a native `<fieldset>` (no role/labelledby needed). */
|
|
59
|
+
isNativeFieldset = computed(() => this.#host.nativeElement.tagName === 'FIELDSET', /* @ts-ignore */
|
|
60
|
+
...(ngDevMode ? [{ debugName: "isNativeFieldset" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/**
|
|
62
|
+
* Native `disabled` is reflected only on a real `<fieldset>` (where it
|
|
63
|
+
* natively disables descendant controls); a non-`<fieldset>` host advertises
|
|
64
|
+
* its disabled state via `aria-disabled` instead.
|
|
65
|
+
*/
|
|
66
|
+
nativeDisabled = computed(() => this.isNativeFieldset() && this.disabled(), /* @ts-ignore */
|
|
67
|
+
...(ngDevMode ? [{ debugName: "nativeDisabled" }] : /* istanbul ignore next */ []));
|
|
68
|
+
constructor() {
|
|
69
|
+
reflectDisabled(this.nativeDisabled);
|
|
70
|
+
}
|
|
71
|
+
/** `role="group"` on a non-`<fieldset>` element, else null. */
|
|
72
|
+
roleAttr = computed(() => (this.isNativeFieldset() ? null : 'group'), /* @ts-ignore */
|
|
73
|
+
...(ngDevMode ? [{ debugName: "roleAttr" }] : /* istanbul ignore next */ []));
|
|
74
|
+
/**
|
|
75
|
+
* Resolved `aria-labelledby`: the legend id on a non-`<fieldset>` element when
|
|
76
|
+
* a legend is registered, else null (a native `<fieldset>`/`<legend>` groups
|
|
77
|
+
* implicitly and needs none).
|
|
78
|
+
*/
|
|
79
|
+
labelledBy = computed(() => !this.isNativeFieldset() && this.#legends() > 0 ? this.legendId() : null, /* @ts-ignore */
|
|
80
|
+
...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
81
|
+
/** Mark a legend present; returns an unregister callback. */
|
|
82
|
+
registerLegend() {
|
|
83
|
+
this.#legends.update((n) => n + 1);
|
|
84
|
+
return () => this.#legends.update((n) => n - 1);
|
|
85
|
+
}
|
|
86
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldset, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
87
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForFieldset, isStandalone: true, selector: "[forFieldset]", inputs: { disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "roleAttr()", "attr.aria-labelledby": "labelledBy()", "attr.aria-disabled": "!isNativeFieldset() && disabled() ? \"true\" : null", "attr.data-disabled": "disabled() ? \"\" : null" } }, providers: [{ provide: FOR_FIELDSET_CONTEXT, useExisting: ForFieldset }], exportAs: ["forFieldset"], ngImport: i0 });
|
|
88
|
+
}
|
|
89
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldset, decorators: [{
|
|
90
|
+
type: Directive,
|
|
91
|
+
args: [{
|
|
92
|
+
selector: '[forFieldset]',
|
|
93
|
+
exportAs: 'forFieldset',
|
|
94
|
+
host: {
|
|
95
|
+
'[attr.role]': 'roleAttr()',
|
|
96
|
+
'[attr.aria-labelledby]': 'labelledBy()',
|
|
97
|
+
'[attr.aria-disabled]': '!isNativeFieldset() && disabled() ? "true" : null',
|
|
98
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
99
|
+
},
|
|
100
|
+
providers: [{ provide: FOR_FIELDSET_CONTEXT, useExisting: ForFieldset }],
|
|
101
|
+
}]
|
|
102
|
+
}], ctorParameters: () => [], propDecorators: { disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Accessible group label for a `[forFieldset]`. Inside a fieldset it adopts the
|
|
106
|
+
* fieldset's `legendId` and registers itself so the group's `aria-labelledby`
|
|
107
|
+
* resolves; on a native `<fieldset>`/`<legend>` the id is harmless extra wiring
|
|
108
|
+
* (the browser groups implicitly).
|
|
109
|
+
*
|
|
110
|
+
* Usable standalone outside a fieldset — there it is an inert marker.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```html
|
|
114
|
+
* <div forFieldset>
|
|
115
|
+
* <span forFieldsetLegend>Notification preferences</span>
|
|
116
|
+
* <!-- … fields … -->
|
|
117
|
+
* </div>
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
class ForFieldsetLegend {
|
|
121
|
+
ctx = inject(FOR_FIELDSET_CONTEXT, { optional: true });
|
|
122
|
+
/** The legend's id when inside a fieldset, else null. */
|
|
123
|
+
legendId = computed(() => this.ctx?.legendId() ?? null, /* @ts-ignore */
|
|
124
|
+
...(ngDevMode ? [{ debugName: "legendId" }] : /* istanbul ignore next */ []));
|
|
125
|
+
constructor() {
|
|
126
|
+
const ctx = this.ctx;
|
|
127
|
+
if (ctx) {
|
|
128
|
+
const unregister = ctx.registerLegend();
|
|
129
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldsetLegend, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
133
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForFieldsetLegend, isStandalone: true, selector: "[forFieldsetLegend]", host: { properties: { "attr.id": "legendId()" } }, exportAs: ["forFieldsetLegend"], ngImport: i0 });
|
|
134
|
+
}
|
|
135
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForFieldsetLegend, decorators: [{
|
|
136
|
+
type: Directive,
|
|
137
|
+
args: [{
|
|
138
|
+
selector: '[forFieldsetLegend]',
|
|
139
|
+
exportAs: 'forFieldsetLegend',
|
|
140
|
+
host: {
|
|
141
|
+
'[attr.id]': 'legendId()',
|
|
142
|
+
},
|
|
143
|
+
}]
|
|
144
|
+
}], ctorParameters: () => [] });
|
|
145
|
+
|
|
146
|
+
const FALLBACK = {};
|
|
147
|
+
const { token, provideDefaults } = createDefaults('FOR_FIELDSET_DEFAULTS', FALLBACK);
|
|
148
|
+
/** Token holding the resolved fieldset defaults for the current scope. */
|
|
149
|
+
const FOR_FIELDSET_DEFAULTS = token;
|
|
150
|
+
/**
|
|
151
|
+
* Configures forty-cdk fieldset defaults for this injector scope. Partial
|
|
152
|
+
* overrides inherit unspecified keys from the parent scope (or library
|
|
153
|
+
* defaults at the root).
|
|
154
|
+
*/
|
|
155
|
+
function provideForFieldsetDefaults(defaults = {}) {
|
|
156
|
+
return provideDefaults(defaults);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generated bundle index. Do not edit.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
export { FOR_FIELDSET_DEFAULTS, ForFieldset, ForFieldsetLegend, provideForFieldsetDefaults };
|
|
164
|
+
//# sourceMappingURL=forty-cdk-fieldset.mjs.map
|