Saltar al contenido principal

TdnInputDate

Paquete: @tiendana/ngx-core/forms

Componente de selector de fecha con calendario desplegable, soporte para rangos, integración con selector de hora y estilos consistentes con el Sistema de Diseño Tiendana.

Características​

  • ✅ Selector de fecha simple o rango de fechas
  • ✅ Integración con TdnInputTime para selección de fecha y hora
  • ✅ Calendario desplegable que se aloja en document.body mientras está abierto, con posicionamiento inteligente
  • ✅ Formateado de fechas a través de TDN_DATE_FORMATTER
  • ✅ Soporte para validaciones de Angular Reactive Forms
  • ✅ Botón de limpieza opcional
  • ✅ Reposiciona el calendario con el scroll y el redimensionado
  • ✅ Modo fullscreen cuando no cabe ni arriba ni abajo
  • ✅ Localización en español por defecto
  • ✅ Implementa ControlValueAccessor

Selector​

tdn-input-date

Requiere TDN_DATE_FORMATTER

El componente inyecta TDN_DATE_FORMATTER sin ser opcional y el token no tiene fábrica: si la aplicación no lo provee, se lanza NullInjectorError al crear el componente. Toda aplicación debe proveerlo. Ver Personalización avanzada.

Uso Básico​

Cargando demo...
Ver código

Inputs​

InputTipoDescripción
settingsTdnDateInputSettingsConfiguración del componente (ver configuración disponible)
timeInputTdnInputTimeComponentReferencia a componente de tiempo para selección de fecha/hora

Outputs​

OutputTipoDescripción
onSelectDate | DateRangeResult | nullSe emite cuando cambia la selección: Date, { from, to } en modo rango, o null
onDeSelectanyDeclarado por paridad con tdn-select; el componente nunca lo emite
onClearanySe emite cuando se limpia la selección
onOpenbooleanSe emite cuando se abre el dropdown
onClosebooleanSe emite cuando se cierra el dropdown
focusvoidSe emite al abrir el calendario
blurvoidSe emite al cerrarlo

Métodos públicos​

openDropdown(), closeDropdown(), toggleDropdown(e), clearSelection(e), emitValue() y setPositionFixedDropdown().

Clases del host​

tdn-input-date, disabled, active y ontop (el calendario abre por encima del campo), más las de settings.classes.

Atributos HTML​

AtributoDescripción
rangeHabilita modo de rango de fechas
timeHabilita selector de tiempo integrado

Ejemplos y Demos​

Selector de Fecha Simple​

Cargando demo...
Ver código

Rango de Fechas​

Usa el atributo range para habilitar selección de rango:

Cargando demo...
Ver código

Integración con Selector de Hora​

Combina con TdnInputTime para selección completa de fecha y hora:

Cargando demo...
Ver código

Configuración​

Interface TdnDateInputSettings​

Ver todas las opciones de configuración disponibles
interface TdnDateInputSettings {
// Clases CSS adicionales
classes?: string;

// Habilita selector de tiempo integrado
timePicker?: boolean;

// Sin efecto: el componente no lo lee (el formato lo da TDN_DATE_FORMATTER)
inputFormat?: string;

// Muestra botón de limpieza
clear?: boolean;

// Cierra el dropdown al presionar Escape
escapeToClose?: boolean;

// Sin efecto: el componente no lee shortcuts (ni IDateOptionShotcuts)
shortcuts?: IDateOptionShotcuts;

// Sin efecto: el formato lo da TDN_DATE_FORMATTER
formatter?: Intl.DateTimeFormat;

// Sin efecto: al seleccionar una fecha el calendario siempre se cierra
autoClose?: boolean;

// Opciones del calendario (Datepicker)
calendarOptions?: DatepickerOptions;

// Iconos personalizados
icons?: {
clear: string | SafeHtml;
chevron: string | SafeHtml;
};

// Función para renderizar celdas personalizadas
onRenderCell?: (params: {
date: Date | Date[],
cellType: CellType
}) => {
html?: string,
classes?: string,
attrs?: { title: string }
} | void;
}

interface TdnDateRangeInputSettings extends TdnDateInputSettings {
startDateField?: string; // Sin efecto: el componente no lo lee
endDateField?: string; // Sin efecto: el componente no lo lee
forceRangeField?: string; // Sin efecto: el componente no lo lee
}

Valores predeterminados​

{
classes: '',
timePicker: false,
inputFormat: 'dd/MM/y', // sin efecto
clear: true,
escapeToClose: true,
autoClose: true, // sin efecto
// calendarOptions: configuración del calendario en español (locale es)
}
Ajustes sin efecto

inputFormat, formatter, shortcuts, autoClose y los campos de TdnDateRangeInputSettings están declarados, pero el componente no los lee. En particular, los «atajos de fecha» no hacen nada. El calendario siempre está en español: calendarOptions.locale se ignora.

Ejemplo con Configuración Personalizada​

Cargando demo...
Ver código

Eventos​

Capturar Selección de Fecha​

Cargando demo...
Ver código

Detectar Apertura/Cierre del Calendario​

Cargando demo...
Ver código

El calendario abierto vive en el <body>​

Al abrirse, el calendario (.tdn-input-date__dropdown) se mueve a document.body, queda fijo al viewport y se coloca a partir del rectángulo del campo. Al cerrarse se oculta con display: none pero permanece en el body; se retira cuando el campo se destruye.

  • Se abre debajo del campo si hay espacio; si solo cabe arriba, encima (clase ontop en el host y en el calendario); si no cabe en ninguno de los dos lados, toma la clase fullscreen.
  • Se centra horizontalmente sobre el campo y se mantiene a 5 px dentro de la ventana.
  • Se reposiciona con el redimensionado y con el scroll del documento o de cualquier ancestro con scroll.
  • Un clic dentro del calendario cuenta como clic dentro del campo y no se propaga: un panel que se cierra con clics externos sigue abierto mientras eliges una fecha.
  • Se cierra con un clic externo, con Escape (si escapeToClose) o, en ventanas de más de 600 px, cuando el campo sale de la vista por scroll.
Trampa al estilizar el calendario

Una regla como .mi-formulario .tdn-input-date__dropdown no coincide mientras el calendario está abierto, y settings.classes se aplica solo al host. Estiliza el calendario con un selector que se sostenga solo.

Personalización de Celdas​

Usa onRenderCell para personalizar la apariencia de días específicos con HTML, clases CSS y atributos:

Cargando demo...
Ver código

Validación​

nota

Elegir una fecha no marca el control como touched: un mensaje con when="touched" aparece al limpiar el campo o tras markAllAsTouched().

Validadores Personalizados​

Cargando demo...
Ver código

Validación de Rango​

Cargando demo...
Ver código

Ejemplo Completo​

Ver formulario completo con validación y manejo de errores
import { Component, inject } from '@angular/core';
import {
ReactiveFormsModule,
FormBuilder,
Validators,
AbstractControl,
ValidationErrors
} from '@angular/forms';
import {
TdnFormElement,
TdnControlDirective,
TdnInputDateComponent,
TdnErrorsDirective,
TdnErrorDirective,
TdnDateInputSettings
} from '@tiendana/ngx-core/forms';

@Component({
selector: 'app-reservation-form',
imports: [
ReactiveFormsModule,
TdnFormElement,
TdnControlDirective,
TdnInputDateComponent,
TdnErrorsDirective,
TdnErrorDirective
],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<!-- Fecha Simple -->
<tdn-form-element>
<label>Check-in</label>
<tdn-input-date
tdnControl
formControlName="checkIn"
[settings]="checkInSettings"
(onSelect)="updateCheckOutMinDate($event)"
/>

<div tdnErrors="checkIn">
<div tdnError="required" when="touched">
La fecha de check-in es obligatoria
</div>
<div tdnError="minDate">
La fecha debe ser hoy o posterior
</div>
</div>
</tdn-form-element>

<!-- Rango de Fechas -->
<tdn-form-element>
<label>Período de Reserva</label>
<tdn-input-date
range
tdnControl
formControlName="reservationPeriod"
[settings]="rangeSettings"
/>

<div tdnErrors="reservationPeriod">
<div tdnError="required" when="touched">
Selecciona el período de reserva
</div>
<div tdnError="invalidRange">
La fecha de fin debe ser posterior al inicio
</div>
<div tdnError="maxRangeDays">
El período máximo es de 30 días
</div>
</div>
</tdn-form-element>

<button type="submit" class="bttn bttn--primary">
Confirmar Reserva
</button>
</form>
`
})
export class ReservationFormComponent {
private fb = inject(FormBuilder);

checkInSettings: TdnDateInputSettings = {
clear: true,
calendarOptions: {
minDate: new Date(),
firstDay: 1
}
};

rangeSettings: TdnDateInputSettings = {
clear: true,
calendarOptions: {
minDate: new Date(),
maxDate: new Date(new Date().setMonth(new Date().getMonth() + 6))
}
};

form = this.fb.group({
checkIn: [null, [Validators.required, this.minDateValidator()]],
reservationPeriod: [null, [Validators.required, this.validRangeValidator()]]
});

minDateValidator() {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) return null;
const today = new Date();
today.setHours(0, 0, 0, 0);
return new Date(control.value) < today ? { minDate: true } : null;
};
}

validRangeValidator() {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (!value?.from || !value?.to) return null;

const from = new Date(value.from);
const to = new Date(value.to);

if (to < from) return { invalidRange: true };

const diffDays = (to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24);
return diffDays > 30 ? { maxRangeDays: true } : null;
};
}

updateCheckOutMinDate(date: Date) {
// Actualizar configuración del segundo selector
console.log('Check-in seleccionado:', date);
}

onSubmit() {
this.form.markAllAsTouched();
if (this.form.valid) {
console.log('Reserva:', this.form.value);
}
}
}

Personalización Avanzada​

Tokens de Angular​

El componente usa dos tokens de inyección, exportados junto a TDN_DATE_INPUT_DEFAULT_SETTINGS (los valores integrados) y la interfaz DateFormatter. La fábrica de TDN_DATE_INPUT_DEFAULT_SETTINGS_TOKEN clona los valores por defecto, de modo que el valor del token no comparte objetos con la constante exportada TDN_DATE_INPUT_DEFAULT_SETTINGS.

TDN_DATE_INPUT_DEFAULT_SETTINGS_TOKEN​

Token para configurar el comportamiento predeterminado de todos los componentes TdnInputDate en la aplicación. Un proveedor reemplaza el objeto completo: parte de TDN_DATE_INPUT_DEFAULT_SETTINGS.

import {
TDN_DATE_INPUT_DEFAULT_SETTINGS,
TDN_DATE_INPUT_DEFAULT_SETTINGS_TOKEN,
TdnDateInputSettings
} from '@tiendana/ngx-core/forms';
import { bootstrapApplication } from '@angular/platform-browser';

const customDateSettings: TdnDateInputSettings = {
...TDN_DATE_INPUT_DEFAULT_SETTINGS,
clear: false,
calendarOptions: {
...TDN_DATE_INPUT_DEFAULT_SETTINGS.calendarOptions,
firstDay: 1, // Lunes como primer día de la semana
showOtherMonths: false
}
};

bootstrapApplication(AppComponent, {
providers: [
{
provide: TDN_DATE_INPUT_DEFAULT_SETTINGS_TOKEN,
useValue: customDateSettings
}
]
});

TDN_DATE_FORMATTER​

Token obligatorio que el componente usa para mostrar la fecha en el campo: llama directamente a format y formatRange. No tiene fábrica ni valor por defecto, y el componente lo inyecta sin optional, así que omitir el proveedor lanza NullInjectorError al crear el componente. (El comentario del código que menciona un respaldo con DatePipe e inputFormat no está implementado.)

El showcase lo provee en app.config.ts con Intl.DateTimeFormat('es-ES'):

import { TDN_DATE_FORMATTER, DateFormatter } from '@tiendana/ngx-core/forms';

const dateFormatter = new Intl.DateTimeFormat('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric' });

export const appConfig: ApplicationConfig = {
providers: [
{
provide: TDN_DATE_FORMATTER,
useValue: {
format: (date: Date) => dateFormatter.format(date),
formatRange: (from: Date, to: Date) => `${dateFormatter.format(from)} - ${dateFormatter.format(to)}`
} satisfies DateFormatter
}
]
};

También puede ser una clase:

class CustomDateFormatter implements DateFormatter {
format(date: Date): string {
const month = date.toLocaleDateString('es', { month: 'short' });
return `${date.getDate()} ${month} ${date.getFullYear()}`;
}

formatRange(from: Date, to: Date): string {
return `${this.format(from)} - ${this.format(to)}`;
}
}

bootstrapApplication(AppComponent, {
providers: [{ provide: TDN_DATE_FORMATTER, useClass: CustomDateFormatter }]
});

Interfaz DateFormatter:

interface DateFormatter {
format(date: Date): string; // Formato para fecha única
formatRange(from: Date, to: Date): string; // Formato para rango de fechas
}

Configuración a Nivel de Módulo/Componente​

También puedes proporcionar configuraciones específicas a nivel de un módulo o componente:

import { Component } from '@angular/core';
import {
TDN_DATE_INPUT_DEFAULT_SETTINGS,
TDN_DATE_INPUT_DEFAULT_SETTINGS_TOKEN
} from '@tiendana/ngx-core/forms';

@Component({
selector: 'app-date-section',
providers: [
{
provide: TDN_DATE_INPUT_DEFAULT_SETTINGS_TOKEN,
useValue: {
...TDN_DATE_INPUT_DEFAULT_SETTINGS,
clear: false // Solo para este componente y sus hijos
}
}
],
template: `
<!-- Todos los TdnInputDate aquí usarán la configuración local -->
`
})
export class DateSectionComponent {}

Buenas Prácticas​

  1. Usa validación de fechas: Siempre valida fechas mínimas/máximas en el backend también
  2. Maneja rangos correctamente: Valida que la fecha de fin sea posterior al inicio
  3. Configura minDate/maxDate: Previene selección de fechas no válidas
  4. Localización: el calendario es siempre en español; el texto del campo lo da TDN_DATE_FORMATTER

Accesibilidad​

  • ✅ Escape cierra el calendario (con escapeToClose)
  • ✅ Estados visuales claros (focus, disabled, active)
  • ✅ Compatible con validación de formularios de Angular
  • ✅ Responsive: modo fullscreen cuando el calendario no cabe ni arriba ni abajo
tdnControl no describe este campo

El campo no tiene un elemento data-tdn-aria-target, por lo que tdnControl no escribe aria-describedby ni aria-invalid en él.

Componentes Relacionados​