ng-form-foundry 0.4.0 → 0.5.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/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { FormControl, FormArray, FormGroup } from '@angular/forms';
1
+ import { FormControl, FormArray, FormGroup, ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
2
2
  import * as _angular_core from '@angular/core';
3
- import { OnInit, OnDestroy, AfterViewInit, ElementRef, EventEmitter, QueryList, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core';
3
+ import { OnInit, OnDestroy, AfterViewInit, ElementRef, EventEmitter, OnChanges, QueryList, ChangeDetectorRef, 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;
@@ -51,6 +51,13 @@ type LeafString = LeafBase & {
51
51
  maxLength?: number;
52
52
  /** Semantic string format (JSON Schema `format`); adds a matching validator. */
53
53
  format?: 'email' | 'uri' | 'url';
54
+ /**
55
+ * Present the value in this base — for the string leaves that carry an
56
+ * integer beyond ±2^53 as its exact decimal digits. The control value stays
57
+ * that decimal-digit string (`pattern` keeps validating it); only the
58
+ * rendered text is based. See {@link LeafNumber.radix}.
59
+ */
60
+ radix?: 2 | 8 | 16;
54
61
  };
55
62
  type LeafNumber = LeafBase & {
56
63
  type: 'number';
@@ -63,6 +70,14 @@ type LeafNumber = LeafBase & {
63
70
  max?: number;
64
71
  /** Require the value to be an integer multiple of this number (JSON Schema `multipleOf`). */
65
72
  multipleOf?: number;
73
+ /**
74
+ * Present the value in this base (16 hex, 8 octal, 2 binary) instead of
75
+ * decimal — a display hint set by transformers when the source document
76
+ * wrote the literal that way (`0x1A`, `0o17`, `0b101`). The control value
77
+ * stays a plain number, so every numeric validator applies unchanged; only
78
+ * the rendered text is based. See `RadixInputDirective`.
79
+ */
80
+ radix?: 2 | 8 | 16;
66
81
  };
67
82
  type LeafBoolean = LeafBase & {
68
83
  type: 'boolean';
@@ -135,6 +150,8 @@ type LeafList<TKind extends Leaf['type'] = Leaf['type']> = {
135
150
  type: TKind;
136
151
  minItems?: number;
137
152
  maxItems?: number;
153
+ /** Present every item in this base — see {@link LeafNumber.radix}. */
154
+ radix?: 2 | 8 | 16;
138
155
  };
139
156
  type NodeGroupList = {
140
157
  kind: 'nodeGroupList';
@@ -409,6 +426,17 @@ declare class DynamicRecursiveFormComponent implements OnInit {
409
426
  readonly title: _angular_core.InputSignal<string | undefined>;
410
427
  /** Whether fields accept input. Two-way: also toggled by the built-in edit control. */
411
428
  readonly editable: _angular_core.ModelSignal<boolean>;
429
+ /**
430
+ * Preview absent optional (presence) leaves as **ghost fields**: the field
431
+ * renders read-only and empty — its schema `default`, if any, shown as the
432
+ * placeholder — with a (+) button that incorporates it into the form, so the
433
+ * user sees the complete form surface. A ghost holds **no control in the
434
+ * form**: it never appears in `value`/`getRawValue()`/`serializeForm` output
435
+ * and never affects validity until incorporated. Replaces the default
436
+ * "Add <field>" button affordance; ignored while the form is not editable
437
+ * (read-only mode hides structural affordances, ghosts included).
438
+ */
439
+ readonly showAbsentOptionals: _angular_core.InputSignal<boolean>;
412
440
  /** Invoked with {@link index} to append a new sibling form to a parent list. */
413
441
  readonly addButtonCallback: _angular_core.InputSignal<((index: number) => void) | null>;
414
442
  /**
@@ -493,6 +521,22 @@ declare class DynamicRecursiveFormComponent implements OnInit {
493
521
  * children start absent).
494
522
  */
495
523
  toggleNodePresence(key: string, schema: NodeType, present: boolean): void;
524
+ /**
525
+ * The detached stand-in control a ghost field renders against. Never added
526
+ * to {@link formGroup} — that detachment is the whole guarantee: a ghost
527
+ * cannot reach `value`, `getRawValue()`, or validity. Always `null`-valued
528
+ * (the schema default shows as a placeholder instead, see
529
+ * {@link ghostPlaceholder}); cached per key so change detection re-reads a
530
+ * stable instance.
531
+ */
532
+ protected ghostControl(key: string): FormControl;
533
+ private readonly ghostControls;
534
+ /**
535
+ * A ghost field's placeholder: the leaf's `default`, or empty — spelled in
536
+ * the leaf's `radix` when it has one, matching how the incorporated field
537
+ * will display it.
538
+ */
539
+ protected ghostPlaceholder(schema: Leaf): string;
496
540
  /**
497
541
  * {@link toggleNodePresence} for a presence leaf, additionally focusing the
498
542
  * rendered field when the toggle just created it.
@@ -525,7 +569,7 @@ declare class DynamicRecursiveFormComponent implements OnInit {
525
569
  protected readonly asFormArray: typeof asFormArray;
526
570
  protected readonly asFormControl: typeof asFormControl;
527
571
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DynamicRecursiveFormComponent, never>;
528
- 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; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "editable": "editableChange"; }, never, never, true, never>;
572
+ 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; }; "showAbsentOptionals": { "alias": "showAbsentOptionals"; "required": false; "isSignal": true; }; "addButtonCallback": { "alias": "addButtonCallback"; "required": false; "isSignal": true; }; "focusLeaf": { "alias": "focusLeaf"; "required": false; "isSignal": true; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "editable": "editableChange"; }, never, never, true, never>;
529
573
  }
530
574
 
531
575
  /** Metadata on a list-container node that lets the tree add items to its FormArray. */
@@ -849,6 +893,8 @@ declare class LeafRendererComponent implements AfterViewInit {
849
893
  editable: boolean;
850
894
  /** Focus the field once rendered — for fields the user just added (e.g. an enabled presence leaf). */
851
895
  autofocus: boolean;
896
+ /** Placeholder for the empty field — e.g. a ghost preview showing the schema default. */
897
+ placeholder: string;
852
898
  /** Emitted when the user removes this field (e.g. an optional presence leaf). */
853
899
  readonly remove: _angular_core.OutputEmitterRef<void>;
854
900
  constructor(elementRef: ElementRef<HTMLElement>);
@@ -862,7 +908,7 @@ declare class LeafRendererComponent implements AfterViewInit {
862
908
  */
863
909
  get errorText(): string;
864
910
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafRendererComponent, never>;
865
- 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>;
911
+ 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; }; "placeholder": { "alias": "placeholder"; "required": false; }; }, { "remove": "remove"; }, never, never, true, never>;
866
912
  }
867
913
 
868
914
  declare class LeafListRendererComponent implements OnInit {
@@ -905,8 +951,71 @@ declare class LeafEnumRendererComponent {
905
951
  removable: boolean;
906
952
  remove: any;
907
953
  editable: boolean;
954
+ /** Placeholder for the empty select — e.g. a ghost preview showing the schema default. */
955
+ placeholder: string;
908
956
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LeafEnumRendererComponent, never>;
909
- 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>;
957
+ 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; }; "placeholder": { "alias": "placeholder"; "required": false; }; }, {}, never, never, true, never>;
958
+ }
959
+
960
+ type Radix = 2 | 8 | 16;
961
+ /**
962
+ * An integer as a prefixed literal in the given base: `0x1A`, `0o17`, `0b101`,
963
+ * hex digits uppercase, a leading `-` for negatives. Accepts a number or a
964
+ * decimal-digit string (the beyond-safe-range carry).
965
+ */
966
+ declare function formatRadix(value: number | string, radix: Radix): string;
967
+ /**
968
+ * Presents an integer control in hex/octal/binary while the control value
969
+ * stays what the schema and validators expect: a plain `number`, or — with
970
+ * `nffRadixValueType="string"` — the exact decimal-digit string that carries
971
+ * integers beyond the safe range (±(2^53 − 1)). Only the visible text is
972
+ * based (`0x1A`, `0o17`, `0b101`); `min`/`max`/`integer`/`pattern` validators
973
+ * therefore apply unchanged.
974
+ *
975
+ * Typing accepts the base's digits with or without prefix, case-insensitive
976
+ * (`0q` is also accepted for octal, as in libconfig); the text normalizes to
977
+ * the prefixed spelling on blur. Unparseable text sets the control to `null`
978
+ * with a `radixFormat` error — never the raw text, so an invalid entry can
979
+ * reach neither the wire value nor a typed write-back; the text itself stays
980
+ * in the input for the user to correct. In number mode a magnitude beyond the
981
+ * safe range likewise yields `null` with `radixRange` instead of a silently
982
+ * rounded number.
983
+ */
984
+ declare class RadixInputDirective implements ControlValueAccessor, Validator, OnChanges {
985
+ protected readonly el: ElementRef<HTMLInputElement>;
986
+ /** The display base. */
987
+ radix: Radix;
988
+ /**
989
+ * What the control carries: `'number'` (default), or `'string'` for the
990
+ * decimal-digit bigint carry of a string leaf.
991
+ */
992
+ valueType: 'number' | 'string';
993
+ private modelValue;
994
+ private parseError;
995
+ private onChange;
996
+ private onTouched;
997
+ private onValidatorChange;
998
+ constructor(el: ElementRef<HTMLInputElement>);
999
+ /** A changed base (or carry mode) re-renders the model and re-validates. */
1000
+ ngOnChanges(): void;
1001
+ writeValue(value: unknown): void;
1002
+ registerOnChange(fn: (value: unknown) => void): void;
1003
+ registerOnTouched(fn: () => void): void;
1004
+ registerOnValidatorChange(fn: () => void): void;
1005
+ setDisabledState(isDisabled: boolean): void;
1006
+ validate(_control: AbstractControl): ValidationErrors | null;
1007
+ protected onInput(text: string): void;
1008
+ protected onBlur(): void;
1009
+ /** Show the model in its base; unrepresentable models pass through as text. */
1010
+ private render;
1011
+ /**
1012
+ * `[-+]?` + this base's optional prefix + this base's digits, or null. The
1013
+ * prefix is base-specific so hex digit runs that merely *look* like another
1014
+ * base's prefix (`0b11` as hex 0x0B11) parse as digits, not as a rejection.
1015
+ */
1016
+ private parse;
1017
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RadixInputDirective, never>;
1018
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<RadixInputDirective, "input[nffRadixInput]", never, { "radix": { "alias": "nffRadixInput"; "required": true; }; "valueType": { "alias": "nffRadixValueType"; "required": false; }; }, {}, never, never, true, never>;
910
1019
  }
911
1020
 
912
1021
  declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
@@ -914,6 +1023,8 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
914
1023
  initialValue: number[] | string[] | boolean[];
915
1024
  formArray: FormArray<any>;
916
1025
  editable: boolean;
1026
+ /** Forwarded to each entry's embedded form — see the form's input of the same name. */
1027
+ showAbsentOptionals: boolean;
917
1028
  minItems: number;
918
1029
  maxItems: number;
919
1030
  /** Field-layout appearance from the enclosing group, forwarded to every item form. */
@@ -929,7 +1040,7 @@ declare class NodeGroupListRendererComponent implements OnInit, AfterViewInit {
929
1040
  asFormGroup(group: any): FormGroup;
930
1041
  getTitle($index: number): string;
931
1042
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeGroupListRendererComponent, never>;
932
- 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; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
1043
+ 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; }; "showAbsentOptionals": { "alias": "showAbsentOptionals"; "required": false; }; "minItems": { "alias": "minItems"; "required": false; }; "maxItems": { "alias": "maxItems"; "required": false; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, { "message": "message"; }, never, never, true, never>;
933
1044
  }
934
1045
 
935
1046
  /**
@@ -943,6 +1054,8 @@ declare class NodeMapRendererComponent implements OnChanges {
943
1054
  nodeMap: NodeMap;
944
1055
  formGroup: FormGroup<any>;
945
1056
  editable: boolean;
1057
+ /** Forwarded to each entry's embedded form — see the form's input of the same name. */
1058
+ showAbsentOptionals: boolean;
946
1059
  /** Field-layout appearance from the enclosing group, forwarded to group-valued entry forms. */
947
1060
  inheritedAppearance: Appearance | null;
948
1061
  /** Ordered view of entry keys, kept stable across renames (`addControl` appends). */
@@ -973,8 +1086,8 @@ declare class NodeMapRendererComponent implements OnChanges {
973
1086
  protected readonly asFormControl: typeof asFormControl;
974
1087
  protected readonly asFormGroup: typeof asFormGroup;
975
1088
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NodeMapRendererComponent, never>;
976
- 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; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, {}, never, never, true, never>;
1089
+ 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; }; "showAbsentOptionals": { "alias": "showAbsentOptionals"; "required": false; }; "inheritedAppearance": { "alias": "inheritedAppearance"; "required": false; }; }, {}, never, never, true, never>;
977
1090
  }
978
1091
 
979
- export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, addMapEntry, buildControl, buildFormFromSchema, caseDisplayLabels, caseFields, defineSchema, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
980
- export type { AnonLeaf, Appearance, ChoiceCase, DFormControl, DFormGroup, FormGroupType, Leaf, LeafBase, LeafBoolean, LeafEnum, LeafList, LeafNumber, LeafRuntimeType, LeafString, NodeChoice, NodeGroup, NodeGroupList, NodeMap, NodeType };
1092
+ export { CASE_KEY, ConfigEditorComponent, DynamicRecursiveFormComponent, LeafEnumRendererComponent, LeafListRendererComponent, LeafRendererComponent, NodeGroupListRendererComponent, NodeMapRendererComponent, RadixInputDirective, addMapEntry, buildControl, buildFormFromSchema, caseDisplayLabels, caseFields, defineSchema, formatRadix, removeMapEntry, renameMapEntry, resolveChoiceCase, serializeForm, switchChoiceCase, toWireValue };
1093
+ export type { AnonLeaf, Appearance, ChoiceCase, DFormControl, DFormGroup, FormGroupType, Leaf, LeafBase, LeafBoolean, LeafEnum, LeafList, LeafNumber, LeafRuntimeType, LeafString, NodeChoice, NodeGroup, NodeGroupList, NodeMap, NodeType, Radix };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Build fully-typed Angular Reactive Forms and Angular Material UI from a declarative, recursive form-description schema.",
5
5
  "keywords": [
6
6
  "angular",