ng-form-foundry 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/fesm2022/ng-form-foundry.mjs +603 -335
- package/fesm2022/ng-form-foundry.mjs.map +1 -1
- package/index.d.ts +272 -146
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FormControl, FormArray, FormGroup } from '@angular/forms';
|
|
2
|
-
import * as
|
|
3
|
-
import { OnInit,
|
|
2
|
+
import * as _angular_core from '@angular/core';
|
|
3
|
+
import { OnInit, OnDestroy, AfterViewInit, ElementRef, EventEmitter, QueryList, ChangeDetectorRef, OnChanges, SimpleChanges } 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;
|
|
@@ -153,7 +153,11 @@ type NodeChoice = {
|
|
|
153
153
|
presence?: boolean;
|
|
154
154
|
appearance?: Appearance;
|
|
155
155
|
};
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* The control name that records which case of a {@link NodeChoice} is active.
|
|
158
|
+
* The name is reserved: it cannot be used as a case field name (the builder
|
|
159
|
+
* throws) or as a map entry key (the entry helpers reject it).
|
|
160
|
+
*/
|
|
157
161
|
declare const CASE_KEY = "__case";
|
|
158
162
|
/**
|
|
159
163
|
* An open, arbitrary-keyed record: unlike {@link NodeGroup} (a fixed, declared
|
|
@@ -189,28 +193,15 @@ type FormGroupType<T extends NodeGroup> = {
|
|
|
189
193
|
};
|
|
190
194
|
type DFormGroup<T extends NodeGroup> = FormGroup<FormGroupType<T>>;
|
|
191
195
|
|
|
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
196
|
/**
|
|
210
197
|
* Normalize a {@link ChoiceCase} to a field record. A field record is returned
|
|
211
198
|
* as-is; a single node (a leaf-bodied case, e.g. a scalar `anyOf` branch) becomes
|
|
212
199
|
* a one-field record keyed by the node's `name`. The discriminant is a top-level
|
|
213
200
|
* `kind` string, which a field record never has (its keys are field names).
|
|
201
|
+
*
|
|
202
|
+
* Throws when a case field is keyed `__case`: that name is reserved for the
|
|
203
|
+
* choice discriminator ({@link CASE_KEY}) and a field under it would silently
|
|
204
|
+
* clobber the active-case control.
|
|
214
205
|
*/
|
|
215
206
|
declare function caseFields(body: ChoiceCase): Record<string, NodeType>;
|
|
216
207
|
/**
|
|
@@ -223,7 +214,10 @@ declare function resolveChoiceCase(choice: NodeChoice, initial?: Record<string,
|
|
|
223
214
|
/**
|
|
224
215
|
* Switch a choice's FormGroup to `caseName`: sets `__case`, removes every other
|
|
225
216
|
* control, and builds `caseName`'s fields (normalized via {@link caseFields})
|
|
226
|
-
* with their defaults.
|
|
217
|
+
* with their defaults. Presence fields of the new case start absent — the
|
|
218
|
+
* switch carries no data that could make them present. An unknown case name
|
|
219
|
+
* leaves only `__case`. The swap is atomic: one value change fires, and every
|
|
220
|
+
* observable snapshot has fields matching its discriminator.
|
|
227
221
|
*/
|
|
228
222
|
declare function switchChoiceCase(group: FormGroup, choice: NodeChoice, caseName: string): void;
|
|
229
223
|
/**
|
|
@@ -236,15 +230,72 @@ declare function switchChoiceCase(group: FormGroup, choice: NodeChoice, caseName
|
|
|
236
230
|
declare function addMapEntry(group: FormGroup, map: NodeMap, key?: string): string | null;
|
|
237
231
|
/**
|
|
238
232
|
* Rename entry `oldKey` to `newKey.trim()`, preserving the control instance
|
|
239
|
-
* (
|
|
240
|
-
*
|
|
241
|
-
*
|
|
233
|
+
* (so the value survives) and the entry's position in the group's key order —
|
|
234
|
+
* the order `getRawValue()` serializes and the tree editor renders. Returns
|
|
235
|
+
* whether the rename was committed: an empty, reserved (`__case`), unchanged,
|
|
236
|
+
* duplicate, or `keyPattern`-violating key is a no-op, leaving the entry under
|
|
237
|
+
* its current name. Entry keys are looked up verbatim — never via
|
|
238
|
+
* `AbstractControl.get`, which would split keys like `10.0.0.1` into
|
|
239
|
+
* dot-delimited paths. Emits a single value change.
|
|
242
240
|
*/
|
|
243
241
|
declare function renameMapEntry(group: FormGroup, map: NodeMap, oldKey: string, newKey: string): boolean;
|
|
244
242
|
/** Remove entry `key` unless the map is at `minEntries`. Returns whether it was removed. */
|
|
245
243
|
declare function removeMapEntry(group: FormGroup, map: NodeMap, key: string): boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Build the `AbstractControl` for a single schema node.
|
|
246
|
+
*
|
|
247
|
+
* Dispatches on `node.kind`: a `leaf` becomes a `FormControl`, a `leafList` a
|
|
248
|
+
* `FormArray` of controls, a `nodeGroup` a nested `FormGroup`, and a
|
|
249
|
+
* `nodeGroupList` a `FormArray` of groups. `initial` is the runtime value for
|
|
250
|
+
* this node — a scalar for a leaf, an array for a list, an object for a group —
|
|
251
|
+
* and seeds the control's value (falling back to the node's `default`).
|
|
252
|
+
*
|
|
253
|
+
* Most callers use {@link buildFormFromSchema}; this is exposed for building a
|
|
254
|
+
* control from a single non-root node.
|
|
255
|
+
*/
|
|
246
256
|
declare function buildControl<T extends NodeType>(node: T, initial?: unknown | null): DFormControl<T> | FormControl<LeafRuntimeType<any>> | FormArray<any>;
|
|
257
|
+
/**
|
|
258
|
+
* Build a typed `FormGroup` from a root `NodeGroup` schema.
|
|
259
|
+
*
|
|
260
|
+
* The returned group's control structure, keys, and value types are inferred
|
|
261
|
+
* from the schema literal — a `leaf` of `type: 'number'` yields a
|
|
262
|
+
* `FormControl<number>`, a `nodeGroup` a nested `FormGroup`, and so on. `initial`
|
|
263
|
+
* is an optional value object keyed by the schema's `children` keys; each child
|
|
264
|
+
* control is seeded from its matching slice (falling back to the node `default`).
|
|
265
|
+
*
|
|
266
|
+
* Presence nodes whose key is absent from `initial` get no control, at any depth
|
|
267
|
+
* — plain children, list items, map values, and choice case fields alike — so
|
|
268
|
+
* they are absent from the form value until enabled. A key present with an
|
|
269
|
+
* explicit `null` keeps its control: `null` is a value, absence is the missing
|
|
270
|
+
* key.
|
|
271
|
+
*
|
|
272
|
+
* Inference only holds when `schema`'s literal type is preserved. Author schemas
|
|
273
|
+
* with {@link defineSchema} or a `satisfies NodeGroup` annotation — never
|
|
274
|
+
* `const schema: NodeGroup = ...`, which widens `children` and erases the field
|
|
275
|
+
* names and value types.
|
|
276
|
+
*/
|
|
247
277
|
declare function buildFormFromSchema<S extends NodeGroup>(schema: S, initial?: Record<string, unknown> | null): DFormGroup<S>;
|
|
278
|
+
/**
|
|
279
|
+
* The wire value at `node`: `value` rebuilt with every choice discriminator
|
|
280
|
+
* removed.
|
|
281
|
+
*
|
|
282
|
+
* A choice's form value is `{ __case, ...fields }` ({@link CASE_KEY}); its wire
|
|
283
|
+
* encoding is the active case's fields inline, with no discriminator — the case
|
|
284
|
+
* is recovered from the field shape when the data is seeded back in
|
|
285
|
+
* ({@link resolveChoiceCase}). The walk is schema-driven: only positions the
|
|
286
|
+
* schema declares as choices are stripped, so a group child or map entry that
|
|
287
|
+
* happens to be named `__case` passes through untouched. Values at positions
|
|
288
|
+
* the schema does not describe pass through unchanged.
|
|
289
|
+
*/
|
|
290
|
+
declare function toWireValue(node: NodeType, value: unknown): unknown;
|
|
291
|
+
/**
|
|
292
|
+
* Serialize a form built by {@link buildFormFromSchema} to its wire value:
|
|
293
|
+
* `form.getRawValue()` with every choice's {@link CASE_KEY} discriminator
|
|
294
|
+
* stripped (see {@link toWireValue}). The result is the inline encoding that
|
|
295
|
+
* `buildFormFromSchema` accepts back as `initial`, so serialize → rebuild
|
|
296
|
+
* round-trips the value.
|
|
297
|
+
*/
|
|
298
|
+
declare function serializeForm(schema: NodeGroup, form: FormGroup): Record<string, unknown>;
|
|
248
299
|
/**
|
|
249
300
|
* Capture a schema literal with its exact type while checking it against
|
|
250
301
|
* `NodeGroup`.
|
|
@@ -266,56 +317,56 @@ declare function asFormGroup(control: any): FormGroup;
|
|
|
266
317
|
|
|
267
318
|
declare class DynamicRecursiveFormComponent implements OnInit {
|
|
268
319
|
/** The form-description schema to render (a root or nested `NodeGroup`). */
|
|
269
|
-
readonly schema:
|
|
320
|
+
readonly schema: _angular_core.InputSignal<NodeGroup>;
|
|
270
321
|
/** Optional value object to seed the form; keyed by the schema's `children` keys. */
|
|
271
|
-
readonly initialValue:
|
|
322
|
+
readonly initialValue: _angular_core.InputSignal<Record<string, unknown> | null>;
|
|
272
323
|
/** The reactive group this form binds to. Defaults to an empty group. */
|
|
273
|
-
readonly formGroup:
|
|
324
|
+
readonly formGroup: _angular_core.InputSignal<FormGroup<any>>;
|
|
274
325
|
/** Index of this form within a parent list, used by `addButtonCallback`. */
|
|
275
|
-
readonly index:
|
|
326
|
+
readonly index: _angular_core.InputSignal<number | null>;
|
|
276
327
|
/** Whether this form may be removed from a parent list (shows a remove control). */
|
|
277
|
-
readonly removable:
|
|
328
|
+
readonly removable: _angular_core.InputSignal<boolean>;
|
|
278
329
|
/** Emitted when the user removes this form from a parent list. */
|
|
279
|
-
readonly remove:
|
|
330
|
+
readonly remove: _angular_core.OutputEmitterRef<void>;
|
|
280
331
|
/** Card/section title; falls back to the schema label or name. */
|
|
281
|
-
readonly title:
|
|
332
|
+
readonly title: _angular_core.InputSignal<string | undefined>;
|
|
282
333
|
/** Whether fields accept input. Two-way: also toggled by the built-in edit control. */
|
|
283
|
-
readonly editable:
|
|
334
|
+
readonly editable: _angular_core.ModelSignal<boolean>;
|
|
284
335
|
/** Invoked with {@link index} to append a new sibling form to a parent list. */
|
|
285
|
-
readonly addButtonCallback:
|
|
336
|
+
readonly addButtonCallback: _angular_core.InputSignal<((index: number) => void) | null>;
|
|
337
|
+
/**
|
|
338
|
+
* Key of a presence leaf whose field should grab focus when it renders — lets
|
|
339
|
+
* a host that added the control itself (e.g. the tree editor's optionals
|
|
340
|
+
* menu) hand focus to the new field, like the form's own add button does.
|
|
341
|
+
*/
|
|
342
|
+
readonly focusLeaf: _angular_core.InputSignal<string | null>;
|
|
286
343
|
/** True when the schema is a root group (rendered flat, without a wrapping card). */
|
|
287
|
-
readonly root:
|
|
344
|
+
readonly root: _angular_core.Signal<boolean>;
|
|
288
345
|
ngOnInit(): void;
|
|
289
|
-
get nodeGroupChildrenList(): Array<{
|
|
346
|
+
protected get nodeGroupChildrenList(): Array<{
|
|
290
347
|
key: string;
|
|
291
348
|
value: NodeType;
|
|
292
349
|
}>;
|
|
293
|
-
emitRemoveEvent(): void;
|
|
294
|
-
/**
|
|
295
|
-
* Add or remove a presence group's control on this form. Removing it drops the
|
|
296
|
-
* group from `form.value`; adding it rebuilds the sub-group from its schema.
|
|
297
|
-
*/
|
|
298
|
-
togglePresence(key: string, schema: NodeGroup, present: boolean): void;
|
|
350
|
+
protected emitRemoveEvent(): void;
|
|
299
351
|
/** The key of the presence leaf the user just enabled; its field grabs focus when rendered. */
|
|
300
352
|
protected presenceFocusKey: string | null;
|
|
301
353
|
/**
|
|
302
|
-
* Add or remove a presence
|
|
303
|
-
* leaf
|
|
304
|
-
*
|
|
354
|
+
* Add or remove a presence node's control on this form — any presence kind:
|
|
355
|
+
* group, leaf, map, or choice. Removing it drops the key from `form.value`;
|
|
356
|
+
* adding it builds the control fresh from its schema (nested presence
|
|
357
|
+
* children start absent).
|
|
305
358
|
*/
|
|
306
|
-
|
|
359
|
+
toggleNodePresence(key: string, schema: NodeType, present: boolean): void;
|
|
307
360
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* map, or a choice group holding `__case`) from its schema.
|
|
361
|
+
* {@link toggleNodePresence} for a presence leaf, additionally focusing the
|
|
362
|
+
* rendered field when the toggle just created it.
|
|
311
363
|
*/
|
|
312
|
-
|
|
313
|
-
protected
|
|
314
|
-
objectKeys(obj: Record<string, unknown>): string[];
|
|
364
|
+
toggleLeafPresence(key: string, schema: Leaf, present: boolean): void;
|
|
365
|
+
protected objectKeys(obj: Record<string, unknown>): string[];
|
|
315
366
|
/** The active case name of a choice control, or null if none is selected. */
|
|
316
|
-
activeCase(key: string): string | null;
|
|
367
|
+
protected activeCase(key: string): string | null;
|
|
317
368
|
/** A synthetic flattened NodeGroup used to render the active case's fields against the choice's FormGroup. */
|
|
318
|
-
caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
|
|
369
|
+
protected caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
|
|
319
370
|
/** The display label for a case: the schema's `caseLabels` entry, else the case name. */
|
|
320
371
|
caseLabel(choice: NodeChoice, caseName: string): string;
|
|
321
372
|
/**
|
|
@@ -323,14 +374,14 @@ declare class DynamicRecursiveFormComponent implements OnInit {
|
|
|
323
374
|
* for a presence group's body, whose container is the presence panel itself, so
|
|
324
375
|
* the group's own section panel would be a redundant second box.
|
|
325
376
|
*/
|
|
326
|
-
flatGroup(group: NodeGroup): NodeGroup;
|
|
377
|
+
protected flatGroup(group: NodeGroup): NodeGroup;
|
|
327
378
|
/** Swap a choice's field controls when the selected case changes. Delegates to {@link switchChoiceCase}. */
|
|
328
379
|
switchCase(key: string, choice: NodeChoice, caseName: string): void;
|
|
329
380
|
protected readonly asFormGroup: typeof asFormGroup;
|
|
330
381
|
protected readonly asFormArray: typeof asFormArray;
|
|
331
382
|
protected readonly asFormControl: typeof asFormControl;
|
|
332
|
-
static ɵfac:
|
|
333
|
-
static ɵcmp:
|
|
383
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicRecursiveFormComponent, never>;
|
|
384
|
+
static ɵcmp: _angular_core.ɵɵ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; }; "focusLeaf": { "alias": "focusLeaf"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "editable": "editableChange"; }, never, never, true, never>;
|
|
334
385
|
}
|
|
335
386
|
|
|
336
387
|
/** Metadata on a list-container node that lets the tree add items to its FormArray. */
|
|
@@ -339,51 +390,62 @@ interface ListRef {
|
|
|
339
390
|
itemSchema: NodeGroup;
|
|
340
391
|
itemLabel: string;
|
|
341
392
|
minItems: number;
|
|
393
|
+
maxItems?: number;
|
|
342
394
|
}
|
|
343
395
|
/** An absent optional (presence) child, offered by its parent node's "+ Optional field" menu. */
|
|
344
396
|
interface OptionalEntry {
|
|
345
397
|
key: string;
|
|
346
398
|
schema: NodeType;
|
|
347
399
|
label: string;
|
|
348
|
-
/** Index in the parent schema's children iteration; keeps the menu in schema order. */
|
|
349
|
-
order: number;
|
|
350
400
|
}
|
|
351
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* One flat section of the detail pane. The selected node's subtree renders as a
|
|
403
|
+
* pre-order list of these — no nesting chrome: each section after the first is
|
|
404
|
+
* separated by a breadcrumb heading (`trail`), and holds only the node's *own*
|
|
405
|
+
* fields (`schema` is a leaf-only slice; complex children are sections of their
|
|
406
|
+
* own, deeper in the list).
|
|
407
|
+
*/
|
|
408
|
+
interface DetailSection {
|
|
409
|
+
node: TreeNode;
|
|
410
|
+
/** Selected-node-to-here trail, rendered as the section's breadcrumb heading (omitted on the first section). */
|
|
411
|
+
trail: TreeNode[];
|
|
412
|
+
/** Leaf-only slice of the node's own schema, or null when it has no own fields. */
|
|
413
|
+
schema: NodeGroup | null;
|
|
414
|
+
/** The group `schema` binds to (the node's group; for a choice, the choice group). */
|
|
415
|
+
group: FormGroup | null;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* A navigable node in the config tree. Its `id` is the node's stable path from
|
|
419
|
+
* the root (`system/ntp`, `ifaces/0`, `servers/web1`), so expansion and
|
|
420
|
+
* selection survive tree rebuilds. Segments are `%`/`/`-escaped, since map
|
|
421
|
+
* entry keys are arbitrary runtime data; list-item identity is positional.
|
|
422
|
+
*/
|
|
352
423
|
interface TreeNode {
|
|
353
424
|
id: string;
|
|
354
425
|
label: string;
|
|
355
426
|
children: TreeNode[];
|
|
356
|
-
/**
|
|
357
|
-
leaves: {
|
|
358
|
-
key: string;
|
|
359
|
-
node: Leaf;
|
|
360
|
-
optional?: OptionalEntry;
|
|
361
|
-
}[];
|
|
362
|
-
leafLists: {
|
|
363
|
-
key: string;
|
|
364
|
-
node: LeafList;
|
|
365
|
-
}[];
|
|
366
|
-
/** The FormGroup holding this node's leaves, or null for a list-container or map node. */
|
|
427
|
+
/** The node's own FormGroup, or null for a list-container or map node. */
|
|
367
428
|
group: FormGroup | null;
|
|
429
|
+
/** The node's own schema, set on group-backed nodes (groups, list items, group-valued map entries). */
|
|
430
|
+
schema?: NodeGroup;
|
|
368
431
|
/** Present on a nodeGroupList node: lets a `+` add an item. */
|
|
369
432
|
list?: ListRef;
|
|
370
|
-
/** Present on a list-item node: the
|
|
433
|
+
/** Present on a list-item node: its current index in the parent list (removal goes through the parent list array). */
|
|
371
434
|
removable?: {
|
|
372
|
-
array: FormArray;
|
|
373
435
|
index: number;
|
|
374
436
|
};
|
|
375
|
-
/** Absent optional children of this node, offered by its "+ Optional field" menu. */
|
|
437
|
+
/** Absent optional children of this node, offered by its "+ Optional field" menu (schema order). */
|
|
376
438
|
optionals?: OptionalEntry[];
|
|
377
|
-
/** Present on a present optional child node: drives the row's remove control
|
|
439
|
+
/** Present on a present optional child node: drives the row's remove control. */
|
|
378
440
|
presenceRemovable?: {
|
|
379
|
-
|
|
441
|
+
key: string;
|
|
380
442
|
};
|
|
381
443
|
/** Present on a choice node: the choice schema and its FormGroup (holding `__case` + the active case's fields). */
|
|
382
444
|
choice?: {
|
|
383
445
|
schema: NodeChoice;
|
|
384
446
|
group: FormGroup;
|
|
385
447
|
};
|
|
386
|
-
/** Present on a map node. `complex` maps expand entries as child nodes
|
|
448
|
+
/** Present on a map node. `complex` maps expand entries as child nodes. */
|
|
387
449
|
map?: {
|
|
388
450
|
schema: NodeMap;
|
|
389
451
|
group: FormGroup;
|
|
@@ -398,115 +460,179 @@ interface TreeNode {
|
|
|
398
460
|
}
|
|
399
461
|
/**
|
|
400
462
|
* 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
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
463
|
+
* maps, choices) is a tree on the left, and selecting a node renders that
|
|
464
|
+
* node's **entire subtree** on the right as a **flat list of sections** — the
|
|
465
|
+
* node's own fields first, then every descendant's fields, each separated by a
|
|
466
|
+
* breadcrumb heading (`Service / Deploy scope / …`) instead of nested panels.
|
|
467
|
+
* Leaf fields render through {@link DynamicRecursiveFormComponent} with a
|
|
468
|
+
* leaf-only schema slice; choice selectors, map rows, and add controls render
|
|
469
|
+
* inline in their section. The tree adds row conveniences of its own: `+` on
|
|
470
|
+
* list and map rows, a delete control on removable rows, and a
|
|
471
|
+
* "+ Optional field" menu for absent presence children.
|
|
472
|
+
*
|
|
473
|
+
* The tree is **derived state**: any structural change to the form — made
|
|
474
|
+
* through the tree rows or through the detail sections — triggers a rebuild (a
|
|
475
|
+
* cheap shape signature over `valueChanges` detects it). Node ids are stable
|
|
476
|
+
* paths, so expansion and selection survive rebuilds. Swapping the `schema` or
|
|
477
|
+
* `formGroup` input rebinds the editor to the new pair, resetting expansion
|
|
478
|
+
* and selection.
|
|
406
479
|
*
|
|
407
480
|
* The component draws no outer container — only a divider between the tree and
|
|
408
|
-
* detail panes — so the embedding client owns the surrounding chrome.
|
|
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.
|
|
481
|
+
* detail panes — so the embedding client owns the surrounding chrome.
|
|
412
482
|
*/
|
|
413
|
-
declare class ConfigEditorComponent implements
|
|
483
|
+
declare class ConfigEditorComponent implements OnDestroy {
|
|
414
484
|
/** The form-description schema whose structure the tree renders. */
|
|
415
|
-
readonly schema:
|
|
485
|
+
readonly schema: _angular_core.InputSignal<NodeGroup>;
|
|
416
486
|
/** The schema-built reactive group the editor binds to and mutates. */
|
|
417
|
-
readonly formGroup:
|
|
487
|
+
readonly formGroup: _angular_core.InputSignal<FormGroup<any>>;
|
|
418
488
|
/** Whether fields accept input and structural controls (add/remove/menus) show. */
|
|
419
|
-
readonly editable:
|
|
489
|
+
readonly editable: _angular_core.InputSignal<boolean>;
|
|
420
490
|
root: TreeNode;
|
|
421
491
|
selected: TreeNode | null;
|
|
492
|
+
/** The selected subtree as a flat, breadcrumb-separated section list. */
|
|
493
|
+
sections: DetailSection[];
|
|
494
|
+
/** Root-to-selection trail for the detail-pane breadcrumb, computed once per selection. */
|
|
495
|
+
breadcrumb: TreeNode[];
|
|
422
496
|
readonly expanded: Set<string>;
|
|
423
|
-
private
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
|
|
497
|
+
private shape;
|
|
498
|
+
private changes?;
|
|
499
|
+
private readonly host;
|
|
500
|
+
/** Stable per-instance ids for the shape signature, so replacing a control (setControl) reads as a structural change. */
|
|
501
|
+
private readonly controlIds;
|
|
502
|
+
private controlIdSeq;
|
|
503
|
+
constructor();
|
|
504
|
+
ngOnDestroy(): void;
|
|
505
|
+
/** Bind the editor to a schema/form pair: fresh tree, root selection, and shape-sync subscription. */
|
|
506
|
+
private attach;
|
|
507
|
+
select(node: TreeNode, reveal?: boolean): void;
|
|
428
508
|
/**
|
|
429
509
|
* Root-to-`target` path for the detail-pane breadcrumb (inclusive of both ends),
|
|
430
|
-
* or an empty array if `target` is not in the current tree.
|
|
431
|
-
* call so it stays correct after add/remove/presence mutations rebuild subtrees.
|
|
510
|
+
* or an empty array if `target` is not in the current tree.
|
|
432
511
|
*/
|
|
433
512
|
pathTo(target: TreeNode): TreeNode[];
|
|
434
|
-
toggle(node: TreeNode): void;
|
|
513
|
+
protected toggle(node: TreeNode): void;
|
|
514
|
+
/** Keyboard access for tree rows: Enter/Space selects, ArrowRight expands, ArrowLeft collapses. */
|
|
515
|
+
protected onRowKeydown(event: KeyboardEvent, node: TreeNode): void;
|
|
435
516
|
/** Whether the row shows an expand twisty: it has child rows to reveal (children or an optionals menu row). */
|
|
436
|
-
hasExpandableContent(node: TreeNode): boolean;
|
|
517
|
+
protected hasExpandableContent(node: TreeNode): boolean;
|
|
437
518
|
/**
|
|
438
519
|
* Whether the node's form subtree holds a validation error. Group/choice, list,
|
|
439
520
|
* and map nodes each check their backing control; `invalid` aggregates over
|
|
440
521
|
* descendants, so an error anywhere below lights every ancestor row.
|
|
441
522
|
*/
|
|
442
|
-
hasError(node: TreeNode): boolean;
|
|
443
|
-
/** Append a new item to a list node's FormArray
|
|
523
|
+
protected hasError(node: TreeNode): boolean;
|
|
524
|
+
/** Append a new item to a list node's FormArray (up to `maxItems`), then select it. */
|
|
444
525
|
addItem(listNode: TreeNode): void;
|
|
445
|
-
/**
|
|
526
|
+
/**
|
|
527
|
+
* Remove a list item from its FormArray (down to `minItems`). List-item
|
|
528
|
+
* identity is positional, so expansion state under the list is cleared —
|
|
529
|
+
* otherwise it would silently migrate to the items that shift into the
|
|
530
|
+
* removed indexes.
|
|
531
|
+
*/
|
|
446
532
|
removeItem(listNode: TreeNode, item: TreeNode): void;
|
|
447
|
-
/** The
|
|
533
|
+
/** The just-added optional leaf (section path + key) whose field grabs focus when rendered. */
|
|
534
|
+
protected focusSectionId: string | null;
|
|
448
535
|
protected focusLeafKey: string | null;
|
|
449
|
-
/** Add an absent optional child from the menu: build its control
|
|
536
|
+
/** Add an absent optional child from the menu: build its control and select the node it lands on. */
|
|
450
537
|
addOptional(node: TreeNode, entry: OptionalEntry): void;
|
|
451
538
|
/** Remove a present optional child node, returning its entry to the parent's menu. */
|
|
452
539
|
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
540
|
/** The active case name of a choice node, or null when none is selected. */
|
|
460
541
|
activeCase(node: TreeNode): string | null;
|
|
461
542
|
/** The display label of a choice node's active case, or null when no case is selected. */
|
|
462
543
|
activeCaseLabel(node: TreeNode): string | null;
|
|
463
544
|
/** 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
|
|
545
|
+
protected caseLabel(choice: NodeChoice, caseName: string): string;
|
|
546
|
+
/** Switch a choice node's active case; the structural sync rebuilds the tree and sections. */
|
|
466
547
|
switchTreeCase(node: TreeNode, caseName: string): void;
|
|
548
|
+
protected objectKeys(obj: Record<string, unknown>): string[];
|
|
467
549
|
/** Append a new entry to a complex map node under a generated unique key, then select it. */
|
|
468
550
|
addTreeMapEntry(mapNode: TreeNode): void;
|
|
469
|
-
/** Remove a complex map entry (down to `minEntries`)
|
|
551
|
+
/** Remove a complex map entry (down to `minEntries`). */
|
|
470
552
|
removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode): void;
|
|
471
|
-
/**
|
|
553
|
+
/**
|
|
554
|
+
* Commit a rename-on-blur of a map entry's key; on success the entry is
|
|
555
|
+
* selected under its new path and its fresh key field regains focus (the
|
|
556
|
+
* rename re-renders the section under a new id, destroying the input that
|
|
557
|
+
* held focus).
|
|
558
|
+
*/
|
|
472
559
|
renameTreeMapEntry(entryNode: TreeNode, rawKey: string): void;
|
|
560
|
+
/** Move keyboard focus to the selected tree row once the action's re-render settles. */
|
|
561
|
+
private focusSelectedRow;
|
|
562
|
+
/** A muted hint for a section whose node currently renders no content of its own. */
|
|
563
|
+
protected emptySectionHint(s: DetailSection): string | null;
|
|
564
|
+
/** Whether a list node is at `maxItems` (the add control is hidden). */
|
|
565
|
+
protected listAtMax(node: TreeNode | undefined): boolean;
|
|
566
|
+
/** Whether a list node is at `minItems` (item remove controls are hidden). */
|
|
567
|
+
protected listAtMin(node: TreeNode | undefined): boolean;
|
|
473
568
|
/** Whether a map node is at `maxEntries` (the add control is hidden). */
|
|
474
|
-
mapAtMax(node: TreeNode | undefined): boolean;
|
|
569
|
+
protected mapAtMax(node: TreeNode | undefined): boolean;
|
|
475
570
|
/** Whether a map node is at `minEntries` (entry remove controls are hidden). */
|
|
476
|
-
mapAtMin(node: TreeNode | undefined): boolean;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
private
|
|
487
|
-
/**
|
|
488
|
-
private
|
|
571
|
+
protected mapAtMin(node: TreeNode | undefined): boolean;
|
|
572
|
+
/** Rebuild the tree from the current form structure if its shape changed. */
|
|
573
|
+
private syncShape;
|
|
574
|
+
/**
|
|
575
|
+
* A cheap structural signature of a control tree: group keys (plus the active
|
|
576
|
+
* `__case`), array lengths, leaf placeholders. Value edits don't change it;
|
|
577
|
+
* added/removed/renamed controls and case switches do. Reading `__case` from
|
|
578
|
+
* any group is safe because the name is reserved (the builder rejects it as a
|
|
579
|
+
* case field name or map entry key), so only choice groups carry it.
|
|
580
|
+
*/
|
|
581
|
+
private shapeOf;
|
|
582
|
+
/** The instance id a container contributes to the shape signature. */
|
|
583
|
+
private uidOf;
|
|
584
|
+
/** Rebuild the whole tree from schema + form and refresh the shape signature. */
|
|
585
|
+
private rebuild;
|
|
586
|
+
/** Re-point `selected` at the rebuilt tree: same path, else the closest surviving ancestor. */
|
|
587
|
+
private reconcileSelection;
|
|
588
|
+
/** The node at `path` in the current tree, or null. Walks by id-prefix segments. */
|
|
589
|
+
private byPath;
|
|
590
|
+
/** Select the node at `path`, falling back through its ancestors to the root. */
|
|
591
|
+
private selectByPath;
|
|
592
|
+
/**
|
|
593
|
+
* Flatten a subtree into detail sections, pre-order: the node's own fields
|
|
594
|
+
* first, then each child as its own breadcrumb-headed section. No nesting
|
|
595
|
+
* chrome — the headings are the boundaries between children.
|
|
596
|
+
*/
|
|
597
|
+
private buildSections;
|
|
598
|
+
/** A section's own renderable fields: a leaf-only schema slice and the group it binds to. */
|
|
599
|
+
private sectionContent;
|
|
600
|
+
/**
|
|
601
|
+
* The node's own fields as a flattened schema: leaf and leafList children
|
|
602
|
+
* only. Complex children are rendered as sections of their own, so the
|
|
603
|
+
* embedded form never draws nested section chrome. Null when there are none.
|
|
604
|
+
*/
|
|
605
|
+
private leafOnly;
|
|
489
606
|
private buildTree;
|
|
490
607
|
/** Build the tree node for a single non-leaf child, dispatching on its kind. Null when its control is missing. */
|
|
491
608
|
private buildChildNode;
|
|
609
|
+
/** Synthetic group over a case's normalized fields, so a case body builds like any group. */
|
|
610
|
+
private caseAsGroup;
|
|
492
611
|
/** A node's display label: its schema `label`, else its record key. */
|
|
493
612
|
private labelOf;
|
|
494
|
-
|
|
495
|
-
|
|
613
|
+
/** Join a parent path and a segment; the root's path is the empty string. */
|
|
614
|
+
private join;
|
|
615
|
+
/**
|
|
616
|
+
* Escape a path segment: `/` is the id separator, and map entry keys are
|
|
617
|
+
* arbitrary runtime data that may contain it. Labels stay unescaped — only
|
|
618
|
+
* node identities encode.
|
|
619
|
+
*/
|
|
620
|
+
private escapeSeg;
|
|
621
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ConfigEditorComponent, never>;
|
|
622
|
+
static ɵcmp: _angular_core.ɵɵ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>;
|
|
496
623
|
}
|
|
497
624
|
|
|
498
|
-
declare class LeafRendererComponent implements
|
|
625
|
+
declare class LeafRendererComponent implements AfterViewInit {
|
|
499
626
|
private readonly elementRef;
|
|
500
627
|
leaf_: Leaf | AnonLeaf;
|
|
501
628
|
control: FormControl;
|
|
502
|
-
initialValue?: any;
|
|
503
629
|
removable: boolean;
|
|
504
630
|
editable: boolean;
|
|
505
631
|
/** Focus the field once rendered — for fields the user just added (e.g. an enabled presence leaf). */
|
|
506
632
|
autofocus: boolean;
|
|
507
|
-
|
|
633
|
+
/** Emitted when the user removes this field (e.g. an optional presence leaf). */
|
|
634
|
+
readonly remove: _angular_core.OutputEmitterRef<void>;
|
|
508
635
|
constructor(elementRef: ElementRef<HTMLElement>);
|
|
509
|
-
ngOnInit(): void;
|
|
510
636
|
ngAfterViewInit(): void;
|
|
511
637
|
/** Whether this field accepts input: the form is editable and the leaf is not `readOnly`. */
|
|
512
638
|
get fieldEditable(): boolean;
|
|
@@ -516,8 +642,8 @@ declare class LeafRendererComponent implements OnInit, AfterViewInit {
|
|
|
516
642
|
* state (invalid and touched), so it can be bound unconditionally.
|
|
517
643
|
*/
|
|
518
644
|
get errorText(): string;
|
|
519
|
-
static ɵfac:
|
|
520
|
-
static ɵcmp:
|
|
645
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafRendererComponent, never>;
|
|
646
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<LeafRendererComponent, "nff-leaf-renderer", never, { "leaf_": { "alias": "leaf_"; "required": false; }; "control": { "alias": "control"; "required": false; }; "removable": { "alias": "removable"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "autofocus": { "alias": "autofocus"; "required": false; }; }, { "remove": "remove"; }, never, never, true, never>;
|
|
521
647
|
}
|
|
522
648
|
|
|
523
649
|
declare class LeafListRendererComponent implements OnInit {
|
|
@@ -538,8 +664,8 @@ declare class LeafListRendererComponent implements OnInit {
|
|
|
538
664
|
ngOnInit(): void;
|
|
539
665
|
removeItem($index: number): void;
|
|
540
666
|
addItem(): void;
|
|
541
|
-
static ɵfac:
|
|
542
|
-
static ɵcmp:
|
|
667
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafListRendererComponent, never>;
|
|
668
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<LeafListRendererComponent, "nff-leaf-list-renderer", never, { "leaf_": { "alias": "leaf_"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "formArray": { "alias": "formArray"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
|
|
543
669
|
}
|
|
544
670
|
|
|
545
671
|
declare class LeafEnumRendererComponent {
|
|
@@ -549,8 +675,8 @@ declare class LeafEnumRendererComponent {
|
|
|
549
675
|
removable: boolean;
|
|
550
676
|
remove: any;
|
|
551
677
|
editable: boolean;
|
|
552
|
-
static ɵfac:
|
|
553
|
-
static ɵcmp:
|
|
678
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafEnumRendererComponent, never>;
|
|
679
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<LeafEnumRendererComponent, "nff-leaf-enum-renderer", never, { "leafEnum": { "alias": "leafEnum"; "required": false; }; "initialValue": { "alias": "initialValue"; "required": false; }; "control": { "alias": "control"; "required": false; }; "removable": { "alias": "removable"; "required": false; }; "remove": { "alias": "remove"; "required": false; }; "editable": { "alias": "editable"; "required": false; }; }, {}, never, never, true, never>;
|
|
554
680
|
}
|
|
555
681
|
|
|
556
682
|
declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
@@ -570,8 +696,8 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
|
570
696
|
setLastEditable(): void;
|
|
571
697
|
asFormGroup(group: any): FormGroup;
|
|
572
698
|
getTitle($index: number): string;
|
|
573
|
-
static ɵfac:
|
|
574
|
-
static ɵcmp:
|
|
699
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeGroupListRendererComponent, never>;
|
|
700
|
+
static ɵcmp: _angular_core.ɵɵ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>;
|
|
575
701
|
}
|
|
576
702
|
|
|
577
703
|
/**
|
|
@@ -581,13 +707,13 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
|
581
707
|
* names are the entry keys, so the key is edited as a *rename* (remove + re-add
|
|
582
708
|
* the same control) committed on blur — see {@link renameEntry}.
|
|
583
709
|
*/
|
|
584
|
-
declare class NodeMapRendererComponent implements
|
|
710
|
+
declare class NodeMapRendererComponent implements OnChanges {
|
|
585
711
|
nodeMap: NodeMap;
|
|
586
712
|
formGroup: FormGroup<any>;
|
|
587
713
|
editable: boolean;
|
|
588
714
|
/** Ordered view of entry keys, kept stable across renames (`addControl` appends). */
|
|
589
715
|
entryKeys: string[];
|
|
590
|
-
|
|
716
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
591
717
|
/** The value schema as a leaf (used when `value.kind === 'leaf'`). */
|
|
592
718
|
get valueLeaf(): Leaf;
|
|
593
719
|
/** The value schema as a group (used when `value.kind === 'nodeGroup'`). */
|
|
@@ -606,9 +732,9 @@ declare class NodeMapRendererComponent implements OnInit {
|
|
|
606
732
|
renameEntry(oldKey: string, rawKey: string): void;
|
|
607
733
|
protected readonly asFormControl: typeof asFormControl;
|
|
608
734
|
protected readonly asFormGroup: typeof asFormGroup;
|
|
609
|
-
static ɵfac:
|
|
610
|
-
static ɵcmp:
|
|
735
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeMapRendererComponent, never>;
|
|
736
|
+
static ɵcmp: _angular_core.ɵɵ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
737
|
}
|
|
612
738
|
|
|
613
|
-
export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, switchChoiceCase };
|
|
739
|
+
export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
|
|
614
740
|
export type { AnonLeaf, Appearance, ChoiceCase, DFormControl, DFormGroup, FormGroupType, Leaf, LeafBase, LeafBoolean, LeafEnum, LeafList, LeafNumber, LeafRuntimeType, LeafString, NodeChoice, NodeGroup, NodeGroupList, NodeMap, NodeType };
|