import { ChangeDetectionStrategy, Component, input, signal } from '@angular/core'; import { DatePickerComponent } from '../date-picker/date-picker.component'; import { BaseInput } from '../base-input.directive'; import { InputComponent } from '../input/input.component'; /** * Wrapper that conditionally renders a plain on Safari * and the custom on Chrome-based browsers. * * Why Safari needs a completely different component (not just an @if branch * inside DatePickerComponent): * * DatePickerComponent wraps the inside a container
that has * tabindex="0", (focus), and (blur) handlers. This makes the custom "click the * whole field to open the picker" UX work on Chrome-based browsers. The problem is * that even if we render a visible for Safari inside that same * template, the input is still nested inside that focusable container div. Safari's * native date picker is tightly coupled to focus transitions on the input element. * A parent div competing for focus causes blur events to fire on the input when * focus moves between the container and the input, which Safari interprets as the * user leaving the field, closing the picker or preventing interaction. * * has no focusable wrapper div, so the input fully owns its focus * lifecycle and Safari's native date widget works without interference. * * Additionally, Safari auto-selects today's date when a date picker opens. The * shared InputComponent validates date bounds in JS only when a complete YYYY-MM-DD * value is present, avoiding partial-input clearing and Safari picker issues. */ @Component({ selector: 'app-date-input', standalone: true, imports: [InputComponent, DatePickerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: ` @if (isSafari()) { } @else { } `, }) export class DateInputComponent extends BaseInput { showLabel = input(true); dateMin = input(''); dateMax = input(''); isSafari = signal( typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) ); onValueChange(value: string) { this.value.set(value); } onTouchedChange(touched: boolean) { this.touched.set(touched); } }