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);
}
}