import Bluebird from "bluebird"; import * as defs from "./FormDefinition"; import type { FormComponent } from "./FormComponent"; import type { FormPresenterHost } from "./FormPresenterHost"; import type { FormatOptions } from "./numberFormatter"; import type { ExternalEvent } from "../execution/ExternalEvent"; import type { ComponentType, ReactNode } from "react"; export { defs }; interface FormEventSubscription { (): boolean; active: boolean; } interface FormEventHandler { (event: defs.Event): void; } /** * Details about the most recently focused DOM element. * This might be assigned programmatically or automatically * via observing the focus events of the container. */ interface LastFocusInfo { /** True or false based on the error property of the element, if known. */ isInvalid?: boolean; /** The name of the form element that `target` belongs to, if known. */ name?: string; /** The position within the row of the form element, if known. */ rowIndex?: number; /** The row of the form element, if known. */ rowNumber?: number; /** The DOM element that was focused. */ target: HTMLElement; } /** Exposes functionality for driving a form. */ export declare abstract class FormHost implements FormPresenterHost { /** The DOM container node. */ container: HTMLElement | undefined; /** Indicates if the form is a custom form. */ custom: boolean; /** Indicates if the form should be dismissed. */ dismiss: boolean; /** Indicates if the form is enabled. */ enabled: boolean; /** Indicates all errors to be assigned. */ errors: Record; /** Indicates the overall state of the form. */ form: defs.Form; /** Indicates any reference information relevant to components. */ refs: Record; /** Indicates if the form is visible. */ visible: boolean; /** * We keep track of the outermost DOM element for each form element here * so that when any DOM element gains focus in the form we can determine which element * it belongs to. This helps with deciding where to move focus to when validation fails. */ protected domContainers: Record; /** The next event index. */ protected eventIndex: number; /** The event queue. */ protected eventQueue: defs.Event[]; /** Indicates the focus observer. */ protected focusObserver: ((event: FocusEvent) => void) | undefined; /** Indicates a method to restore the last focus. */ protected lastFocus: LastFocusInfo | undefined; /** Indicates the next element that should receive focus. */ protected nextFocus: boolean | string; /** Indicates the next element that should receive a refresh. */ protected nextRefresh: boolean | Record; /** * This stores values which are to be re-applied after elements have been refreshed * due to the value of a depended-upon element changing. The values here are copied from elements prior to * the "populate" or "suggest" handler being executed, so they may not be the same exact Items * as next time around, but the underlying values can still be used to find the equivalent and select them. */ protected pendingValues: Record; /** Indicates the style o */ protected style: HTMLStyleElement | undefined; /** The timer handler for idle/busy handling. */ protected timerHandle: (() => void) | undefined; /** The event promise, for when the consumer is waiting. */ private eventTask; /** * All elements of type "Section" with a format that is considered mutually exclusive, grouped based on continuity. * If there is another element of type "Section" with a different format in between, two of the same mutually exclusive type, * this represents the boundary between two different sets of mutually exclusive sections. * * IMPORTANT: Do not reference this directly. Instead use, `getMutuallyExclusiveSectionGroup(string)` which populates it. */ private mutuallyExclusiveSections; private resolveEvent; /** * This method is used to update the values of form elements that depend on another * and have "Persist Value on Refresh" set to true. */ applyPendingValues(): void; /** Starts the busy timer. */ busy(): void; /** Coerces a number. */ coerceNumber(value: string, locale?: string): number; /** Dequeues the next event (used by observe()). */ dequeue(): defs.Event | undefined; deriveLocale(): string; displayDialog(children: (closeDialog: () => void) => ReactNode, DialogComponent: ComponentType): Promise; /** Disposes the form. */ dispose(): boolean; /** Enqueues an event (used by publish()). */ enqueue(type: defs.EventType, event?: defs.Event): defs.Event; /** * Pass on an external event to the queue. * This method is an arrow function so that the consumer can use it without an object reference. * @param event The event to be added to the queue. */ enqueueExternalEvent: (event: ExternalEvent) => void; /** Finds an element. */ find(name: string | { name: string; } | undefined): defs.Element | undefined; /** Finds the name of an element. */ findName(element: defs.Element | undefined): string | undefined; /** Focuses the form. */ focus(): void; /** Focuses the form optionally focusing the initial element. */ focus(initial: boolean): void; /** Focuses the form focusing the named element. */ focus(name: string): void; /** Formats a number. */ formatNumber(value: number, options?: FormatOptions): string; /** Checks the form's validity. */ hasErrors(): boolean; /** * Checks the specified element to determine if it has a selection, * which would justify a dependent being populated at this time. * * This function is only interested in _initial_ selection defined in the designer * rather than changes made in an element's `load` event. Those are handled separately. * @param elementName The name of the element being depended upon. * @returns True if the element has a selection at this time. */ hasInitialSelectionToDependOn(elementName: string): boolean; hasInvalidChild(element: defs.Element): boolean; /** Hides the form in a disabled state. */ hide(): boolean; /** Clears the busy timer. */ idle(): void; /** Invalidates the form with a full refresh. */ invalidate(): void; /** Invalidates the form with optionally a full refresh. */ invalidate(full: boolean): void; /** Invalidates the form refreshing a single element. */ invalidate(name: string | undefined): void; /** Loads the form. */ load(form?: defs.Form, template?: defs.Form): boolean; /** Observes the form. */ observe(): Bluebird; /** Posts an event. */ post(type: defs.EventType, event?: defs.Event): void; /** Publishes an event (used by post()). */ publish(type: defs.EventType, event?: defs.Event): defs.Event; /** Forcefully refresh the form (use this with care). */ refresh(name?: string): void; /** * This method renders focus for just rendered elements. * It also moves focus to another element when `nextFocus` is the name of an element. */ renderFocus(target: HTMLElement, name?: string): boolean; /** Renders a component's state. */ renderState(name: string, type: "geometry", state?: defs.GeometryState[]): boolean; /** Renders a component's state. */ renderState(name: string, type: "item-picker", state?: defs.ItemPickerState): boolean; /** Renders a component's state. */ renderState(name: string, type: string, state?: object): boolean; /** Renders form text as plain text. */ renderText(content: defs.Text | undefined): string; /** Renders the form component. */ renderVisual(component: FormComponent): any; /** Routes an event to achieve default behavior. */ route(event: defs.Event): boolean; /** Shows the form. */ show(): boolean; /** Shows the form in a disabled state. */ spin(): boolean; /** Subscribes to all posted events (this does not capture enqueued events). */ subscribe(action: FormEventHandler): FormEventSubscription; /** * Translates the text. This is a bit of a no-op as we only have access to * the language strings in FormRenderer. * @param content The string or {@link defs.StatusRef} to be translated. * @returns The underlying text. */ translateText(content: string | defs.StatusRef | undefined): string | undefined; /** * Translates the text. This is a bit of a no-op as we only have access to * the language strings in FormRenderer. * @param content The {@link defs.MarkdownRef} to be translated. * @returns The underlying text. */ translateText(content: defs.MarkdownRef | undefined): defs.MarkdownRef | undefined; /** * Translates the text. This is a bit of a no-op as we only have access to * the language strings in FormRenderer. * @param content The {@link defs.Text} to be translated. * @returns The underlying text. */ translateText(content: defs.Text | undefined): string | defs.MarkdownRef | undefined; /** * Attempts to trap the event. This is relevant during cascading behavior which affects * other elements but afterwards. */ trap(event: defs.Event): boolean; /** Updates the form if appropriate. */ update(): void; /** * Collapses all neighbouring sections except the one specified. * @param expandedSectionName The name of the mutually exclusive section to be expanded. All others in the group will be collapsed. */ updateMutuallyExclusiveSections(expandedSectionName: string): void; /** * Cascade the form by clearing the value of any elements downstream of the specified one * and triggering the "populate" event for any elements that depend on it directly. * @param elementName The name of an elements which has just changed. * @returns True if the operation was a success, false otherwise. */ protected cascade(elementName: string): boolean; /** Generates a new changed event given the source event. */ protected changed(event: defs.Event): boolean; /** Routes a click event. */ protected click(event: defs.Event): boolean; /** Routes a prepare event. */ protected hydrate(event: defs.Event): boolean; /** * Returns true if the supplied element is effectively visible (i.e. has parent * section - if it exists - visible) as well as itself, false otherwise. * @param element The element to be tested. */ protected isElementEffectivelyVisible(element: defs.Element | undefined): boolean; /** * Returns true if the supplied element is considered valid, false otherwise. * @param element The element to be tested. * @param isInvisibleValid True if the element should be considered valid when not visible, false otherwise. Defaults to true. * @param result An optional object which can be passed in to capture the error status, if any. */ protected isElementValid(element: defs.Element, isInvisibleValid?: boolean, result?: Partial): boolean; /** * Returns true if the element has a value defined. False otherwise. * @param element The element to be tested. */ protected isElementValueDefined(element: defs.Element | undefined): boolean; /** Mounts the form into the DOM. */ protected mount(): void; /** * Intelligently tracks the last focus, in response to focus events within * the form container. We attempt to determine which form element `target` belongs to, * so that we can have a better idea where we are in the order of elements. * * This comes into play later when we might want to decide where to move focus * based on where we currently are. * @param target The DOM element that has just gained focus. */ protected observeFocus(target: EventTarget): void; /** Qualifies an event. */ protected qualify(type: defs.EventType, event?: defs.Event): defs.Event; /** Qualifies an event with defaults. */ protected qualifyDefault(event: defs.Event): string | undefined; /** Determines if the event origin is a submit button. */ protected shouldTriggerValidation(event: defs.Event): boolean | void; /** Routes a submit event. */ protected submit(event: defs.Event): boolean; /** Routes a suggest event. */ protected suggest(event: defs.Event): boolean; /** Removes the form from the DOM. */ protected unmount(): void; /** Routes a validate event. */ protected validate(event: defs.Event): boolean; /** Verifies the element given the event. */ protected verify(event: defs.Event): boolean; private enforceMutuallyExclusiveConstraints; private getAllSectionElements; private getInvalidElements; private getMutuallyExclusiveSectionGroup; private getParentSection; /** * This method checks if the specified element is considered before the current value of `this.lastFocus`. * When the form is initially loading, we focus the first visible element in the form, regardless of error state. * When validating the form, we focus the first _invalid_ element. * @param elementName The name of a Form Element. * @param invalidOnly True if we only care about invalid elements, false otherwise. * @returns True if the named element is considered before the one specified in `this.lastFocus`. */ private isElementPositionBeforeLastFocused; private isInvalid; private isSectionCollapsed; /** * * @param target The DOM element that was focused * @param name The name of the element that contains `target`. (if known) * @param isInvalid True or false if the invalid state is known. Otherwise we check the element itself. * This is necessary as some functions like `verify()` will use `this.errors` to track errors before * we update `element.error` later. * @returns */ private setLastFocus; }