@dataverse-kit/form-runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/businessRules-U1_MBgyG.d.cts +372 -0
- package/dist/businessRules-U1_MBgyG.d.ts +372 -0
- package/dist/context.cjs +151 -0
- package/dist/context.cjs.map +1 -0
- package/dist/context.d.cts +132 -0
- package/dist/context.d.ts +132 -0
- package/dist/context.mjs +113 -0
- package/dist/context.mjs.map +1 -0
- package/dist/control-DFOg_pc_.d.cts +1027 -0
- package/dist/control-DaXBm-52.d.ts +1027 -0
- package/dist/gridCustomizer-C0V9FAE_.d.ts +569 -0
- package/dist/gridCustomizer-mJO-kmQ4.d.cts +569 -0
- package/dist/hooks.cjs +85 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +24 -0
- package/dist/hooks.d.ts +24 -0
- package/dist/hooks.mjs +60 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/icons.cjs +202 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.d.cts +130 -0
- package/dist/icons.d.ts +130 -0
- package/dist/icons.mjs +165 -0
- package/dist/icons.mjs.map +1 -0
- package/dist/index.cjs +6509 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +410 -0
- package/dist/index.d.ts +410 -0
- package/dist/index.mjs +6490 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runtime-capabilities-BdGDdu0d.d.cts +119 -0
- package/dist/runtime-capabilities-Brfc7loJ.d.ts +119 -0
- package/dist/theme-BfeZIxmZ.d.cts +74 -0
- package/dist/theme-BfeZIxmZ.d.ts +74 -0
- package/dist/theme.cjs +215 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +32 -0
- package/dist/theme.d.ts +32 -0
- package/dist/theme.mjs +186 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/types.cjs +976 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +813 -0
- package/dist/types.d.ts +813 -0
- package/dist/types.mjs +902 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils.cjs +250 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +99 -0
- package/dist/utils.d.ts +99 -0
- package/dist/utils.mjs +220 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/v8.cjs +4622 -0
- package/dist/v8.cjs.map +1 -0
- package/dist/v8.d.cts +730 -0
- package/dist/v8.d.ts +730 -0
- package/dist/v8.mjs +4622 -0
- package/dist/v8.mjs.map +1 -0
- package/dist/v9.cjs +19 -0
- package/dist/v9.cjs.map +1 -0
- package/dist/v9.d.cts +2 -0
- package/dist/v9.d.ts +2 -0
- package/dist/v9.mjs +1 -0
- package/dist/v9.mjs.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types/control.ts","../src/types/form.ts","../src/types/theme.ts","../src/types/focusedView.ts","../src/types/businessRules.ts","../src/types/bpf.ts","../src/types/formSelector.ts","../src/types/gridCustomizer.ts"],"sourcesContent":["import type { CalloutTrigger } from './form';\nimport type { NavigationAction } from './navigation';\n\nexport type ControlType =\n | 'text'\n | 'textarea'\n | 'number'\n | 'decimal'\n | 'currency'\n | 'date'\n | 'datetime'\n | 'dropdown'\n | 'optionset'\n | 'checkbox'\n | 'toggle'\n | 'combobox'\n | 'choicegroup'\n | 'lookup'\n | 'phone'\n | 'email'\n | 'url'\n | 'subgrid'\n | 'timeline'\n | 'webresource'\n | 'slider'\n | 'rating'\n | 'spinbutton'\n | 'formlink'\n | 'persona'\n | 'spacer'\n | 'label'\n | 'button'\n // Chart controls (Phase 1b) — first real consumer of the per-feature\n // v9 gating mechanism. Most are dual v8/v9; funnel/scatter are v9-only.\n | 'barchart'\n | 'linechart'\n | 'areachart'\n | 'piechart'\n | 'donutchart'\n | 'horizontalbarchart'\n | 'gaugechart'\n | 'sparkline'\n | 'heatmapchart'\n | 'kpicard'\n | 'funnelchart'\n | 'scatterchart';\n\nexport type ControlCategory = 'popular' | 'input' | 'choice' | 'display' | 'grid' | 'layout' | 'action' | 'chart';\n\n// ============================================================================\n// Chart configuration (Phase 1b)\n// ============================================================================\n\n/** Aggregation function applied when multiple rows share a category. */\nexport type ChartAggregation = 'count' | 'sum' | 'avg' | 'min' | 'max';\n\n/** Color palette key for chart rendering. */\nexport type ChartPalette = 'default' | 'categorical' | 'sequential' | 'diverging';\n\n/**\n * Per-control configuration for chart-category controls. Lives on\n * `ControlDefinition.properties.chartConfig`. The renderer reads from\n * `dataSourceId` to look up the FetchXML data source on the parent form\n * (form.settings.dataSources). Sample data is used when dataSourceId is\n * undefined so the designer canvas shows a plausible visualization\n * before the user wires up data.\n */\nexport interface ChartConfig {\n /**\n * Id of a `FormDataSource` on the parent form. When undefined, the\n * chart renders with library sample data (designer + preview only —\n * runtime emitted code requires a real source).\n */\n dataSourceId?: string;\n /**\n * Attribute used as the x-axis / category. For pie/donut, this is\n * the slice label. Ignored for gauge/kpi (single-value controls).\n */\n categoryAttribute?: string;\n /** Attribute used as the y-axis / value. */\n valueAttribute?: string;\n /** Aggregation when multiple rows share a category. */\n aggregation?: ChartAggregation;\n /** Chart title displayed above the visualization. */\n title?: string;\n /** Color palette. */\n palette?: ChartPalette;\n /** Whether to show the legend (most chart types). */\n showLegend?: boolean;\n /** Gauge/KPI: target value for trend comparison. */\n target?: number;\n /** Gauge: min value of the range. */\n min?: number;\n /** Gauge: max value of the range. */\n max?: number;\n}\n\nexport type SubgridColumnDataType = 'text' | 'numeric' | 'yesno' | 'date';\n\nexport interface SubgridColumn {\n key: string;\n name: string;\n fieldName: string;\n minWidth: number;\n maxWidth?: number;\n isResizable?: boolean;\n dataType?: SubgridColumnDataType;\n isCustom?: boolean;\n}\n\nexport type SubgridGridType = 'standard' | 'readonly' | 'focused-view';\n\nexport interface ControlDataBinding {\n attributeLogicalName: string;\n attributeType: string;\n metadataDriven: boolean;\n customFetchXml?: string;\n lookupTargets?: string[];\n optionSetOptions?: Array<{ value: number; label: string; color?: string }>;\n viewId?: string;\n viewFetchXml?: string;\n /** If bound to a FetchXML data source instead of primary entity */\n dataSourceId?: string;\n /** Field from the data source (logical name or alias) */\n dataSourceField?: string;\n /**\n * Use the formatted/display value instead of raw value.\n * For option sets: \"Active\" instead of 0\n * For lookups: Display name instead of GUID\n * For dates: Localized date string\n * For money: Formatted currency string\n * Requires data source to have includeFormattedValues=true\n */\n useFormattedValue?: boolean;\n /** Lookup-specific: Default view ID for filtering lookup records */\n lookupViewId?: string;\n /** Lookup-specific: Custom FetchXML for filtering lookup records */\n lookupFetchXml?: string;\n /** Lookup-specific: Whether to use custom FetchXML filter instead of view */\n useLookupFetchXml?: boolean;\n /** Global option set options (for dropdown/optionset with global source) */\n globalOptionSetOptions?: Array<{ value: number; label: string; color?: string }>;\n /** Global option set name (for dropdown/optionset with global source) */\n globalOptionSetName?: string;\n}\n\nexport interface ControlDefinition {\n type: ControlType;\n label: string;\n name: string;\n required: boolean;\n readOnly: boolean;\n /** When true, the control is hidden by default on the form. Business rules can override visibility at runtime. */\n hidden?: boolean;\n hideLabel: boolean;\n /** Manual label color override. When set, takes precedence over auto-contrast. */\n labelColor?: string;\n /** Per-control label position override. When set, takes precedence over section-level setting. */\n labelPosition?: 'beside' | 'above';\n /** Per-control label width override (percentage). When set, takes precedence over section-level setting. */\n labelWidth?: number;\n /** When true, label wraps instead of truncating with ellipsis. Overrides section-level setting. */\n wrapLabel?: boolean;\n /** Manual value/input text color override. When set, overrides theme default. */\n valueColor?: string;\n properties: Record<string, unknown>;\n validation: ValidationRule[];\n responsive: ResponsiveSettings;\n dataBinding?: ControlDataBinding;\n /** References a callout in parent form's callouts array */\n calloutId?: string;\n /** Override the callout's default trigger for this control */\n calloutTrigger?: CalloutTrigger;\n}\n\nexport interface ValidationRule {\n type: 'required' | 'minLength' | 'maxLength' | 'min' | 'max' | 'pattern' | 'custom';\n value?: string | number;\n message: string;\n}\n\nexport interface ResponsiveSettings {\n hideOnPhone: boolean;\n hideOnTablet: boolean;\n}\n\nexport interface ControlRegistryEntry {\n type: ControlType;\n label: string;\n icon: string;\n category: ControlCategory;\n defaultProperties: Record<string, unknown>;\n description: string;\n /**\n * If set, this control type is an alias for another type.\n * When dragged from palette, it creates a control of the aliasFor type\n * with the specified defaultProperties.\n */\n aliasFor?: ControlType;\n /**\n * If true, this control type should not appear in the Type dropdown\n * in control properties (e.g., phone, email, url are aliases of text)\n */\n hideFromTypeDropdown?: boolean;\n /**\n * When true, this control type has no Fluent UI v8 equivalent and can\n * only render under v9 export targets. ExportDialog blocks v8-only\n * targets (Power Pages Web Resource, Canvas App PCF) when the project\n * uses any v9-required control. Symmetric counterpart to the existing\n * `V9_INCOMPATIBLE_CONTROL_TYPES` set (controls with no v9 equivalent).\n */\n requiresV9?: boolean;\n}\n\n// ============================================================================\n// Text Field Configuration Types\n// ============================================================================\n\n/** Text field format types (similar to Dynamics 365) */\nexport type TextFormat = 'text' | 'email' | 'url' | 'phone' | 'password';\n\n/** Text field configuration */\nexport interface TextFieldConfig {\n /** Text format type */\n textFormat?: TextFormat;\n /** Max character length */\n maxLength?: number;\n /** Placeholder text (custom override) */\n placeholder?: string;\n /** Whether to show placeholder text (default: true) */\n showPlaceholder?: boolean;\n /** Show action button (mailto, tel:, open link) */\n showActionButton?: boolean;\n /** For password: allow show/hide toggle */\n allowPasswordToggle?: boolean;\n}\n\n// ============================================================================\n// Dropdown / OptionSet Configuration Types\n// ============================================================================\n\n/** Mode for determining option source */\nexport type OptionSourceMode = 'attribute' | 'global' | 'custom';\n\n/** Custom option item with optional color and icon */\nexport interface CustomOptionItem {\n value: number;\n label: string;\n color?: string;\n icon?: string;\n imageSrc?: string;\n /** For ComboBox grouping: 'normal' (default), 'header', or 'divider' */\n itemType?: 'normal' | 'header' | 'divider';\n}\n\n/** Configuration for dropdown/optionset controls */\nexport interface DropdownConfig {\n /** Source mode for options */\n optionSourceMode: OptionSourceMode;\n /** Bound attribute name (for attribute mode) */\n boundAttributeName?: string;\n /** Global option set name (for global mode) */\n globalOptionSetName?: string;\n /** Custom options (for custom mode) */\n customOptions?: CustomOptionItem[];\n /** Enable multi-select (uses ComboBox) */\n multiSelect?: boolean;\n /** Show option colors as badges */\n showOptionColors?: boolean;\n}\n\n// ============================================================================\n// ComboBox Configuration Types\n// ============================================================================\n\n/** Configuration for combobox controls */\nexport interface ComboBoxConfig {\n /** Source mode for options */\n optionSourceMode: OptionSourceMode;\n /** Bound attribute name (for attribute mode) */\n boundAttributeName?: string;\n /** Global option set name (for global mode) */\n globalOptionSetName?: string;\n /** Custom options (for custom mode) */\n customOptions?: CustomOptionItem[];\n /** Enable multi-select */\n multiSelect?: boolean;\n /** Allow typing values not in the option list */\n allowFreeform?: boolean;\n /** Type-ahead auto-complete */\n autoComplete?: 'on' | 'off';\n /** Show option colors as badges */\n showOptionColors?: boolean;\n}\n\n// ============================================================================\n// Button Configuration Types\n// ============================================================================\n\nexport type ButtonVariant =\n | 'default' | 'primary' | 'compound' | 'icon'\n | 'action' | 'command' | 'split' | 'toggle';\n\n/** Variants that support the `primary` (blue) styling toggle */\nexport const PRIMARY_CAPABLE_VARIANTS: ButtonVariant[] = ['default', 'compound', 'split', 'toggle'];\n\nexport type ButtonSize = 'small' | 'medium' | 'large';\n\nexport type ButtonAction =\n | { type: 'none' }\n | { type: 'switchTab'; targetTabId: string }\n | { type: 'navigate'; target: NavigationAction }\n | { type: 'triggerBusinessRule'; ruleId: string }\n | { type: 'navigateUrl'; url: string; openInNewTab?: boolean }\n | { type: 'submitForm' }\n | { type: 'scrollToSection'; sectionId: string }\n | { type: 'custom'; actionId: string };\n\nexport interface ButtonMenuItem {\n id: string;\n text: string;\n iconName?: string;\n action: ButtonAction;\n}\n\nexport interface ButtonCommandBarItem {\n id: string;\n text: string;\n iconName?: string;\n iconOnly?: boolean;\n action: ButtonAction;\n subMenuItems?: ButtonMenuItem[];\n}\n\nexport interface ButtonConfig {\n buttonText: string;\n buttonVariant: ButtonVariant;\n buttonAction: ButtonAction;\n primary?: boolean;\n iconName?: string;\n iconPosition?: 'before' | 'after';\n size?: ButtonSize;\n fullWidth?: boolean;\n alignment?: 'left' | 'center' | 'right';\n disabled?: boolean;\n tooltip?: string;\n secondaryText?: string;\n iconOnly?: boolean;\n backgroundColor?: string;\n textColor?: string;\n iconColor?: string;\n menuItems?: ButtonMenuItem[];\n commandBarItems?: ButtonCommandBarItem[];\n commandBarIconOnly?: boolean;\n /** Position of the main button within the combined command bar list (default 0 = first) */\n commandBarMainPosition?: number;\n checkedText?: string;\n uncheckedText?: string;\n checkedIconName?: string;\n checkedBackgroundColor?: string;\n checkedTextColor?: string;\n checkedIconColor?: string;\n}\n\n// ============================================================================\n// Layout Types\n// ============================================================================\n\nexport type LayoutActionType =\n | 'tab-1col'\n | 'tab-2col'\n | 'tab-3col'\n | 'section-1col'\n | 'section-2col'\n | 'section-3col'\n | 'section-4col'\n | 'tabbed-section';\n\nexport interface LayoutActionEntry {\n type: LayoutActionType;\n label: string;\n icon: string;\n description: string;\n columns?: 1 | 2 | 3 | 4;\n targetStructure: 'tab' | 'section' | 'tabbed-section';\n disabled?: boolean;\n}\n","import { ControlDefinition, SubgridColumn } from './control';\nimport { FormThemeSettings, BackgroundConfig } from './theme';\nimport type { NavigationAction, FormParameter } from './navigation';\n\nexport type FormType = 'main' | 'dialog' | 'panel' | 'callout';\n\n/**\n * Layout mode for a form. Tabbed forms use the classic tab/section/row/cell\n * flow; grid forms place sections as tiles on a CSS Grid (dashboard-style).\n *\n * Defaulting absent values to `'tabbed'` at read time preserves backwards\n * compatibility with v12 projects (see StorageService migration v12 → v13).\n */\nexport type FormLayoutMode = 'tabbed' | 'grid';\n\n/**\n * Grid container configuration used when `FormDefinition.layoutMode === 'grid'`.\n * Sections position themselves with `FormSection.gridPosition`; sections lacking\n * a position get auto-placed by the renderer (top-to-bottom, column-first).\n */\nexport interface FormGridLayout {\n /** Total columns in the grid (e.g. 12 for a responsive 12-column grid). */\n columns: number;\n /** Auto-row height in pixels. Sections span H rows. */\n rowHeight: number;\n /** Gap between grid cells in pixels (applies to both axes). */\n gap: number;\n /** Optional responsive column overrides — designer hint only in v1. */\n breakpoints?: { sm?: number; md?: number; lg?: number };\n}\n\n/** Default grid layout applied when a form is created in grid mode. */\nexport const DEFAULT_FORM_GRID_LAYOUT: FormGridLayout = {\n columns: 12,\n rowHeight: 80,\n gap: 12,\n};\n\n// ============================================================================\n// Dialog Action Buttons\n// ============================================================================\n\n/** Button appearance style */\nexport type DialogButtonAppearance = 'primary' | 'default' | 'subtle';\n\n/** Button position in dialog footer */\nexport type DialogButtonPosition = 'left' | 'right';\n\n/**\n * Action to perform when a dialog button is clicked.\n * Supports close, submit, navigate, and custom actions.\n */\nexport type DialogButtonAction =\n | { type: 'close' } // Close dialog without saving\n | { type: 'submit' } // Submit form and close\n | { type: 'navigate'; target: NavigationAction } // Navigate to another form/URL\n | { type: 'custom'; actionId: string }; // Custom JS action identifier\n\n/**\n * Configuration for a dialog action button.\n * Used in dialog footers for confirm/cancel/custom actions.\n */\nexport interface DialogActionButton {\n /** Unique identifier */\n id: string;\n /** Button label text */\n label: string;\n /** Optional Fluent UI icon name */\n icon?: string;\n /** Button appearance style */\n appearance: DialogButtonAppearance;\n /** Position in dialog footer (left or right side) */\n position: DialogButtonPosition;\n /** Sort order within position group (lower = first) */\n order: number;\n /** Whether button is disabled */\n disabled?: boolean;\n /** Whether button is hidden */\n hidden?: boolean;\n /** Action when button is clicked */\n action: DialogButtonAction;\n}\n\nexport type CommandBarItemPosition = 'primary' | 'overflow' | 'far';\n\n/**\n * Where a command bar item is shown. Only meaningful for grid/subgrid\n * command bars (which also expose a row right-click menu); form-level\n * command bars treat any value as `commandBar`. Defaults to `both` at\n * read time so legacy items keep their current behavior.\n */\nexport type CommandBarItemVisibility = 'commandBar' | 'contextMenu' | 'both';\n\n/**\n * Default Dynamics action behaviors that a command bar item can be wired to.\n * - `custom` no default behavior (legacy items default here)\n * - `navigate` opens another form via `navigationAction`\n * - `new` opens the create form for the grid's entity\n * - `addExisting` opens the lookup dialog to associate an existing record\n * (only valid on related/nested grids)\n * - `edit` opens the edit form for the selected row; multi-select\n * falls through to the OOB bulk edit form\n * - `delete` deletes selected rows via `Xrm.WebApi.deleteRecord`\n * - `activate` sets statecode to active for selected rows\n * - `deactivate` sets statecode to inactive for selected rows\n * - `refresh` re-fetches the grid (always whole-grid)\n * - `export` exports the current view (always whole-grid)\n * - `bulkEdit` explicitly invokes `Xrm.Navigation.openBulkEditForm`\n */\nexport type CommandBarItemActionType =\n | 'custom'\n | 'navigate'\n | 'new'\n | 'addExisting'\n | 'edit'\n | 'delete'\n | 'activate'\n | 'deactivate'\n | 'refresh'\n | 'export'\n | 'bulkEdit';\n\n/**\n * Whether an action operates on selected rows or the whole grid.\n * - `auto` derived from `actionType`. Resolution:\n * refresh/export/new/addExisting → grid;\n * edit/delete/activate/deactivate/bulkEdit → selection;\n * navigate/custom → grid (caller decides).\n * - `selection` requires at least one selected row\n * - `grid` operates on the whole grid\n */\nexport type CommandBarItemActionScope = 'auto' | 'selection' | 'grid';\n\nexport interface CommandBarItem {\n id: string;\n text: string;\n iconName: string;\n position: CommandBarItemPosition;\n iconOnly?: boolean;\n /**\n * Optional visibility override (grids only). Defaults to `both` —\n * the item appears in both the toolbar and the row right-click menu.\n */\n visibility?: CommandBarItemVisibility;\n /** Navigation action when clicked (opens another form) */\n navigationAction?: NavigationAction;\n /**\n * Default Dynamics action behavior. Defaults to `custom` for legacy items\n * (no wired behavior). When set to `navigate`, `navigationAction` is used.\n */\n actionType?: CommandBarItemActionType;\n /** Whether the action operates on selected rows or the whole grid. Defaults to `auto`. */\n actionScope?: CommandBarItemActionScope;\n /**\n * Hide this item until at least N rows are selected. `0` (or undefined)\n * means always show. Most useful for `delete`, `edit`, `activate`,\n * `deactivate`, and `bulkEdit` — defaults are derived from `actionType`\n * when this is unset.\n */\n minSelectionCount?: number;\n}\n\n/** Number of header field slots (0-4) */\nexport type HeaderSlotCount = 0 | 1 | 2 | 3 | 4;\n\n/** Header display modes */\nexport type HeaderDisplayMode = 'inline' | 'flyout';\n\n/** Persona size options for header */\nexport type HeaderPersonaSize = 'size32' | 'size48' | 'size72';\n\n/** Preset colors for persona avatar */\nexport type HeaderPersonaColor =\n | 'lightBlue'\n | 'blue'\n | 'darkBlue'\n | 'teal'\n | 'lightGreen'\n | 'green'\n | 'darkGreen'\n | 'lightPink'\n | 'pink'\n | 'magenta'\n | 'purple'\n | 'orange'\n | 'red'\n | 'darkRed'\n | 'violet'\n | 'gold'\n | 'burgundy'\n | 'warmGray'\n | 'coolGray'\n | 'cyan'\n | 'rust'\n | 'custom';\n\n/** Source mode for header persona data */\nexport type HeaderPersonaSourceMode = 'static' | 'dataverse';\n\n/** Configuration for the header persona/avatar */\nexport interface HeaderPersonaConfig {\n /** Whether to show the persona in the header */\n enabled: boolean;\n /** Source mode: 'static' for manual text/image, 'dataverse' for record-driven */\n sourceMode?: HeaderPersonaSourceMode;\n\n // ── Static mode properties ──\n /** Field logical name to use for the display name (e.g., \"fullname\", \"name\") */\n nameField?: string;\n /** Field logical name to use for the image URL (e.g., \"entityimage_url\") */\n imageField?: string;\n /** Secondary text to display (e.g., entity type). Leave empty to use entity display name */\n secondaryText?: string;\n /** Whether to show the secondary text line */\n showSecondaryText?: boolean;\n /** Size of the persona avatar */\n size?: HeaderPersonaSize;\n /** Preset color for the avatar background */\n color?: HeaderPersonaColor;\n /** Custom hex color (used when color is 'custom') */\n customColor?: string;\n /** Static image URL or base64 data URL for the avatar */\n imageUrl?: string;\n /** Whether to use an uploaded image instead of initials */\n useImage?: boolean;\n\n // ── Dataverse record mode properties ──\n /** Dataverse entity logical name (e.g., 'systemuser', 'contact', 'account') */\n dvRecordEntityName?: string;\n /** Selected record ID */\n dvRecordId?: string;\n /** Selected record primary name */\n dvRecordName?: string;\n /** Selected record secondary text (jobtitle, description, etc.) */\n dvRecordSecondaryText?: string;\n /** Base64 data URL of the record's entity image */\n dvRecordImageDataUrl?: string;\n /** Whether the record is disabled (systemuser only, for presence derivation) */\n dvRecordIsDisabled?: boolean;\n /** Show presence indicator */\n showPresence?: boolean;\n /** Presence status override */\n presence?: string;\n}\n\nexport interface FormHeader {\n /** Display title shown in header; falls back to form.name when empty */\n title?: string;\n /** Subtitle shown below title; falls back to form type label when empty */\n subtitle?: string;\n /** Manual title text color override. Overrides auto-contrast when set. */\n titleColor?: string;\n /** Manual subtitle text color override. Overrides auto-contrast when set. */\n subtitleColor?: string;\n /** Number of field slots to display (0-4), defaults to 4 */\n slotCount?: HeaderSlotCount;\n /** Persona configuration for showing an avatar with name */\n persona?: HeaderPersonaConfig;\n cells: FormCell[];\n}\n\nexport interface FormDefinition {\n id: string;\n name: string;\n type: FormType;\n /**\n * Layout mode. `'tabbed'` (default) uses the classic tabs/sections flow.\n * `'grid'` places sections as tiles on a CSS Grid (dashboard-style). Only\n * meaningful when `type === 'main'` in v1; dialog/panel/callout ignore it.\n * Absent on v12-and-earlier projects → treated as `'tabbed'`.\n */\n layoutMode?: FormLayoutMode;\n /**\n * Grid container configuration. Only read when `layoutMode === 'grid'`.\n * Undefined means use `DEFAULT_FORM_GRID_LAYOUT`.\n */\n gridLayout?: FormGridLayout;\n settings: FormSettings;\n /** Header fields for main forms (up to 4 key fields) */\n header?: FormHeader;\n /** Command bar items for main forms */\n commandBar?: CommandBarItem[];\n /** Tabs for main forms */\n tabs: FormTab[];\n /** Flat sections for dialog/panel/callout */\n sections: FormSection[];\n\n /** Display order within type category (for sidebar organization) */\n order?: number;\n /** Description for documentation */\n description?: string;\n /** Parameters this form accepts when opened from another form */\n inputParameters?: FormParameter[];\n /** ID of the template this form was created from (for template editing workflow) */\n sourceTemplateId?: string;\n\n /** Embedded callout definitions owned by this form */\n callouts?: CalloutDefinition[];\n /** Callout-to-target attachments (maps callouts to controls/sections/etc.) */\n calloutAttachments?: CalloutAttachment[];\n}\n\n/** Aggregate function types supported in FetchXML */\nexport type FetchXmlAggregateFunction = 'avg' | 'count' | 'countcolumn' | 'max' | 'min' | 'sum';\n\n/** Attribute parsed from FetchXML */\nexport interface FetchXmlAttribute {\n /** The logical name of the attribute, e.g., \"fullname\" */\n logicalName: string;\n /** Optional alias if specified in FetchXML */\n alias?: string;\n /** If from a link-entity, the alias of that link-entity */\n linkedEntityAlias?: string;\n /** Whether to use the formatted value (e.g., \"Active\" instead of 0 for option sets) */\n useFormattedValue?: boolean;\n /** Aggregate function if this is an aggregate attribute (avg, count, countcolumn, max, min, sum) */\n aggregateFunction?: FetchXmlAggregateFunction;\n}\n\n/**\n * A FetchXML-based data source.\n *\n * The FIRST entry in `form.settings.dataSources` is the form's primary source and carries\n * the primary-entity metadata fields (entitySetName, primaryIdAttribute, etc.) needed for\n * runtime record fetching and PCF manifest generation. Secondary entries (index >= 1) are\n * related-record queries and may leave the primary-only fields undefined.\n */\nexport interface FormDataSource {\n /** Unique identifier for this data source */\n id: string;\n /** User-friendly name, e.g., \"Related Contacts\" */\n name: string;\n /** The raw FetchXML query */\n fetchXml: string;\n /** Entity name parsed from <entity name=\"...\"> */\n entityName: string;\n /** Attributes parsed from <attribute name=\"...\"/> */\n attributes: FetchXmlAttribute[];\n /** Placeholder tokens like {*accountid*} found in the FetchXML */\n placeholders: string[];\n /**\n * Include formatted values in API response.\n * When true, adds `Prefer: odata.include-annotations=\"OData.Community.Display.V1.FormattedValue\"`\n * header to the request. This provides display names for option sets, lookups, dates, etc.\n */\n includeFormattedValues?: boolean;\n /** True if fetch element has aggregate='true' - returns computed values instead of records */\n isAggregate?: boolean;\n\n // --- Primary-entity metadata (populated only on dataSources[0]) ---\n\n /** OData entity set name, e.g., \"accounts\". Required on the primary source for record fetch. */\n entitySetName?: string;\n /** Primary key attribute, e.g., \"accountid\". Required on the primary source. */\n primaryIdAttribute?: string;\n /** Primary name attribute, e.g., \"name\". Used by headers and lookup displays. */\n primaryNameAttribute?: string;\n /** Localized entity display name, e.g., \"Account\". */\n displayName?: string;\n /** Localized plural entity display name, e.g., \"Accounts\". */\n displayNamePlural?: string;\n}\n\nexport interface FormSettings {\n width?: number;\n height?: number;\n position?: 'left' | 'right' | 'center';\n showCommandBar: boolean;\n /** Whether to show text labels on command bar items (default: true) */\n showCommandBarLabels?: boolean;\n showHeader: boolean;\n /** How header fields are displayed: 'inline' (always visible) or 'flyout' (expandable callout). Default: 'flyout' */\n headerDisplayMode?: HeaderDisplayMode;\n /**\n * FetchXML-based data sources. The first entry (index [0]) is the form's primary source,\n * carrying entity metadata (entitySetName, primaryIdAttribute, etc.). Secondary entries are\n * related-record queries. Use `getPrimaryDataSource()` from `utils/formDataSource` to read.\n */\n dataSources?: FormDataSource[];\n /** Theme configuration (preset and custom colors) */\n theme?: FormThemeSettings;\n\n /** Business rule IDs applied to this form (references project.businessRules) */\n businessRuleIds?: string[];\n\n /** Overrides for entity-scoped rules on this form */\n ruleOverrides?: { ruleId: string; enabled: boolean }[];\n\n /** Dialog action buttons (only applicable for dialog form type) */\n dialogButtons?: DialogActionButton[];\n\n /** Alignment of the dialog button footer: left, center, or right (default: 'right') */\n dialogButtonAlignment?: 'left' | 'center' | 'right';\n\n /** Optional subheader text shown below the dialog title */\n dialogSubheader?: string;\n\n /** When true, dialog sections render as Pivot tabs instead of stacking vertically */\n dialogUsePivots?: boolean;\n\n /** Column layout for dialog sections (like tab layout for main forms) */\n dialogLayout?: TabLayout;\n\n /** When true, dialog expands to full viewport width */\n dialogFullWidth?: boolean;\n\n /** Optional record counter shown in dialog header (e.g., \"3 of 12\") */\n dialogRecordCounter?: {\n enabled: boolean;\n current: number; // 1-based\n total: number;\n position?: 'left' | 'right'; // default: 'right'\n };\n\n /** Panel action buttons (footer buttons for panel form type) */\n panelButtons?: DialogActionButton[];\n /** Alignment of the panel button footer (default: 'right') */\n panelButtonAlignment?: 'left' | 'center' | 'right';\n /** Optional subheader text shown below the panel title */\n panelSubheader?: string;\n /** When true, panel sections render as Pivot tabs */\n panelUsePivots?: boolean;\n\n /** Column layout for panel sections */\n panelLayout?: TabLayout;\n /** Fluent UI PanelType for controlling panel size/position */\n panelType?: 'smallFixedFar' | 'smallFixedNear' | 'medium' | 'large' | 'largeFixed' | 'extraLarge' | 'custom' | 'customNear';\n /** Whether panel shows a close button (default: true) */\n panelHasCloseButton?: boolean;\n /** Whether clicking outside the panel dismisses it (default: false) */\n panelIsLightDismiss?: boolean;\n /** Whether footer buttons are pinned to the bottom (default: true) */\n panelIsFooterAtBottom?: boolean;\n /** Whether the panel is non-modal (allows interaction with content behind it, no overlay) */\n panelIsNonModal?: boolean;\n\n /** Business Process Flow ID this form is associated with */\n bpfId?: string;\n\n /** BPF stage ID this form is linked to (for stage-specific forms) */\n bpfStageId?: string;\n\n /** Whether to show the BPF progress indicator on this form */\n showBpfProgress?: boolean;\n\n /** BPF progress indicator alignment */\n bpfAlignment?: 'left' | 'center' | 'right';\n\n /** Whether the BPF progress indicator should span full width */\n bpfFullWidth?: boolean;\n\n // ─── Form Selector Settings ────────────────────────────────────────────────\n\n /** Whether this is the default form for the entity (only one per entity) */\n isDefaultForEntity?: boolean;\n\n /** Order in form selector dropdown (lower = first) */\n formSelectorOrder?: number;\n\n /**\n * Form group ID for manual grouping.\n * Forms with the same formGroupId are grouped together in the form selector,\n * independent of or in addition to entity binding.\n */\n formGroupId?: string;\n\n /**\n * Display name for the form group (used when this form defines a new group)\n */\n formGroupName?: string;\n\n /**\n * Optional color for the form group indicator (hex color, e.g., \"#0078d4\")\n * When set, the link icon in the sidebar uses this color instead of default blue.\n */\n formGroupColor?: string;\n\n}\n\nexport interface TabLayout {\n columns: 1 | 2 | 3;\n columnWidths: number[]; // percentage widths, length matches columns\n}\n\n// ============================================================================\n// Tab Types (Discriminated Union)\n// ============================================================================\n\n/** Base tab properties shared by all tab types */\ninterface FormTabBase {\n id: string;\n label: string;\n expanded: boolean;\n showLabel: boolean;\n}\n\n/** Main content tab with sections (existing behavior) */\nexport interface MainFormTab extends FormTabBase {\n tabType: 'main';\n layout: TabLayout;\n sections: FormSection[];\n}\n\n/** Related records tab configuration */\nexport interface RelatedTabConfig {\n /** Logical name of the related entity (e.g., \"contact\") */\n entityLogicalName: string;\n /** Display name of the related entity (e.g., \"Contacts\") */\n entityDisplayName: string;\n /** FetchXML query for retrieving related records */\n fetchXml: string;\n /** Attributes parsed from the FetchXML */\n attributes: FetchXmlAttribute[];\n /** Placeholder tokens found in the FetchXML (e.g., \"{*accountid*}\") */\n placeholders: string[];\n /** Grid display type */\n gridType: 'standard' | 'readonly';\n /** Column configuration for the grid (optional - auto-generated from attributes if not provided) */\n columns?: SubgridColumn[];\n /** Maximum rows to display (default: 5) */\n maxRows?: number;\n /** Whether to show the command bar (Add, Edit, Delete buttons) */\n showCommandBar?: boolean;\n}\n\n/** Related records tab */\nexport interface RelatedFormTab extends FormTabBase {\n tabType: 'related';\n relatedConfig: RelatedTabConfig;\n}\n\n// ============================================================================\n// Audit History Tab\n// ============================================================================\n\n/** Configuration for audit history tab */\nexport interface AuditHistoryConfig {\n /** Maximum number of audit records to display (default: 20) */\n maxRows?: number;\n /** Whether to show the user who made the change (default: true) */\n showChangedBy?: boolean;\n /** Whether to show detailed field changes (default: true) */\n showFieldChanges?: boolean;\n /** Sort order for audit records (default: 'newest') */\n sortOrder?: 'newest' | 'oldest';\n}\n\n/** Audit history tab */\nexport interface AuditFormTab extends FormTabBase {\n tabType: 'audit';\n auditConfig: AuditHistoryConfig;\n}\n\n/**\n * Union type for form tabs.\n * - Main tabs: Have sections and layout (existing behavior)\n * - Related tabs: Display related records via FetchXML\n * - Audit tabs: Display change history from the audit entity\n *\n * Note: For backward compatibility, tabs without tabType are treated as 'main'\n */\nexport type FormTab = MainFormTab | RelatedFormTab | AuditFormTab;\n\n// ============================================================================\n// Tab Type Guards\n// ============================================================================\n\n/**\n * Check if a tab is a main content tab (has sections).\n * Also handles backward compatibility for tabs without tabType property.\n */\nexport function isMainTab(tab: FormTab): tab is MainFormTab {\n return tab.tabType === 'main' || !('tabType' in tab) || tab.tabType === undefined;\n}\n\n/**\n * Check if a tab is a related records tab.\n */\nexport function isRelatedTab(tab: FormTab): tab is RelatedFormTab {\n return tab.tabType === 'related';\n}\n\n/**\n * Check if a tab is an audit history tab.\n */\nexport function isAuditTab(tab: FormTab): tab is AuditFormTab {\n return tab.tabType === 'audit';\n}\n\n/**\n * Position of a section within a grid-mode form. `x`/`y` are top-left cell\n * indices; `w`/`h` are spans in grid cells. Only read when the parent\n * `FormDefinition.layoutMode === 'grid'`. Tabbed mode ignores this field.\n */\nexport interface FormSectionGridPosition {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface FormSection {\n id: string;\n label: string;\n columns: 1 | 2 | 3 | 4;\n showLabel: boolean;\n collapsible: boolean;\n variant: 'card' | 'flat' | 'placeholder';\n tabColumn?: number; // 0-indexed column within parent tab layout\n /**\n * Grid placement for this section. Only honored when the parent form's\n * `layoutMode === 'grid'`. Absent → renderer auto-places the section.\n */\n gridPosition?: FormSectionGridPosition;\n rows: FormRow[];\n // Section sizing\n minHeight?: number; // Minimum height in pixels (optional)\n widthPercent?: number; // Width as percentage of column (1-100, optional)\n fullWidth?: boolean; // Span all columns when true (overrides tabColumn)\n /** When true, this section renders as a tab in a pivot group with adjacent pivot sections */\n isPivotTab?: boolean;\n /** Per-section background override (color, image, or gradient). When undefined, inherits from theme. */\n background?: BackgroundConfig;\n /** Manual text color override for section header label. Overrides auto-contrast when set. */\n textColor?: string;\n /** Label position: 'beside' (left of field, default) or 'above' (stacked on top) */\n labelPosition?: 'beside' | 'above';\n /** Label column width as a percentage (default 35). Only applies when labelPosition is 'beside'. */\n labelWidth?: number;\n /** Vertical gap between rows in pixels (default 4) */\n rowGap?: number;\n /** Horizontal gap between cells in pixels (default 8) */\n cellGap?: number;\n /** When true, labels wrap instead of truncating with ellipsis */\n wrapLabel?: boolean;\n /** When true, hidden fields collapse and remaining fields reflow to fill space */\n collapseHidden?: boolean;\n}\n\nexport interface FormRow {\n id: string;\n columns: 1 | 2 | 3 | 4;\n cells: FormCell[];\n}\n\nexport interface FormCell {\n id: string;\n control: ControlDefinition | null;\n colSpan: number;\n rowSpan: number;\n}\n\n// ============================================================================\n// Callout Definitions (Embedded Utilities)\n// ============================================================================\n\n/** Directional hint for callout positioning relative to target */\nexport type CalloutDirectionalHint =\n | 'topLeftEdge' | 'topCenter' | 'topRightEdge' | 'topAutoEdge'\n | 'bottomLeftEdge' | 'bottomCenter' | 'bottomRightEdge' | 'bottomAutoEdge'\n | 'leftTopEdge' | 'leftCenter' | 'leftBottomEdge'\n | 'rightTopEdge' | 'rightCenter' | 'rightBottomEdge';\n\n/** How the callout is triggered on the target element */\nexport type CalloutTrigger = 'click' | 'hover' | 'icon-click';\n\n/** Action button in callout footer */\nexport interface CalloutActionButton {\n id: string;\n label: string;\n icon?: string;\n appearance: 'primary' | 'default';\n action: 'dismiss' | 'submit';\n}\n\n/** A field binding for data-bound grid callouts */\nexport interface CalloutFieldBinding {\n id: string;\n /** Row field name to read the value from */\n fieldName: string;\n /** Display label shown in the callout */\n label: string;\n /** How to render the value */\n displayType: 'text' | 'badge' | 'currency' | 'date' | 'link' | 'icon-value';\n /** Optional icon to show next to the label */\n iconName?: string;\n /** Display order */\n order: number;\n}\n\n/**\n * A callout definition embedded within a parent form or grid.\n * Callouts are floating popups attached to controls/sections on a form,\n * or to grid columns for data-aware cell popups.\n * Static callouts use FormSection structure for content.\n * Data-bound callouts use fieldBindings resolved against row data.\n */\nexport interface CalloutDefinition {\n id: string;\n name: string;\n /** Callout width in pixels (default: 320) */\n width?: number;\n /** Position relative to target (default: bottomAutoEdge) */\n directionalHint?: CalloutDirectionalHint;\n /** Show the beak/arrow pointing to target (default: true) */\n isBeakVisible?: boolean;\n /** Beak width in pixels (default: 16) */\n beakWidth?: number;\n /** Gap between callout and target in pixels (default: 0) */\n gapSpace?: number;\n /** How the callout is triggered (default: click) */\n trigger?: CalloutTrigger;\n /** Whether clicking outside dismisses the callout (default: true) */\n dismissOnClickOutside?: boolean;\n /** Action buttons in callout footer */\n actionButtons?: CalloutActionButton[];\n /** Footer button alignment (default: right) */\n buttonAlignment?: 'left' | 'center' | 'right';\n /** When true, sections render as Pivot tabs instead of stacking vertically */\n usePivots?: boolean;\n /** Callout content (reuses existing section structure) — used when contentMode is 'static' */\n sections: FormSection[];\n /** Content mode: 'static' uses sections, 'data-bound' uses fieldBindings (default: 'static') */\n contentMode?: 'static' | 'data-bound';\n /** Field bindings for data-bound callouts — each maps a row field to a display slot */\n fieldBindings?: CalloutFieldBinding[];\n /** Tracks which factory preset was used for data-bound callouts */\n presetType?: 'lookup-detail' | 'status-detail' | 'custom';\n}\n\n/** Type of element a callout can be attached to */\nexport type CalloutTargetType = 'control' | 'section' | 'commandBarItem' | 'headerField';\n\n/** Describes a callout's attachment to a specific target element */\nexport interface CalloutAttachment {\n calloutId: string;\n targetType: CalloutTargetType;\n targetId: string;\n /** Override the callout's default trigger for this specific attachment */\n trigger?: CalloutTrigger;\n}\n","/**\n * Theme configuration for Form Builder\n * Provides Canvas-app-style theming with 9 preset themes\n */\n\nexport interface FormThemeColors {\n /** Main accent color for buttons, links, focus rings */\n primary: string;\n /** Darker variant of primary */\n primaryDark: string;\n /** Form header bar background */\n headerBackground: string;\n /** Section card background */\n sectionBackground: string;\n /** Canvas/page background */\n canvasBackground: string;\n /** Main text color */\n textPrimary: string;\n /** Muted/secondary text color */\n textSecondary: string;\n /** Section and component borders */\n border: string;\n /** Input field background */\n inputBackground: string;\n /** Input field borders */\n inputBorder: string;\n}\n\nexport interface FormTheme {\n id: string;\n name: string;\n colors: FormThemeColors;\n}\n\nexport interface GradientStop {\n color: string;\n position: number; // 0-100\n}\n\nexport type BackgroundConfig =\n | { type: 'color'; color: string }\n | { type: 'image'; src: string; size: 'cover' | 'contain' | 'auto'; position: string;\n repeat: 'no-repeat' | 'repeat' | 'repeat-x' | 'repeat-y'; opacity?: number }\n | { type: 'gradient'; direction: number; stops: GradientStop[] };\n\nexport interface FormThemeSettings {\n /** The preset theme ID (e.g., 'blue', 'coral') */\n presetId: string;\n /** Optional custom background color that overrides theme's canvasBackground */\n customBackgroundColor?: string;\n /** Enhanced canvas background (color, image, or gradient) */\n canvasBackground?: BackgroundConfig;\n /** Enhanced header background (color, image, or gradient). When undefined, uses theme's headerBackground. */\n headerBackground?: BackgroundConfig;\n}\n\n/**\n * 9 preset themes matching Canvas app styling\n */\nexport const THEME_PRESETS: FormTheme[] = [\n {\n id: 'soft-blue',\n name: 'Soft blue',\n colors: {\n primary: '#0078d4',\n primaryDark: '#005a9e',\n headerBackground: '#ffffff',\n sectionBackground: '#ffffff',\n canvasBackground: '#f3f2f1',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#edebe9',\n inputBackground: '#ffffff',\n inputBorder: '#8a8886',\n },\n },\n {\n id: 'blue',\n name: 'Blue',\n colors: {\n primary: '#0078d4',\n primaryDark: '#005a9e',\n headerBackground: '#0078d4',\n sectionBackground: '#e6f2ff',\n canvasBackground: '#e6f2ff',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#c7e0f4',\n inputBackground: '#ffffff',\n inputBorder: '#0078d4',\n },\n },\n {\n id: 'light',\n name: 'Light',\n colors: {\n primary: '#605e5c',\n primaryDark: '#3b3a39',\n headerBackground: '#f5f5f5',\n sectionBackground: '#ffffff',\n canvasBackground: '#fafafa',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#e1dfdd',\n inputBackground: '#ffffff',\n inputBorder: '#8a8886',\n },\n },\n {\n id: 'coral',\n name: 'Coral',\n colors: {\n primary: '#d83b01',\n primaryDark: '#a52a00',\n headerBackground: '#d83b01',\n sectionBackground: '#fff4f0',\n canvasBackground: '#fff4f0',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#f3d6cd',\n inputBackground: '#ffffff',\n inputBorder: '#d83b01',\n },\n },\n {\n id: 'red',\n name: 'Red',\n colors: {\n primary: '#a4262c',\n primaryDark: '#7e1e23',\n headerBackground: '#a4262c',\n sectionBackground: '#fdf3f4',\n canvasBackground: '#fdf3f4',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#f1d3d5',\n inputBackground: '#ffffff',\n inputBorder: '#a4262c',\n },\n },\n {\n id: 'steel',\n name: 'Steel',\n colors: {\n primary: '#004578',\n primaryDark: '#002d4e',\n headerBackground: '#004578',\n sectionBackground: '#f0f4f7',\n canvasBackground: '#e8eef2',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#c8d4dc',\n inputBackground: '#ffffff',\n inputBorder: '#004578',\n },\n },\n {\n id: 'dune',\n name: 'Dune',\n colors: {\n primary: '#7a6855',\n primaryDark: '#5c4f3f',\n headerBackground: '#7a6855',\n sectionBackground: '#f5f3f0',\n canvasBackground: '#e8e4df',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#d6d0c8',\n inputBackground: '#ffffff',\n inputBorder: '#7a6855',\n },\n },\n {\n id: 'lavender',\n name: 'Lavender',\n colors: {\n primary: '#5c2d91',\n primaryDark: '#472270',\n headerBackground: '#5c2d91',\n sectionBackground: '#f9f5ff',\n canvasBackground: '#f5f0fa',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#e1d4f0',\n inputBackground: '#ffffff',\n inputBorder: '#5c2d91',\n },\n },\n {\n id: 'brown',\n name: 'Brown',\n colors: {\n primary: '#6d4c41',\n primaryDark: '#4e362e',\n headerBackground: '#6d4c41',\n sectionBackground: '#f5f0ed',\n canvasBackground: '#ebe5e1',\n textPrimary: '#323130',\n textSecondary: '#605e5c',\n border: '#d4ccc7',\n inputBackground: '#ffffff',\n inputBorder: '#6d4c41',\n },\n },\n];\n\n/** Default theme (Soft blue) */\nexport const DEFAULT_THEME = THEME_PRESETS[0];\n\n/** Standard color palette for the color picker (like Canvas app) */\nexport const STANDARD_COLORS = [\n '#ffffff', '#000000', '#1a1a1a', '#333333', '#4d4d4d',\n '#666666', '#808080', '#999999', '#b3b3b3', '#cccccc',\n '#a4262c', '#d83b01', '#ff8c00', '#ffb900', '#fff100',\n '#bad80a', '#107c10', '#00b294', '#0078d4', '#5c2d91',\n];\n\n/** Get theme by ID, falls back to default */\nexport function getThemeById(id: string): FormTheme {\n return THEME_PRESETS.find((t) => t.id === id) ?? DEFAULT_THEME;\n}\n\n/** Get theme colors for a form, applying custom background if set */\nexport function getResolvedThemeColors(\n themeSettings?: FormThemeSettings\n): FormThemeColors {\n const theme = getThemeById(themeSettings?.presetId ?? 'soft-blue');\n\n if (themeSettings?.customBackgroundColor) {\n return {\n ...theme.colors,\n canvasBackground: themeSettings.customBackgroundColor,\n };\n }\n\n return theme.colors;\n}\n","/**\n * Focused View configuration types for Work Item Appearance settings.\n * Supports up to 4 rows, each with primary + optional secondary fields and icons.\n */\n\n/**\n * A field reference in the focused view configuration.\n */\nexport interface FocusedViewField {\n /** Logical field name (e.g., \"emailaddress1\") */\n fieldName: string;\n /** Display label for the field */\n label: string;\n /** Alias for linked entity fields (e.g., \"primarycontact\") */\n linkedEntityAlias?: string;\n /** Attribute type for icon display (e.g., \"String\", \"DateTime\") */\n attributeType?: string;\n}\n\n/**\n * A row in the focused view configuration.\n * Each row can have 1-2 fields and an optional icon.\n */\nexport interface FocusedViewRow {\n /** Unique identifier for the row */\n id: string;\n /** Primary field (required) */\n primaryField: FocusedViewField;\n /** Secondary field (optional, shown on the right) */\n secondaryField?: FocusedViewField;\n /** Icon name to display (e.g., \"FavoriteStarFill\") */\n iconName?: string;\n}\n\n/**\n * Complete focused view configuration matching Dynamics 365 Work Item Appearance.\n */\nexport interface FocusedViewConfig {\n /** 1-4 rows of field configuration */\n rows: FocusedViewRow[];\n /** Show \"Up next activity\" section at bottom */\n showUpNextActivity?: boolean;\n}\n\n/**\n * Icon options for focused view rows.\n */\nexport const FOCUSED_VIEW_ICON_OPTIONS = [\n { iconName: '', label: 'None' },\n { iconName: 'FavoriteStarFill', label: 'Star' },\n { iconName: 'NumberSymbol', label: 'Number' },\n { iconName: 'Clock', label: 'Time' },\n { iconName: 'Phone', label: 'Phone' },\n { iconName: 'Mail', label: 'Email' },\n { iconName: 'Calendar', label: 'Calendar' },\n { iconName: 'Contact', label: 'Contact' },\n { iconName: 'Money', label: 'Currency' },\n { iconName: 'StatusCircleCheckmark', label: 'Status' },\n] as const;\n\n/**\n * Maximum number of rows allowed in focused view configuration.\n */\nexport const MAX_FOCUSED_VIEW_ROWS = 4;\n\n/**\n * Legacy summary field format for backward compatibility.\n */\nexport interface LegacySummaryField {\n label: string;\n fieldName: string;\n}\n\n/**\n * Migrates legacy summary fields format to new FocusedViewConfig.\n * @param primaryNameField - Legacy primary name field\n * @param summaryFields - Legacy summary fields array\n * @returns New FocusedViewConfig\n */\nexport function migrateLegacyConfig(\n primaryNameField: string,\n summaryFields: LegacySummaryField[]\n): FocusedViewConfig {\n const rows: FocusedViewRow[] = [];\n\n // Row 1: Primary name field\n if (primaryNameField) {\n rows.push({\n id: 'row-1',\n primaryField: {\n fieldName: primaryNameField,\n label: toDisplayName(primaryNameField),\n },\n });\n }\n\n // Convert summary fields to additional rows\n summaryFields.slice(0, MAX_FOCUSED_VIEW_ROWS - 1).forEach((sf, index) => {\n rows.push({\n id: `row-${index + 2}`,\n primaryField: {\n fieldName: sf.fieldName,\n label: sf.label,\n },\n });\n });\n\n return { rows };\n}\n\n/**\n * Converts a field name to a display name.\n * @param fieldName - Logical field name\n * @returns Formatted display name\n */\nfunction toDisplayName(fieldName: string): string {\n return fieldName\n .replace(/([A-Z])/g, ' $1')\n .replace(/_/g, ' ')\n .replace(/^\\s+/, '')\n .split(' ')\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n .trim();\n}\n\n/**\n * Creates a default focused view configuration.\n * @param primaryNameField - Optional primary name field\n * @returns Default FocusedViewConfig\n */\nexport function createDefaultFocusedViewConfig(\n primaryNameField = 'name'\n): FocusedViewConfig {\n return {\n rows: [\n {\n id: 'row-1',\n primaryField: {\n fieldName: primaryNameField,\n label: toDisplayName(primaryNameField),\n },\n },\n ],\n showUpNextActivity: false,\n };\n}\n\n/**\n * Validates a focused view configuration.\n * @param config - Configuration to validate\n * @returns True if valid\n */\nexport function isValidFocusedViewConfig(config: unknown): config is FocusedViewConfig {\n if (!config || typeof config !== 'object') return false;\n const c = config as FocusedViewConfig;\n\n if (!Array.isArray(c.rows)) return false;\n if (c.rows.length === 0 || c.rows.length > MAX_FOCUSED_VIEW_ROWS) return false;\n\n return c.rows.every(row =>\n row.id &&\n row.primaryField &&\n typeof row.primaryField.fieldName === 'string' &&\n typeof row.primaryField.label === 'string'\n );\n}\n\n/**\n * Grid column interface (subset of SubgridColumn for typing).\n */\nexport interface GridColumnInput {\n key: string;\n name: string;\n fieldName: string;\n}\n\n/**\n * Creates a focused view configuration from grid columns.\n * Takes the first 4 columns and converts them to focused view rows.\n * @param columns - Array of grid columns\n * @returns FocusedViewConfig with rows based on columns\n */\nexport function createConfigFromColumns(\n columns: GridColumnInput[]\n): FocusedViewConfig {\n if (!columns || columns.length === 0) {\n return createDefaultFocusedViewConfig();\n }\n\n const rows: FocusedViewRow[] = columns\n .slice(0, MAX_FOCUSED_VIEW_ROWS)\n .map((col, index) => ({\n id: `row-${index + 1}`,\n primaryField: {\n fieldName: col.fieldName,\n label: col.name || toDisplayName(col.fieldName),\n },\n }));\n\n return {\n rows,\n showUpNextActivity: false,\n };\n}\n","/**\n * Business Rules Engine — Type Definitions\n *\n * Defines the complete type system for visual business rules:\n * rule definitions, conditions, actions, variables, data sources,\n * execution context, workflows, highlights, and canvas layout.\n *\n * Design principles:\n * - FetchXML-style operator names (eq, gt, like) for Dynamics consultant familiarity\n * - Discriminated unions with `kind` for exhaustive pattern matching on action configs\n * - Record<string, unknown> for JSON-serializable state (not Map/Set)\n * - Reuses existing FormDataSource for FetchXML data sources\n */\n\nimport type { FormDataSource } from './form';\n\n// ─── Rule Definition ────────────────────────────────────────────────────────\n\n/** A complete business rule — stored at entity level, enforced at form level */\nexport interface BusinessRuleDefinition {\n id: string;\n name: string;\n description?: string;\n\n /** Entity logical name (e.g., \"stn_order\", \"account\") */\n entityLogicalName: string;\n\n /** Where the rule runs */\n scope: 'entity' | 'form' | 'global';\n\n /** For form-scoped rules */\n targetFormId?: string;\n\n /** When the rule evaluates */\n trigger: RuleTrigger;\n\n /** Execution priority (lower = first) */\n priority: number;\n\n /** Active/inactive toggle */\n enabled: boolean;\n\n /** Rule-specific data sources (beyond form-level shared ones) */\n dataSources: RuleDataSource[];\n\n /** Variables computed from data sources, fields, or expressions */\n variables: RuleVariable[];\n\n /** The IF condition tree */\n conditionTree: RuleConditionGroup;\n\n /** Actions when condition is TRUE */\n thenActions: RuleAction[];\n\n /** Actions when condition is FALSE */\n elseActions: RuleAction[];\n\n /** Visual layout for SVG canvas (Phase 2b) */\n canvas?: RuleCanvasLayout;\n\n /** Metadata */\n createdAt: string;\n updatedAt: string;\n createdBy?: string;\n}\n\n// ─── Triggers ───────────────────────────────────────────────────────────────\n\nexport interface RuleTrigger {\n type: 'onLoad' | 'onChange' | 'onSave' | 'manual';\n /** For onChange: which fields trigger re-evaluation */\n watchFields?: string[];\n /** For manual: command/button id */\n commandId?: string;\n}\n\n/** Event that triggers rule evaluation */\nexport interface TriggerEvent {\n type: RuleTrigger['type'];\n /** For onChange: which field changed */\n fieldName?: string;\n}\n\n// ─── Conditions ─────────────────────────────────────────────────────────────\n\n/** A group of conditions joined by AND/OR */\nexport interface RuleConditionGroup {\n id: string;\n logic: 'and' | 'or';\n conditions: (RuleCondition | RuleConditionGroup)[];\n}\n\n/** A single condition that evaluates to true/false */\nexport interface RuleCondition {\n id: string;\n\n /** Left-hand side source */\n source: 'field' | 'data-source' | 'calculated' | 'form-state';\n\n /** Field logical name (when source = 'field' or 'form-state') */\n fieldName?: string;\n\n /** Comparison operator — matches FetchXML operator names */\n operator: ConditionOperator;\n\n /** Right-hand side literal value */\n value?: ConditionValue;\n\n /** Right-hand side is another field */\n compareToField?: string;\n\n /** Data source reference (when source = 'data-source') */\n dataSourceId?: string;\n dataSourceField?: string;\n\n /** Expression string (when source = 'calculated') */\n expression?: string;\n}\n\n/** Type guard: is this a condition group (has `logic`) or a leaf condition? */\nexport function isConditionGroup(\n c: RuleCondition | RuleConditionGroup\n): c is RuleConditionGroup {\n return 'logic' in c;\n}\n\n/** Operators match FetchXML names for consultant familiarity */\nexport type ConditionOperator =\n // Equality\n | 'eq' | 'ne'\n // Comparison\n | 'gt' | 'ge' | 'lt' | 'le'\n // String matching\n | 'like' | 'not-like' | 'begins-with' | 'ends-with'\n // Null checks\n | 'null' | 'not-null'\n // Collection\n | 'between' | 'in' | 'not-in'\n // Contains (for multi-select optionsets and text)\n | 'contain-values' | 'not-contain-values'\n // Dirty tracking (ProForma pattern)\n | 'changed' | 'not-changed';\n\nexport type ConditionValue =\n | string | number | boolean | null\n | string[] // for 'in' / 'not-in' / 'contain-values'\n | [unknown, unknown]; // for 'between'\n\n// ─── Actions ────────────────────────────────────────────────────────────────\n\nexport interface RuleAction {\n id: string;\n type: RuleActionType;\n /** Primary target — always synced to targets[0] */\n target: ActionTarget;\n /** Multi-target list (when action applies to multiple fields/sections/tabs) */\n targets?: ActionTarget[];\n /** Optional label for the target group (e.g., \"Meter Fields\") */\n groupName?: string;\n config: ActionConfig;\n /** Execution order within the branch */\n order: number;\n}\n\n/** Returns targets array, falling back to [action.target] for legacy data */\nexport function getActionTargets(action: RuleAction): ActionTarget[] {\n return action.targets?.length ? action.targets : [action.target];\n}\n\nexport type RuleActionType =\n // Field-level\n | 'setFieldValue'\n | 'setDefaultValue'\n | 'setVisibility'\n | 'setLockState'\n | 'setRequired'\n | 'showErrorMessage'\n | 'showRecommendation'\n | 'setHighlight'\n\n // Section/Tab-level\n | 'setSectionVisibility'\n | 'setTabVisibility'\n | 'setSectionLockState'\n\n // Form-level\n | 'setFormReadOnly'\n | 'setFormNotification'\n | 'preventSave'\n\n // Command bar\n | 'setButtonVisibility'\n | 'setButtonEnabled'\n\n // CRUD blocking\n | 'blockCreate'\n | 'blockUpdate'\n | 'blockDelete'\n\n // API (Phase 3)\n | 'callCustomAction'\n | 'callCustomApi'\n\n // Workflow (Phase 3)\n | 'triggerWorkflow'\n\n // Navigation\n | 'navigateToForm'\n | 'openDialog';\n\nexport interface ActionTarget {\n type: 'field' | 'section' | 'tab' | 'form' | 'button' | 'control';\n /** Control name, section id, tab id, or form id */\n id: string;\n displayName?: string;\n}\n\n// ─── Action Configs (Discriminated Union) ───────────────────────────────────\n// Every RuleActionType has a corresponding config with a `kind` discriminator.\n\nexport type ActionConfig =\n | SetFieldValueConfig\n | SetDefaultValueConfig\n | SetVisibilityConfig\n | SetLockConfig\n | SetRequiredConfig\n | ShowMessageConfig\n | SetHighlightConfig\n | SetFormReadOnlyConfig\n | SetFormNotificationConfig\n | PreventSaveConfig\n | CrudBlockConfig\n | CallApiConfig\n | TriggerWorkflowConfig\n | NavigateToFormConfig\n | OpenDialogConfig;\n\nexport interface SetFieldValueConfig {\n kind: 'setFieldValue';\n source: 'constant' | 'variable' | 'expression' | 'field';\n value: unknown;\n}\n\nexport interface SetDefaultValueConfig {\n kind: 'setDefaultValue';\n source: 'constant' | 'variable' | 'expression' | 'field';\n value: unknown;\n /** Only apply if field is currently empty */\n onlyIfEmpty: boolean;\n}\n\nexport interface SetVisibilityConfig {\n kind: 'setVisibility';\n visible: boolean;\n}\n\nexport interface SetLockConfig {\n kind: 'setLock';\n locked: boolean;\n}\n\nexport interface SetRequiredConfig {\n kind: 'setRequired';\n level: 'required' | 'recommended' | 'none';\n}\n\nexport interface ShowMessageConfig {\n kind: 'showMessage';\n messageType: 'error' | 'warning' | 'info' | 'recommendation';\n message: string;\n /** Supports {{variableName}} interpolation */\n interpolateVariables: boolean;\n}\n\nexport interface SetHighlightConfig {\n kind: 'setHighlight';\n /** Hex color (e.g., \"#63d868\") */\n color: string;\n /** Higher priority wins when multiple rules match */\n priority: number;\n}\n\nexport interface SetFormReadOnlyConfig {\n kind: 'setFormReadOnly';\n readOnly: boolean;\n}\n\nexport interface SetFormNotificationConfig {\n kind: 'setFormNotification';\n messageType: 'error' | 'warning' | 'info';\n message: string;\n interpolateVariables: boolean;\n}\n\nexport interface PreventSaveConfig {\n kind: 'preventSave';\n message: string;\n}\n\nexport interface CrudBlockConfig {\n kind: 'crudBlock';\n operation: 'create' | 'update' | 'delete';\n message?: string;\n}\n\nexport interface CallApiConfig {\n kind: 'callApi';\n dataSourceId: string;\n resultVariableId?: string;\n onSuccessActions?: RuleAction[];\n onFailureActions?: RuleAction[];\n}\n\nexport interface TriggerWorkflowConfig {\n kind: 'triggerWorkflow';\n workflowId: string;\n}\n\nexport interface NavigateToFormConfig {\n kind: 'navigateToForm';\n formId: string;\n /** Pass field values to the target form */\n parameterMapping?: Record<string, string>;\n}\n\nexport interface OpenDialogConfig {\n kind: 'openDialog';\n formId: string;\n width?: number;\n height?: number;\n}\n\n// ─── Variables ──────────────────────────────────────────────────────────────\n\nexport interface RuleVariable {\n id: string;\n name: string;\n displayName: string;\n type: 'string' | 'number' | 'boolean' | 'datetime' | 'lookup' | 'optionset';\n\n source:\n | { kind: 'field'; fieldName: string }\n | { kind: 'dataSource'; dataSourceId: string; outputField: string }\n | { kind: 'expression'; expression: string }\n | { kind: 'constant'; value: unknown }\n | { kind: 'context'; contextPath: string };\n\n defaultValue?: unknown;\n}\n\n// ─── Data Sources ───────────────────────────────────────────────────────────\n\n/**\n * Rule data source — wraps existing FormDataSource with caching,\n * or defines a Custom Action / API source.\n */\nexport interface RuleDataSource {\n id: string;\n name: string;\n displayName: string;\n type: 'fetchXml' | 'aggregateFetchXml' | 'customAction' | 'customApi';\n\n /**\n * For fetchXml/aggregateFetchXml: reference an existing FormDataSource by id.\n * Avoids duplicating FetchXML definitions.\n */\n formDataSourceId?: string;\n\n /**\n * Inline FetchXML when not referencing a shared FormDataSource.\n * Supports {{fieldName}} placeholders.\n */\n fetchXml?: string;\n\n /** Custom Action / API configuration (Phase 3) */\n apiConfig?: {\n actionName: string;\n inputParameters: ApiParameter[];\n outputParameters: ApiParameter[];\n };\n\n /** Caching strategy */\n cache: {\n enabled: boolean;\n ttlSeconds: number;\n invalidateOn?: string[];\n };\n}\n\nexport interface ApiParameter {\n name: string;\n type: string; // \"EntityReference\", \"String\", \"Integer\", etc.\n source: 'field' | 'variable' | 'constant';\n value: string;\n}\n\n// ─── Execution Context ──────────────────────────────────────────────────────\n\n/** Full runtime context — plain objects for JSON-serializable portions */\nexport interface RuleExecutionContext {\n /** Current form field values */\n formData: Record<string, unknown>;\n\n /** Original values at load time (for dirty detection) */\n originalData: Record<string, unknown>;\n\n /** Fields changed since load (runtime only, not serialized) */\n dirtyFields: Set<string>;\n\n /** Resolved data source results */\n dataSourceResults: Record<string, unknown>;\n\n /** Resolved variable values */\n variableValues: Record<string, unknown>;\n\n /** Form-level state */\n formState: {\n isNew: boolean;\n isDirty: boolean;\n isValid: boolean;\n isSaving: boolean;\n formId: string;\n entityLogicalName: string;\n recordId?: string;\n lastSaved?: string; // ISO date string\n };\n\n /** Custom Action results (Phase 3) */\n customActionResults: Record<string, unknown>;\n}\n\n// ─── Execution Results ──────────────────────────────────────────────────────\n\nexport interface RuleExecutionResult {\n ruleId: string;\n ruleName: string;\n conditionMet: boolean;\n actionsExecuted: ActionResult[];\n error?: string;\n durationMs: number;\n}\n\nexport interface ActionResult {\n actionId: string;\n actionType: RuleActionType;\n targetId: string;\n success: boolean;\n error?: string;\n}\n\n// ─── Field / Section / Tab State ────────────────────────────────────────────\n\nexport interface FieldState {\n visible: boolean;\n locked: boolean;\n required: 'required' | 'recommended' | 'none';\n errorMessage?: string;\n warningMessage?: string;\n recommendationMessage?: string;\n highlightColor?: string;\n}\n\nexport interface SectionState {\n visible: boolean;\n locked: boolean;\n}\n\nexport interface TabState {\n visible: boolean;\n}\n\nexport interface FormNotification {\n id: string;\n type: 'error' | 'warning' | 'info';\n message: string;\n ruleId: string;\n}\n\n// ─── Workflows (Phase 3) ───────────────────────────────────────────────────\n\nexport interface WorkflowDefinition {\n id: string;\n name: string;\n description?: string;\n trigger: 'on-save' | 'on-field-change' | 'on-load' | 'manual';\n triggerField?: string;\n steps: WorkflowStep[];\n}\n\nexport interface WorkflowStep {\n id: string;\n type: 'validate' | 'save' | 'call-action' | 'refresh' | 'evaluate-rules' | 'set-field';\n actionName?: string;\n fieldName?: string;\n fieldValue?: unknown;\n onError: 'stop' | 'continue' | 'rollback';\n}\n\nexport interface WorkflowResult {\n workflowId: string;\n success: boolean;\n steps: WorkflowStepResult[];\n}\n\nexport interface WorkflowStepResult {\n stepId: string;\n success: boolean;\n result?: unknown;\n error?: string;\n}\n\n// ─── Canvas Layout (Phase 2b) ──────────────────────────────────────────────\n\nexport interface RuleCanvasLayout {\n nodes: CanvasNode[];\n connections: CanvasConnection[];\n zoom: number;\n panX: number;\n panY: number;\n}\n\nexport interface CanvasNode {\n id: string;\n type: 'condition' | 'action' | 'branch';\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface CanvasConnection {\n fromNodeId: string;\n toNodeId: string;\n type: 'then' | 'else' | 'sequence';\n}\n\n// ─── Default Factory ────────────────────────────────────────────────────────\n\n/** Creates a minimal empty rule definition */\nexport function createDefaultBusinessRule(\n entityLogicalName: string,\n name: string = 'New Rule'\n): BusinessRuleDefinition {\n const id = `rule-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n const now = new Date().toISOString();\n\n return {\n id,\n name,\n entityLogicalName,\n scope: 'entity',\n trigger: { type: 'onLoad' },\n priority: 100,\n enabled: true,\n dataSources: [],\n variables: [],\n conditionTree: {\n id: `grp-${Date.now()}`,\n logic: 'and',\n conditions: [],\n },\n thenActions: [],\n elseActions: [],\n createdAt: now,\n updatedAt: now,\n };\n}\n\n/** Creates a default field state (visible, unlocked, not required) */\nexport function createDefaultFieldState(): FieldState {\n return {\n visible: true,\n locked: false,\n required: 'none',\n };\n}\n\n// ─── Re-export FormDataSource for convenience ───────────────────────────────\nexport type { FormDataSource };\n","/**\n * Business Process Flow Type Definitions\n *\n * Models the BPF structure matching Dynamics 365's process flow system.\n * Supports both importing existing BPFs from Dataverse and creating custom ones.\n *\n * Design principles:\n * - Matches Dynamics 365 BPF structure (workflow, processstage, processstageattribute)\n * - Reuses RuleConditionGroup and RuleAction from businessRules for conditions/actions\n * - Supports runtime instance tracking per record\n * - JSON-serializable for persistence\n */\n\nimport type { RuleConditionGroup, RuleAction, RuleCondition, ConditionOperator, ConditionValue } from './businessRules';\n\n// ─── Source & Status Types ──────────────────────────────────────────────────\n\n/** BPF source - imported from Dataverse or created custom */\nexport type BPFSource = 'dataverse' | 'custom';\n\n/** Stage completion status */\nexport type BPFStageStatus = 'pending' | 'active' | 'completed' | 'skipped';\n\n/** Step types matching Dynamics 365 BPF step types */\nexport type BPFStepType = 'data' | 'workflow' | 'action' | 'flow';\n\n/** Field types for data steps - determines input control in callout */\nexport type BPFStepFieldType =\n | 'text' // Single line text\n | 'textarea' // Multi-line text\n | 'number' // Whole number\n | 'decimal' // Decimal number\n | 'currency' // Currency field\n | 'date' // Date only\n | 'datetime' // Date and time\n | 'lookup' // Lookup to another entity\n | 'optionset' // Single select dropdown\n | 'multiselect' // Multi-select dropdown\n | 'boolean' // Yes/No toggle\n | 'owner'; // Owner lookup\n\n// ─── BPF Edge (Branching Support) ───────────────────────────────────────────\n\n/** Represents a connection between stages (or from condition nodes) */\nexport interface BPFEdge {\n /** Unique identifier */\n id: string;\n\n /** Source stage ID (or condition node ID) */\n fromStageId: string;\n\n /** Target stage ID */\n toStageId: string;\n\n /** Optional condition - if set, edge is only taken when condition evaluates true */\n condition?: RuleConditionGroup;\n\n /** Label for the edge (e.g., \"Yes\", \"No\", \"Approved\") */\n label?: string;\n\n /** Evaluation order when multiple edges from same source (lower = evaluated first) */\n order?: number;\n\n /** Whether this is the default/fallback edge when no conditions match */\n isDefault?: boolean;\n}\n\n// ─── BPF Condition Node ─────────────────────────────────────────────────────\n\n/** A condition branch point in the BPF (the purple diamond in Dynamics) */\nexport interface BPFConditionNode {\n /** Unique identifier */\n id: string;\n\n /** Display name (e.g., \"Met Budget?\") */\n name: string;\n\n /** Optional description */\n description?: string;\n\n /** The condition to evaluate (determines which outgoing edge to take) */\n condition: RuleConditionGroup;\n\n /** Position for visual layout */\n position?: { x: number; y: number };\n}\n\n// ─── Position Type ──────────────────────────────────────────────────────────\n\n/** 2D position for flowchart layout */\nexport interface BPFNodePosition {\n x: number;\n y: number;\n}\n\n// ─── BPF Step ───────────────────────────────────────────────────────────────\n\n/** A step within a BPF stage */\nexport interface BPFStep {\n /** Unique identifier */\n id: string;\n\n /** Step name/label */\n name: string;\n\n /** Display order within stage (1-based) */\n order: number;\n\n /** Step type */\n type: BPFStepType;\n\n /** Whether this step must be completed to advance */\n required: boolean;\n\n /** Optional description */\n description?: string;\n\n // ─── Data Step Configuration ──────────────────────────────────────────────\n\n /** For data steps - field logical name to fill */\n fieldLogicalName?: string;\n\n /** For data steps - field display name */\n fieldDisplayName?: string;\n\n /** For data steps - field type (determines input control) */\n fieldType?: BPFStepFieldType;\n\n /** For optionset fields - available options */\n options?: Array<{ value: string | number; label: string }>;\n\n /** For lookup fields - target entity */\n lookupTargetEntity?: string;\n\n /** For lookup fields - additional target entities (polymorphic lookups like Customer) */\n lookupTargetEntities?: string[];\n\n // ─── Lookup Filtering ───────────────────────────────────────────────────────\n\n /** For lookup fields - OData filter expression (e.g., \"statecode eq 0\") */\n lookupFilter?: string;\n\n /** For lookup fields - custom FetchXML for advanced filtering */\n lookupFetchXml?: string;\n\n /** For lookup fields - view ID to use for filtering (uses view's FetchXML) */\n lookupViewId?: string;\n\n /** For lookup fields - whether to allow creating new records inline */\n lookupAllowCreate?: boolean;\n\n /** For lookup fields - columns to display in search results */\n lookupDisplayColumns?: string[];\n\n // ─── Workflow Step Configuration ──────────────────────────────────────────\n\n /** For workflow steps - workflow ID */\n workflowId?: string;\n\n /** For workflow steps - workflow name */\n workflowName?: string;\n\n // ─── Action Step Configuration ────────────────────────────────────────────\n\n /** For action steps - action ID */\n actionId?: string;\n\n /** For action steps - action name */\n actionName?: string;\n\n // ─── Flow Step Configuration ──────────────────────────────────────────────\n\n /** For Power Automate flow steps - flow ID */\n flowId?: string;\n\n /** For Power Automate flow steps - flow name */\n flowName?: string;\n\n // ─── Dataverse Reference ──────────────────────────────────────────────────\n\n /** Dataverse process stage attribute ID (for imported BPFs) */\n dataverseAttributeId?: string;\n}\n\n// ─── BPF Stage ──────────────────────────────────────────────────────────────\n\n/** A stage in the BPF */\nexport interface BPFStage {\n /** Unique identifier */\n id: string;\n\n /** Stage name/label */\n name: string;\n\n /** Display order (1-based) */\n order: number;\n\n /** Current status (runtime) */\n status: BPFStageStatus;\n\n /** Optional description */\n description?: string;\n\n // ─── Visual Customization ─────────────────────────────────────────────────\n\n /** Fluent UI icon name */\n icon?: string;\n\n /** Custom color (hex) */\n color?: string;\n\n // ─── Steps ────────────────────────────────────────────────────────────────\n\n /** Steps within this stage */\n steps: BPFStep[];\n\n // ─── Conditions & Actions ─────────────────────────────────────────────────\n\n /** Conditions required to enter this stage */\n entryCondition?: RuleConditionGroup;\n\n /** Conditions required to exit this stage */\n exitCondition?: RuleConditionGroup;\n\n /** Actions to execute when entering this stage */\n onEnterActions?: RuleAction[];\n\n /** Actions to execute when exiting this stage */\n onExitActions?: RuleAction[];\n\n // ─── Form Binding ─────────────────────────────────────────────────────────\n\n /** Link to a form to show for this stage */\n linkedFormId?: string;\n\n // ─── Dataverse Reference ──────────────────────────────────────────────────\n\n /** Dataverse process stage ID (for imported BPFs) */\n dataverseStageId?: string;\n\n /** Dataverse stage category (for imported BPFs) */\n dataverseStageCategory?: number;\n\n // ─── Branching Support ─────────────────────────────────────────────────────\n\n /** Position for flowchart layout (used when edges are present) */\n position?: BPFNodePosition;\n}\n\n// ─── Business Process Flow Definition ───────────────────────────────────────\n\n/** Complete BPF definition */\nexport interface BusinessProcessFlowDefinition {\n /** Unique identifier */\n id: string;\n\n /** BPF name */\n name: string;\n\n /** Optional description */\n description?: string;\n\n // ─── Entity Binding ───────────────────────────────────────────────────────\n\n /** Entity this BPF applies to */\n entityLogicalName: string;\n\n /** Entity display name */\n entityDisplayName?: string;\n\n // ─── Source Tracking ──────────────────────────────────────────────────────\n\n /** Whether imported from Dataverse or created custom */\n source: BPFSource;\n\n /** Dataverse workflow ID (for imported BPFs) */\n dataverseWorkflowId?: string;\n\n /** Dataverse unique name (for imported BPFs) */\n dataverseUniqueName?: string;\n\n // ─── Stages ───────────────────────────────────────────────────────────────\n\n /** Ordered list of stages */\n stages: BPFStage[];\n\n // ─── Branching (Edge-Based) ───────────────────────────────────────────────\n\n /**\n * Edges connecting stages/condition nodes.\n * If present, navigation uses edge traversal instead of index arithmetic.\n * Empty or undefined = linear navigation (backward compatible).\n */\n edges?: BPFEdge[];\n\n /**\n * Condition branch points (purple diamonds in Dynamics designer).\n * Referenced by edges via their IDs.\n */\n conditionNodes?: BPFConditionNode[];\n\n // ─── Runtime State ────────────────────────────────────────────────────────\n\n /** Currently active stage index (0-based, -1 if not started) */\n activeStageIndex: number;\n\n // ─── Configuration ────────────────────────────────────────────────────────\n\n /** Whether this BPF is active */\n isActive: boolean;\n\n /** Allow navigating to previous stages */\n allowBackNavigation: boolean;\n\n /** Show stage labels in progress indicator */\n showStageLabels: boolean;\n\n /** Progress indicator variant */\n progressVariant?: 'chevron' | 'circle' | 'minimal';\n\n /** BPF-wide accent color applied to all stages (individual stage.color overrides) */\n accentColor?: string;\n\n // ─── Metadata ─────────────────────────────────────────────────────────────\n\n /** ISO timestamp when created */\n createdAt: string;\n\n /** ISO timestamp when last updated */\n updatedAt: string;\n\n /** Creator user ID */\n createdBy?: string;\n}\n\n// ─── BPF Instance (Runtime) ─────────────────────────────────────────────────\n\n/** Runtime instance of a BPF for a specific record */\nexport interface BPFInstance {\n /** Instance ID */\n id: string;\n\n /** Reference to the BPF definition */\n bpfDefinitionId: string;\n\n /** Record ID this instance is tracking */\n recordId: string;\n\n /** Entity logical name of the record */\n entityLogicalName: string;\n\n // ─── Current State ────────────────────────────────────────────────────────\n\n /** Current active stage ID */\n activeStageId: string;\n\n /** IDs of completed stages */\n completedStageIds: string[];\n\n /** IDs of skipped stages */\n skippedStageIds: string[];\n\n // ─── Step Completion ──────────────────────────────────────────────────────\n\n /** IDs of completed steps (across all stages) */\n completedStepIds: string[];\n\n // ─── Branching Support ───────────────────────────────────────────────────\n\n /**\n * Path history - ordered list of stage/node IDs visited.\n * Used for tracking non-linear traversal in branching BPFs.\n */\n pathHistory?: string[];\n\n // ─── Timestamps ───────────────────────────────────────────────────────────\n\n /** When the BPF was started for this record */\n startedAt: string;\n\n /** When the BPF was completed (all stages done) */\n completedAt?: string;\n\n // ─── Dataverse Reference ──────────────────────────────────────────────────\n\n /** Dataverse process instance ID */\n dataverseInstanceId?: string;\n}\n\n// ─── Dataverse BPF Metadata (for Import) ────────────────────────────────────\n\n/** Dataverse BPF metadata from workflow entity */\nexport interface DataverseBPFMetadata {\n /** Workflow ID (GUID) */\n workflowId: string;\n\n /** BPF name */\n name: string;\n\n /** Description */\n description?: string;\n\n /** Primary entity logical name */\n primaryEntity: string;\n\n /** Category (4 = Business Process Flow) */\n category: number;\n\n /** State code (1 = Activated) */\n stateCode: number;\n\n /** Status code */\n statusCode: number;\n\n /** Unique name */\n uniqueName?: string;\n\n /** Stages within this BPF */\n stages: DataverseBPFStageMetadata[];\n\n /** Branch data (for branching BPFs) */\n branchData?: DataverseBPFBranchData;\n}\n\n/** Dataverse attribute types */\nexport type DataverseAttributeType =\n | 'String' // Single line text\n | 'Memo' // Multi-line text\n | 'Integer' // Whole number\n | 'BigInt' // Large integer\n | 'Decimal' // Decimal number\n | 'Double' // Float\n | 'Money' // Currency\n | 'DateTime' // Date and/or time\n | 'Boolean' // Two options (yes/no)\n | 'Lookup' // Lookup to another entity\n | 'Owner' // Owner lookup (User or Team)\n | 'Customer' // Customer lookup (Account or Contact)\n | 'Picklist' // Option set (single select)\n | 'State' // State option set\n | 'Status' // Status option set\n | 'MultiSelectPicklist' // Multi-select option set\n | 'Uniqueidentifier' // GUID\n | 'Image' // Image field\n | 'File' // File field\n | 'EntityName' // Entity name reference\n | 'Virtual' // Virtual/calculated field\n | 'ManagedProperty' // Managed property\n | 'PartyList'; // Party list (multiple lookups)\n\n/** Parsed step info from clientdata */\nexport interface DataverseBPFStepInfo {\n /** Field logical name (e.g., \"parentcontactid\") */\n controlId: string;\n /** Display name/label (e.g., \"Existing Contact\") */\n displayName: string;\n /** Whether this step is required to advance */\n isRequired?: boolean;\n\n // ─── Attribute Metadata (populated during import) ───────────────────────────\n\n /** Attribute type from Dataverse (e.g., \"Lookup\", \"Picklist\", \"Money\") */\n attributeType?: DataverseAttributeType;\n\n /** For lookup fields - target entity logical names */\n lookupTargets?: string[];\n\n /** For optionset fields - available options */\n options?: Array<{ value: number; label: string }>;\n\n /** Format hint (e.g., \"Phone\", \"Email\", \"Url\" for String attributes) */\n format?: string;\n\n /** Max length for string fields */\n maxLength?: number;\n\n /** Precision for decimal/currency fields */\n precision?: number;\n}\n\n/** Dataverse process stage metadata */\nexport interface DataverseBPFStageMetadata {\n /** Process stage ID (GUID) */\n stageId: string;\n\n /** Stage name */\n name: string;\n\n /** Stage category (order indicator) */\n stageCategory: number;\n\n /** Primary entity for this stage */\n primaryEntityLogicalName: string;\n\n /** Required attributes (field logical names) - legacy */\n requiredAttributes: string[];\n\n /** Parsed step info with both controlId and displayName */\n parsedSteps?: DataverseBPFStepInfo[];\n\n /** Process ID this stage belongs to */\n processId: string;\n}\n\n// ─── Dataverse BPF Branch Metadata ──────────────────────────────────────────\n\n/** Branch connection from clientdata */\nexport interface DataverseBPFBranchInfo {\n /** Source stage or condition node ID */\n fromNodeId: string;\n\n /** Target stage ID */\n toStageId: string;\n\n /** Branch label (e.g., \"Yes\", \"No\") */\n label?: string;\n\n /** Condition expression (raw from Dynamics) */\n conditionExpression?: string;\n\n /** Parsed condition field */\n conditionField?: string;\n\n /** Parsed condition operator */\n conditionOperator?: string;\n\n /** Parsed condition value */\n conditionValue?: unknown;\n\n /** Whether this is the default/else branch */\n isDefault?: boolean;\n\n /** Branch order (for evaluation priority) */\n order?: number;\n}\n\n/** Condition node from clientdata (the decision point) */\nexport interface DataverseBPFConditionInfo {\n /** Condition node ID */\n nodeId: string;\n\n /** Display name (e.g., \"Met Budget?\") */\n name: string;\n\n /** The field being evaluated */\n conditionField?: string;\n\n /** The operator (eq, ne, gt, etc.) */\n conditionOperator?: string;\n\n /** The value being compared */\n conditionValue?: unknown;\n\n /** Raw condition expression */\n conditionExpression?: string;\n\n /** Stage ID this condition belongs to (the \"parent\" stage) */\n parentStageId?: string;\n}\n\n/** Extended BPF metadata with branching info */\nexport interface DataverseBPFBranchData {\n /** Branch connections */\n branches: DataverseBPFBranchInfo[];\n\n /** Condition nodes (decision points) */\n conditionNodes: DataverseBPFConditionInfo[];\n\n /** Whether this BPF has branching */\n hasBranching: boolean;\n}\n\n// ─── Stage Navigation Result ────────────────────────────────────────────────\n\n/** Result of a stage navigation attempt */\nexport interface BPFNavigationResult {\n /** Whether navigation succeeded */\n success: boolean;\n\n /** New active stage ID (if successful) */\n newActiveStageId?: string;\n\n /** Error message (if failed) */\n error?: string;\n\n /** Validation errors preventing navigation */\n validationErrors?: BPFValidationError[];\n}\n\n/** Validation error for BPF operations */\nexport interface BPFValidationError {\n /** Stage ID where error occurred */\n stageId?: string;\n\n /** Step ID where error occurred */\n stepId?: string;\n\n /** Error message */\n message: string;\n\n /** Error type */\n type: 'required-step' | 'exit-condition' | 'entry-condition' | 'workflow-failed';\n}\n\n// ─── Default Factory Functions ──────────────────────────────────────────────\n\n/** Generate a unique ID with prefix */\nfunction uid(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/** Create a default BPF step */\nexport function createDefaultBPFStep(\n type: BPFStepType = 'data',\n order: number = 1\n): BPFStep {\n return {\n id: uid('step'),\n name: 'New Step',\n order,\n type,\n required: false,\n };\n}\n\n/** Create a default BPF stage */\nexport function createDefaultBPFStage(order: number = 1): BPFStage {\n return {\n id: uid('stage'),\n name: 'New Stage',\n order,\n status: 'pending',\n steps: [],\n };\n}\n\n/** Create a default BPF edge */\nexport function createDefaultBPFEdge(\n fromStageId: string,\n toStageId: string,\n options?: {\n label?: string;\n condition?: RuleConditionGroup;\n isDefault?: boolean;\n order?: number;\n }\n): BPFEdge {\n return {\n id: uid('edge'),\n fromStageId,\n toStageId,\n label: options?.label,\n condition: options?.condition,\n isDefault: options?.isDefault ?? false,\n order: options?.order ?? 0,\n };\n}\n\n/** Create a default BPF condition node */\nexport function createDefaultBPFConditionNode(\n name: string = 'New Condition',\n condition?: RuleConditionGroup\n): BPFConditionNode {\n return {\n id: uid('cond'),\n name,\n condition: condition ?? {\n id: uid('grp'),\n logic: 'and',\n conditions: [],\n },\n };\n}\n\n/** Create a default BPF definition */\nexport function createDefaultBPF(\n entityLogicalName: string,\n name: string = 'New Business Process Flow'\n): BusinessProcessFlowDefinition {\n const now = new Date().toISOString();\n\n return {\n id: uid('bpf'),\n name,\n entityLogicalName,\n source: 'custom',\n stages: [\n {\n ...createDefaultBPFStage(1),\n name: 'Stage 1',\n status: 'active',\n },\n {\n ...createDefaultBPFStage(2),\n name: 'Stage 2',\n },\n {\n ...createDefaultBPFStage(3),\n name: 'Stage 3',\n },\n ],\n activeStageIndex: 0,\n isActive: true,\n allowBackNavigation: true,\n showStageLabels: true,\n progressVariant: 'chevron',\n createdAt: now,\n updatedAt: now,\n };\n}\n\n/** Create a BPF instance for a record */\nexport function createBPFInstance(\n bpf: BusinessProcessFlowDefinition,\n recordId: string\n): BPFInstance {\n const firstStage = bpf.stages[0];\n\n return {\n id: uid('inst'),\n bpfDefinitionId: bpf.id,\n recordId,\n entityLogicalName: bpf.entityLogicalName,\n activeStageId: firstStage?.id ?? '',\n completedStageIds: [],\n skippedStageIds: [],\n completedStepIds: [],\n pathHistory: firstStage ? [firstStage.id] : [],\n startedAt: new Date().toISOString(),\n };\n}\n\n/**\n * Map Dataverse attribute type to BPF field type\n */\nfunction mapAttributeTypeToFieldType(\n attributeType?: DataverseAttributeType,\n format?: string\n): BPFStepFieldType {\n switch (attributeType) {\n case 'String':\n // Check format for special string types\n if (format === 'Email' || format === 'Url' || format === 'Phone') {\n return 'text';\n }\n return 'text';\n\n case 'Memo':\n return 'textarea';\n\n case 'Integer':\n case 'BigInt':\n return 'number';\n\n case 'Decimal':\n case 'Double':\n return 'decimal';\n\n case 'Money':\n return 'currency';\n\n case 'DateTime':\n // DateTime has format like \"DateOnly\" or \"DateAndTime\"\n if (format === 'DateOnly') {\n return 'date';\n }\n return 'datetime';\n\n case 'Boolean':\n return 'boolean';\n\n case 'Lookup':\n case 'Customer':\n case 'PartyList':\n return 'lookup';\n\n case 'Owner':\n return 'owner';\n\n case 'Picklist':\n case 'State':\n case 'Status':\n return 'optionset';\n\n case 'MultiSelectPicklist':\n return 'multiselect';\n\n default:\n return 'text';\n }\n}\n\n/**\n * Create a BPFStep from DataverseBPFStepInfo\n */\nfunction createStepFromStepInfo(\n stepInfo: DataverseBPFStepInfo,\n stepIndex: number\n): BPFStep {\n const fieldType = mapAttributeTypeToFieldType(\n stepInfo.attributeType,\n stepInfo.format\n );\n\n const step: BPFStep = {\n id: uid('step'),\n name: stepInfo.displayName || stepInfo.controlId,\n order: stepIndex + 1,\n type: 'data' as BPFStepType,\n required: stepInfo.isRequired ?? false,\n fieldLogicalName: stepInfo.controlId,\n fieldDisplayName: stepInfo.displayName,\n fieldType,\n };\n\n // Add lookup target entities if it's a lookup\n if (\n (fieldType === 'lookup' || fieldType === 'owner') &&\n stepInfo.lookupTargets &&\n stepInfo.lookupTargets.length > 0\n ) {\n step.lookupTargetEntity = stepInfo.lookupTargets[0];\n // For polymorphic lookups (Customer, PartyList), store all targets\n if (stepInfo.lookupTargets.length > 1) {\n step.lookupTargetEntities = stepInfo.lookupTargets;\n }\n }\n\n // Add options for optionset fields\n if (\n (fieldType === 'optionset' || fieldType === 'multiselect') &&\n stepInfo.options &&\n stepInfo.options.length > 0\n ) {\n step.options = stepInfo.options;\n }\n\n return step;\n}\n\n/** Convert Dataverse BPF metadata to BPF definition */\nexport function convertDataverseBPFToDefinition(\n metadata: DataverseBPFMetadata\n): BusinessProcessFlowDefinition {\n const now = new Date().toISOString();\n\n // Sort stages by category\n const sortedStages = [...metadata.stages].sort(\n (a, b) => a.stageCategory - b.stageCategory\n );\n\n // Build mapping from Dataverse stage IDs to internal IDs\n const stageIdMap = new Map<string, string>();\n\n const stages: BPFStage[] = sortedStages.map((stageMeta, index) => {\n const internalId = uid('stage');\n stageIdMap.set(stageMeta.stageId.toLowerCase(), internalId);\n\n // Use parsedSteps if available (has display names and attribute metadata), otherwise fall back to requiredAttributes\n let steps: BPFStep[];\n if (stageMeta.parsedSteps && stageMeta.parsedSteps.length > 0) {\n steps = stageMeta.parsedSteps.map((stepInfo, stepIndex) =>\n createStepFromStepInfo(stepInfo, stepIndex)\n );\n } else {\n steps = stageMeta.requiredAttributes.map((attr, stepIndex) => ({\n id: uid('step'),\n name: attr,\n order: stepIndex + 1,\n type: 'data' as BPFStepType,\n required: false, // Default to not required when we don't have parsed data\n fieldLogicalName: attr,\n fieldType: 'text' as BPFStepFieldType, // Default to text when no metadata\n }));\n }\n\n return {\n id: internalId,\n name: stageMeta.name,\n order: index + 1,\n status: index === 0 ? 'active' : 'pending',\n dataverseStageId: stageMeta.stageId,\n dataverseStageCategory: stageMeta.stageCategory,\n steps,\n } as BPFStage;\n });\n\n // Build edges and condition nodes from branch data\n let edges: BPFEdge[] | undefined;\n let conditionNodes: BPFConditionNode[] | undefined;\n\n if (metadata.branchData?.hasBranching) {\n const condNodeIdMap = new Map<string, string>();\n\n // Convert condition nodes\n conditionNodes = metadata.branchData.conditionNodes.map((condInfo) => {\n const internalId = uid('cond');\n condNodeIdMap.set(condInfo.nodeId.toLowerCase(), internalId);\n\n // Build condition group from parsed expression\n const condition = buildConditionGroupFromBranch(condInfo);\n\n return {\n id: internalId,\n name: condInfo.name,\n description: condInfo.conditionExpression,\n condition,\n } as BPFConditionNode;\n });\n\n // Convert branches to edges\n edges = metadata.branchData.branches\n .map((branch) => {\n // Resolve IDs - could be stage or condition node\n let fromId = stageIdMap.get(branch.fromNodeId.toLowerCase()) ||\n condNodeIdMap.get(branch.fromNodeId.toLowerCase());\n let toId = stageIdMap.get(branch.toStageId.toLowerCase());\n\n // If we can't resolve IDs, skip this edge\n if (!fromId || !toId) {\n return null;\n }\n\n // Build condition for non-default branches\n let edgeCondition: RuleConditionGroup | undefined;\n if (!branch.isDefault && branch.conditionField) {\n edgeCondition = buildConditionGroupFromBranch({\n nodeId: '',\n name: '',\n conditionField: branch.conditionField,\n conditionOperator: branch.conditionOperator,\n conditionValue: branch.conditionValue,\n });\n }\n\n return {\n id: uid('edge'),\n fromStageId: fromId,\n toStageId: toId,\n label: branch.label,\n condition: edgeCondition,\n isDefault: branch.isDefault,\n order: branch.order ?? 0,\n } as BPFEdge;\n })\n .filter((e): e is BPFEdge => e !== null);\n }\n\n const bpf: BusinessProcessFlowDefinition = {\n id: uid('bpf'),\n name: metadata.name,\n description: metadata.description,\n entityLogicalName: metadata.primaryEntity,\n source: 'dataverse',\n dataverseWorkflowId: metadata.workflowId,\n dataverseUniqueName: metadata.uniqueName,\n stages,\n activeStageIndex: 0,\n isActive: metadata.stateCode === 1,\n allowBackNavigation: true,\n showStageLabels: true,\n progressVariant: 'chevron',\n createdAt: now,\n updatedAt: now,\n };\n\n // Add branching data if present\n if (edges && edges.length > 0) {\n bpf.edges = edges;\n }\n if (conditionNodes && conditionNodes.length > 0) {\n bpf.conditionNodes = conditionNodes;\n }\n\n return bpf;\n}\n\n/**\n * Build a RuleConditionGroup from branch condition info.\n */\nfunction buildConditionGroupFromBranch(\n condInfo: DataverseBPFConditionInfo | { conditionField?: string; conditionOperator?: string; conditionValue?: unknown }\n): RuleConditionGroup {\n const conditions: RuleCondition[] = [];\n\n if (condInfo.conditionField) {\n // Map operator to FetchXML-style operators\n const opMap: Record<string, ConditionOperator> = {\n 'eq': 'eq',\n '=': 'eq',\n 'equals': 'eq',\n 'ne': 'ne',\n '!=': 'ne',\n 'notequals': 'ne',\n 'gt': 'gt',\n '>': 'gt',\n 'ge': 'ge',\n '>=': 'ge',\n 'lt': 'lt',\n '<': 'lt',\n 'le': 'le',\n '<=': 'le',\n 'like': 'like',\n 'null': 'null',\n 'not-null': 'not-null',\n 'notnull': 'not-null',\n };\n\n const operator = opMap[condInfo.conditionOperator?.toLowerCase() ?? 'eq'] ?? 'eq';\n\n conditions.push({\n id: uid('cond'),\n source: 'field',\n fieldName: condInfo.conditionField,\n operator,\n value: condInfo.conditionValue as ConditionValue,\n });\n }\n\n return {\n id: uid('grp'),\n logic: 'and',\n conditions,\n };\n}\n\n\n// ─── Step Type Metadata ─────────────────────────────────────────────────────\n\n/** Step type display information */\nexport const BPF_STEP_TYPES: Record<BPFStepType, { label: string; icon: string; description: string }> = {\n data: {\n label: 'Data Entry',\n icon: 'TextField',\n description: 'Requires a field value to be entered',\n },\n workflow: {\n label: 'Workflow',\n icon: 'Flow',\n description: 'Runs a Dynamics 365 workflow',\n },\n action: {\n label: 'Custom Action',\n icon: 'LightningBolt',\n description: 'Executes a custom action',\n },\n flow: {\n label: 'Power Automate',\n icon: 'MicrosoftFlowLogo',\n description: 'Triggers a Power Automate flow',\n },\n};\n\n/** Stage status display information */\nexport const BPF_STAGE_STATUSES: Record<BPFStageStatus, { label: string; icon: string; color: string }> = {\n pending: {\n label: 'Pending',\n icon: 'CircleRing',\n color: '#605e5c',\n },\n active: {\n label: 'Active',\n icon: 'CircleFill',\n color: '#0078d4',\n },\n completed: {\n label: 'Completed',\n icon: 'CheckMark',\n color: '#107c10',\n },\n skipped: {\n label: 'Skipped',\n icon: 'SkipForward',\n color: '#8a8886',\n },\n};\n\n/** Condition node display information */\nexport const BPF_CONDITION_NODE_DISPLAY = {\n label: 'Condition',\n icon: 'BranchFork2',\n color: '#8764b8', // Purple like Dynamics\n shape: 'diamond',\n};\n\n/** Edge display information */\nexport const BPF_EDGE_DISPLAY = {\n defaultLabel: 'Next',\n yesLabel: 'Yes',\n noLabel: 'No',\n defaultColor: '#605e5c',\n conditionalColor: '#0078d4',\n};\n\n// ─── Branching Helper Functions ─────────────────────────────────────────────\n\n/**\n * Check if a BPF uses edge-based branching.\n * Returns false for linear BPFs (backward compatible).\n */\nexport function hasBranching(bpf: BusinessProcessFlowDefinition): boolean {\n return Boolean(bpf.edges && bpf.edges.length > 0);\n}\n\n/**\n * Get outgoing edges from a stage or condition node.\n */\nexport function getOutgoingEdges(\n bpf: BusinessProcessFlowDefinition,\n nodeId: string\n): BPFEdge[] {\n if (!bpf.edges) return [];\n return bpf.edges\n .filter(e => e.fromStageId === nodeId)\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n}\n\n/**\n * Get incoming edges to a stage or condition node.\n */\nexport function getIncomingEdges(\n bpf: BusinessProcessFlowDefinition,\n nodeId: string\n): BPFEdge[] {\n if (!bpf.edges) return [];\n return bpf.edges.filter(e => e.toStageId === nodeId);\n}\n\n/**\n * Get condition node by ID.\n */\nexport function getConditionNodeById(\n bpf: BusinessProcessFlowDefinition,\n nodeId: string\n): BPFConditionNode | undefined {\n return bpf.conditionNodes?.find(n => n.id === nodeId);\n}\n\n/**\n * Check if a node ID is a condition node (vs a stage).\n */\nexport function isConditionNode(\n bpf: BusinessProcessFlowDefinition,\n nodeId: string\n): boolean {\n return Boolean(bpf.conditionNodes?.some(n => n.id === nodeId));\n}\n\n/**\n * Get the default (fallback) edge from a node.\n */\nexport function getDefaultEdge(\n bpf: BusinessProcessFlowDefinition,\n nodeId: string\n): BPFEdge | undefined {\n const edges = getOutgoingEdges(bpf, nodeId);\n return edges.find(e => e.isDefault) ?? edges.find(e => !e.condition);\n}\n","/**\n * Form Selector Types\n *\n * Defines types for Dynamics 365-style form switching functionality:\n * - Form grouping by entity (like D365 form selector dropdown)\n * - Form selection rules (auto-select based on conditions)\n * - Entity-based sidebar organization\n * - Default form per entity\n */\n\nimport type { FormType } from './form';\nimport type { RuleConditionGroup, ConditionOperator, ConditionValue } from './businessRules';\n\n// ─── Form Summary ────────────────────────────────────────────────────────────\n\n/** Lightweight form reference for dropdowns and lists */\nexport interface FormSummary {\n id: string;\n name: string;\n type: FormType;\n entityLogicalName?: string;\n isDefault?: boolean;\n order?: number;\n description?: string;\n /** Number of embedded callouts on this form */\n calloutCount?: number;\n}\n\n// ─── Form Selector Rule ──────────────────────────────────────────────────────\n\n/**\n * Determines which form to display based on conditions.\n * Rules are evaluated in priority order; first match wins.\n */\nexport interface FormSelectorRule {\n id: string;\n name: string;\n\n /** Entity this rule applies to */\n entityLogicalName: string;\n\n /** Form type this rule applies to (e.g., only 'main' forms) */\n formType: FormType;\n\n /** The form to show when this rule matches */\n targetFormId: string;\n\n /** Condition tree that determines when this rule applies */\n condition: RuleConditionGroup;\n\n /** Evaluation order (lower = higher priority) */\n priority: number;\n\n /** Whether this rule is active */\n enabled: boolean;\n\n /** Metadata */\n createdAt: string;\n updatedAt: string;\n}\n\n// ─── Rule Condition Types ────────────────────────────────────────────────────\n\n/** Type of condition for form selection */\nexport type FormSelectorConditionType =\n | 'field-value' // Check a field's value (e.g., revenue > 1000000)\n | 'security-role' // Check user's security role\n | 'business-unit' // Check user's business unit\n | 'team' // Check user's team membership\n | 'form-context' // Check form context (create/update mode, mobile, etc.)\n | 'record-type' // Check related record type\n | 'custom'; // Custom expression\n\n/** Condition for checking user's security role */\nexport interface SecurityRoleCondition {\n type: 'security-role';\n roleId?: string;\n roleName?: string;\n operator: 'has-role' | 'not-has-role';\n}\n\n/** Condition for checking user's business unit */\nexport interface BusinessUnitCondition {\n type: 'business-unit';\n businessUnitId?: string;\n businessUnitName?: string;\n operator: 'in-bu' | 'not-in-bu' | 'in-bu-hierarchy';\n}\n\n/** Condition for checking form context */\nexport interface FormContextCondition {\n type: 'form-context';\n context:\n | 'is-create-mode'\n | 'is-update-mode'\n | 'is-mobile'\n | 'is-tablet'\n | 'is-desktop'\n | 'is-offline'\n | 'is-quick-create';\n}\n\n/** Condition for checking field values */\nexport interface FieldValueCondition {\n type: 'field-value';\n fieldName: string;\n operator: ConditionOperator;\n value?: ConditionValue;\n compareToField?: string;\n}\n\n/** Union of all form selector condition types */\nexport type FormSelectorCondition =\n | SecurityRoleCondition\n | BusinessUnitCondition\n | FormContextCondition\n | FieldValueCondition;\n\n// ─── Entity Form Group ───────────────────────────────────────────────────────\n\n/**\n * Computed grouping of forms by entity.\n * Derived from FormDefinition[] - not stored directly.\n */\nexport interface EntityFormGroup {\n /** Entity logical name (e.g., \"contact\", \"account\") */\n entityLogicalName: string;\n\n /** Display name for the entity (from form data binding) */\n entityDisplayName: string;\n\n /** Icon name for the entity (optional) */\n entityIcon?: string;\n\n /** Forms grouped by their type */\n formsByType: Partial<Record<FormType, FormSummary[]>>;\n\n /** Total number of forms for this entity */\n totalForms: number;\n\n /** ID of the default main form for this entity */\n defaultMainFormId?: string;\n\n /** Form selector rules for this entity */\n rules: FormSelectorRule[];\n}\n\n/**\n * Group for forms without entity binding.\n * These are typically dialogs, panels, or utility forms.\n */\nexport interface UnboundFormGroup {\n /** Forms grouped by their type */\n formsByType: Partial<Record<FormType, FormSummary[]>>;\n\n /** Total number of unbound forms */\n totalForms: number;\n}\n\n// ─── Form Selection ──────────────────────────────────────────────────────────\n\n/**\n * Runtime context for selecting a form.\n * Passed to the form selection engine.\n */\nexport interface FormSelectionContext {\n /** Current user's security roles */\n userRoles?: string[];\n\n /** Current user's business unit ID */\n userBusinessUnitId?: string;\n\n /** Current user's team IDs */\n userTeamIds?: string[];\n\n /** Whether creating a new record */\n isCreateMode?: boolean;\n\n /** Whether updating an existing record */\n isUpdateMode?: boolean;\n\n /** Client type */\n clientType?: 'web' | 'mobile' | 'tablet';\n\n /** Whether offline */\n isOffline?: boolean;\n\n /** Whether quick create mode */\n isQuickCreate?: boolean;\n\n /** Current record data (for field-value conditions) */\n recordData?: Record<string, unknown>;\n\n /** Explicitly requested form ID (overrides rules) */\n requestedFormId?: string;\n}\n\n/**\n * Result of form selection.\n * Indicates which form was selected and why.\n */\nexport interface FormSelectionResult {\n /** The selected form ID */\n formId: string;\n\n /** Form name for display */\n formName: string;\n\n /** How the form was selected */\n selectionReason:\n | 'explicit-request' // User/code requested specific form\n | 'rule-match' // A selector rule matched\n | 'default-form' // Using entity's default form\n | 'first-available'; // No rules or defaults, using first form\n\n /** If selected by rule, the rule that matched */\n matchedRuleId?: string;\n matchedRuleName?: string;\n\n /** Other forms available for this entity */\n alternativeForms: FormSummary[];\n}\n\n// ─── Sidebar View Mode ───────────────────────────────────────────────────────\n\n/** How forms are organized in the sidebar */\nexport type SidebarViewMode = 'by-type' | 'by-entity';\n\n// ─── Linked Forms Info ───────────────────────────────────────────────────────\n\n/**\n * Information about forms linked by sharing the same entity.\n * Used in properties panel to show linked forms.\n */\nexport interface LinkedFormsInfo {\n /** Entity logical name */\n entityLogicalName: string;\n\n /** Entity display name */\n entityDisplayName: string;\n\n /** Other forms that share this entity */\n linkedForms: FormSummary[];\n\n /** Associated BPF (if any) */\n bpfId?: string;\n bpfName?: string;\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/** Creates a new form selector rule with defaults */\nexport function createFormSelectorRule(\n entityLogicalName: string,\n targetFormId: string,\n name: string = 'New Rule'\n): FormSelectorRule {\n const now = new Date().toISOString();\n const id = `fsr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n\n return {\n id,\n name,\n entityLogicalName,\n formType: 'main',\n targetFormId,\n condition: {\n id: `grp-${Date.now()}`,\n logic: 'and',\n conditions: [],\n },\n priority: 100,\n enabled: true,\n createdAt: now,\n updatedAt: now,\n };\n}\n\n/** Converts a FormDefinition to a FormSummary */\nexport function toFormSummary(form: {\n id: string;\n name: string;\n type: FormType;\n settings?: {\n dataSources?: { entityName?: string }[];\n isDefaultForEntity?: boolean;\n };\n order?: number;\n description?: string;\n callouts?: unknown[];\n}): FormSummary {\n return {\n id: form.id,\n name: form.name,\n type: form.type,\n entityLogicalName: form.settings?.dataSources?.[0]?.entityName,\n isDefault: form.settings?.isDefaultForEntity,\n order: form.order,\n description: form.description,\n calloutCount: form.callouts?.length ?? 0,\n };\n}\n\n// ─── Entity Icons ────────────────────────────────────────────────────────────\n\n/** Map of common entity names to Fluent UI icons */\nexport const entityIconMap: Record<string, string> = {\n contact: 'Contact',\n account: 'CityNext',\n opportunity: 'Money',\n lead: 'People',\n case: 'Inbox',\n incident: 'Inbox',\n task: 'TaskLogo',\n appointment: 'Calendar',\n email: 'Mail',\n phonecall: 'Phone',\n quote: 'Documentation',\n order: 'Package',\n invoice: 'Money',\n product: 'Product',\n user: 'Contact',\n team: 'Group',\n businessunit: 'Org',\n systemuser: 'Contact',\n};\n\n/** Get icon for an entity, with fallback */\nexport function getEntityIcon(entityLogicalName: string): string {\n const normalizedName = entityLogicalName.toLowerCase().replace(/^new_|^cr\\d+_/, '');\n return entityIconMap[normalizedName] || 'TableGroup';\n}\n","/**\n * Grid Customizer Type Definitions\n *\n * Models the grid customization structure for PCF Grid Customizers and\n * Fluent UI DetailsList configurations. Supports column-level renderer\n * selection, callouts, nested grids, aggregations, and export to code.\n *\n * Design principles:\n * - Matches Microsoft PCF Grid Customizer cellRendererOverrides pattern\n * - Reuses CalloutDefinition from form.ts for column callouts\n * - JSON-serializable for IndexedDB persistence\n * - Supports undo/redo via Immer patches (all types are plain objects)\n */\n\nimport type { CommandBarItem, FormDataSource } from './form';\nimport type { FocusedViewConfig, LegacySummaryField } from './focusedView';\n\n// ─── Column Data Types ────────────────────────────────────────────────────────\n\n/** Data types for grid columns (maps to Dynamics 365 attribute types) */\nexport type GridColumnDataType =\n | 'text'\n | 'numeric'\n | 'currency'\n | 'date'\n | 'datetime'\n | 'boolean'\n | 'optionset'\n | 'lookup'\n | 'image'\n | 'url'\n | 'email'\n | 'phone';\n\n// ─── Cell Renderer Types ──────────────────────────────────────────────────────\n\n/** Available renderer types for grid columns */\nexport type CellRendererType =\n | 'text'\n | 'lookup'\n | 'optionset'\n | 'currency'\n | 'progress'\n | 'date'\n | 'boolean'\n | 'rating'\n | 'composite';\n\n/** Available editor types for editable grid columns */\nexport type CellEditorType =\n | 'text'\n | 'dropdown'\n | 'number'\n | 'date'\n | 'toggle'\n | 'none';\n\n// ─── Aggregate Functions ──────────────────────────────────────────────────────\n\n/** Aggregate functions for footer row */\nexport type AggregateFunction = 'sum' | 'avg' | 'count' | 'min' | 'max';\n\n// ─── Grid Selection & Pagination ──────────────────────────────────────────────\n\nexport type GridSelectionMode = 'none' | 'single' | 'multiple';\nexport type GridPaginationMode = 'pagination' | 'infinite-scroll';\n\n// ─── Grid Type ────────────────────────────────────────────────────────────────\n\n/**\n * Grid layout/behavior mode. Replaces the older subgrid-control `gridType`\n * property and folds together what used to be the `isEditable` toggle and\n * `cardView.enabled` flag.\n *\n * - `standard` — default DetailsList rendering, read-only cells\n * - `editable` — inline cell editing enabled (column-level `isLocked` overrides)\n * - `readonly` — like standard but hides the command bar and disables selection\n * - `focused-view` — list-detail layout (Dynamics 365 Work Item Appearance)\n * - `card-list` — card layout instead of rows; uses `cardView` config\n */\nexport type GridType =\n | 'standard'\n | 'editable'\n | 'readonly'\n | 'focused-view'\n | 'card-list';\n\n// ─── Chart Configuration ──────────────────────────────────────────────────────\n\n/**\n * Chart pane configuration shown alongside or in place of the grid records.\n * Mirrors Dynamics 365 grid chart options.\n */\nexport interface GridChartConfig {\n /** Saved chart ID (`savedqueryvisualizationid`) shown by default */\n defaultChartId?: string;\n /** When true, hides the records list and only shows the chart */\n showChartOnly?: boolean;\n /** Whether the user can switch charts at runtime */\n allowChartChange?: boolean;\n}\n\n// ─── Focused View Wrapper ─────────────────────────────────────────────────────\n\n/**\n * Focused-view configuration on the grid customizer. Used only when\n * `gridType === 'focused-view'`. Fields legacy-migrated from the subgrid\n * control's `primaryNameField` + `summaryFields` continue to work — see\n * `LegacySummaryField` and `migrateLegacyConfig`.\n */\nexport interface GridFocusedViewSettings {\n /** Structured row configuration */\n config?: FocusedViewConfig;\n /** Legacy primary name attribute (kept for backward compatibility) */\n primaryNameField?: string;\n /** Legacy flat summary fields (kept for backward compatibility) */\n summaryFields?: LegacySummaryField[];\n}\n\n// ─── Composite Renderer Types ─────────────────────────────────────────────────\n\n/** Slot types for the composite renderer */\nexport type CompositeSlotType =\n | 'icon'\n | 'text'\n | 'badge'\n | 'link'\n | 'image'\n | 'spacer'\n | 'progress'\n | 'rating';\n\n/** Conditional style rule — first match wins in an ordered list */\nexport interface ConditionalStyle {\n /** Field to evaluate (can be any field in the row, not just this column) */\n field: string;\n /** Comparison operator */\n operator: 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | 'contains' | 'between';\n /** Value to compare against */\n value: string | number;\n /** End value for 'between' operator */\n valueTo?: string | number;\n /** Style to apply when condition matches */\n style: Record<string, string>;\n}\n\n/** A composable slot within a composite cell renderer */\nexport interface CompositeSlot {\n /** Unique identifier */\n id: string;\n /** Slot type determines what is rendered */\n type: CompositeSlotType;\n /** Bind to a row field (dynamic), or omit for static value */\n fieldBinding?: string;\n /** Static value (icon name, text, URL, etc.) when no field binding */\n staticValue?: string;\n /** Base style applied when no conditional style matches */\n defaultStyle?: Record<string, string>;\n /**\n * Ordered list of conditional styles — first match wins.\n * Falls through to defaultStyle if no condition matches.\n * Consistent with StatusBadgeConfig's option-value-to-color pattern.\n */\n conditionalStyles?: ConditionalStyle[];\n}\n\n/** Configuration for the composite renderer */\nexport interface CompositeRendererConfig {\n /** Layout direction for slots */\n layout: 'horizontal' | 'vertical';\n /** Gap between slots in pixels */\n gap: number;\n /** Ordered list of composable slots */\n slots: CompositeSlot[];\n}\n\n// ─── Grid Column Definition ───────────────────────────────────────────────────\n\n/** Configuration for a single grid column */\nexport interface GridColumnDefinition {\n /** Unique identifier */\n id: string;\n /** Logical field name (maps to entity attribute) */\n fieldName: string;\n /**\n * Dataverse attribute logical name this column renders. When set alongside\n * the grid's `dataSource`, enables code generation (models/constants/hooks)\n * and validation against the entity's attribute list. Defaults to the same\n * value as `fieldName` when the column is added from the entity picker, but\n * the two stay independent so `fieldName` can remain a render-only key.\n */\n attributeLogicalName?: string;\n /** Display header text */\n displayName: string;\n /** Data type of the column */\n dataType: GridColumnDataType;\n /** Column width in pixels */\n width: number;\n /** Minimum column width */\n minWidth?: number;\n /** Maximum column width */\n maxWidth?: number;\n /** Whether the column is resizable */\n isResizable: boolean;\n /** Whether the column is visible */\n isVisible: boolean;\n /** Display order (lower = leftmost) */\n order: number;\n\n // ── Renderer ──\n /** Cell renderer type */\n rendererType: CellRendererType;\n /**\n * Type-specific renderer configuration.\n * Shape depends on rendererType — enforced at the UI layer per config panel.\n * Examples:\n * optionset → { displayMode: 'box', options: [...], showIcon: true }\n * currency → { currencyCode: 'USD', showTrend: true, decimals: 2 }\n * progress → { min: 0, max: 100, thresholds: { warning: 30, danger: 10 } }\n * composite → CompositeRendererConfig\n */\n rendererConfig: Record<string, unknown>;\n\n // ── Editor (editable grids) ──\n /** Cell editor type (for editable grids). 'none' disables editing. */\n editorType?: CellEditorType;\n /** Type-specific editor configuration */\n editorConfig?: Record<string, unknown>;\n /**\n * When true, this column is locked even if the parent grid is editable.\n * Used for read-only fields, computed values, or fields the current user\n * lacks permission to edit. Renders a lock icon in the column header.\n */\n isLocked?: boolean;\n\n // ── Sort & Filter ──\n /** Whether this column is sortable */\n isSortable: boolean;\n /** Default sort direction (when column is the initial sort) */\n defaultSortDirection?: 'asc' | 'desc';\n /** Whether this column is filterable */\n isFilterable: boolean;\n /** Filter control type */\n filterType?: 'text' | 'dropdown' | 'date-range' | 'number-range';\n\n // ── Footer Aggregation ──\n /** Aggregate function for footer row (only valid on numeric columns) */\n aggregateFunction?: AggregateFunction;\n\n // ── Lookup Preview ──\n /** Whether to show a lookup preview card on hover (only applies to lookup columns) */\n showLookupPreview?: boolean;\n /**\n * Field logical names (on rendererConfig.targetEntity) to render in the\n * preview card. Empty/undefined shows an empty-state prompting the user\n * to pick fields. Values are resolved against the row's Dataverse record,\n * preferring the `@OData.Community.Display.V1.FormattedValue` sibling key.\n */\n previewFields?: string[];\n\n}\n\n// ─── Grid Toolbar Configuration ───────────────────────────────────────────────\n\n/** Custom toolbar button */\nexport interface GridToolbarButton {\n /** Unique identifier */\n id: string;\n /** Button label text */\n text: string;\n /** Fluent UI icon name */\n iconName?: string;\n /** Position in toolbar */\n position: 'primary' | 'overflow';\n}\n\n/** Toolbar feature toggles */\nexport interface GridToolbarConfig {\n /** Show search box */\n showSearch: boolean;\n /** Show column filter dropdowns */\n showFilters: boolean;\n /** Show list/card view toggle */\n showViewToggle: boolean;\n /** Show export button */\n showExport: boolean;\n /** Show refresh button */\n showRefresh: boolean;\n /** Show column chooser button */\n showColumnChooser: boolean;\n /** Custom toolbar buttons */\n customButtons: GridToolbarButton[];\n}\n\n// ─── Card View Configuration ──────────────────────────────────────────────────\n\n/** Configuration for card view alternative layout */\nexport interface GridCardViewConfig {\n /** Whether card view is available */\n enabled: boolean;\n /** Number of cards per row */\n cardsPerRow: number;\n /** Card height in pixels */\n cardHeight: number;\n /** Field to use as card title */\n titleField?: string;\n /** Field to use as card subtitle */\n subtitleField?: string;\n /** Field to use as card image */\n imageField?: string;\n}\n\n// ─── Grid Customizer Definition ───────────────────────────────────────────────\n\n/**\n * Top-level grid customizer definition.\n// ─── Nested Relationship Binding ─────────────────────────────────────────────\n\n/**\n * Records which Dataverse relationship links a parent grid to its nested\n * child grid. Stored on `GridCustomizerDefinition.nestedRelationship`.\n *\n * - `parentField` is the attribute on the parent row whose value gets\n * substituted into the child grid's FetchXML placeholder.\n * - `childField` is the FK on the child entity that holds the parent's id.\n *\n * Example for Account → Contacts via `contact_customer_accounts`:\n * parentField = \"accountid\", childField = \"parentcustomerid\",\n * relatedEntity = \"contact\", relationshipType = \"OneToMany\".\n */\nexport interface GridNestedRelationship {\n /** Dataverse relationship SchemaName. */\n schemaName: string;\n /** Relationship type. */\n relationshipType: 'OneToMany' | 'ManyToOne';\n /** Child entity logical name (should match the nested grid's dataSource.entityName). */\n relatedEntity: string;\n /** Parent attribute that provides the id to substitute into the child FetchXML. */\n parentField: string;\n /** Child attribute holding the FK back to the parent. */\n childField: string;\n /** Friendly label shown in the designer + codegen comments. */\n displayName?: string;\n}\n\n// ─── Lookup Preview Configuration ────────────────────────────────────────────\n\n/** Configuration for lookup preview cards shown on hover */\nexport interface LookupPreviewConfig {\n /** Master toggle for the entire grid */\n enabled: boolean;\n /** Hover delay in ms before showing preview (default: 300) */\n hoverDelay?: number;\n /** Card width in pixels (default: 320) */\n cardWidth?: number;\n}\n\n// ─── Nested Display Configuration ────────────────────────────────────────────\n\n/**\n * How nested-grid child records surface to the user. The `inline` mode is the\n * legacy chevron-expand affordance available on standard / editable / readonly\n * grids. The other three modes are required for `focused-view` and `card-list`\n * grids, which have no row-level chevron column.\n *\n * - `inline` — chevron expand under the parent row (DetailsList only)\n * - `detail-pane` — render child grid inside the focused-view detail panel\n * - `side-panel` — click \"View N related\" → opens a Panel with full child grid\n * - `hover-callout` — hover a count badge → compact callout preview (top N rows)\n */\nexport type NestedDisplayMode = 'inline' | 'detail-pane' | 'side-panel' | 'hover-callout';\n\n/** Side-panel size token shared with Fluent UI Panel sizes */\nexport type NestedSidePanelSize = 'small' | 'medium' | 'large';\n\n/**\n * Configuration for how a parent grid's nested grid (`nestedGridId`) surfaces\n * its child records. Lives on the parent `GridCustomizerDefinition` — the\n * single source of truth — and is ignored when `nestedGridId` is unset.\n *\n * Defaults populated by the v11 → v12 migration based on parent `gridType`:\n * - focused-view → `{ mode: 'detail-pane' }`\n * - card-list → `{ mode: 'side-panel', panelSize: 'medium' }`\n * - everything else → `{ mode: 'inline' }`\n */\nexport interface NestedDisplayConfig {\n /** How children are surfaced. */\n mode: NestedDisplayMode;\n /** Side-panel size when `mode === 'side-panel'`. Default: 'medium'. */\n panelSize?: NestedSidePanelSize;\n /** Hover delay (ms) when `mode === 'hover-callout'`. Default: 300. */\n hoverDelay?: number;\n /** Max rows shown in the hover-callout preview. Default: 5. */\n calloutMaxRows?: number;\n /**\n * Trigger label rendered on cards / detail-pane buttons. Supports the\n * `{count}` token for child-row count substitution. Default:\n * \"View {count} related\".\n */\n triggerLabel?: string;\n /** Fluent icon name for the trigger button. Default: 'OpenPaneMirrored'. */\n triggerIcon?: string;\n}\n\n// ─── Grid Customizer Definition ──────────────────────────────────────────────\n\n/**\n * Lives at the project level (FormBuilderProject.gridCustomizers[]).\n * Can be referenced by subgrid controls via gridCustomizerId.\n */\nexport interface GridCustomizerDefinition {\n /** Unique identifier */\n id: string;\n /** Display name */\n name: string;\n /** Optional description */\n description?: string;\n /**\n * @deprecated Use `dataSource.entityName` instead. Kept for schema\n * compatibility; the v6 → v7 migration synthesizes a `dataSource` from this\n * field when it is the only signal available.\n */\n entityName?: string;\n\n /**\n * Dataverse data source this grid renders. When set, unlocks the column\n * picker, metadata-aware previews, and DAL emission on export. Grids without\n * a `dataSource` still work as free-form renderer overlays (legacy behavior).\n */\n dataSource?: FormDataSource;\n\n /**\n * Saved Dataverse view ID (`savedqueryid`/`userqueryid`) selected as the\n * default view for this grid. When set, the dataSource.fetchXml is normally\n * derived from this view's authored query. Optional — grids can also use\n * custom FetchXML (`useCustomFetchXml`) or no view binding at all.\n */\n viewId?: string;\n /**\n * When true, `dataSource.fetchXml` is treated as user-authored and `viewId`\n * is ignored. Mirrors the legacy subgrid-control `useCustomFetchXml` flag.\n */\n useCustomFetchXml?: boolean;\n /** When true, runtime users can switch views from the grid header. */\n allowViewChange?: boolean;\n\n /** Column definitions */\n columns: GridColumnDefinition[];\n\n // ── Grid-level settings ──\n /**\n * Layout/behavior mode. New code should branch on this enum; the older\n * `isEditable` boolean and `cardView.enabled` flag are derived/back-filled\n * from it for backward compatibility.\n */\n gridType?: GridType;\n /**\n * Maximum number of rows visible in the form-embedded preview / generated\n * subgrid. Distinct from `pageSize` (which controls Dataverse paging in\n * standalone PCF exports). Mirrors the legacy subgrid `maxRows`.\n */\n maxVisibleRows?: number;\n /** Row selection mode */\n selectionMode: GridSelectionMode;\n /** Pagination strategy */\n paginationMode: GridPaginationMode;\n /** Rows per page */\n pageSize: number;\n /** Whether to show the command bar above the grid */\n showCommandBar: boolean;\n /**\n * Whether command bar buttons display labels next to icons. When false,\n * buttons render as icon-only. Per-item `iconOnly` overrides this for\n * individual items. Defaults to true (set by the storage migration for\n * legacy projects). Mirrors `form.settings.showCommandBarLabels`.\n */\n showCommandBarLabels?: boolean;\n /**\n * Whether the grid allows inline cell editing. When false, all cells are\n * read-only regardless of column-level editor settings. When true, cells\n * can be edited unless the column has `isLocked` set.\n */\n isEditable: boolean;\n /** Toolbar feature configuration */\n toolbar: GridToolbarConfig;\n /** Card view configuration */\n cardView: GridCardViewConfig;\n /** Whether to show the grid title/name header */\n showTitle: boolean;\n /** Use compact row height */\n compactMode: boolean;\n /** Alternate row background color */\n alternateRowColor: boolean;\n /** Colors for alternating rows (used when alternateRowColor is true) */\n alternateRowColors?: { even: string; odd: string };\n\n /** References another GridCustomizerDefinition by ID for expandable row detail */\n nestedGridId?: string;\n /**\n * Controls whether child-row selection in the nested grid requires the parent\n * row to be selected first. 'independent' (default) lets users tick child rows\n * without selecting the parent; 'requires-parent' disables child checkboxes\n * until the parent row is selected (gating).\n */\n nestedSelectionMode?: 'independent' | 'requires-parent';\n\n /**\n * When set, the nested grid is bound to a Dataverse relationship so each\n * parent row expands to show its actual child records. Without this field\n * the nested grid renders static/mock data (legacy behavior).\n */\n nestedRelationship?: GridNestedRelationship;\n\n /**\n * How nested-grid child records surface when the parent gridType doesn't\n * support inline expansion (focused-view, card-list). Ignored when\n * `nestedGridId` is unset. The v11 → v12 migration backfills sensible\n * defaults based on the parent gridType.\n */\n nestedDisplay?: NestedDisplayConfig;\n\n /** Command bar items (shown when showCommandBar is true) */\n commandBarItems?: CommandBarItem[];\n\n /** Lookup preview card configuration for hover-over-lookup behavior */\n lookupPreview?: LookupPreviewConfig;\n\n /** Chart pane configuration (only used when chart features are enabled) */\n chart?: GridChartConfig;\n /**\n * Focused-view configuration. Populated from the legacy subgrid\n * `focusedViewConfig` / `primaryNameField` / `summaryFields` properties\n * during migration. Read only when `gridType === 'focused-view'`.\n */\n focusedView?: GridFocusedViewSettings;\n\n // ── Sample/Preview Data ──\n /** Sample data rows for preview */\n sampleData?: Record<string, unknown>[];\n /** How sample data is sourced */\n sampleDataMode: 'auto' | 'manual' | 'dataverse';\n\n /** ISO timestamp of creation */\n createdAt: string;\n /** ISO timestamp of last modification */\n updatedAt: string;\n}\n\n// ─── Factory Functions ────────────────────────────────────────────────────────\n\nlet _gridIdCounter = 0;\n\n/** Generate a unique ID for grid customizer entities */\nexport function generateGridId(prefix: string = 'gc'): string {\n _gridIdCounter++;\n return `${prefix}_${Date.now()}_${_gridIdCounter}`;\n}\n\n/** Create a default grid column definition */\nexport function createDefaultGridColumn(overrides?: Partial<GridColumnDefinition>): GridColumnDefinition {\n return {\n id: generateGridId('col'),\n fieldName: '',\n displayName: 'New Column',\n dataType: 'text',\n width: 150,\n minWidth: 50,\n isResizable: true,\n isVisible: true,\n order: 0,\n rendererType: 'text',\n rendererConfig: {},\n isSortable: true,\n isFilterable: false,\n ...overrides,\n };\n}\n\n/** Create a default grid toolbar configuration */\nexport function createDefaultToolbarConfig(): GridToolbarConfig {\n return {\n showSearch: true,\n showFilters: false,\n showViewToggle: false,\n showExport: false,\n showRefresh: true,\n showColumnChooser: false,\n customButtons: [],\n };\n}\n\n/** Create a default card view configuration */\nexport function createDefaultCardViewConfig(): GridCardViewConfig {\n return {\n enabled: false,\n cardsPerRow: 3,\n cardHeight: 200,\n };\n}\n\n/** Create a default grid customizer definition */\nexport function createDefaultGridCustomizer(overrides?: Partial<GridCustomizerDefinition>): GridCustomizerDefinition {\n const now = new Date().toISOString();\n return {\n id: generateGridId('grid'),\n name: 'New Grid',\n columns: [\n createDefaultGridColumn({ fieldName: 'name', displayName: 'Name', order: 0 }),\n createDefaultGridColumn({ fieldName: 'status', displayName: 'Status', dataType: 'optionset', rendererType: 'optionset', order: 1 }),\n createDefaultGridColumn({ fieldName: 'created', displayName: 'Created On', dataType: 'date', rendererType: 'date', order: 2 }),\n ],\n gridType: 'standard',\n maxVisibleRows: 5,\n selectionMode: 'multiple',\n paginationMode: 'pagination',\n pageSize: 10,\n showCommandBar: true,\n showCommandBarLabels: true,\n isEditable: false,\n allowViewChange: true,\n toolbar: createDefaultToolbarConfig(),\n cardView: createDefaultCardViewConfig(),\n showTitle: true,\n compactMode: false,\n alternateRowColor: true,\n alternateRowColors: { even: '#ffffff', odd: '#faf9f8' },\n nestedSelectionMode: 'independent',\n commandBarItems: [\n { id: 'cmd-new', text: 'New', iconName: 'Add', position: 'primary' as const, actionType: 'new' as const },\n { id: 'cmd-delete', text: 'Delete', iconName: 'Delete', position: 'primary' as const, actionType: 'delete' as const },\n ],\n lookupPreview: { enabled: true },\n sampleDataMode: 'auto',\n createdAt: now,\n updatedAt: now,\n ...overrides,\n };\n}\n\n/** Create a default composite slot */\nexport function createDefaultCompositeSlot(type: CompositeSlotType = 'text'): CompositeSlot {\n return {\n id: generateGridId('slot'),\n type,\n };\n}\n\n/** Create a default composite renderer config */\nexport function createDefaultCompositeRendererConfig(): CompositeRendererConfig {\n return {\n layout: 'horizontal',\n gap: 4,\n slots: [\n createDefaultCompositeSlot('text'),\n ],\n };\n}\n"],"mappings":"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","names":[]}
|