/// /// Copyright 2015-2026 Micro Focus or one of its affiliates. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { AutofillMonitor } from '@angular/cdk/text-field'; import { Directive, ElementRef, HostBinding, inject, Input, OnChanges, OnDestroy, OnInit, Renderer2, } from '@angular/core'; import { Subscription } from 'rxjs'; @Directive({ selector: '[uxFloatLabel]', host: { class: 'ux-float-label', }, }) export class FloatLabelDirective implements OnInit, OnChanges, OnDestroy { private readonly _elementRef = inject(ElementRef); private readonly _renderer = inject(Renderer2); private readonly _autofillMonitor = inject(AutofillMonitor); @Input('uxFloatLabel') set input(input: HTMLInputElement) { // remove any previous autofill subscriptions if (this._input) { this._autofillMonitor.stopMonitoring(this._input); } this._subscription.unsubscribe(); this._input = input; // if the input is null then don't need to subscribe to autofillMonitor if (!input) { return; } // create a new autofillMonitor subscription this._subscription = this._autofillMonitor.monitor(input).subscribe(event => { if (!this.raised && event.isAutofilled) { this.raised = true; } if (this.raised && !event.isAutofilled && !this.hasText()) { this.raised = false; } }); } get input(): HTMLInputElement { return this._input; } @Input() value: T; @Input() mode: 'focus' | 'input' = 'focus'; @HostBinding('class.ux-float-label-raised') raised: boolean = false; private _input: HTMLInputElement; private _focused = false; private readonly _eventHandles: any[] = []; private _subscription = new Subscription(); ngOnInit(): void { this._eventHandles.push( this._renderer.listen(this.input, 'focus', this.inputFocus.bind(this)), this._renderer.listen(this.input, 'blur', this.inputBlur.bind(this)), this._renderer.listen(this.input, 'input', this.inputChange.bind(this)) ); // Check initial input value this.raised = this.hasText(); // Ensure that the `for` attribute is set if (!this._elementRef.nativeElement.getAttribute('for') && this.input.getAttribute('id')) { this._renderer.setAttribute( this._elementRef.nativeElement, 'for', this.input.getAttribute('id') ); } } ngOnChanges(): void { if (!(this.mode === 'focus' && this._focused)) { this.raised = this.hasText(); } } ngOnDestroy(): void { // Unsubscribe event handles this._eventHandles.forEach(eventHandle => eventHandle()); this._autofillMonitor.stopMonitoring(this._input); this._subscription.unsubscribe(); } private hasText(): boolean { if (this.value === undefined) { return !!this.input.value; } return !!this.value; } private inputFocus(): void { if (this.mode === 'focus') { this._focused = true; this.raised = true; } } private inputBlur(): void { if (this.mode === 'focus') { this._focused = false; this.raised = this.hasText(); } } private inputChange(): void { if (this.mode === 'input') { this.raised = this.hasText(); } } }