import type { VNode } from 'vue'; import type { ValidationState } from '../../common/static-wrappers/interfaces/validation-interface'; import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item'; import { Tooltip } from 'bootstrap'; import { Prop, toNative } from 'vue-facing-decorator'; import TsxComponent, { Component } from '../../app/vuetsx'; import { isNullOrEmpty } from '../../common/utils/is-null-or-empty'; import { PortalUtils } from '../../common/utils/utils'; import DropdownButtonItem from '../dropdown-button/dropdown-button-item'; import './css/form-item-wrapper.css'; export enum HintType { Default = 0, Label = 1, } export enum MarginType { Standard = 0, None = 1, Small = 2, } export interface FormItemWrapperArgs { label?: string | VNode; mandatory?: boolean; hint?: string; hintType?: HintType; marginType?: MarginType; subtitle?: string; labelButtons?: DropdownButtonItemArgs[]; flex?: boolean; cssClass?: string; maxWidth?: number; validationState?: ValidationState; showClearValueButton?: boolean; appendIcon?: string; prependIcon?: string; appendClicked?: () => void; prependClicked?: () => void; prependIconClicked?: () => void; appendIconClicked?: () => void; wrap?: boolean; labelInputRow?: boolean; errorId?: string; } export class FormItemWrapperConfig { static labelColumnClass: string = 'col-md-4'; static contentColumnClass: string = 'col-md-8'; static customRender: (this: typeof FormItemWrapper.prototype, h, instance?: typeof FormItemWrapper.prototype) => void; } @Component class FormItemWrapperComponent extends TsxComponent implements FormItemWrapperArgs { @Prop() label: string | VNode; @Prop() labelButtons: DropdownButtonItemArgs[]; @Prop() mandatory: boolean; @Prop() flex: boolean; @Prop() hint: string; @Prop() subtitle: string; @Prop() hintType: HintType; @Prop() marginType?: MarginType; @Prop() maxWidth?: number; @Prop() appendIcon: string; @Prop() prependIcon: string; @Prop() cssClass: string; @Prop() showClearValueButton?: boolean; @Prop() appendClicked: () => void; @Prop() prependClicked: () => void; @Prop() prependIconClicked: () => void; @Prop() appendIconClicked: () => void; @Prop() wrap: boolean; @Prop() labelInputRow?: boolean; @Prop() errorId?: string; tooltipBound: boolean = false; uuid: string = PortalUtils.randomString(8); private labelAssociationDone: boolean = false; get labelId(): string { return `fiw-label-${this.uuid}`; } mounted() { if (this.showClearValueButton) { this.bindClearValueButton(); } this.ensureControlLabelled(); } updated() { if (this.showClearValueButton) { this.handleCurrentInputState(); } this.ensureControlLabelled(); } // ── Accessible-name association ─────────────────────────────────────────── // The visible field label lives in this wrapper, but the actual control is // passed in via the default slot, so we cannot wire `for`/`id` at render // time. Instead we associate them on the client (mounted/updated never run // during SSR) by pointing the control at the label via `aria-labelledby`. // Conservative: only ever ADDS a name to a control that has none, and skips // controls that are already named natively or via an explicit/implicit //