ng-form-foundry 0.3.0 → 0.3.1
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/fesm2022/ng-form-foundry.mjs +553 -339
- package/fesm2022/ng-form-foundry.mjs.map +1 -1
- package/index.d.ts +250 -145
- 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,14 +230,50 @@ 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>;
|
|
248
278
|
/**
|
|
249
279
|
* Capture a schema literal with its exact type while checking it against
|
|
@@ -266,56 +296,56 @@ declare function asFormGroup(control: any): FormGroup;
|
|
|
266
296
|
|
|
267
297
|
declare class DynamicRecursiveFormComponent implements OnInit {
|
|
268
298
|
/** The form-description schema to render (a root or nested `NodeGroup`). */
|
|
269
|
-
readonly schema:
|
|
299
|
+
readonly schema: _angular_core.InputSignal<NodeGroup>;
|
|
270
300
|
/** Optional value object to seed the form; keyed by the schema's `children` keys. */
|
|
271
|
-
readonly initialValue:
|
|
301
|
+
readonly initialValue: _angular_core.InputSignal<Record<string, unknown> | null>;
|
|
272
302
|
/** The reactive group this form binds to. Defaults to an empty group. */
|
|
273
|
-
readonly formGroup:
|
|
303
|
+
readonly formGroup: _angular_core.InputSignal<FormGroup<any>>;
|
|
274
304
|
/** Index of this form within a parent list, used by `addButtonCallback`. */
|
|
275
|
-
readonly index:
|
|
305
|
+
readonly index: _angular_core.InputSignal<number | null>;
|
|
276
306
|
/** Whether this form may be removed from a parent list (shows a remove control). */
|
|
277
|
-
readonly removable:
|
|
307
|
+
readonly removable: _angular_core.InputSignal<boolean>;
|
|
278
308
|
/** Emitted when the user removes this form from a parent list. */
|
|
279
|
-
readonly remove:
|
|
309
|
+
readonly remove: _angular_core.OutputEmitterRef<void>;
|
|
280
310
|
/** Card/section title; falls back to the schema label or name. */
|
|
281
|
-
readonly title:
|
|
311
|
+
readonly title: _angular_core.InputSignal<string | undefined>;
|
|
282
312
|
/** Whether fields accept input. Two-way: also toggled by the built-in edit control. */
|
|
283
|
-
readonly editable:
|
|
313
|
+
readonly editable: _angular_core.ModelSignal<boolean>;
|
|
284
314
|
/** Invoked with {@link index} to append a new sibling form to a parent list. */
|
|
285
|
-
readonly addButtonCallback:
|
|
315
|
+
readonly addButtonCallback: _angular_core.InputSignal<((index: number) => void) | null>;
|
|
316
|
+
/**
|
|
317
|
+
* Key of a presence leaf whose field should grab focus when it renders — lets
|
|
318
|
+
* a host that added the control itself (e.g. the tree editor's optionals
|
|
319
|
+
* menu) hand focus to the new field, like the form's own add button does.
|
|
320
|
+
*/
|
|
321
|
+
readonly focusLeaf: _angular_core.InputSignal<string | null>;
|
|
286
322
|
/** True when the schema is a root group (rendered flat, without a wrapping card). */
|
|
287
|
-
readonly root:
|
|
323
|
+
readonly root: _angular_core.Signal<boolean>;
|
|
288
324
|
ngOnInit(): void;
|
|
289
|
-
get nodeGroupChildrenList(): Array<{
|
|
325
|
+
protected get nodeGroupChildrenList(): Array<{
|
|
290
326
|
key: string;
|
|
291
327
|
value: NodeType;
|
|
292
328
|
}>;
|
|
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;
|
|
329
|
+
protected emitRemoveEvent(): void;
|
|
299
330
|
/** The key of the presence leaf the user just enabled; its field grabs focus when rendered. */
|
|
300
331
|
protected presenceFocusKey: string | null;
|
|
301
332
|
/**
|
|
302
|
-
* Add or remove a presence
|
|
303
|
-
* leaf
|
|
304
|
-
*
|
|
333
|
+
* Add or remove a presence node's control on this form — any presence kind:
|
|
334
|
+
* group, leaf, map, or choice. Removing it drops the key from `form.value`;
|
|
335
|
+
* adding it builds the control fresh from its schema (nested presence
|
|
336
|
+
* children start absent).
|
|
305
337
|
*/
|
|
306
|
-
|
|
338
|
+
toggleNodePresence(key: string, schema: NodeType, present: boolean): void;
|
|
307
339
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* map, or a choice group holding `__case`) from its schema.
|
|
340
|
+
* {@link toggleNodePresence} for a presence leaf, additionally focusing the
|
|
341
|
+
* rendered field when the toggle just created it.
|
|
311
342
|
*/
|
|
312
|
-
|
|
313
|
-
protected
|
|
314
|
-
objectKeys(obj: Record<string, unknown>): string[];
|
|
343
|
+
toggleLeafPresence(key: string, schema: Leaf, present: boolean): void;
|
|
344
|
+
protected objectKeys(obj: Record<string, unknown>): string[];
|
|
315
345
|
/** The active case name of a choice control, or null if none is selected. */
|
|
316
|
-
activeCase(key: string): string | null;
|
|
346
|
+
protected activeCase(key: string): string | null;
|
|
317
347
|
/** A synthetic flattened NodeGroup used to render the active case's fields against the choice's FormGroup. */
|
|
318
|
-
caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
|
|
348
|
+
protected caseAsGroup(choice: NodeChoice, caseName: string): NodeGroup;
|
|
319
349
|
/** The display label for a case: the schema's `caseLabels` entry, else the case name. */
|
|
320
350
|
caseLabel(choice: NodeChoice, caseName: string): string;
|
|
321
351
|
/**
|
|
@@ -323,14 +353,14 @@ declare class DynamicRecursiveFormComponent implements OnInit {
|
|
|
323
353
|
* for a presence group's body, whose container is the presence panel itself, so
|
|
324
354
|
* the group's own section panel would be a redundant second box.
|
|
325
355
|
*/
|
|
326
|
-
flatGroup(group: NodeGroup): NodeGroup;
|
|
356
|
+
protected flatGroup(group: NodeGroup): NodeGroup;
|
|
327
357
|
/** Swap a choice's field controls when the selected case changes. Delegates to {@link switchChoiceCase}. */
|
|
328
358
|
switchCase(key: string, choice: NodeChoice, caseName: string): void;
|
|
329
359
|
protected readonly asFormGroup: typeof asFormGroup;
|
|
330
360
|
protected readonly asFormArray: typeof asFormArray;
|
|
331
361
|
protected readonly asFormControl: typeof asFormControl;
|
|
332
|
-
static ɵfac:
|
|
333
|
-
static ɵcmp:
|
|
362
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicRecursiveFormComponent, never>;
|
|
363
|
+
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
364
|
}
|
|
335
365
|
|
|
336
366
|
/** Metadata on a list-container node that lets the tree add items to its FormArray. */
|
|
@@ -339,51 +369,62 @@ interface ListRef {
|
|
|
339
369
|
itemSchema: NodeGroup;
|
|
340
370
|
itemLabel: string;
|
|
341
371
|
minItems: number;
|
|
372
|
+
maxItems?: number;
|
|
342
373
|
}
|
|
343
374
|
/** An absent optional (presence) child, offered by its parent node's "+ Optional field" menu. */
|
|
344
375
|
interface OptionalEntry {
|
|
345
376
|
key: string;
|
|
346
377
|
schema: NodeType;
|
|
347
378
|
label: string;
|
|
348
|
-
/** Index in the parent schema's children iteration; keeps the menu in schema order. */
|
|
349
|
-
order: number;
|
|
350
379
|
}
|
|
351
|
-
/**
|
|
380
|
+
/**
|
|
381
|
+
* One flat section of the detail pane. The selected node's subtree renders as a
|
|
382
|
+
* pre-order list of these — no nesting chrome: each section after the first is
|
|
383
|
+
* separated by a breadcrumb heading (`trail`), and holds only the node's *own*
|
|
384
|
+
* fields (`schema` is a leaf-only slice; complex children are sections of their
|
|
385
|
+
* own, deeper in the list).
|
|
386
|
+
*/
|
|
387
|
+
interface DetailSection {
|
|
388
|
+
node: TreeNode;
|
|
389
|
+
/** Selected-node-to-here trail, rendered as the section's breadcrumb heading (omitted on the first section). */
|
|
390
|
+
trail: TreeNode[];
|
|
391
|
+
/** Leaf-only slice of the node's own schema, or null when it has no own fields. */
|
|
392
|
+
schema: NodeGroup | null;
|
|
393
|
+
/** The group `schema` binds to (the node's group; for a choice, the choice group). */
|
|
394
|
+
group: FormGroup | null;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* A navigable node in the config tree. Its `id` is the node's stable path from
|
|
398
|
+
* the root (`system/ntp`, `ifaces/0`, `servers/web1`), so expansion and
|
|
399
|
+
* selection survive tree rebuilds. Segments are `%`/`/`-escaped, since map
|
|
400
|
+
* entry keys are arbitrary runtime data; list-item identity is positional.
|
|
401
|
+
*/
|
|
352
402
|
interface TreeNode {
|
|
353
403
|
id: string;
|
|
354
404
|
label: string;
|
|
355
405
|
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. */
|
|
406
|
+
/** The node's own FormGroup, or null for a list-container or map node. */
|
|
367
407
|
group: FormGroup | null;
|
|
408
|
+
/** The node's own schema, set on group-backed nodes (groups, list items, group-valued map entries). */
|
|
409
|
+
schema?: NodeGroup;
|
|
368
410
|
/** Present on a nodeGroupList node: lets a `+` add an item. */
|
|
369
411
|
list?: ListRef;
|
|
370
|
-
/** Present on a list-item node: the
|
|
412
|
+
/** Present on a list-item node: its current index in the parent list (removal goes through the parent list array). */
|
|
371
413
|
removable?: {
|
|
372
|
-
array: FormArray;
|
|
373
414
|
index: number;
|
|
374
415
|
};
|
|
375
|
-
/** Absent optional children of this node, offered by its "+ Optional field" menu. */
|
|
416
|
+
/** Absent optional children of this node, offered by its "+ Optional field" menu (schema order). */
|
|
376
417
|
optionals?: OptionalEntry[];
|
|
377
|
-
/** Present on a present optional child node: drives the row's remove control
|
|
418
|
+
/** Present on a present optional child node: drives the row's remove control. */
|
|
378
419
|
presenceRemovable?: {
|
|
379
|
-
|
|
420
|
+
key: string;
|
|
380
421
|
};
|
|
381
422
|
/** Present on a choice node: the choice schema and its FormGroup (holding `__case` + the active case's fields). */
|
|
382
423
|
choice?: {
|
|
383
424
|
schema: NodeChoice;
|
|
384
425
|
group: FormGroup;
|
|
385
426
|
};
|
|
386
|
-
/** Present on a map node. `complex` maps expand entries as child nodes
|
|
427
|
+
/** Present on a map node. `complex` maps expand entries as child nodes. */
|
|
387
428
|
map?: {
|
|
388
429
|
schema: NodeMap;
|
|
389
430
|
group: FormGroup;
|
|
@@ -398,115 +439,179 @@ interface TreeNode {
|
|
|
398
439
|
}
|
|
399
440
|
/**
|
|
400
441
|
* 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
|
-
*
|
|
442
|
+
* maps, choices) is a tree on the left, and selecting a node renders that
|
|
443
|
+
* node's **entire subtree** on the right as a **flat list of sections** — the
|
|
444
|
+
* node's own fields first, then every descendant's fields, each separated by a
|
|
445
|
+
* breadcrumb heading (`Service / Deploy scope / …`) instead of nested panels.
|
|
446
|
+
* Leaf fields render through {@link DynamicRecursiveFormComponent} with a
|
|
447
|
+
* leaf-only schema slice; choice selectors, map rows, and add controls render
|
|
448
|
+
* inline in their section. The tree adds row conveniences of its own: `+` on
|
|
449
|
+
* list and map rows, a delete control on removable rows, and a
|
|
450
|
+
* "+ Optional field" menu for absent presence children.
|
|
451
|
+
*
|
|
452
|
+
* The tree is **derived state**: any structural change to the form — made
|
|
453
|
+
* through the tree rows or through the detail sections — triggers a rebuild (a
|
|
454
|
+
* cheap shape signature over `valueChanges` detects it). Node ids are stable
|
|
455
|
+
* paths, so expansion and selection survive rebuilds. Swapping the `schema` or
|
|
456
|
+
* `formGroup` input rebinds the editor to the new pair, resetting expansion
|
|
457
|
+
* and selection.
|
|
406
458
|
*
|
|
407
459
|
* 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.
|
|
460
|
+
* detail panes — so the embedding client owns the surrounding chrome.
|
|
412
461
|
*/
|
|
413
|
-
declare class ConfigEditorComponent implements
|
|
462
|
+
declare class ConfigEditorComponent implements OnDestroy {
|
|
414
463
|
/** The form-description schema whose structure the tree renders. */
|
|
415
|
-
readonly schema:
|
|
464
|
+
readonly schema: _angular_core.InputSignal<NodeGroup>;
|
|
416
465
|
/** The schema-built reactive group the editor binds to and mutates. */
|
|
417
|
-
readonly formGroup:
|
|
466
|
+
readonly formGroup: _angular_core.InputSignal<FormGroup<any>>;
|
|
418
467
|
/** Whether fields accept input and structural controls (add/remove/menus) show. */
|
|
419
|
-
readonly editable:
|
|
468
|
+
readonly editable: _angular_core.InputSignal<boolean>;
|
|
420
469
|
root: TreeNode;
|
|
421
470
|
selected: TreeNode | null;
|
|
471
|
+
/** The selected subtree as a flat, breadcrumb-separated section list. */
|
|
472
|
+
sections: DetailSection[];
|
|
473
|
+
/** Root-to-selection trail for the detail-pane breadcrumb, computed once per selection. */
|
|
474
|
+
breadcrumb: TreeNode[];
|
|
422
475
|
readonly expanded: Set<string>;
|
|
423
|
-
private
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
|
|
476
|
+
private shape;
|
|
477
|
+
private changes?;
|
|
478
|
+
private readonly host;
|
|
479
|
+
/** Stable per-instance ids for the shape signature, so replacing a control (setControl) reads as a structural change. */
|
|
480
|
+
private readonly controlIds;
|
|
481
|
+
private controlIdSeq;
|
|
482
|
+
constructor();
|
|
483
|
+
ngOnDestroy(): void;
|
|
484
|
+
/** Bind the editor to a schema/form pair: fresh tree, root selection, and shape-sync subscription. */
|
|
485
|
+
private attach;
|
|
486
|
+
select(node: TreeNode, reveal?: boolean): void;
|
|
428
487
|
/**
|
|
429
488
|
* 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.
|
|
489
|
+
* or an empty array if `target` is not in the current tree.
|
|
432
490
|
*/
|
|
433
491
|
pathTo(target: TreeNode): TreeNode[];
|
|
434
|
-
toggle(node: TreeNode): void;
|
|
492
|
+
protected toggle(node: TreeNode): void;
|
|
493
|
+
/** Keyboard access for tree rows: Enter/Space selects, ArrowRight expands, ArrowLeft collapses. */
|
|
494
|
+
protected onRowKeydown(event: KeyboardEvent, node: TreeNode): void;
|
|
435
495
|
/** Whether the row shows an expand twisty: it has child rows to reveal (children or an optionals menu row). */
|
|
436
|
-
hasExpandableContent(node: TreeNode): boolean;
|
|
496
|
+
protected hasExpandableContent(node: TreeNode): boolean;
|
|
437
497
|
/**
|
|
438
498
|
* Whether the node's form subtree holds a validation error. Group/choice, list,
|
|
439
499
|
* and map nodes each check their backing control; `invalid` aggregates over
|
|
440
500
|
* descendants, so an error anywhere below lights every ancestor row.
|
|
441
501
|
*/
|
|
442
|
-
hasError(node: TreeNode): boolean;
|
|
443
|
-
/** Append a new item to a list node's FormArray
|
|
502
|
+
protected hasError(node: TreeNode): boolean;
|
|
503
|
+
/** Append a new item to a list node's FormArray (up to `maxItems`), then select it. */
|
|
444
504
|
addItem(listNode: TreeNode): void;
|
|
445
|
-
/**
|
|
505
|
+
/**
|
|
506
|
+
* Remove a list item from its FormArray (down to `minItems`). List-item
|
|
507
|
+
* identity is positional, so expansion state under the list is cleared —
|
|
508
|
+
* otherwise it would silently migrate to the items that shift into the
|
|
509
|
+
* removed indexes.
|
|
510
|
+
*/
|
|
446
511
|
removeItem(listNode: TreeNode, item: TreeNode): void;
|
|
447
|
-
/** The
|
|
512
|
+
/** The just-added optional leaf (section path + key) whose field grabs focus when rendered. */
|
|
513
|
+
protected focusSectionId: string | null;
|
|
448
514
|
protected focusLeafKey: string | null;
|
|
449
|
-
/** Add an absent optional child from the menu: build its control
|
|
515
|
+
/** Add an absent optional child from the menu: build its control and select the node it lands on. */
|
|
450
516
|
addOptional(node: TreeNode, entry: OptionalEntry): void;
|
|
451
517
|
/** Remove a present optional child node, returning its entry to the parent's menu. */
|
|
452
518
|
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
519
|
/** The active case name of a choice node, or null when none is selected. */
|
|
460
520
|
activeCase(node: TreeNode): string | null;
|
|
461
521
|
/** The display label of a choice node's active case, or null when no case is selected. */
|
|
462
522
|
activeCaseLabel(node: TreeNode): string | null;
|
|
463
523
|
/** 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
|
|
524
|
+
protected caseLabel(choice: NodeChoice, caseName: string): string;
|
|
525
|
+
/** Switch a choice node's active case; the structural sync rebuilds the tree and sections. */
|
|
466
526
|
switchTreeCase(node: TreeNode, caseName: string): void;
|
|
527
|
+
protected objectKeys(obj: Record<string, unknown>): string[];
|
|
467
528
|
/** Append a new entry to a complex map node under a generated unique key, then select it. */
|
|
468
529
|
addTreeMapEntry(mapNode: TreeNode): void;
|
|
469
|
-
/** Remove a complex map entry (down to `minEntries`)
|
|
530
|
+
/** Remove a complex map entry (down to `minEntries`). */
|
|
470
531
|
removeTreeMapEntry(mapNode: TreeNode, entryNode: TreeNode): void;
|
|
471
|
-
/**
|
|
532
|
+
/**
|
|
533
|
+
* Commit a rename-on-blur of a map entry's key; on success the entry is
|
|
534
|
+
* selected under its new path and its fresh key field regains focus (the
|
|
535
|
+
* rename re-renders the section under a new id, destroying the input that
|
|
536
|
+
* held focus).
|
|
537
|
+
*/
|
|
472
538
|
renameTreeMapEntry(entryNode: TreeNode, rawKey: string): void;
|
|
539
|
+
/** Move keyboard focus to the selected tree row once the action's re-render settles. */
|
|
540
|
+
private focusSelectedRow;
|
|
541
|
+
/** A muted hint for a section whose node currently renders no content of its own. */
|
|
542
|
+
protected emptySectionHint(s: DetailSection): string | null;
|
|
543
|
+
/** Whether a list node is at `maxItems` (the add control is hidden). */
|
|
544
|
+
protected listAtMax(node: TreeNode | undefined): boolean;
|
|
545
|
+
/** Whether a list node is at `minItems` (item remove controls are hidden). */
|
|
546
|
+
protected listAtMin(node: TreeNode | undefined): boolean;
|
|
473
547
|
/** Whether a map node is at `maxEntries` (the add control is hidden). */
|
|
474
|
-
mapAtMax(node: TreeNode | undefined): boolean;
|
|
548
|
+
protected mapAtMax(node: TreeNode | undefined): boolean;
|
|
475
549
|
/** 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
|
|
550
|
+
protected mapAtMin(node: TreeNode | undefined): boolean;
|
|
551
|
+
/** Rebuild the tree from the current form structure if its shape changed. */
|
|
552
|
+
private syncShape;
|
|
553
|
+
/**
|
|
554
|
+
* A cheap structural signature of a control tree: group keys (plus the active
|
|
555
|
+
* `__case`), array lengths, leaf placeholders. Value edits don't change it;
|
|
556
|
+
* added/removed/renamed controls and case switches do. Reading `__case` from
|
|
557
|
+
* any group is safe because the name is reserved (the builder rejects it as a
|
|
558
|
+
* case field name or map entry key), so only choice groups carry it.
|
|
559
|
+
*/
|
|
560
|
+
private shapeOf;
|
|
561
|
+
/** The instance id a container contributes to the shape signature. */
|
|
562
|
+
private uidOf;
|
|
563
|
+
/** Rebuild the whole tree from schema + form and refresh the shape signature. */
|
|
564
|
+
private rebuild;
|
|
565
|
+
/** Re-point `selected` at the rebuilt tree: same path, else the closest surviving ancestor. */
|
|
566
|
+
private reconcileSelection;
|
|
567
|
+
/** The node at `path` in the current tree, or null. Walks by id-prefix segments. */
|
|
568
|
+
private byPath;
|
|
569
|
+
/** Select the node at `path`, falling back through its ancestors to the root. */
|
|
570
|
+
private selectByPath;
|
|
571
|
+
/**
|
|
572
|
+
* Flatten a subtree into detail sections, pre-order: the node's own fields
|
|
573
|
+
* first, then each child as its own breadcrumb-headed section. No nesting
|
|
574
|
+
* chrome — the headings are the boundaries between children.
|
|
575
|
+
*/
|
|
576
|
+
private buildSections;
|
|
577
|
+
/** A section's own renderable fields: a leaf-only schema slice and the group it binds to. */
|
|
578
|
+
private sectionContent;
|
|
579
|
+
/**
|
|
580
|
+
* The node's own fields as a flattened schema: leaf and leafList children
|
|
581
|
+
* only. Complex children are rendered as sections of their own, so the
|
|
582
|
+
* embedded form never draws nested section chrome. Null when there are none.
|
|
583
|
+
*/
|
|
584
|
+
private leafOnly;
|
|
489
585
|
private buildTree;
|
|
490
586
|
/** Build the tree node for a single non-leaf child, dispatching on its kind. Null when its control is missing. */
|
|
491
587
|
private buildChildNode;
|
|
588
|
+
/** Synthetic group over a case's normalized fields, so a case body builds like any group. */
|
|
589
|
+
private caseAsGroup;
|
|
492
590
|
/** A node's display label: its schema `label`, else its record key. */
|
|
493
591
|
private labelOf;
|
|
494
|
-
|
|
495
|
-
|
|
592
|
+
/** Join a parent path and a segment; the root's path is the empty string. */
|
|
593
|
+
private join;
|
|
594
|
+
/**
|
|
595
|
+
* Escape a path segment: `/` is the id separator, and map entry keys are
|
|
596
|
+
* arbitrary runtime data that may contain it. Labels stay unescaped — only
|
|
597
|
+
* node identities encode.
|
|
598
|
+
*/
|
|
599
|
+
private escapeSeg;
|
|
600
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ConfigEditorComponent, never>;
|
|
601
|
+
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
602
|
}
|
|
497
603
|
|
|
498
|
-
declare class LeafRendererComponent implements
|
|
604
|
+
declare class LeafRendererComponent implements AfterViewInit {
|
|
499
605
|
private readonly elementRef;
|
|
500
606
|
leaf_: Leaf | AnonLeaf;
|
|
501
607
|
control: FormControl;
|
|
502
|
-
initialValue?: any;
|
|
503
608
|
removable: boolean;
|
|
504
609
|
editable: boolean;
|
|
505
610
|
/** Focus the field once rendered — for fields the user just added (e.g. an enabled presence leaf). */
|
|
506
611
|
autofocus: boolean;
|
|
507
|
-
|
|
612
|
+
/** Emitted when the user removes this field (e.g. an optional presence leaf). */
|
|
613
|
+
readonly remove: _angular_core.OutputEmitterRef<void>;
|
|
508
614
|
constructor(elementRef: ElementRef<HTMLElement>);
|
|
509
|
-
ngOnInit(): void;
|
|
510
615
|
ngAfterViewInit(): void;
|
|
511
616
|
/** Whether this field accepts input: the form is editable and the leaf is not `readOnly`. */
|
|
512
617
|
get fieldEditable(): boolean;
|
|
@@ -516,8 +621,8 @@ declare class LeafRendererComponent implements OnInit, AfterViewInit {
|
|
|
516
621
|
* state (invalid and touched), so it can be bound unconditionally.
|
|
517
622
|
*/
|
|
518
623
|
get errorText(): string;
|
|
519
|
-
static ɵfac:
|
|
520
|
-
static ɵcmp:
|
|
624
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafRendererComponent, never>;
|
|
625
|
+
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
626
|
}
|
|
522
627
|
|
|
523
628
|
declare class LeafListRendererComponent implements OnInit {
|
|
@@ -538,8 +643,8 @@ declare class LeafListRendererComponent implements OnInit {
|
|
|
538
643
|
ngOnInit(): void;
|
|
539
644
|
removeItem($index: number): void;
|
|
540
645
|
addItem(): void;
|
|
541
|
-
static ɵfac:
|
|
542
|
-
static ɵcmp:
|
|
646
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafListRendererComponent, never>;
|
|
647
|
+
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
648
|
}
|
|
544
649
|
|
|
545
650
|
declare class LeafEnumRendererComponent {
|
|
@@ -549,8 +654,8 @@ declare class LeafEnumRendererComponent {
|
|
|
549
654
|
removable: boolean;
|
|
550
655
|
remove: any;
|
|
551
656
|
editable: boolean;
|
|
552
|
-
static ɵfac:
|
|
553
|
-
static ɵcmp:
|
|
657
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafEnumRendererComponent, never>;
|
|
658
|
+
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
659
|
}
|
|
555
660
|
|
|
556
661
|
declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
@@ -570,8 +675,8 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
|
570
675
|
setLastEditable(): void;
|
|
571
676
|
asFormGroup(group: any): FormGroup;
|
|
572
677
|
getTitle($index: number): string;
|
|
573
|
-
static ɵfac:
|
|
574
|
-
static ɵcmp:
|
|
678
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeGroupListRendererComponent, never>;
|
|
679
|
+
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
680
|
}
|
|
576
681
|
|
|
577
682
|
/**
|
|
@@ -581,13 +686,13 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
|
|
|
581
686
|
* names are the entry keys, so the key is edited as a *rename* (remove + re-add
|
|
582
687
|
* the same control) committed on blur — see {@link renameEntry}.
|
|
583
688
|
*/
|
|
584
|
-
declare class NodeMapRendererComponent implements
|
|
689
|
+
declare class NodeMapRendererComponent implements OnChanges {
|
|
585
690
|
nodeMap: NodeMap;
|
|
586
691
|
formGroup: FormGroup<any>;
|
|
587
692
|
editable: boolean;
|
|
588
693
|
/** Ordered view of entry keys, kept stable across renames (`addControl` appends). */
|
|
589
694
|
entryKeys: string[];
|
|
590
|
-
|
|
695
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
591
696
|
/** The value schema as a leaf (used when `value.kind === 'leaf'`). */
|
|
592
697
|
get valueLeaf(): Leaf;
|
|
593
698
|
/** The value schema as a group (used when `value.kind === 'nodeGroup'`). */
|
|
@@ -606,8 +711,8 @@ declare class NodeMapRendererComponent implements OnInit {
|
|
|
606
711
|
renameEntry(oldKey: string, rawKey: string): void;
|
|
607
712
|
protected readonly asFormControl: typeof asFormControl;
|
|
608
713
|
protected readonly asFormGroup: typeof asFormGroup;
|
|
609
|
-
static ɵfac:
|
|
610
|
-
static ɵcmp:
|
|
714
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeMapRendererComponent, never>;
|
|
715
|
+
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
716
|
}
|
|
612
717
|
|
|
613
718
|
export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, switchChoiceCase };
|