import { ElementRef, EventEmitter } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { CheckboxOption, InterestFieldType, InterestFormAnswer, InterestFormQuestion, SelectOption } from '@ieeesa-npm/models'; import { RadioOption } from '../../models/radio-option.model'; import { FormModel } from '../../models/form.model'; import { FormGrid } from '../../models/form-grid.model'; import { CalendarType } from '../../models/calendar.model'; import * as i0 from "@angular/core"; /** * A custom interest-form question flattened for the template, with normalized * options and the live answer value. Rendered field-by-field with the plain * label + control markup (no shared dynamic-form) so every label matches the * standard fields' styling. */ interface QuestionField { question: InterestFormQuestion; type: InterestFieldType; label: string; placeholder: string; helpText: string; required: boolean; /** SELECT options (ieeesa-select). */ options: SelectOption[]; /** RADIO options (ieeesa-radio). */ radioOptions: RadioOption[]; /** CHECKBOX options (ieeesa-checkbox). */ checkboxOptions: CheckboxOption[]; value: any; touched: boolean; /** * Single-field dynamic-form config for a DATE question. The shared * ieeesa-date-picker is driven through DynamicFormComponent (its supported * integration path), which wires the config/group/value-accessor correctly — * standalone use triggers NG01203. Undefined for non-DATE questions. */ dateForm?: FormModel; /** Live reactive form group for a DATE question's dynamic form. */ dateFormGroup?: FormGroup; } /** * Express Interest dialog (SP3M-315, Component B) — PRESENTATIONAL. Collects the * standard information (read-only Name/Email, required affiliations, optional * read-only Address / Phone) and, when the group's interest form carries custom * questions (FORM mode), an Additional Questions section. All data arrives via * `@Input` and all actions leave via `@Output`; the component performs NO API, * service or MatDialog work, so it can be reused by any host (my-profile today, * groups later). The host owns the profile/interest-form fetch, the employer * typeahead lookup, and the submit. Every field is rendered with the same plain * label + control markup so labels share one colour / font. * @export * @class ExpressInterestDialogComponent */ export declare class ExpressInterestDialogComponent { private elementRef; /** Dialog title (host-provided; the heading markup reads texts?.heading). */ title: string; /** Subtitle shown under the title (e.g. the selected group's name). */ subtitle: string; /** Read-only display name resolved by the host from the user's profile. */ userName: string; /** Read-only email resolved by the host from the user's profile. */ userEmail: string; /** * Standard-section fields other than affiliations (Address, Phone Number). * Display-only — the host resolves them from the user profile and they are * never submitted (the BE sources address/phone from the profile). */ standardFields: { key: string; label: string; value: string; }[]; /** True while an express-interest submission is in flight (guards double-submit). */ isSubmitting: boolean; /** * Preview (read-only) mode. When true the dialog renders the form exactly as * an end user would see it but non-interactive: the affiliation entry, Add * button and Save button are hidden and all question controls are disabled, * leaving only Cancel to close. Used by the admin "Preview Form" action. */ isPreview: boolean; /** * True when the host's initialization (profile + interest-form fetch) failed. * In this state the form is NOT rendered and Save stays disabled: a transient * form-fetch failure must not be mistaken for BASIC mode (empty questions), * which could otherwise submit without required custom answers. */ loadFailed: boolean; /** * UI strings supplied by the host (headings, labels, placeholders, error * messages). The template reads texts?.* and labels?.* so the host controls * all copy without this component depending on any constants file. */ texts: any; /** * Employer suggestions for the affiliation typeahead, fed back by the host in * response to {@link affiliationSearch}. The setter also drives the dropdown * visibility (shown only when there are suggestions and the search box is * non-empty), preserving the old reactive dropdown behavior. * @memberof ExpressInterestDialogComponent */ set affiliationSuggestions(value: SelectOption[]); get affiliationSuggestions(): SelectOption[]; private _affiliationSuggestions; /** * Custom questions (FORM mode) rendered in the Additional Questions section. * The setter runs {@link buildQuestions} to flatten/normalize the questions * (ordered by view order, options parsed, radio/checkbox arrays built, DATE * dynamic-form config built). * @memberof ExpressInterestDialogComponent */ set customQuestions(value: InterestFormQuestion[]); /** Emits the trimmed search term (>= min chars) for the host to look up. */ affiliationSearch: EventEmitter; /** Emits the collected affiliations + answers when Save passes validation. */ save: EventEmitter<{ affiliations: string[]; answers: InterestFormAnswer[]; }>; /** Emits when the user closes/cancels the dialog. */ cancel: EventEmitter; /** Field labels block (template reads labels?.*). */ get labels(): any; /** Free-text/typeahead search term for the affiliation picker. */ affiliationSearchText: string; /** Whether the affiliation typeahead dropdown is visible. */ showAffiliationDropdown: boolean; /** * Index of the keyboard-active suggestion in the dropdown (-1 = none), used * for Arrow/Enter navigation and aria-activedescendant. */ activeSuggestionIndex: number; /** Affiliations the user has added (free strings sent as-is to the BE). */ selectedAffiliations: string[]; /** True once the user interacts, used to surface the min-affiliation error. */ affiliationTouched: boolean; /** Non-null validation message shown under the affiliation picker. */ affiliationError: string; /** Custom questions (FORM mode) rendered in the Additional Questions section. */ questions: QuestionField[]; /** Single-date mode for the shared date-picker (custom DATE questions). */ readonly singleDateCalendar = CalendarType.SINGLE_DATE_SELECTION; /** Full-width single-column layout for the DATE dynamic form. */ readonly colWidth: FormGrid; constructor(elementRef: ElementRef); /** * Builds the custom-question view models (ordered by view order), normalizing * each question's options and seeding its initial answer value. * @param {InterestFormQuestion[]} customQuestions * @memberof ExpressInterestDialogComponent */ private buildQuestions; /** * Builds a single-field dynamic-form config for a DATE question so the shared * date-picker renders through its supported DynamicFormComponent path. * @param {InterestFormQuestion} question * @return {FormModel} * @memberof ExpressInterestDialogComponent */ private buildDateForm; /** * Normalizes a question's `options` into SelectOption[]. The BE sends options * as a JSON array of plain strings (e.g. ["Student","Member"]); an array of * {label,value}/{name,id} objects and a raw JSON string are also tolerated. * @param {(string[] | string | null)} raw * @return {SelectOption[]} * @memberof ExpressInterestDialogComponent */ private parseOptions; /** * Stores the chosen RADIO option (shared ieeesa-radio emits a RadioOption); * its label is the answer value. * @param {QuestionField} field * @param {RadioOption} option * @memberof ExpressInterestDialogComponent */ onRadioChange(field: QuestionField, option: RadioOption): void; /** * Stores the selected CHECKBOX options (shared ieeesa-checkbox emits the * selected CheckboxOption[]); their names form the answer array. * @param {QuestionField} field * @param {CheckboxOption[]} selected * @memberof ExpressInterestDialogComponent */ onCheckboxChange(field: QuestionField, selected: CheckboxOption[]): void; /** * Stores the chosen SELECT option (shared ieeesa-select emits a SelectOption). * @param {QuestionField} field * @param {SelectOption} option * @memberof ExpressInterestDialogComponent */ onSelectChange(field: QuestionField, option: SelectOption): void; /** * Captures a DATE question's dynamic-form group so its value feeds Save * enablement and the submit payload. * @param {QuestionField} field * @param {FormGroup[]} form * @memberof ExpressInterestDialogComponent */ onDateFormReady(field: QuestionField, form: FormGroup[]): void; /** * Prevents the DATE DynamicFormComponent's one-shot auto-focus from landing * on the date input. The date input renders during the DynamicForm's view * init and the component focuses it via setTimeout(0) in ngAfterViewInit; * this handler fires at its ngAfterContentInit — slightly before the input * exists. So we microtask-poll (queueMicrotask) for the input the instant it * appears and replace its focus() with a no-op, which runs before the * macrotask setTimeout(0) auto-focus. Real focus() is restored shortly after * so the user can still open the picker. No blur-after, so no flicker. * @memberof ExpressInterestDialogComponent */ private neutralizeDateAutoFocus; /** * Keeps a DATE question's value current as the user picks a date. * @param {QuestionField} field * @memberof ExpressInterestDialogComponent */ onDateFormChange(field: QuestionField): void; /** * Mirrors the DATE question's dynamic-form control value onto field.value. * @param {QuestionField} field * @memberof ExpressInterestDialogComponent */ private syncDateValue; /** * Emits the affiliation search term for the host to look up once at least * {@link EMPLOYER_SEARCH_MIN_CHARS} characters are typed. Below that the * dropdown is hidden and suggestions cleared; free-text add via "Add" still * works. The host feeds matching suggestions back via the * {@link affiliationSuggestions} input. * @memberof ExpressInterestDialogComponent */ onAffiliationSearch(): void; /** * Keyboard navigation for the affiliation combobox: ArrowDown/Up move the * active suggestion, Enter selects the active suggestion (or adds the typed * value when none is active), and Escape closes the dropdown. * @param {KeyboardEvent} event * @memberof ExpressInterestDialogComponent */ onAffiliationKeydown(event: KeyboardEvent): void; /** * Adds a suggestion picked from the typeahead dropdown. * @param {SelectOption} suggestion * @memberof ExpressInterestDialogComponent */ selectAffiliationSuggestion(suggestion: SelectOption): void; /** * Adds the current free-text search value as an affiliation. * @memberof ExpressInterestDialogComponent */ onAddAffiliation(): void; /** * Validates and appends an affiliation, enforcing the non-empty, max-count * and max-length rules and surfacing any validation message. * @param {string} value * @memberof ExpressInterestDialogComponent */ private addAffiliationValue; /** * Removes an affiliation chip. * @param {string} value * @memberof ExpressInterestDialogComponent */ removeAffiliation(value: string): void; /** * Hides the typeahead dropdown shortly after the input loses focus, allowing * a suggestion mousedown to register first. * @memberof ExpressInterestDialogComponent */ onAffiliationBlur(): void; /** * Whether a single question has a valid answer (required questions must have * a non-empty value). * @param {QuestionField} field * @return {boolean} * @memberof ExpressInterestDialogComponent */ private isQuestionValid; /** * Whether Save is enabled: at least one affiliation AND every required custom * question has an answer. Standard Address/Phone are optional and never block. * @readonly * @memberof ExpressInterestDialogComponent */ get isSaveDisabled(): boolean; /** * Validates the form locally and, when valid, emits the collected affiliations * and answers for the host to submit. Performs NO API work and does not close * any dialog — the host owns the submit and dialog lifecycle. * @memberof ExpressInterestDialogComponent */ onSave(): void; /** * Builds the answers array from the custom questions. Standard-section data * (affiliations) is sent via the `affiliations` field only, never in answers. * CHECKBOX values are serialized as a JSON array string; other types send * their string value. * @return {InterestFormAnswer[]} * @memberof ExpressInterestDialogComponent */ private buildAnswers; /** * Emits cancel so the host can close the dialog. * @memberof ExpressInterestDialogComponent */ onCancel(): void; /** * TrackBy for the affiliation chips list. * @param {number} _index * @param {string} affiliation * @return {string} * @memberof ExpressInterestDialogComponent */ trackByAffiliation(_index: number, affiliation: string): string; /** * TrackBy for the affiliation suggestions list. * @param {number} _index * @param {SelectOption} suggestion * @return {(string | number)} * @memberof ExpressInterestDialogComponent */ trackBySuggestion(_index: number, suggestion: SelectOption): string | number; /** * TrackBy for the custom-question list. * @param {number} _index * @param {QuestionField} field * @return {string} * @memberof ExpressInterestDialogComponent */ trackByQuestion(_index: number, field: QuestionField): string; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } export {};