refactor(web): filas de formulario con varios controles combinables
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>
This commit is contained in:
1 parent
aa2c483723
commit
68caf401a7
16 files changed
+531
-298
No files matched your search
@@ -1,8 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, booleanAttribute, input, output } from '@angular/core';
|
||||
|
||||
let nextId = 0;
|
||||
|
||||
/**
|
||||
* Fila de formulario SBO: etiqueta a la izquierda y el control proyectado a la derecha.
|
||||
* Fila de formulario SBO: etiqueta a la izquierda y, a la derecha, uno o varios
|
||||
* controles en una misma línea (input + botón, select + botón, serie + número...).
|
||||
* `action` pinta la flecha dorada de SAP (campo que enlaza a otro objeto o a una lista).
|
||||
*
|
||||
* El primer control de la fila toma el `id` al que apunta la etiqueta (ver `claimId`).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'sbo-field',
|
||||
@@ -10,7 +15,7 @@ import { ChangeDetectionStrategy, Component, booleanAttribute, input, output } f
|
||||
template: `
|
||||
<div class="f">
|
||||
<label
|
||||
[attr.for]="for()"
|
||||
[attr.for]="controlId"
|
||||
[class.req]="required()"
|
||||
[class.ga]="action()"
|
||||
[class.link]="linkable()"
|
||||
@@ -18,9 +23,9 @@ import { ChangeDetectionStrategy, Component, booleanAttribute, input, output } f
|
||||
>{{ label() }}</label
|
||||
>
|
||||
<div class="ctl">
|
||||
<ng-content />
|
||||
<div class="sbo-group"><ng-content /></div>
|
||||
@if (error()) {
|
||||
<span class="err" role="alert" [id]="for() + '-err'">{{ error() }}</span>
|
||||
<span class="err" role="alert" [id]="errorId">{{ error() }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,8 +84,6 @@ import { ChangeDetectionStrategy, Component, booleanAttribute, input, output } f
|
||||
})
|
||||
export class SboField {
|
||||
readonly label = input.required<string>();
|
||||
/** `id` del control, para enlazar la etiqueta. */
|
||||
readonly for = input<string>();
|
||||
readonly required = input(false, { transform: booleanAttribute });
|
||||
readonly action = input(false, { transform: booleanAttribute });
|
||||
/** La etiqueta es un enlace (ej. abrir el socio de negocio). */
|
||||
@@ -88,4 +91,15 @@ export class SboField {
|
||||
readonly error = input<string>();
|
||||
|
||||
readonly labelClick = output<void>();
|
||||
|
||||
readonly controlId = `sbo-f-${++nextId}`;
|
||||
readonly errorId = `${this.controlId}-err`;
|
||||
private claimed = false;
|
||||
|
||||
/** El primer control de la fila recibe el id de la etiqueta; los demás deben llevar su `ariaLabel`. */
|
||||
claimId(): string | null {
|
||||
if (this.claimed) return null;
|
||||
this.claimed = true;
|
||||
return this.controlId;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user