Files
basic-erp/apps/web/src/app/shared/form/sbo-field.ts
T

121 lines
3.3 KiB
TypeScript

import { ChangeDetectionStrategy, Component, booleanAttribute, inject, input, output } from '@angular/core';
import { FieldNames } from '../../core/field-names';
let nextId = 0;
/**
* 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',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="f">
<label
[attr.for]="controlId"
[class.req]="required()"
[class.ga]="action()"
[class.link]="linkable()"
[attr.title]="dbName() ?? null"
(click)="labelClick.emit()"
><span class="t">{{ label() }}</span>@if (dbName() && names.show()) {
<span class="db">{{ dbName() }}</span>
}</label
>
<div class="ctl">
<div class="sbo-group"><ng-content /></div>
@if (error()) {
<span class="err" role="alert" [id]="errorId">{{ error() }}</span>
}
</div>
</div>
`,
styles: `
:host {
display: block;
}
.f {
display: grid;
grid-template-columns: var(--label-w, 165px) minmax(0, 1fr);
align-items: start;
gap: 8px;
min-height: 24px;
}
label {
padding-top: 3px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
label.req .t::after {
content: ' *';
color: var(--err);
}
label.ga {
position: relative;
padding-left: 14px;
}
label.ga::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
border-left: 8px solid var(--gold);
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
}
.db {
display: block;
margin-top: -2px;
font: 10.5px/1.2 var(--mono, monospace);
color: var(--quiet);
white-space: normal;
overflow-wrap: anywhere;
}
label.link {
cursor: pointer;
}
label.link:hover {
text-decoration: underline;
}
.ctl {
min-width: 0;
}
.err {
display: block;
margin-top: 1px;
font-size: 11.5px;
color: var(--err);
}
`,
})
export class SboField {
protected readonly names = inject(FieldNames);
readonly label = input.required<string>();
/** Nombre del campo de SAP; sale como ayuda del cursor y, con «Ver campos», debajo de la etiqueta. */
readonly dbName = 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). */
readonly linkable = input(false, { transform: booleanAttribute });
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;
}
}