El mockup muestra que una fila es etiqueta + uno o varios controles en la misma línea (Cliente = input + "...", Canal / Zona = dos mitades, Serie + Número = 72 px + resto, select + número + botón) y que la cabecera tiene a lo sumo 2 columnas. Los campos de un solo control no lo permitían. - Controles sin etiqueta combinables dentro de sbo-field: sbo-input, sbo-textarea, sbo-number, sbo-date, sbo-select y el botón .sbo-btn; ocupan el espacio sobrante o un width fijo. - El primer control de la fila toma el id de la etiqueta (claimId); los demás se nombran con ariaLabel. El error de la fila marca todos sus controles (aria-invalid / aria-describedby). - Los campos con etiqueta (sbo-text-field, sbo-number-field, ..., sbo-lookup-field) pasan a ser atajos de un solo control con la misma API. - CSS global: .sbo-group y .sbo-btn reemplazan a .sbo-pick/.sbo-pair. - CLAUDE.md apunta al mockup renombrado (tools/oferta_venta.html). - Verificado en Chrome contra SAP real, con una cabecera que reproduce las filas compuestas del mockup. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
|
|
import { FieldBase } from './field-base';
|
|
import { DateInput, NumberInput, SelectInput, type SelectOption, TextInput, TextareaInput } from './controls';
|
|
import { SboField } from './sbo-field';
|
|
|
|
/*
|
|
* Campos con etiqueta de un solo control: atajo de `<sbo-field>` + el control correspondiente.
|
|
* Para filas con varios controles (input + botón, serie + número...) usar `sbo-field` directamente.
|
|
*/
|
|
|
|
@Component({
|
|
selector: 'sbo-text-field',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [SboField, TextInput],
|
|
template: `
|
|
<sbo-field [label]="label()" [required]="required()" [error]="error()">
|
|
<sbo-input
|
|
[(value)]="value"
|
|
[readonly]="readOnly()"
|
|
[disabled]="disabled()"
|
|
[required]="required()"
|
|
[maxlength]="maxlength()"
|
|
[placeholder]="placeholder()"
|
|
/>
|
|
</sbo-field>
|
|
`,
|
|
styles: ':host { display: block; }',
|
|
})
|
|
export class TextField extends FieldBase {
|
|
readonly value = model('');
|
|
readonly maxlength = input<number>();
|
|
readonly placeholder = input('');
|
|
}
|
|
|
|
@Component({
|
|
selector: 'sbo-textarea-field',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [SboField, TextareaInput],
|
|
template: `
|
|
<sbo-field [label]="label()" [required]="required()" [error]="error()">
|
|
<sbo-textarea
|
|
[(value)]="value"
|
|
[readonly]="readOnly()"
|
|
[disabled]="disabled()"
|
|
[required]="required()"
|
|
[maxlength]="maxlength()"
|
|
[placeholder]="placeholder()"
|
|
[rows]="rows()"
|
|
/>
|
|
</sbo-field>
|
|
`,
|
|
styles: ':host { display: block; }',
|
|
})
|
|
export class TextareaField extends FieldBase {
|
|
readonly value = model('');
|
|
readonly maxlength = input<number>();
|
|
readonly placeholder = input('');
|
|
readonly rows = input(3);
|
|
}
|
|
|
|
@Component({
|
|
selector: 'sbo-number-field',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [SboField, NumberInput],
|
|
template: `
|
|
<sbo-field [label]="label()" [required]="required()" [error]="error()">
|
|
<sbo-number
|
|
[(value)]="value"
|
|
[decimals]="decimals()"
|
|
[readonly]="readOnly()"
|
|
[disabled]="disabled()"
|
|
[required]="required()"
|
|
/>
|
|
</sbo-field>
|
|
`,
|
|
styles: ':host { display: block; }',
|
|
})
|
|
export class NumberField extends FieldBase {
|
|
readonly value = model<number | null>(null);
|
|
readonly decimals = input<number>();
|
|
}
|
|
|
|
@Component({
|
|
selector: 'sbo-date-field',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [SboField, DateInput],
|
|
template: `
|
|
<sbo-field [label]="label()" [required]="required()" [error]="error()">
|
|
<sbo-date [(value)]="value" [readonly]="readOnly()" [disabled]="disabled()" [required]="required()" />
|
|
</sbo-field>
|
|
`,
|
|
styles: ':host { display: block; }',
|
|
})
|
|
export class DateField extends FieldBase {
|
|
readonly value = model('');
|
|
}
|
|
|
|
@Component({
|
|
selector: 'sbo-select-field',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
imports: [SboField, SelectInput],
|
|
template: `
|
|
<sbo-field [label]="label()" [required]="required()" [error]="error()">
|
|
<sbo-select
|
|
[(value)]="value"
|
|
[options]="options()"
|
|
[placeholder]="placeholder()"
|
|
[readonly]="readOnly()"
|
|
[disabled]="disabled()"
|
|
[required]="required()"
|
|
/>
|
|
</sbo-field>
|
|
`,
|
|
styles: ':host { display: block; }',
|
|
})
|
|
export class SelectField extends FieldBase {
|
|
readonly options = input.required<readonly SelectOption[]>();
|
|
readonly value = model<string | number | null>(null);
|
|
readonly placeholder = input<string>();
|
|
}
|