@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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/businessRules-U1_MBgyG.d.cts +372 -0
  4. package/dist/businessRules-U1_MBgyG.d.ts +372 -0
  5. package/dist/context.cjs +151 -0
  6. package/dist/context.cjs.map +1 -0
  7. package/dist/context.d.cts +132 -0
  8. package/dist/context.d.ts +132 -0
  9. package/dist/context.mjs +113 -0
  10. package/dist/context.mjs.map +1 -0
  11. package/dist/control-DFOg_pc_.d.cts +1027 -0
  12. package/dist/control-DaXBm-52.d.ts +1027 -0
  13. package/dist/gridCustomizer-C0V9FAE_.d.ts +569 -0
  14. package/dist/gridCustomizer-mJO-kmQ4.d.cts +569 -0
  15. package/dist/hooks.cjs +85 -0
  16. package/dist/hooks.cjs.map +1 -0
  17. package/dist/hooks.d.cts +24 -0
  18. package/dist/hooks.d.ts +24 -0
  19. package/dist/hooks.mjs +60 -0
  20. package/dist/hooks.mjs.map +1 -0
  21. package/dist/icons.cjs +202 -0
  22. package/dist/icons.cjs.map +1 -0
  23. package/dist/icons.d.cts +130 -0
  24. package/dist/icons.d.ts +130 -0
  25. package/dist/icons.mjs +165 -0
  26. package/dist/icons.mjs.map +1 -0
  27. package/dist/index.cjs +6509 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +410 -0
  30. package/dist/index.d.ts +410 -0
  31. package/dist/index.mjs +6490 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/runtime-capabilities-BdGDdu0d.d.cts +119 -0
  34. package/dist/runtime-capabilities-Brfc7loJ.d.ts +119 -0
  35. package/dist/theme-BfeZIxmZ.d.cts +74 -0
  36. package/dist/theme-BfeZIxmZ.d.ts +74 -0
  37. package/dist/theme.cjs +215 -0
  38. package/dist/theme.cjs.map +1 -0
  39. package/dist/theme.d.cts +32 -0
  40. package/dist/theme.d.ts +32 -0
  41. package/dist/theme.mjs +186 -0
  42. package/dist/theme.mjs.map +1 -0
  43. package/dist/types.cjs +976 -0
  44. package/dist/types.cjs.map +1 -0
  45. package/dist/types.d.cts +813 -0
  46. package/dist/types.d.ts +813 -0
  47. package/dist/types.mjs +902 -0
  48. package/dist/types.mjs.map +1 -0
  49. package/dist/utils.cjs +250 -0
  50. package/dist/utils.cjs.map +1 -0
  51. package/dist/utils.d.cts +99 -0
  52. package/dist/utils.d.ts +99 -0
  53. package/dist/utils.mjs +220 -0
  54. package/dist/utils.mjs.map +1 -0
  55. package/dist/v8.cjs +4622 -0
  56. package/dist/v8.cjs.map +1 -0
  57. package/dist/v8.d.cts +730 -0
  58. package/dist/v8.d.ts +730 -0
  59. package/dist/v8.mjs +4622 -0
  60. package/dist/v8.mjs.map +1 -0
  61. package/dist/v9.cjs +19 -0
  62. package/dist/v9.cjs.map +1 -0
  63. package/dist/v9.d.cts +2 -0
  64. package/dist/v9.d.ts +2 -0
  65. package/dist/v9.mjs +1 -0
  66. package/dist/v9.mjs.map +1 -0
  67. package/package.json +113 -0
package/dist/utils.mjs ADDED
@@ -0,0 +1,220 @@
1
+ // src/types/form.ts
2
+ var DEFAULT_FORM_GRID_LAYOUT = {
3
+ columns: 12,
4
+ rowHeight: 80,
5
+ gap: 12
6
+ };
7
+ function isMainTab(tab) {
8
+ return tab.tabType === "main" || !("tabType" in tab) || tab.tabType === void 0;
9
+ }
10
+
11
+ // src/types/theme.ts
12
+ var THEME_PRESETS = [
13
+ {
14
+ id: "soft-blue",
15
+ name: "Soft blue",
16
+ colors: {
17
+ primary: "#0078d4",
18
+ primaryDark: "#005a9e",
19
+ headerBackground: "#ffffff",
20
+ sectionBackground: "#ffffff",
21
+ canvasBackground: "#f3f2f1",
22
+ textPrimary: "#323130",
23
+ textSecondary: "#605e5c",
24
+ border: "#edebe9",
25
+ inputBackground: "#ffffff",
26
+ inputBorder: "#8a8886"
27
+ }
28
+ },
29
+ {
30
+ id: "blue",
31
+ name: "Blue",
32
+ colors: {
33
+ primary: "#0078d4",
34
+ primaryDark: "#005a9e",
35
+ headerBackground: "#0078d4",
36
+ sectionBackground: "#e6f2ff",
37
+ canvasBackground: "#e6f2ff",
38
+ textPrimary: "#323130",
39
+ textSecondary: "#605e5c",
40
+ border: "#c7e0f4",
41
+ inputBackground: "#ffffff",
42
+ inputBorder: "#0078d4"
43
+ }
44
+ },
45
+ {
46
+ id: "light",
47
+ name: "Light",
48
+ colors: {
49
+ primary: "#605e5c",
50
+ primaryDark: "#3b3a39",
51
+ headerBackground: "#f5f5f5",
52
+ sectionBackground: "#ffffff",
53
+ canvasBackground: "#fafafa",
54
+ textPrimary: "#323130",
55
+ textSecondary: "#605e5c",
56
+ border: "#e1dfdd",
57
+ inputBackground: "#ffffff",
58
+ inputBorder: "#8a8886"
59
+ }
60
+ },
61
+ {
62
+ id: "coral",
63
+ name: "Coral",
64
+ colors: {
65
+ primary: "#d83b01",
66
+ primaryDark: "#a52a00",
67
+ headerBackground: "#d83b01",
68
+ sectionBackground: "#fff4f0",
69
+ canvasBackground: "#fff4f0",
70
+ textPrimary: "#323130",
71
+ textSecondary: "#605e5c",
72
+ border: "#f3d6cd",
73
+ inputBackground: "#ffffff",
74
+ inputBorder: "#d83b01"
75
+ }
76
+ },
77
+ {
78
+ id: "red",
79
+ name: "Red",
80
+ colors: {
81
+ primary: "#a4262c",
82
+ primaryDark: "#7e1e23",
83
+ headerBackground: "#a4262c",
84
+ sectionBackground: "#fdf3f4",
85
+ canvasBackground: "#fdf3f4",
86
+ textPrimary: "#323130",
87
+ textSecondary: "#605e5c",
88
+ border: "#f1d3d5",
89
+ inputBackground: "#ffffff",
90
+ inputBorder: "#a4262c"
91
+ }
92
+ },
93
+ {
94
+ id: "steel",
95
+ name: "Steel",
96
+ colors: {
97
+ primary: "#004578",
98
+ primaryDark: "#002d4e",
99
+ headerBackground: "#004578",
100
+ sectionBackground: "#f0f4f7",
101
+ canvasBackground: "#e8eef2",
102
+ textPrimary: "#323130",
103
+ textSecondary: "#605e5c",
104
+ border: "#c8d4dc",
105
+ inputBackground: "#ffffff",
106
+ inputBorder: "#004578"
107
+ }
108
+ },
109
+ {
110
+ id: "dune",
111
+ name: "Dune",
112
+ colors: {
113
+ primary: "#7a6855",
114
+ primaryDark: "#5c4f3f",
115
+ headerBackground: "#7a6855",
116
+ sectionBackground: "#f5f3f0",
117
+ canvasBackground: "#e8e4df",
118
+ textPrimary: "#323130",
119
+ textSecondary: "#605e5c",
120
+ border: "#d6d0c8",
121
+ inputBackground: "#ffffff",
122
+ inputBorder: "#7a6855"
123
+ }
124
+ },
125
+ {
126
+ id: "lavender",
127
+ name: "Lavender",
128
+ colors: {
129
+ primary: "#5c2d91",
130
+ primaryDark: "#472270",
131
+ headerBackground: "#5c2d91",
132
+ sectionBackground: "#f9f5ff",
133
+ canvasBackground: "#f5f0fa",
134
+ textPrimary: "#323130",
135
+ textSecondary: "#605e5c",
136
+ border: "#e1d4f0",
137
+ inputBackground: "#ffffff",
138
+ inputBorder: "#5c2d91"
139
+ }
140
+ },
141
+ {
142
+ id: "brown",
143
+ name: "Brown",
144
+ colors: {
145
+ primary: "#6d4c41",
146
+ primaryDark: "#4e362e",
147
+ headerBackground: "#6d4c41",
148
+ sectionBackground: "#f5f0ed",
149
+ canvasBackground: "#ebe5e1",
150
+ textPrimary: "#323130",
151
+ textSecondary: "#605e5c",
152
+ border: "#d4ccc7",
153
+ inputBackground: "#ffffff",
154
+ inputBorder: "#6d4c41"
155
+ }
156
+ }
157
+ ];
158
+ var DEFAULT_THEME = THEME_PRESETS[0];
159
+
160
+ // src/utils/gridLayoutUtils.ts
161
+ function pickGridTab(form) {
162
+ const firstMain = form.tabs.find((t) => isMainTab(t));
163
+ if (firstMain && isMainTab(firstMain)) {
164
+ return { tabId: firstMain.id, sections: firstMain.sections };
165
+ }
166
+ return { tabId: null, sections: form.sections };
167
+ }
168
+ function gridPositionStyle(section, layout) {
169
+ const pos = section.gridPosition;
170
+ if (!pos) {
171
+ const halfWidth = Math.max(1, Math.floor(layout.columns / 2));
172
+ return { gridColumn: `span ${halfWidth}`, gridRow: "span 4" };
173
+ }
174
+ const w = Math.max(1, Math.min(pos.w, layout.columns));
175
+ const h = Math.max(1, pos.h);
176
+ const x = Math.max(0, Math.min(pos.x, Math.max(0, layout.columns - 1)));
177
+ const y = Math.max(0, pos.y);
178
+ return {
179
+ gridColumnStart: x + 1,
180
+ gridColumnEnd: `span ${w}`,
181
+ gridRowStart: y + 1,
182
+ gridRowEnd: `span ${h}`
183
+ };
184
+ }
185
+ function resolveGridLayout(form) {
186
+ return form.gridLayout ?? DEFAULT_FORM_GRID_LAYOUT;
187
+ }
188
+
189
+ // src/utils/backgroundUtils.ts
190
+ function backgroundConfigToCss(config, fallbackColor) {
191
+ if (!config) {
192
+ return fallbackColor ? { backgroundColor: fallbackColor } : {};
193
+ }
194
+ switch (config.type) {
195
+ case "color":
196
+ return { backgroundColor: config.color };
197
+ case "image":
198
+ return {
199
+ backgroundImage: `url(${config.src})`,
200
+ backgroundSize: config.size,
201
+ backgroundPosition: config.position,
202
+ backgroundRepeat: config.repeat
203
+ };
204
+ case "gradient": {
205
+ const stops = config.stops.slice().sort((a, b) => a.position - b.position).map((s) => `${s.color} ${s.position}%`).join(", ");
206
+ return {
207
+ background: `linear-gradient(${config.direction}deg, ${stops})`
208
+ };
209
+ }
210
+ default:
211
+ return fallbackColor ? { backgroundColor: fallbackColor } : {};
212
+ }
213
+ }
214
+ export {
215
+ backgroundConfigToCss,
216
+ gridPositionStyle,
217
+ pickGridTab,
218
+ resolveGridLayout
219
+ };
220
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/form.ts","../src/types/theme.ts","../src/utils/gridLayoutUtils.ts","../src/utils/backgroundUtils.ts"],"sourcesContent":["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","import {\n DEFAULT_FORM_GRID_LAYOUT,\n FormDefinition,\n FormGridLayout,\n FormSection,\n isMainTab,\n} from '../types';\n\n/**\n * Narrow inline-style shape returned by `gridPositionStyle`. Structurally\n * compatible with `React.CSSProperties` (callers spread or assign it\n * directly onto a `style={{...}}` prop), but declared locally so we don't\n * pull `@types/react` into the package's *return-type* surface. The\n * monorepo has two copies of `@types/react` resolved (one under\n * dynamics-toolkit/node_modules, one under dynamics-ui-kit/node_modules);\n * using `CSSProperties` here makes form-builder consumers see a different\n * type identity than their own `React.CSSProperties` and reject the value.\n */\nexport interface GridPositionStyle {\n gridColumn?: string;\n gridRow?: string;\n gridColumnStart?: number;\n gridColumnEnd?: string;\n gridRowStart?: number;\n gridRowEnd?: string;\n}\n\n/**\n * Shared helpers for rendering grid-mode (Dashboard) forms.\n *\n * Designer (`GridFormCanvas`) and preview (`GridFormPreview`) must stay in\n * lockstep — if one calculates section coordinates differently from the\n * other, the user sees one layout while authoring and a different one in\n * preview. Centralizing the helpers in form-runtime makes the designer\n * canvas, preview, generator emission path, and (Phase 1+) the shared\n * runtime mount-point all consume a single source of truth.\n */\n\n/**\n * Resolve the grid-mode form's section host. Grid mode hides the TabBar\n * and renders the first main tab's sections; falls back to flat\n * `form.sections` for forms migrated outside the normal wizard path.\n *\n * Returns `tabId: null` only when the form has no main tab at all — call\n * sites must check this before invoking section-mutating store actions\n * that need a target tab.\n */\nexport function pickGridTab(form: FormDefinition): {\n tabId: string | null;\n sections: FormSection[];\n} {\n const firstMain = form.tabs.find((t) => isMainTab(t));\n if (firstMain && isMainTab(firstMain)) {\n return { tabId: firstMain.id, sections: firstMain.sections };\n }\n return { tabId: null, sections: form.sections };\n}\n\n/**\n * Compute the inline grid-position style for a section.\n *\n * When `gridPosition` is absent we emit `span` values only (no\n * `grid-column-start`) and let CSS Grid auto-flow pack the tile. The\n * default span is half-width × four rows.\n *\n * `pos.w` is clamped to `layout.columns` so a wider-than-grid tile can't\n * overflow horizontally. `pos.h` and `pos.x` are clamped to non-negative\n * sane defaults so persisted-bad data doesn't crash the renderer.\n */\nexport function gridPositionStyle(\n section: FormSection,\n layout: FormGridLayout,\n): GridPositionStyle {\n const pos = section.gridPosition;\n if (!pos) {\n const halfWidth = Math.max(1, Math.floor(layout.columns / 2));\n return { gridColumn: `span ${halfWidth}`, gridRow: 'span 4' };\n }\n const w = Math.max(1, Math.min(pos.w, layout.columns));\n const h = Math.max(1, pos.h);\n const x = Math.max(0, Math.min(pos.x, Math.max(0, layout.columns - 1)));\n const y = Math.max(0, pos.y);\n return {\n gridColumnStart: x + 1,\n gridColumnEnd: `span ${w}`,\n gridRowStart: y + 1,\n gridRowEnd: `span ${h}`,\n };\n}\n\n/**\n * Resolve the effective grid layout (caller-provided or library default).\n * Use this instead of `form.gridLayout ?? DEFAULT_FORM_GRID_LAYOUT` at the\n * call site so future changes to the default-resolution rule live in one\n * place.\n */\nexport function resolveGridLayout(form: FormDefinition): FormGridLayout {\n return form.gridLayout ?? DEFAULT_FORM_GRID_LAYOUT;\n}\n","/**\n * backgroundUtils — converts a {@link BackgroundConfig} into CSS so the\n * shells can render per-section / per-theme color, image, or gradient\n * backgrounds.\n *\n * Vendored slim from the form-builder\n * (`apps/form-builder/src/utils/backgroundUtils.ts`) — the form-builder\n * version also owns image compression + preset palettes, which are\n * editor-side concerns and stay out of the runtime.\n */\n\nimport type { BackgroundConfig } from '../types/theme';\n\n/**\n * A subset of React.CSSProperties wide enough for any output of\n * {@link backgroundConfigToCss}. Inlined to avoid pinning the helper\n * to form-runtime's `react` / `csstype` install — downstream consumers\n * (form-builder) bring their own csstype tree and the duplicate\n * installs trip TS2322 when the type signature carries the full\n * `React.CSSProperties`.\n */\nexport type BackgroundStyle = {\n backgroundColor?: string;\n backgroundImage?: string;\n backgroundSize?: string;\n backgroundPosition?: string;\n backgroundRepeat?: string;\n background?: string;\n};\n\n/**\n * Convert a {@link BackgroundConfig} into style props. Returns a\n * solid-color fallback when `config` is undefined and `fallbackColor`\n * is supplied; otherwise an empty style object.\n */\nexport function backgroundConfigToCss(\n config: BackgroundConfig | undefined,\n fallbackColor?: string,\n): BackgroundStyle {\n if (!config) {\n return fallbackColor ? { backgroundColor: fallbackColor } : {};\n }\n\n switch (config.type) {\n case 'color':\n return { backgroundColor: config.color };\n\n case 'image':\n return {\n backgroundImage: `url(${config.src})`,\n backgroundSize: config.size,\n backgroundPosition: config.position,\n backgroundRepeat: config.repeat,\n };\n\n case 'gradient': {\n const stops = config.stops\n .slice()\n .sort((a, b) => a.position - b.position)\n .map((s) => `${s.color} ${s.position}%`)\n .join(', ');\n return {\n background: `linear-gradient(${config.direction}deg, ${stops})`,\n };\n }\n\n default:\n return fallbackColor ? { backgroundColor: fallbackColor } : {};\n }\n}\n"],"mappings":";AAgCO,IAAM,2BAA2C;AAAA,EACtD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,KAAK;AACP;AAuhBO,SAAS,UAAU,KAAkC;AAC1D,SAAO,IAAI,YAAY,UAAU,EAAE,aAAa,QAAQ,IAAI,YAAY;AAC1E;;;AClgBO,IAAM,gBAA6B;AAAA,EACxC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,eAAe;AAAA,MACf,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AACF;AAGO,IAAM,gBAAgB,cAAc,CAAC;;;AChKrC,SAAS,YAAY,MAG1B;AACA,QAAM,YAAY,KAAK,KAAK,KAAK,CAAC,MAAM,UAAU,CAAC,CAAC;AACpD,MAAI,aAAa,UAAU,SAAS,GAAG;AACrC,WAAO,EAAE,OAAO,UAAU,IAAI,UAAU,UAAU,SAAS;AAAA,EAC7D;AACA,SAAO,EAAE,OAAO,MAAM,UAAU,KAAK,SAAS;AAChD;AAaO,SAAS,kBACd,SACA,QACmB;AACnB,QAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,KAAK;AACR,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,CAAC,CAAC;AAC5D,WAAO,EAAE,YAAY,QAAQ,SAAS,IAAI,SAAS,SAAS;AAAA,EAC9D;AACA,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,OAAO,OAAO,CAAC;AACrD,QAAM,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;AAC3B,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,UAAU,CAAC,CAAC,CAAC;AACtE,QAAM,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;AAC3B,SAAO;AAAA,IACL,iBAAiB,IAAI;AAAA,IACrB,eAAe,QAAQ,CAAC;AAAA,IACxB,cAAc,IAAI;AAAA,IAClB,YAAY,QAAQ,CAAC;AAAA,EACvB;AACF;AAQO,SAAS,kBAAkB,MAAsC;AACtE,SAAO,KAAK,cAAc;AAC5B;;;AC/DO,SAAS,sBACd,QACA,eACiB;AACjB,MAAI,CAAC,QAAQ;AACX,WAAO,gBAAgB,EAAE,iBAAiB,cAAc,IAAI,CAAC;AAAA,EAC/D;AAEA,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AACH,aAAO,EAAE,iBAAiB,OAAO,MAAM;AAAA,IAEzC,KAAK;AACH,aAAO;AAAA,QACL,iBAAiB,OAAO,OAAO,GAAG;AAAA,QAClC,gBAAgB,OAAO;AAAA,QACvB,oBAAoB,OAAO;AAAA,QAC3B,kBAAkB,OAAO;AAAA,MAC3B;AAAA,IAEF,KAAK,YAAY;AACf,YAAM,QAAQ,OAAO,MAClB,MAAM,EACN,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ,GAAG,EACtC,KAAK,IAAI;AACZ,aAAO;AAAA,QACL,YAAY,mBAAmB,OAAO,SAAS,QAAQ,KAAK;AAAA,MAC9D;AAAA,IACF;AAAA,IAEA;AACE,aAAO,gBAAgB,EAAE,iBAAiB,cAAc,IAAI,CAAC;AAAA,EACjE;AACF;","names":[]}