| 71 | * the responsibilities. |
| 72 | */ |
| 73 | export class FieldNode implements FieldState<unknown> { |
| 74 | readonly structure: FieldNodeStructure; |
| 75 | readonly validationState: ValidationState; |
| 76 | readonly metadataState: FieldMetadataState; |
| 77 | readonly nodeState: FieldNodeState; |
| 78 | readonly submitState: FieldSubmitState; |
| 79 | readonly fieldAdapter: FieldAdapter; |
| 80 | readonly controlValue: ControlValueSignal<unknown>; |
| 81 | |
| 82 | private _context: FieldContext<unknown> | undefined = undefined; |
| 83 | get context(): FieldContext<unknown> { |
| 84 | return (this._context ??= new FieldNodeContext(this)); |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Proxy to this node which allows navigation of the form graph below it. |
| 89 | */ |
| 90 | readonly fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER) as unknown as FieldTree<any>; |
| 91 | private readonly pathNode: FieldPathNode; |
| 92 | |
| 93 | constructor(options: FieldNodeOptions) { |
| 94 | this.pathNode = options.pathNode; |
| 95 | this.fieldAdapter = options.fieldAdapter; |
| 96 | this.structure = this.fieldAdapter.createStructure(this, options); |
| 97 | this.validationState = this.fieldAdapter.createValidationState(this, options); |
| 98 | this.nodeState = this.fieldAdapter.createNodeState(this, options); |
| 99 | this.metadataState = new FieldMetadataState(this); |
| 100 | this.submitState = new FieldSubmitState(this); |
| 101 | this.controlValue = this.controlValueSignal(); |
| 102 | // We eagerly create metadata at the end of construction so that the node is fully constructed |
| 103 | // before metadata creation logic runs (which may access other states on the node). |
| 104 | this.metadataState.runMetadataCreateLifecycle(); |
| 105 | } |
| 106 | |
| 107 | focusBoundControl(options?: FocusOptions): void { |
| 108 | this.getBindingForFocus()?.focus(options); |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Gets the Field directive binding that should be focused when the developer calls |
| 113 | * `focusBoundControl` on this node. |
| 114 | * |
| 115 | * This will prioritize focusable bindings to this node, and if multiple exist, it will return |
| 116 | * the first one in the DOM. If no focusable bindings exist on this node, it will return the |
| 117 | * first focusable binding in the DOM for any descendant node of this one. |
| 118 | */ |
| 119 | private getBindingForFocus(): |
| 120 | | (FormField<unknown> & {focus: (options?: FocusOptions) => void}) |
| 121 | | undefined { |
| 122 | // First try to focus one of our own bindings. |
| 123 | const own = this.formFieldBindings() |
| 124 | .filter( |
| 125 | (b): b is FormField<unknown> & {focus: (options?: FocusOptions) => void} => |
| 126 | b.focus !== undefined, |
| 127 | ) |
| 128 | .reduce( |
| 129 | firstInDom<FormField<unknown> & {focus: (options?: FocusOptions) => void}>, |
| 130 | undefined, |
nothing calls this directly
no test coverage detected
searching dependent graphs…