ng-form-foundry 0.2.1 → 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/README.md +9 -6
- package/fesm2022/ng-form-foundry.mjs +754 -169
- package/fesm2022/ng-form-foundry.mjs.map +1 -1
- package/index.d.ts +347 -40
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FormControl, FormArray, FormGroup } from '@angular/forms';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { OnInit, EventEmitter,
|
|
3
|
+
import { OnInit, AfterViewInit, EventEmitter, ElementRef, QueryList, ChangeDetectorRef } from '@angular/core';
|
|
4
4
|
import { MatDialog } from '@angular/material/dialog';
|
|
5
5
|
|
|
6
6
|
type LeafRuntimeType<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'enum' ? string | number : never;
|
|
@@ -10,6 +10,26 @@ type LeafBase = {
|
|
|
10
10
|
required?: true | undefined;
|
|
11
11
|
label?: string;
|
|
12
12
|
description?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The value may be `null` (JSON Schema `type: [T, 'null']`). Builds a nullable
|
|
15
|
+
* control (drops `nonNullable`), so `null` is a first-class value the
|
|
16
|
+
* constraint validators accept and that survives the round-trip. Distinct from
|
|
17
|
+
* {@link presence}: `nullable` is an explicit `null`, `presence` is an absent key.
|
|
18
|
+
*/
|
|
19
|
+
nullable?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Optional scalar whose *presence itself* is data (mirrors {@link NodeGroup.presence}).
|
|
22
|
+
* Rendered with an on/off toggle; the control is removed from the parent group
|
|
23
|
+
* when absent (so it drops from `form.value`) and re-added when toggled on. The
|
|
24
|
+
* builder omits it unless an initial value is supplied.
|
|
25
|
+
*/
|
|
26
|
+
presence?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Render the field read-only even when the surrounding form is editable.
|
|
29
|
+
* Combine with `default` to express a JSON Schema `const` (a fixed,
|
|
30
|
+
* display-only value); single-element `enum` is the alternative for a constant.
|
|
31
|
+
*/
|
|
32
|
+
readOnly?: boolean;
|
|
13
33
|
};
|
|
14
34
|
type AnonLeaf = {
|
|
15
35
|
[K in Leaf['type']]: {
|
|
@@ -19,10 +39,30 @@ type AnonLeaf = {
|
|
|
19
39
|
type LeafString = LeafBase & {
|
|
20
40
|
type: 'string';
|
|
21
41
|
default?: LeafRuntimeType<'string'>;
|
|
42
|
+
/**
|
|
43
|
+
* Reject values that don't match this regular expression. Follows JSON Schema
|
|
44
|
+
* `pattern` semantics — an *unanchored* `RegExp.test`, so it matches anywhere
|
|
45
|
+
* in the value unless the pattern itself anchors with `^`/`$`.
|
|
46
|
+
*/
|
|
47
|
+
pattern?: string;
|
|
48
|
+
/** Minimum string length (JSON Schema `minLength`). */
|
|
49
|
+
minLength?: number;
|
|
50
|
+
/** Maximum string length (JSON Schema `maxLength`). */
|
|
51
|
+
maxLength?: number;
|
|
52
|
+
/** Semantic string format (JSON Schema `format`); adds a matching validator. */
|
|
53
|
+
format?: 'email' | 'uri' | 'url';
|
|
22
54
|
};
|
|
23
55
|
type LeafNumber = LeafBase & {
|
|
24
56
|
type: 'number';
|
|
25
57
|
default?: LeafRuntimeType<'number'>;
|
|
58
|
+
/** Require a whole-number value (JSON Schema `type: 'integer'`). */
|
|
59
|
+
integer?: boolean;
|
|
60
|
+
/** Inclusive lower bound (JSON Schema `minimum`). */
|
|
61
|
+
min?: number;
|
|
62
|
+
/** Inclusive upper bound (JSON Schema `maximum`). */
|
|
63
|
+
max?: number;
|
|
64
|
+
/** Require the value to be an integer multiple of this number (JSON Schema `multipleOf`). */
|
|
65
|
+
multipleOf?: number;
|
|
26
66
|
};
|
|
27
67
|
type LeafBoolean = LeafBase & {
|
|
28
68
|
type: 'boolean';
|
|
@@ -37,6 +77,8 @@ type LeafEnum = LeafBase & {
|
|
|
37
77
|
type Appearance = {
|
|
38
78
|
flatten?: boolean;
|
|
39
79
|
noBorder?: boolean;
|
|
80
|
+
/** Start this node's section panel collapsed. Ignored when `flatten` is set. */
|
|
81
|
+
collapsed?: boolean;
|
|
40
82
|
};
|
|
41
83
|
type Leaf = LeafString | LeafNumber | LeafBoolean | LeafEnum;
|
|
42
84
|
type LeafList<TKind extends Leaf['type'] = Leaf['type']> = {
|
|
@@ -75,29 +117,132 @@ type NodeGroup = {
|
|
|
75
117
|
children: Record<string, NodeType>;
|
|
76
118
|
appearance?: Appearance;
|
|
77
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* One case of a {@link NodeChoice}: either a record of named fields (an object
|
|
122
|
+
* branch), or a single node (a *leaf-bodied* case — e.g. an `anyOf` branch that
|
|
123
|
+
* is a bare scalar). A single node is normalized to a one-field record keyed by
|
|
124
|
+
* its `name` when the form is built.
|
|
125
|
+
*/
|
|
126
|
+
type ChoiceCase = Record<string, NodeType> | NodeType;
|
|
78
127
|
/**
|
|
79
128
|
* A discriminated selection: the user picks one `case`, and only that case's
|
|
80
129
|
* fields are present. In the form it is a FormGroup holding a `__case` control
|
|
81
130
|
* (the active case name) plus that case's field controls; switching the case
|
|
82
131
|
* swaps the field controls.
|
|
132
|
+
*
|
|
133
|
+
* Cases may be **anonymous / auto-named** (any string key) — for JSON Schema
|
|
134
|
+
* `anyOf`/`oneOf` branches with no name. When a built form is seeded from inline
|
|
135
|
+
* data that carries no `__case`, the builder **infers** the active case from the
|
|
136
|
+
* data shape (the case whose fields best match), so a choice round-trips from
|
|
137
|
+
* real instance data. See the schema reference for the required-set / `const`
|
|
138
|
+
* discriminator recipe.
|
|
83
139
|
*/
|
|
84
140
|
type NodeChoice = {
|
|
85
141
|
kind: 'choice';
|
|
86
142
|
name: string;
|
|
87
143
|
label?: string;
|
|
88
|
-
cases: Record<string,
|
|
144
|
+
cases: Record<string, ChoiceCase>;
|
|
145
|
+
/**
|
|
146
|
+
* Display labels for cases, keyed by case name — for anonymous/auto-named
|
|
147
|
+
* branches whose keys are not human-friendly. Falls back to the case name.
|
|
148
|
+
*/
|
|
149
|
+
caseLabels?: Record<string, string>;
|
|
89
150
|
default?: string;
|
|
90
151
|
mandatory?: boolean;
|
|
152
|
+
/** Optional choice: rendered with an on/off toggle, omitted from the value when absent. */
|
|
153
|
+
presence?: boolean;
|
|
154
|
+
appearance?: Appearance;
|
|
91
155
|
};
|
|
92
156
|
/** The control name that records which case of a {@link NodeChoice} is active. */
|
|
93
157
|
declare const CASE_KEY = "__case";
|
|
94
|
-
|
|
95
|
-
|
|
158
|
+
/**
|
|
159
|
+
* An open, arbitrary-keyed record: unlike {@link NodeGroup} (a fixed, declared
|
|
160
|
+
* key set), a map's keys are runtime data and every value conforms to one shared
|
|
161
|
+
* `value` schema. Maps JSON Schema `additionalProperties: <schema>` /
|
|
162
|
+
* `patternProperties`. In the form it is a `FormGroup` whose control *names* are
|
|
163
|
+
* the entry keys, so `getRawValue()` is the map object directly; the renderer
|
|
164
|
+
* lets the user add, remove, and rename entries.
|
|
165
|
+
*/
|
|
166
|
+
type NodeMap = {
|
|
167
|
+
kind: 'map';
|
|
168
|
+
name: string;
|
|
169
|
+
label?: string;
|
|
170
|
+
description?: string;
|
|
171
|
+
/** The schema every entry's value conforms to. */
|
|
172
|
+
value: NodeType;
|
|
173
|
+
/** Label for the key column in the editor. Defaults to "Key". */
|
|
174
|
+
keyLabel?: string;
|
|
175
|
+
/** `patternProperties`: entry keys must match this regular expression. */
|
|
176
|
+
keyPattern?: string;
|
|
177
|
+
/** Minimum number of entries (JSON Schema `minProperties`). */
|
|
178
|
+
minEntries?: number;
|
|
179
|
+
/** Maximum number of entries (JSON Schema `maxProperties`). */
|
|
180
|
+
maxEntries?: number;
|
|
181
|
+
/** Optional map: rendered with an on/off toggle, omitted from the value when absent. */
|
|
182
|
+
presence?: boolean;
|
|
183
|
+
appearance?: Appearance;
|
|
184
|
+
};
|
|
185
|
+
type NodeType = Leaf | LeafList | NodeGroup | NodeGroupList | NodeChoice | NodeMap;
|
|
186
|
+
type DFormControl<T extends NodeType> = T extends Leaf ? FormControl<LeafRuntimeType<T['type']>> : T extends LeafList ? FormArray<FormControl<LeafRuntimeType<T['type']>>> : T extends NodeGroup ? DFormGroup<T> : T extends NodeGroupList ? FormArray<DFormGroup<T['type']>> : T extends NodeChoice ? FormGroup<any> : T extends NodeMap ? FormGroup<any> : never;
|
|
96
187
|
type FormGroupType<T extends NodeGroup> = {
|
|
97
188
|
[TChild in keyof T['children']]: DFormControl<T['children'][TChild]>;
|
|
98
189
|
};
|
|
99
190
|
type DFormGroup<T extends NodeGroup> = FormGroup<FormGroupType<T>>;
|
|
100
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Build the `AbstractControl` for a single schema node.
|
|
194
|
+
*
|
|
195
|
+
* Dispatches on `node.kind`: a `leaf` becomes a `FormControl`, a `leafList` a
|
|
196
|
+
* `FormArray` of controls, a `nodeGroup` a nested `FormGroup`, and a
|
|
197
|
+
* `nodeGroupList` a `FormArray` of groups. `initial` is the runtime value for
|
|
198
|
+
* this node — a scalar for a leaf, an array for a list, an object for a group —
|
|
199
|
+
* and seeds the control's value (falling back to the node's `default`).
|
|
200
|
+
*
|
|
201
|
+
* Most callers use {@link buildFormFromSchema}; this is exposed for building a
|
|
202
|
+
* control from a single non-root node.
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Build the FormGroup for a choice: a `__case` control holding the active case
|
|
206
|
+
* name plus that case's field controls. Only the active case's fields are built,
|
|
207
|
+
* matching the inline YANG encoding; switching the case swaps them.
|
|
208
|
+
*/
|
|
209
|
+
/**
|
|
210
|
+
* Normalize a {@link ChoiceCase} to a field record. A field record is returned
|
|
211
|
+
* as-is; a single node (a leaf-bodied case, e.g. a scalar `anyOf` branch) becomes
|
|
212
|
+
* a one-field record keyed by the node's `name`. The discriminant is a top-level
|
|
213
|
+
* `kind` string, which a field record never has (its keys are field names).
|
|
214
|
+
*/
|
|
215
|
+
declare function caseFields(body: ChoiceCase): Record<string, NodeType>;
|
|
216
|
+
/**
|
|
217
|
+
* The active case of a choice: an explicit `__case` in the initial value, else
|
|
218
|
+
* the case whose fields best match the initial data (inline wire data carries no
|
|
219
|
+
* `__case`), else the schema `default`. This lets a choice seed from real
|
|
220
|
+
* instance data whose branch is discriminated by which fields are present.
|
|
221
|
+
*/
|
|
222
|
+
declare function resolveChoiceCase(choice: NodeChoice, initial?: Record<string, unknown> | null): string | undefined;
|
|
223
|
+
/**
|
|
224
|
+
* Switch a choice's FormGroup to `caseName`: sets `__case`, removes every other
|
|
225
|
+
* control, and builds `caseName`'s fields (normalized via {@link caseFields})
|
|
226
|
+
* with their defaults. An unknown case name leaves only `__case`.
|
|
227
|
+
*/
|
|
228
|
+
declare function switchChoiceCase(group: FormGroup, choice: NodeChoice, caseName: string): void;
|
|
229
|
+
/**
|
|
230
|
+
* Append a map entry built from `map.value` and return its committed key, or
|
|
231
|
+
* `null` when nothing was added. With no `key`, the first free `keyN`
|
|
232
|
+
* placeholder is generated (not checked against `keyPattern` — placeholders are
|
|
233
|
+
* meant to be renamed). An explicit `key` is rejected when it duplicates an
|
|
234
|
+
* existing entry or violates `keyPattern`. Rejects when `maxEntries` is reached.
|
|
235
|
+
*/
|
|
236
|
+
declare function addMapEntry(group: FormGroup, map: NodeMap, key?: string): string | null;
|
|
237
|
+
/**
|
|
238
|
+
* Rename entry `oldKey` to `newKey.trim()`, preserving the control instance
|
|
239
|
+
* (remove + re-add, so the value survives). Returns whether the rename was
|
|
240
|
+
* committed: an empty, unchanged, duplicate, or `keyPattern`-violating key is a
|
|
241
|
+
* no-op, leaving the entry under its current name.
|
|
242
|
+
*/
|
|
243
|
+
declare function renameMapEntry(group: FormGroup, map: NodeMap, oldKey: string, newKey: string): boolean;
|
|
244
|
+
/** Remove entry `key` unless the map is at `minEntries`. Returns whether it was removed. */
|
|
245
|
+
declare function removeMapEntry(group: FormGroup, map: NodeMap, key: string): boolean;
|
|
101
246
|
declare function buildControl<T extends NodeType>(node: T, initial?: unknown | null): DFormControl<T> | FormControl<LeafRuntimeType<any>> | FormArray<any>;
|
|
102
247
|
declare function buildFormFromSchema<S extends NodeGroup>(schema: S, initial?: Record<string, unknown> | null): DFormGroup<S>;
|
|
103
248
|
/**
|
|
@@ -120,16 +265,26 @@ declare function asFormArray(control: any): FormArray;
|
|
|
120
265
|
declare function asFormGroup(control: any): FormGroup;
|
|
121
266
|
|
|
122
267
|
declare class DynamicRecursiveFormComponent implements OnInit {
|
|
123
|
-
schema
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
268
|
+
/** The form-description schema to render (a root or nested `NodeGroup`). */
|
|
269
|
+
readonly schema: i0.InputSignal<NodeGroup>;
|
|
270
|
+
/** Optional value object to seed the form; keyed by the schema's `children` keys. */
|
|
271
|
+
readonly initialValue: i0.InputSignal<Record<string, unknown> | null>;
|
|
272
|
+
/** The reactive group this form binds to. Defaults to an empty group. */
|
|
273
|
+
readonly formGroup: i0.InputSignal<FormGroup<any>>;
|
|
274
|
+
/** Index of this form within a parent list, used by `addButtonCallback`. */
|
|
275
|
+
readonly index: i0.InputSignal<number | null>;
|
|
276
|
+
/** Whether this form may be removed from a parent list (shows a remove control). */
|
|
277
|
+
readonly removable: i0.InputSignal<boolean>;
|
|
278
|
+
/** Emitted when the user removes this form from a parent list. */
|
|
279
|
+
readonly remove: i0.OutputEmitterRef<void>;
|
|
280
|
+
/** Card/section title; falls back to the schema label or name. */
|
|
281
|
+
readonly title: i0.InputSignal<string | undefined>;
|
|
282
|
+
/** Whether fields accept input. Two-way: also toggled by the built-in edit control. */
|
|
283
|
+
readonly editable: i0.ModelSignal<boolean>;
|
|
284
|
+
/** Invoked with {@link index} to append a new sibling form to a parent list. */
|
|
285
|
+
readonly addButtonCallback: i0.InputSignal<((index: number) => void) | null>;
|
|
286
|
+
/** True when the schema is a root group (rendered flat, without a wrapping card). */
|
|
287
|
+
readonly root: i0.Signal<boolean>;
|
|
133
288
|
ngOnInit(): void;
|
|
134
289
|
get nodeGroupChildrenList(): Array<{
|
|
135
290
|
key: string;
|
|
@@ -141,19 +296,41 @@ declare class DynamicRecursiveFormComponent implements OnInit {
|
|
|
141
296
|
* group from `form.value`; adding it rebuilds the sub-group from its schema.
|
|
142
297
|
*/
|
|
143
298
|
togglePresence(key: string, schema: NodeGroup, present: boolean): void;
|
|
299
|
+
/** The key of the presence leaf the user just enabled; its field grabs focus when rendered. */
|
|
300
|
+
protected presenceFocusKey: string | null;
|
|
301
|
+
/**
|
|
302
|
+
* Add or remove a presence leaf's control on this form. Removing it drops the
|
|
303
|
+
* leaf from `form.value`; adding it rebuilds the control from its schema and
|
|
304
|
+
* focuses the rendered field.
|
|
305
|
+
*/
|
|
306
|
+
toggleLeafPresence(key: string, schema: Leaf, present: boolean): void;
|
|
307
|
+
/**
|
|
308
|
+
* Add or remove a presence map's or choice's control on this form. Removing it
|
|
309
|
+
* drops the node from `form.value`; adding it rebuilds the control (an empty
|
|
310
|
+
* map, or a choice group holding `__case`) from its schema.
|
|
311
|
+
*/
|
|
312
|
+
toggleNodePresence(key: string, schema: NodeType, present: boolean): void;
|
|
144
313
|
protected readonly CASE_KEY = "__case";
|
|
145
314
|
objectKeys(obj: Record<string, unknown>): string[];
|
|
146
315
|
/** The active case name of a choice control, or null if none is selected. */
|
|
147
316
|
activeCase(key: string): string | null;
|
|
148
317
|
/** A synthetic flattened NodeGroup used to render the active case's fields against the choice's FormGroup. */
|
|
149
318
|
caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
|
|
150
|
-
/**
|
|
319
|
+
/** The display label for a case: the schema's `caseLabels` entry, else the case name. */
|
|
320
|
+
caseLabel(choice: NodeChoice, caseName: string): string;
|
|
321
|
+
/**
|
|
322
|
+
* A copy of a group flagged to render its fields inline (no inner panel). Used
|
|
323
|
+
* for a presence group's body, whose container is the presence panel itself, so
|
|
324
|
+
* the group's own section panel would be a redundant second box.
|
|
325
|
+
*/
|
|
326
|
+
flatGroup(group: NodeGroup): NodeGroup;
|
|
327
|
+
/** Swap a choice's field controls when the selected case changes. Delegates to {@link switchChoiceCase}. */
|
|
151
328
|
switchCase(key: string, choice: NodeChoice, caseName: string): void;
|
|
152
329
|
protected readonly asFormGroup: typeof asFormGroup;
|
|
153
330
|
protected readonly asFormArray: typeof asFormArray;
|
|
154
331
|
protected readonly asFormControl: typeof asFormControl;
|
|
155
332
|
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicRecursiveFormComponent, never>;
|
|
156
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicRecursiveFormComponent, "nff-dynamic-recursive-form", never, { "schema": { "alias": "schema"; "required": true; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formGroup": { "alias": "formGroup"; "required": false; }; "index": { "alias": "index"; "required": false; }; "removable": { "alias": "removable"; "required": false; }; "title": { "alias": "title"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "addButtonCallback": { "alias": "addButtonCallback"; "required": false; }; }, { "remove": "remove"; }, never, never, true, never>;
|
|
333
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicRecursiveFormComponent, "nff-dynamic-recursive-form", never, { "schema": { "alias": "schema"; "required": true; "isSignal": true; }; "initialValue": { "alias": "initialValue"; "required": false; "isSignal": true; }; "formGroup": { "alias": "formGroup"; "required": false; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "removable": { "alias": "removable"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; "addButtonCallback": { "alias": "addButtonCallback"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "editable": "editableChange"; }, never, never, true, never>;
|
|
157
334
|
}
|
|
158
335
|
|
|
159
336
|
/** Metadata on a list-container node that lets the tree add items to its FormArray. */
|
|
@@ -163,21 +340,30 @@ interface ListRef {
|
|
|
163
340
|
itemLabel: string;
|
|
164
341
|
minItems: number;
|
|
165
342
|
}
|
|
343
|
+
/** An absent optional (presence) child, offered by its parent node's "+ Optional field" menu. */
|
|
344
|
+
interface OptionalEntry {
|
|
345
|
+
key: string;
|
|
346
|
+
schema: NodeType;
|
|
347
|
+
label: string;
|
|
348
|
+
/** Index in the parent schema's children iteration; keeps the menu in schema order. */
|
|
349
|
+
order: number;
|
|
350
|
+
}
|
|
166
351
|
/** A navigable node in the config tree. Groups and list items are tree nodes; leaves are their detail. */
|
|
167
352
|
interface TreeNode {
|
|
168
353
|
id: string;
|
|
169
354
|
label: string;
|
|
170
355
|
children: TreeNode[];
|
|
171
|
-
/** Leaves editable when this node is selected.
|
|
356
|
+
/** Leaves editable when this node is selected. A presence leaf carries its `optional` entry so it can be removed back to the menu. */
|
|
172
357
|
leaves: {
|
|
173
358
|
key: string;
|
|
174
359
|
node: Leaf;
|
|
360
|
+
optional?: OptionalEntry;
|
|
175
361
|
}[];
|
|
176
362
|
leafLists: {
|
|
177
363
|
key: string;
|
|
178
364
|
node: LeafList;
|
|
179
365
|
}[];
|
|
180
|
-
/** The FormGroup holding this node's leaves, or null for a list-container node. */
|
|
366
|
+
/** The FormGroup holding this node's leaves, or null for a list-container or map node. */
|
|
181
367
|
group: FormGroup | null;
|
|
182
368
|
/** Present on a nodeGroupList node: lets a `+` add an item. */
|
|
183
369
|
list?: ListRef;
|
|
@@ -186,26 +372,51 @@ interface TreeNode {
|
|
|
186
372
|
array: FormArray;
|
|
187
373
|
index: number;
|
|
188
374
|
};
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
|
|
375
|
+
/** Absent optional children of this node, offered by its "+ Optional field" menu. */
|
|
376
|
+
optionals?: OptionalEntry[];
|
|
377
|
+
/** Present on a present optional child node: drives the row's remove control; removal returns `entry` to the parent's menu. */
|
|
378
|
+
presenceRemovable?: {
|
|
379
|
+
entry: OptionalEntry;
|
|
380
|
+
};
|
|
381
|
+
/** Present on a choice node: the choice schema and its FormGroup (holding `__case` + the active case's fields). */
|
|
382
|
+
choice?: {
|
|
383
|
+
schema: NodeChoice;
|
|
384
|
+
group: FormGroup;
|
|
385
|
+
};
|
|
386
|
+
/** Present on a map node. `complex` maps expand entries as child nodes; leaf-valued maps edit inline in the detail pane. */
|
|
387
|
+
map?: {
|
|
388
|
+
schema: NodeMap;
|
|
389
|
+
group: FormGroup;
|
|
390
|
+
complex: boolean;
|
|
391
|
+
};
|
|
392
|
+
/** Present on a complex-map entry node: addresses the entry in its map for remove/rename. */
|
|
393
|
+
mapEntry?: {
|
|
394
|
+
mapGroup: FormGroup;
|
|
395
|
+
mapSchema: NodeMap;
|
|
192
396
|
key: string;
|
|
193
|
-
schema: NodeGroup;
|
|
194
397
|
};
|
|
195
|
-
/** For a presence node: whether the group is currently present. */
|
|
196
|
-
present?: boolean;
|
|
197
398
|
}
|
|
198
399
|
/**
|
|
199
|
-
* A tree/detail editor for a schema-built form: the structure (
|
|
200
|
-
*
|
|
201
|
-
* fields for editing on the right. A `+` on a list node adds an entry; a
|
|
202
|
-
* button on each item removes it.
|
|
203
|
-
*
|
|
400
|
+
* A tree/detail editor for a schema-built form: the structure (groups, lists,
|
|
401
|
+
* maps, choices) is a tree on the left, and selecting a node shows that node's
|
|
402
|
+
* fields for editing on the right. A `+` on a list or map node adds an entry; a
|
|
403
|
+
* delete button on each item removes it. Absent optional (presence) children are
|
|
404
|
+
* offered by a "+ Optional field" menu row at the end of their parent's
|
|
405
|
+
* children; present ones carry a delete button that returns them to the menu.
|
|
406
|
+
*
|
|
407
|
+
* The component draws no outer container — only a divider between the tree and
|
|
408
|
+
* detail panes — so the embedding client owns the surrounding chrome. The tree
|
|
409
|
+
* is built once from the `schema`/`formGroup` provided at initialization.
|
|
410
|
+
* An alternative to the all-in-one {@link DynamicRecursiveFormComponent} for
|
|
411
|
+
* large configs.
|
|
204
412
|
*/
|
|
205
413
|
declare class ConfigEditorComponent implements OnInit {
|
|
206
|
-
schema
|
|
207
|
-
|
|
208
|
-
|
|
414
|
+
/** The form-description schema whose structure the tree renders. */
|
|
415
|
+
readonly schema: i0.InputSignal<NodeGroup>;
|
|
416
|
+
/** The schema-built reactive group the editor binds to and mutates. */
|
|
417
|
+
readonly formGroup: i0.InputSignal<FormGroup<any>>;
|
|
418
|
+
/** Whether fields accept input and structural controls (add/remove/menus) show. */
|
|
419
|
+
readonly editable: i0.InputSignal<boolean>;
|
|
209
420
|
root: TreeNode;
|
|
210
421
|
selected: TreeNode | null;
|
|
211
422
|
readonly expanded: Set<string>;
|
|
@@ -221,32 +432,92 @@ declare class ConfigEditorComponent implements OnInit {
|
|
|
221
432
|
*/
|
|
222
433
|
pathTo(target: TreeNode): TreeNode[];
|
|
223
434
|
toggle(node: TreeNode): void;
|
|
435
|
+
/** Whether the row shows an expand twisty: it has child rows to reveal (children or an optionals menu row). */
|
|
436
|
+
hasExpandableContent(node: TreeNode): boolean;
|
|
437
|
+
/**
|
|
438
|
+
* Whether the node's form subtree holds a validation error. Group/choice, list,
|
|
439
|
+
* and map nodes each check their backing control; `invalid` aggregates over
|
|
440
|
+
* descendants, so an error anywhere below lights every ancestor row.
|
|
441
|
+
*/
|
|
442
|
+
hasError(node: TreeNode): boolean;
|
|
224
443
|
/** Append a new item to a list node's FormArray and to the tree, then select it. */
|
|
225
444
|
addItem(listNode: TreeNode): void;
|
|
226
445
|
/** Remove a list item from its FormArray and the tree (down to `minItems`). */
|
|
227
446
|
removeItem(listNode: TreeNode, item: TreeNode): void;
|
|
228
|
-
/**
|
|
229
|
-
|
|
447
|
+
/** The key of the optional leaf the user just added; its detail-pane field grabs focus when rendered. */
|
|
448
|
+
protected focusLeafKey: string | null;
|
|
449
|
+
/** Add an absent optional child from the menu: build its control, place it in the tree, and select it. */
|
|
450
|
+
addOptional(node: TreeNode, entry: OptionalEntry): void;
|
|
451
|
+
/** Remove a present optional child node, returning its entry to the parent's menu. */
|
|
452
|
+
removeOptional(parent: TreeNode, node: TreeNode): void;
|
|
453
|
+
/** Remove a present optional leaf from the detail pane, returning its entry to the menu. */
|
|
454
|
+
removeOptionalLeaf(node: TreeNode, leaf: {
|
|
455
|
+
key: string;
|
|
456
|
+
node: Leaf;
|
|
457
|
+
optional?: OptionalEntry;
|
|
458
|
+
}): void;
|
|
459
|
+
/** The active case name of a choice node, or null when none is selected. */
|
|
460
|
+
activeCase(node: TreeNode): string | null;
|
|
461
|
+
/** The display label of a choice node's active case, or null when no case is selected. */
|
|
462
|
+
activeCaseLabel(node: TreeNode): string | null;
|
|
463
|
+
/** The display label for a case: the schema's `caseLabels` entry, else the case name. */
|
|
464
|
+
caseLabel(choice: NodeChoice, caseName: string): string;
|
|
465
|
+
/** Switch a choice node's active case: swap the group's controls and rebuild the subtree in place. */
|
|
466
|
+
switchTreeCase(node: TreeNode, caseName: string): void;
|
|
467
|
+
/** Append a new entry to a complex map node under a generated unique key, then select it. */
|
|
468
|
+
addTreeMapEntry(mapNode: TreeNode): void;
|
|
469
|
+
/** Remove a complex map entry (down to `minEntries`) from the form group and the tree. */
|
|
470
|
+
removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode): void;
|
|
471
|
+
/** Commit a rename-on-blur of a map entry's key; on success the node is relabeled. */
|
|
472
|
+
renameTreeMapEntry(entryNode: TreeNode, rawKey: string): void;
|
|
473
|
+
/** Whether a map node is at `maxEntries` (the add control is hidden). */
|
|
474
|
+
mapAtMax(node: TreeNode | undefined): boolean;
|
|
475
|
+
/** Whether a map node is at `minEntries` (entry remove controls are hidden). */
|
|
476
|
+
mapAtMin(node: TreeNode | undefined): boolean;
|
|
230
477
|
protected readonly asFormControl: typeof asFormControl;
|
|
231
478
|
protected readonly asFormArray: typeof asFormArray;
|
|
232
|
-
|
|
479
|
+
protected readonly CASE_KEY = "__case";
|
|
480
|
+
objectKeys(obj: Record<string, unknown>): string[];
|
|
233
481
|
/** Re-index and re-label a list node's item children (just "#n") after add/remove. */
|
|
234
482
|
private renumber;
|
|
483
|
+
/** Select `fallback` when the current selection is no longer reachable in the tree. */
|
|
484
|
+
private reselectIfOrphaned;
|
|
485
|
+
/** Re-insert a removed optional's entry into the node's menu, keeping schema order. */
|
|
486
|
+
private reinsertOptional;
|
|
487
|
+
/** Synthetic group over a case's normalized fields, so a case body builds like any group. */
|
|
488
|
+
private caseAsGroup;
|
|
235
489
|
private buildTree;
|
|
490
|
+
/** Build the tree node for a single non-leaf child, dispatching on its kind. Null when its control is missing. */
|
|
491
|
+
private buildChildNode;
|
|
492
|
+
/** A node's display label: its schema `label`, else its record key. */
|
|
493
|
+
private labelOf;
|
|
236
494
|
static ɵfac: i0.ɵɵFactoryDeclaration<ConfigEditorComponent, never>;
|
|
237
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<ConfigEditorComponent, "nff-config-editor", never, { "schema": { "alias": "schema"; "required": true; }; "formGroup": { "alias": "formGroup"; "required": true; }; "editable": { "alias": "editable"; "required": false; }; }, {}, never, never, true, never>;
|
|
495
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ConfigEditorComponent, "nff-config-editor", never, { "schema": { "alias": "schema"; "required": true; "isSignal": true; }; "formGroup": { "alias": "formGroup"; "required": true; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
238
496
|
}
|
|
239
497
|
|
|
240
|
-
declare class LeafRendererComponent implements OnInit {
|
|
498
|
+
declare class LeafRendererComponent implements OnInit, AfterViewInit {
|
|
499
|
+
private readonly elementRef;
|
|
241
500
|
leaf_: Leaf | AnonLeaf;
|
|
242
501
|
control: FormControl;
|
|
243
502
|
initialValue?: any;
|
|
244
503
|
removable: boolean;
|
|
245
504
|
editable: boolean;
|
|
505
|
+
/** Focus the field once rendered — for fields the user just added (e.g. an enabled presence leaf). */
|
|
506
|
+
autofocus: boolean;
|
|
246
507
|
remove: EventEmitter<number>;
|
|
508
|
+
constructor(elementRef: ElementRef<HTMLElement>);
|
|
247
509
|
ngOnInit(): void;
|
|
510
|
+
ngAfterViewInit(): void;
|
|
511
|
+
/** Whether this field accepts input: the form is editable and the leaf is not `readOnly`. */
|
|
512
|
+
get fieldEditable(): boolean;
|
|
513
|
+
/**
|
|
514
|
+
* A human-readable message for the control's active validation error, or `''`
|
|
515
|
+
* when valid. `mat-form-field` only shows it once the field is in an error
|
|
516
|
+
* state (invalid and touched), so it can be bound unconditionally.
|
|
517
|
+
*/
|
|
518
|
+
get errorText(): string;
|
|
248
519
|
static ɵfac: i0.ɵɵFactoryDeclaration<LeafRendererComponent, never>;
|
|
249
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<LeafRendererComponent, "nff-leaf-renderer", never, { "leaf_": { "alias": "leaf_"; "required": false; }; "control": { "alias": "control"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "removable": { "alias": "removable"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; }, { "remove": "remove"; }, never, never, true, never>;
|
|
520
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LeafRendererComponent, "nff-leaf-renderer", never, { "leaf_": { "alias": "leaf_"; "required": false; }; "control": { "alias": "control"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "removable": { "alias": "removable"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; }, { "remove": "remove"; }, never, never, true, never>;
|
|
250
521
|
}
|
|
251
522
|
|
|
252
523
|
declare class LeafListRendererComponent implements OnInit {
|
|
@@ -303,5 +574,41 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
|
303
574
|
static ɵcmp: i0.ɵɵComponentDeclaration<NodeGroupListRendererComponent, "nff-node-group-list-renderer", never, { "nodeGroupList": { "alias": "nodeGroupList"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formArray": { "alias": "formArray"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
|
|
304
575
|
}
|
|
305
576
|
|
|
306
|
-
|
|
307
|
-
|
|
577
|
+
/**
|
|
578
|
+
* Renders a {@link NodeMap}: an open, arbitrary-keyed record. Each entry is a row
|
|
579
|
+
* with an editable key and the shared value schema's control; the user can add,
|
|
580
|
+
* remove, and rename entries. The map's control is a `FormGroup` whose control
|
|
581
|
+
* names are the entry keys, so the key is edited as a *rename* (remove + re-add
|
|
582
|
+
* the same control) committed on blur — see {@link renameEntry}.
|
|
583
|
+
*/
|
|
584
|
+
declare class NodeMapRendererComponent implements OnInit {
|
|
585
|
+
nodeMap: NodeMap;
|
|
586
|
+
formGroup: FormGroup<any>;
|
|
587
|
+
editable: boolean;
|
|
588
|
+
/** Ordered view of entry keys, kept stable across renames (`addControl` appends). */
|
|
589
|
+
entryKeys: string[];
|
|
590
|
+
ngOnInit(): void;
|
|
591
|
+
/** The value schema as a leaf (used when `value.kind === 'leaf'`). */
|
|
592
|
+
get valueLeaf(): Leaf;
|
|
593
|
+
/** The value schema as a group (used when `value.kind === 'nodeGroup'`). */
|
|
594
|
+
get valueGroup(): NodeGroup;
|
|
595
|
+
get atMax(): boolean;
|
|
596
|
+
get atMin(): boolean;
|
|
597
|
+
/** Append a new entry under a unique placeholder key. Delegates to {@link addMapEntry}. */
|
|
598
|
+
addEntry(): void;
|
|
599
|
+
/** Drop an entry (its control leaves the group, so it drops from the value). Delegates to {@link removeMapEntry}. */
|
|
600
|
+
removeEntry(key: string): void;
|
|
601
|
+
/**
|
|
602
|
+
* Commit an edited key by renaming its control once (the value is preserved).
|
|
603
|
+
* An empty, unchanged, duplicate, or `keyPattern`-violating key is a no-op,
|
|
604
|
+
* leaving the entry under its current name. Delegates to {@link renameMapEntry}.
|
|
605
|
+
*/
|
|
606
|
+
renameEntry(oldKey: string, rawKey: string): void;
|
|
607
|
+
protected readonly asFormControl: typeof asFormControl;
|
|
608
|
+
protected readonly asFormGroup: typeof asFormGroup;
|
|
609
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NodeMapRendererComponent, never>;
|
|
610
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<NodeMapRendererComponent, "nff-node-map-renderer", never, { "nodeMap": { "alias": "nodeMap"; "required": false; }; "formGroup": { "alias": "formGroup"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; }, {}, never, never, true, never>;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, switchChoiceCase };
|
|
614
|
+
export type { AnonLeaf, Appearance, ChoiceCase, DFormControl, DFormGroup, FormGroupType, Leaf, LeafBase, LeafBoolean, LeafEnum, LeafList, LeafNumber, LeafRuntimeType, LeafString, NodeChoice, NodeGroup, NodeGroupList, NodeMap, NodeType };
|
package/package.json
CHANGED