import { ChangeDetectionStrategy, Component, computed, input, model, signal } from '@angular/core'; import { formatNumber, parseDecimal } from '../../core/format'; import { ControlBase } from './control-base'; @Component({ selector: 'sbo-input', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, styles: ':host { display: block; }', }) export class TextInput extends ControlBase { readonly value = model(''); readonly maxlength = input(); readonly placeholder = input(''); } @Component({ selector: 'sbo-textarea', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, styles: ':host { display: block; }', }) export class TextareaInput extends ControlBase { readonly value = model(''); readonly maxlength = input(); readonly placeholder = input(''); readonly rows = input(3); } /** * Número o importe. Fuera de foco se muestra formateado (`1,234.50`); al editar se * escribe en texto libre y se interpreta al salir (acepta `12,5` y `1,234.50`). */ @Component({ selector: 'sbo-number', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, styles: ':host { display: block; }', }) export class NumberInput extends ControlBase { readonly value = model(null); /** Decimales fijos al mostrar y redondear (importes: 2). Sin él: hasta 3, sin ceros sobrantes. */ readonly decimals = input(); protected readonly editing = signal(false); protected readonly draft = signal(''); protected readonly display = computed(() => this.editing() ? this.draft() : formatNumber(this.value(), this.decimals()), ); protected startEditing(event: FocusEvent): void { if (this.readOnly()) return; const v = this.value(); this.draft.set(v === null ? '' : String(v)); this.editing.set(true); // se selecciona el contenido una vez que el valor editable reemplazó al formateado const input = event.target as HTMLInputElement; queueMicrotask(() => input.select()); } protected finishEditing(): void { if (!this.editing()) return; this.editing.set(false); const text = this.draft().trim(); if (text === '') { this.value.set(null); return; } const parsed = parseDecimal(text); if (parsed === null) return; // no es un número: se conserva el valor anterior const d = this.decimals(); this.value.set(d === undefined ? parsed : Math.round((parsed + Number.EPSILON) * 10 ** d) / 10 ** d); } } /** Fecha como texto ISO `yyyy-MM-dd` (el formato del Service Layer). Selector nativo: sin librerías. */ @Component({ selector: 'sbo-date', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, styles: ':host { display: block; }', }) export class DateInput extends ControlBase { readonly value = model(''); } export interface SelectOption { value: string | number; label: string; } @Component({ selector: 'sbo-select', changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, styles: ':host { display: block; }', }) export class SelectInput extends ControlBase { readonly options = input.required(); readonly value = model(null); /** Opción vacía inicial (ej. "— Seleccione —"); sin él, no se ofrece. */ readonly placeholder = input(); protected choose(raw: string): void { const option = this.options().find((o) => String(o.value) === raw); this.value.set(option ? option.value : null); } }