@elliemae/loan-field-renderers 26.2.2

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 (225) hide show
  1. package/dist/cjs/ARCHITECTURE.md +434 -0
  2. package/dist/cjs/OVERVIEW.md +229 -0
  3. package/dist/cjs/bll/constants.js +86 -0
  4. package/dist/cjs/bll/formatters/booleanFormatter.js +51 -0
  5. package/dist/cjs/bll/formatters/dateFormatter.js +78 -0
  6. package/dist/cjs/bll/formatters/dropdownFormatter.js +34 -0
  7. package/dist/cjs/bll/formatters/factory/index.js +115 -0
  8. package/dist/cjs/bll/formatters/index.js +24 -0
  9. package/dist/cjs/bll/formatters/numberFormatter.js +70 -0
  10. package/dist/cjs/bll/formatters/phoneFormatter.js +57 -0
  11. package/dist/cjs/bll/formatters/regexFormatter.js +52 -0
  12. package/dist/cjs/bll/formatters/ssnFormatter.js +50 -0
  13. package/dist/cjs/bll/formatters/textFormatter.js +43 -0
  14. package/dist/cjs/bll/formatters/zipFormatter.js +48 -0
  15. package/dist/cjs/bll/index.js +62 -0
  16. package/dist/cjs/bll/ssf/index.js +48 -0
  17. package/dist/cjs/bll/ssf/loan.js +81 -0
  18. package/dist/cjs/bll/ssf/loconnect.js +70 -0
  19. package/dist/cjs/bll/ssf/ssfBase.js +97 -0
  20. package/dist/cjs/bll/ssf/types.js +16 -0
  21. package/dist/cjs/bll/types.js +16 -0
  22. package/dist/cjs/bll/validators/dateValidator.js +60 -0
  23. package/dist/cjs/bll/validators/emailValidator.js +47 -0
  24. package/dist/cjs/bll/validators/factory/index.js +81 -0
  25. package/dist/cjs/bll/validators/index.js +24 -0
  26. package/dist/cjs/bll/validators/maxCharValidator.js +49 -0
  27. package/dist/cjs/bll/validators/requiredValidator.js +44 -0
  28. package/dist/cjs/bll/validators/zipValidator.js +53 -0
  29. package/dist/cjs/core/index.js +52 -0
  30. package/dist/cjs/demo/config.js +391 -0
  31. package/dist/cjs/demo/index.js +31 -0
  32. package/dist/cjs/package.json +7 -0
  33. package/dist/cjs/renderer/FieldRenderer.js +45 -0
  34. package/dist/cjs/renderer/base/hooks/fieldDescription.js +39 -0
  35. package/dist/cjs/renderer/base/hooks/fieldDisabled.js +53 -0
  36. package/dist/cjs/renderer/base/hooks/fieldGoTo.js +50 -0
  37. package/dist/cjs/renderer/base/hooks/fieldLocked.js +42 -0
  38. package/dist/cjs/renderer/base/hooks/fieldMeta.js +150 -0
  39. package/dist/cjs/renderer/base/hooks/fieldSubscribers.js +66 -0
  40. package/dist/cjs/renderer/base/hooks/fieldValidation.js +45 -0
  41. package/dist/cjs/renderer/base/hooks/fieldValue.js +215 -0
  42. package/dist/cjs/renderer/base/hooks/hookBase.js +29 -0
  43. package/dist/cjs/renderer/base/hooks/index.js +139 -0
  44. package/dist/cjs/renderer/base/renderer.js +198 -0
  45. package/dist/cjs/renderer/base/rendererValidator.js +97 -0
  46. package/dist/cjs/renderer/factory/index.js +58 -0
  47. package/dist/cjs/renderer/field-renderers/AddonRenderer.js +75 -0
  48. package/dist/cjs/renderer/field-renderers/CheckboxRenderer.js +123 -0
  49. package/dist/cjs/renderer/field-renderers/DateRenderer.js +206 -0
  50. package/dist/cjs/renderer/field-renderers/DropdownRenderer/hook.js +99 -0
  51. package/dist/cjs/renderer/field-renderers/DropdownRenderer/index.js +216 -0
  52. package/dist/cjs/renderer/field-renderers/LargeTextRenderer.js +209 -0
  53. package/dist/cjs/renderer/field-renderers/NumberRenderer.js +216 -0
  54. package/dist/cjs/renderer/field-renderers/RadioGroupRenderer.js +128 -0
  55. package/dist/cjs/renderer/field-renderers/RadioRenderer.js +121 -0
  56. package/dist/cjs/renderer/field-renderers/TextRenderer.js +223 -0
  57. package/dist/cjs/renderer/field-renderers/ToggleRenderer.js +121 -0
  58. package/dist/cjs/renderer/field-renderers/ZipCodeRenderer/helper.js +132 -0
  59. package/dist/cjs/renderer/field-renderers/ZipCodeRenderer/hook.js +128 -0
  60. package/dist/cjs/renderer/field-renderers/ZipCodeRenderer/index.js +273 -0
  61. package/dist/cjs/renderer/index.js +24 -0
  62. package/dist/cjs/renderer/styles.js +51 -0
  63. package/dist/cjs/renderer/types.js +16 -0
  64. package/dist/cjs/tests/base/flowBase.js +125 -0
  65. package/dist/cjs/tests/base/index.js +52 -0
  66. package/dist/cjs/tests/flows/checkboxRendererFlows.js +85 -0
  67. package/dist/cjs/tests/flows/dateRendererFlows.js +870 -0
  68. package/dist/cjs/tests/flows/dropdownRendererFlows.js +591 -0
  69. package/dist/cjs/tests/flows/largeTextRendererFlows.js +99 -0
  70. package/dist/cjs/tests/flows/numberRendererFlows.js +175 -0
  71. package/dist/cjs/tests/flows/radioRendererFlows.js +115 -0
  72. package/dist/cjs/tests/flows/textRendererFlows.js +349 -0
  73. package/dist/cjs/tests/flows/toggleRendererFlows.js +106 -0
  74. package/dist/cjs/tests/flows/zipCodeRendererFlows.js +1163 -0
  75. package/dist/cjs/utils/dateHelper.js +65 -0
  76. package/dist/esm/ARCHITECTURE.md +434 -0
  77. package/dist/esm/OVERVIEW.md +229 -0
  78. package/dist/esm/bll/constants.js +66 -0
  79. package/dist/esm/bll/formatters/booleanFormatter.js +33 -0
  80. package/dist/esm/bll/formatters/dateFormatter.js +48 -0
  81. package/dist/esm/bll/formatters/dropdownFormatter.js +14 -0
  82. package/dist/esm/bll/formatters/factory/index.js +97 -0
  83. package/dist/esm/bll/formatters/index.js +4 -0
  84. package/dist/esm/bll/formatters/numberFormatter.js +54 -0
  85. package/dist/esm/bll/formatters/phoneFormatter.js +41 -0
  86. package/dist/esm/bll/formatters/regexFormatter.js +34 -0
  87. package/dist/esm/bll/formatters/ssnFormatter.js +32 -0
  88. package/dist/esm/bll/formatters/textFormatter.js +25 -0
  89. package/dist/esm/bll/formatters/zipFormatter.js +30 -0
  90. package/dist/esm/bll/index.js +44 -0
  91. package/dist/esm/bll/ssf/index.js +30 -0
  92. package/dist/esm/bll/ssf/loan.js +63 -0
  93. package/dist/esm/bll/ssf/loconnect.js +52 -0
  94. package/dist/esm/bll/ssf/ssfBase.js +67 -0
  95. package/dist/esm/bll/ssf/types.js +0 -0
  96. package/dist/esm/bll/types.js +0 -0
  97. package/dist/esm/bll/validators/dateValidator.js +30 -0
  98. package/dist/esm/bll/validators/emailValidator.js +29 -0
  99. package/dist/esm/bll/validators/factory/index.js +63 -0
  100. package/dist/esm/bll/validators/index.js +4 -0
  101. package/dist/esm/bll/validators/maxCharValidator.js +31 -0
  102. package/dist/esm/bll/validators/requiredValidator.js +26 -0
  103. package/dist/esm/bll/validators/zipValidator.js +35 -0
  104. package/dist/esm/core/index.js +34 -0
  105. package/dist/esm/demo/config.js +371 -0
  106. package/dist/esm/demo/index.js +11 -0
  107. package/dist/esm/package.json +7 -0
  108. package/dist/esm/renderer/FieldRenderer.js +15 -0
  109. package/dist/esm/renderer/base/hooks/fieldDescription.js +19 -0
  110. package/dist/esm/renderer/base/hooks/fieldDisabled.js +33 -0
  111. package/dist/esm/renderer/base/hooks/fieldGoTo.js +30 -0
  112. package/dist/esm/renderer/base/hooks/fieldLocked.js +22 -0
  113. package/dist/esm/renderer/base/hooks/fieldMeta.js +132 -0
  114. package/dist/esm/renderer/base/hooks/fieldSubscribers.js +36 -0
  115. package/dist/esm/renderer/base/hooks/fieldValidation.js +25 -0
  116. package/dist/esm/renderer/base/hooks/fieldValue.js +195 -0
  117. package/dist/esm/renderer/base/hooks/hookBase.js +9 -0
  118. package/dist/esm/renderer/base/hooks/index.js +121 -0
  119. package/dist/esm/renderer/base/renderer.js +178 -0
  120. package/dist/esm/renderer/base/rendererValidator.js +77 -0
  121. package/dist/esm/renderer/factory/index.js +38 -0
  122. package/dist/esm/renderer/field-renderers/AddonRenderer.js +55 -0
  123. package/dist/esm/renderer/field-renderers/CheckboxRenderer.js +93 -0
  124. package/dist/esm/renderer/field-renderers/DateRenderer.js +176 -0
  125. package/dist/esm/renderer/field-renderers/DropdownRenderer/hook.js +79 -0
  126. package/dist/esm/renderer/field-renderers/DropdownRenderer/index.js +186 -0
  127. package/dist/esm/renderer/field-renderers/LargeTextRenderer.js +179 -0
  128. package/dist/esm/renderer/field-renderers/NumberRenderer.js +188 -0
  129. package/dist/esm/renderer/field-renderers/RadioGroupRenderer.js +108 -0
  130. package/dist/esm/renderer/field-renderers/RadioRenderer.js +91 -0
  131. package/dist/esm/renderer/field-renderers/TextRenderer.js +197 -0
  132. package/dist/esm/renderer/field-renderers/ToggleRenderer.js +91 -0
  133. package/dist/esm/renderer/field-renderers/ZipCodeRenderer/helper.js +112 -0
  134. package/dist/esm/renderer/field-renderers/ZipCodeRenderer/hook.js +108 -0
  135. package/dist/esm/renderer/field-renderers/ZipCodeRenderer/index.js +247 -0
  136. package/dist/esm/renderer/index.js +4 -0
  137. package/dist/esm/renderer/styles.js +21 -0
  138. package/dist/esm/renderer/types.js +0 -0
  139. package/dist/esm/tests/base/flowBase.js +105 -0
  140. package/dist/esm/tests/base/index.js +22 -0
  141. package/dist/esm/tests/flows/checkboxRendererFlows.js +65 -0
  142. package/dist/esm/tests/flows/dateRendererFlows.js +850 -0
  143. package/dist/esm/tests/flows/dropdownRendererFlows.js +571 -0
  144. package/dist/esm/tests/flows/largeTextRendererFlows.js +79 -0
  145. package/dist/esm/tests/flows/numberRendererFlows.js +155 -0
  146. package/dist/esm/tests/flows/radioRendererFlows.js +95 -0
  147. package/dist/esm/tests/flows/textRendererFlows.js +329 -0
  148. package/dist/esm/tests/flows/toggleRendererFlows.js +86 -0
  149. package/dist/esm/tests/flows/zipCodeRendererFlows.js +1143 -0
  150. package/dist/esm/utils/dateHelper.js +35 -0
  151. package/dist/types/lib/bll/constants.d.ts +9 -0
  152. package/dist/types/lib/bll/formatters/booleanFormatter.d.ts +5 -0
  153. package/dist/types/lib/bll/formatters/dateFormatter.d.ts +28 -0
  154. package/dist/types/lib/bll/formatters/dropdownFormatter.d.ts +6 -0
  155. package/dist/types/lib/bll/formatters/factory/index.d.ts +71 -0
  156. package/dist/types/lib/bll/formatters/index.d.ts +2 -0
  157. package/dist/types/lib/bll/formatters/numberFormatter.d.ts +6 -0
  158. package/dist/types/lib/bll/formatters/phoneFormatter.d.ts +7 -0
  159. package/dist/types/lib/bll/formatters/regexFormatter.d.ts +5 -0
  160. package/dist/types/lib/bll/formatters/ssnFormatter.d.ts +5 -0
  161. package/dist/types/lib/bll/formatters/textFormatter.d.ts +6 -0
  162. package/dist/types/lib/bll/formatters/zipFormatter.d.ts +5 -0
  163. package/dist/types/lib/bll/index.d.ts +20 -0
  164. package/dist/types/lib/bll/ssf/index.d.ts +25 -0
  165. package/dist/types/lib/bll/ssf/loan.d.ts +16 -0
  166. package/dist/types/lib/bll/ssf/loconnect.d.ts +15 -0
  167. package/dist/types/lib/bll/ssf/ssfBase.d.ts +23 -0
  168. package/dist/types/lib/bll/ssf/types.d.ts +99 -0
  169. package/dist/types/lib/bll/types.d.ts +47 -0
  170. package/dist/types/lib/bll/validators/dateValidator.d.ts +16 -0
  171. package/dist/types/lib/bll/validators/emailValidator.d.ts +4 -0
  172. package/dist/types/lib/bll/validators/factory/index.d.ts +15 -0
  173. package/dist/types/lib/bll/validators/index.d.ts +2 -0
  174. package/dist/types/lib/bll/validators/maxCharValidator.d.ts +4 -0
  175. package/dist/types/lib/bll/validators/requiredValidator.d.ts +4 -0
  176. package/dist/types/lib/bll/validators/zipValidator.d.ts +5 -0
  177. package/dist/types/lib/core/index.d.ts +29 -0
  178. package/dist/types/lib/demo/config.d.ts +11 -0
  179. package/dist/types/lib/demo/index.d.ts +1 -0
  180. package/dist/types/lib/renderer/FieldRenderer.d.ts +5 -0
  181. package/dist/types/lib/renderer/base/hooks/fieldDescription.d.ts +5 -0
  182. package/dist/types/lib/renderer/base/hooks/fieldDisabled.d.ts +10 -0
  183. package/dist/types/lib/renderer/base/hooks/fieldGoTo.d.ts +4 -0
  184. package/dist/types/lib/renderer/base/hooks/fieldLocked.d.ts +4 -0
  185. package/dist/types/lib/renderer/base/hooks/fieldMeta.d.ts +10 -0
  186. package/dist/types/lib/renderer/base/hooks/fieldSubscribers.d.ts +6 -0
  187. package/dist/types/lib/renderer/base/hooks/fieldValidation.d.ts +9 -0
  188. package/dist/types/lib/renderer/base/hooks/fieldValue.d.ts +31 -0
  189. package/dist/types/lib/renderer/base/hooks/hookBase.d.ts +9 -0
  190. package/dist/types/lib/renderer/base/hooks/index.d.ts +19 -0
  191. package/dist/types/lib/renderer/base/renderer.d.ts +43 -0
  192. package/dist/types/lib/renderer/base/rendererValidator.d.ts +15 -0
  193. package/dist/types/lib/renderer/factory/index.d.ts +5 -0
  194. package/dist/types/lib/renderer/field-renderers/AddonRenderer.d.ts +12 -0
  195. package/dist/types/lib/renderer/field-renderers/CheckboxRenderer.d.ts +7 -0
  196. package/dist/types/lib/renderer/field-renderers/DateRenderer.d.ts +13 -0
  197. package/dist/types/lib/renderer/field-renderers/DropdownRenderer/hook.d.ts +23 -0
  198. package/dist/types/lib/renderer/field-renderers/DropdownRenderer/index.d.ts +12 -0
  199. package/dist/types/lib/renderer/field-renderers/LargeTextRenderer.d.ts +17 -0
  200. package/dist/types/lib/renderer/field-renderers/NumberRenderer.d.ts +12 -0
  201. package/dist/types/lib/renderer/field-renderers/RadioGroupRenderer.d.ts +8 -0
  202. package/dist/types/lib/renderer/field-renderers/RadioRenderer.d.ts +8 -0
  203. package/dist/types/lib/renderer/field-renderers/TextRenderer.d.ts +8 -0
  204. package/dist/types/lib/renderer/field-renderers/ToggleRenderer.d.ts +24 -0
  205. package/dist/types/lib/renderer/field-renderers/ZipCodeRenderer/helper.d.ts +48 -0
  206. package/dist/types/lib/renderer/field-renderers/ZipCodeRenderer/hook.d.ts +17 -0
  207. package/dist/types/lib/renderer/field-renderers/ZipCodeRenderer/index.d.ts +9 -0
  208. package/dist/types/lib/renderer/index.d.ts +2 -0
  209. package/dist/types/lib/renderer/styles.d.ts +7 -0
  210. package/dist/types/lib/renderer/types.d.ts +325 -0
  211. package/dist/types/lib/tests/base/flowBase.d.ts +13 -0
  212. package/dist/types/lib/tests/base/index.d.ts +6 -0
  213. package/dist/types/lib/tests/flows/checkboxRendererFlows.d.ts +9 -0
  214. package/dist/types/lib/tests/flows/dateRendererFlows.d.ts +120 -0
  215. package/dist/types/lib/tests/flows/dropdownRendererFlows.d.ts +92 -0
  216. package/dist/types/lib/tests/flows/largeTextRendererFlows.d.ts +9 -0
  217. package/dist/types/lib/tests/flows/numberRendererFlows.d.ts +11 -0
  218. package/dist/types/lib/tests/flows/radioRendererFlows.d.ts +10 -0
  219. package/dist/types/lib/tests/flows/textRendererFlows.d.ts +16 -0
  220. package/dist/types/lib/tests/flows/toggleRendererFlows.d.ts +10 -0
  221. package/dist/types/lib/tests/flows/zipCodeRendererFlows.d.ts +169 -0
  222. package/dist/types/lib/tests/loan-field-renderer-flows.test.d.ts +1 -0
  223. package/dist/types/lib/utils/dateHelper.d.ts +8 -0
  224. package/dist/types/tsconfig.tsbuildinfo +1 -0
  225. package/package.json +103 -0
@@ -0,0 +1,229 @@
1
+ # Loan Field Renderers — Overview
2
+
3
+ ## What Is It?
4
+
5
+ A **metadata-driven smart control system** that renders loan form fields. Instead of each page manually handling data fetching, formatting, validation, locking, and persistence — a single component does it all.
6
+
7
+ **Before (without smart controls):**
8
+ Each page manually wires up SSF calls, formatting logic, validation, lock handling, accessibility, and event subscriptions for every field.
9
+
10
+ **After (with smart controls):**
11
+
12
+ ```jsx
13
+ <FieldRenderer fieldId="FR0104" rendererType="number" />
14
+ ```
15
+
16
+ One line. The system handles everything else automatically.
17
+
18
+ ---
19
+
20
+ ## What Does It Do Automatically?
21
+
22
+ | Capability | Description |
23
+ | ----------------- | -------------------------------------------------------------------------------- |
24
+ | **Data Fetch** | Fetches field metadata + current value from SSF on mount |
25
+ | **Formatting** | Applies the correct display format (currency, phone, SSN, date, zip) |
26
+ | **Input Masking** | Enforces input patterns (phone mask, SSN mask, number mask) |
27
+ | **Validation** | Runs configurable validation rules on blur (required, email, date, zip, maxChar) |
28
+ | **Persistence** | Parses display value back to raw data and saves to loan on blur |
29
+ | **Lock/Unlock** | Shows lock button, manages lock state, disables field when locked |
30
+ | **Read-Only** | Respects both field-level and loan-level read-only states |
31
+ | **Live Sync** | Subscribes to loan events — auto-refreshes when data changes externally |
32
+ | **Accessibility** | Builds ARIA attributes from field description metadata |
33
+ | **Go-To-Field** | Highlights the field when navigated to via Go-To-Field |
34
+
35
+ ---
36
+
37
+ ## Supported Renderer Types
38
+
39
+ | Type | UI Component | Use Case |
40
+ | ------------ | ------------------- | --------------------------------------------------------- |
41
+ | `text` | Text input | Names, addresses, general text (supports Phone/SSN masks) |
42
+ | `number` | Masked number input | Amounts, rates, percentages |
43
+ | `date` | Date picker | Dates with configurable format |
44
+ | `dropdown` | Combobox | Picklists, state selection |
45
+ | `zipcode` | Autocomplete input | Zip codes with geo-lookup |
46
+ | `checkbox` | Checkbox | Yes/No boolean fields |
47
+ | `toggle` | Toggle switch | On/Off boolean fields |
48
+ | `radio` | Single radio button | One option in a group |
49
+ | `radiogroup` | Radio button group | Multiple exclusive options |
50
+ | `largetext` | Textarea | Comments, large text with char counter |
51
+ | `button` | Button | Action triggers |
52
+ | `email` | Text input | Email with built-in email validation |
53
+
54
+ ---
55
+
56
+ ## How It Works — 3 Phases
57
+
58
+ ### Phase 1: Mount — "Set Up the Field"
59
+
60
+ ```
61
+ Consumer passes fieldId + rendererType
62
+
63
+
64
+ RendererFactory picks the right renderer
65
+
66
+
67
+ Parallel SSF calls (all at once):
68
+ ┌─────────────────────────────────┐
69
+ │ • Field metadata (format, │
70
+ │ options, readonly, lock, │
71
+ │ maxLength, description) │
72
+ │ • Current field value │
73
+ │ • Lock status │
74
+ │ • Loan readonly status │
75
+ │ • Go-To-Field status │
76
+ └─────────────────────────────────┘
77
+
78
+
79
+ Build ViewModel → Render UI
80
+ ```
81
+
82
+ ### Phase 2: User Interaction — "Handle Input"
83
+
84
+ ```
85
+ User types → onChange
86
+ ┌────────────────────────────┐
87
+ │ Update local display value │
88
+ │ Fire optional callback │
89
+ └────────────────────────────┘
90
+
91
+ User leaves field → onBlur
92
+ ┌────────────────────────────┐
93
+ │ 1. Format the value │
94
+ │ 2. Validate │
95
+ │ └─ Invalid? Show error, │
96
+ │ stop here │
97
+ │ 3. Parse to raw value │
98
+ │ 4. Save to loan via SSF │
99
+ │ 5. Fire optional callback │
100
+ └────────────────────────────┘
101
+ ```
102
+
103
+ ### Phase 3: External Changes — "Stay in Sync"
104
+
105
+ ```
106
+ Business rule or another field changes loan data
107
+
108
+
109
+ SSF fires "change" or "loanUpdated" event
110
+
111
+
112
+ Field re-fetches its value from SSF
113
+
114
+
115
+ Re-formats and updates display automatically
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Architecture — 3 Layers
121
+
122
+ ```
123
+ ┌───────────────────────────────────────────┐
124
+ │ RENDERER LAYER │
125
+ │ UI components, event handling, ViewModel │
126
+ │ 12 specific renderers + shared base class │
127
+ ├───────────────────────────────────────────┤
128
+ │ BUSINESS LOGIC LAYER │
129
+ │ Formatters (10 types) — format ↔ parse │
130
+ │ Validators (5 types) — validate on blur │
131
+ │ SSF Service — loan data access & events │
132
+ ├───────────────────────────────────────────┤
133
+ │ DATA ACCESS LAYER (SSF) │
134
+ │ Field metadata, values, lock status │
135
+ │ Loan readonly, Go-To-Field, state list │
136
+ │ Event subscriptions (change, loanUpdated) │
137
+ └───────────────────────────────────────────┘
138
+ ```
139
+
140
+ **Key design decisions:**
141
+
142
+ - **Factory Pattern** — Renderers, formatters, and validators are created dynamically by type
143
+ - **Mixin Composition** — Hooks and SSF combine focused classes instead of deep inheritance
144
+ - **ViewModel Pattern** — All reactive state lives in one object, decoupled from UI
145
+
146
+ ---
147
+
148
+ ## How Metadata Drives Everything
149
+
150
+ When a field is loaded, its metadata from SSF determines behavior automatically:
151
+
152
+ ```
153
+ fieldFormat: "D2" → NumberFormatter with 2 decimal places
154
+ fieldFormat: "SS" → SSN mask (***-**-1234)
155
+ fieldFormat: "ST" → Auto-fetch state dropdown list
156
+ fieldFormat: "P" → Phone mask ((123) 456-7890)
157
+
158
+ isLockField: true → Show lock button, disable when locked
159
+ isReadOnly: true → Disable the field
160
+ isReadonlyLoan → Loan-level check — if loan is disabled, all fields disable
161
+ maxLength: 50 → Enforce character limit
162
+ description: "..." → Set ARIA label/description
163
+ fieldOptions: [...] → Populate dropdown choices
164
+ ```
165
+
166
+ No conditional logic needed in consuming pages — the metadata configures the control.
167
+
168
+ ---
169
+
170
+ ## Consumer API — Quick Reference
171
+
172
+ ### Basic Usage
173
+
174
+ ```jsx
175
+ // Simple text field
176
+ <FieldRenderer fieldId="FR0001" rendererType="text" label="First Name" />
177
+
178
+ // Number with label
179
+ <FieldRenderer fieldId="FR0104" rendererType="number" label="Loan Amount" />
180
+
181
+ // Dropdown
182
+ <FieldRenderer fieldId="FR0200" rendererType="dropdown" label="State" />
183
+
184
+ // Date
185
+ <FieldRenderer fieldId="FR0300" rendererType="date" label="Closing Date" />
186
+ ```
187
+
188
+ ### With Optional Props
189
+
190
+ ```jsx
191
+ <FieldRenderer
192
+ fieldId="FR0104"
193
+ rendererType="number"
194
+ label="Loan Amount"
195
+ onBlur={(fieldId, value) => console.log('saved', value)}
196
+ onChange={(fieldId, value) => console.log('changed', value)}
197
+ validationRules={[{ type: 'required', message: 'Amount is required' }]}
198
+ autoFieldSet={true} // auto-persist on blur (default: true)
199
+ isDisable={false} // consumer-controlled disable
200
+ forceRefresh={refreshFlag} // trigger re-fetch from SSF
201
+ />
202
+ ```
203
+
204
+ ### Common Props
205
+
206
+ | Prop | Type | Purpose |
207
+ | ----------------- | ----------- | ------------------------------------------------- |
208
+ | `fieldId` | `string` | **(Required)** SSF field identifier |
209
+ | `rendererType` | `string` | **(Required)** Which UI control to render |
210
+ | `label` | `string` | Field label text |
211
+ | `onBlur` | `function` | Callback after value is validated and parsed |
212
+ | `onChange` | `function` | Callback on every value change |
213
+ | `validationRules` | `array` | Custom validation rules |
214
+ | `autoFieldSet` | `boolean` | Auto-save to SSF on blur (default: `true`) |
215
+ | `isDisable` | `boolean` | Consumer-controlled disable override |
216
+ | `forceRefresh` | `boolean` | Toggle to force re-fetch from SSF |
217
+ | `extra` | `object` | Pass-through props to the underlying DS component |
218
+ | `rightAddon` | `ReactNode` | Custom content to the right of the input |
219
+
220
+ ---
221
+
222
+ ## Adding a New Renderer (Extensibility)
223
+
224
+ 1. Create `NewRenderer.tsx` extending `Renderer<T>`
225
+ 2. Define props type in `renderer/types.ts`
226
+ 3. Register in `renderer/factory/index.ts`
227
+ 4. Add controlled props to `bll/constants.ts` (if applicable)
228
+
229
+ The factory pattern means zero changes to existing renderers or the `FieldRenderer` entry point.
@@ -0,0 +1,66 @@
1
+ const ACRONYM_FORMAT_MAP = {
2
+ SS: "SSN",
3
+ T: "DATE",
4
+ TD: "DATETIME",
5
+ I: "INTEGER",
6
+ S: "STRING",
7
+ Y: "YN",
8
+ P: "PHONE",
9
+ Z: "ZIPCODE",
10
+ ST: "STATE",
11
+ D2: "DECIMAL_2",
12
+ D3: "DECIMAL_3",
13
+ D4: "DECIMAL_4",
14
+ D5: "DECIMAL_5",
15
+ D6: "DECIMAL_6",
16
+ RS: "RA_STRING",
17
+ RI: "RA_INTEGER",
18
+ RD2: "RA_DECIMAL_2",
19
+ RD3: "RA_DECIMAL_3",
20
+ RD4: "RA_DECIMAL_4",
21
+ RD5: "RA_DECIMAL_5",
22
+ RD6: "RA_DECIMAL_6"
23
+ };
24
+ const FORMATTER_ACRONYMS = Object.keys(ACRONYM_FORMAT_MAP);
25
+ const FORMATTER_TYPE_MAP = {
26
+ ssn: "SSN",
27
+ date: "DATE",
28
+ datetime: "DATETIME",
29
+ integer: "INTEGER",
30
+ text: "STRING",
31
+ boolean: "YN",
32
+ phone: "PHONE",
33
+ zip: "ZIPCODE",
34
+ state: "STATE"
35
+ };
36
+ const DECIMAL_POINTS_MAP = {
37
+ D2: 2,
38
+ D3: 3,
39
+ D4: 4,
40
+ D5: 5,
41
+ D6: 6,
42
+ I: 0,
43
+ RI: 0,
44
+ RD2: 2,
45
+ RD3: 3,
46
+ RD4: 4,
47
+ RD5: 5,
48
+ RD6: 6
49
+ };
50
+ const RENDERER_CONTROLLED_PROPS = {
51
+ text: ["onChange", "onBlur", "value"],
52
+ number: ["onChange", "onBlur", "value"],
53
+ checkbox: ["value", "checked", "onChange"],
54
+ toggle: ["checked", "onChange"],
55
+ radio: ["onChange", "value", "checked"],
56
+ dropdown: ["selectedValues", "allOptions", "onChange", "onBlur", "onCreate"],
57
+ date: ["onDateChange", "onBlur", "date", "type"],
58
+ zipcode: ["onChange", "onBlur", "value", "onValueChange"]
59
+ };
60
+ export {
61
+ ACRONYM_FORMAT_MAP,
62
+ DECIMAL_POINTS_MAP,
63
+ FORMATTER_ACRONYMS,
64
+ FORMATTER_TYPE_MAP,
65
+ RENDERER_CONTROLLED_PROPS
66
+ };
@@ -0,0 +1,33 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ let BooleanFormatter = class {
13
+ format(input) {
14
+ if (input === null || input === void 0) return false;
15
+ if (typeof input === "boolean") return input;
16
+ if (input === "") return false;
17
+ if (typeof input === "string") {
18
+ const lower = input.toLowerCase();
19
+ return lower === "true" || lower === "yes" || lower === "y";
20
+ }
21
+ return Boolean(input);
22
+ }
23
+ parse(input) {
24
+ if (input === null || input === void 0) return void 0;
25
+ return input;
26
+ }
27
+ };
28
+ BooleanFormatter = __decorateClass([
29
+ decorators.class.Singleton
30
+ ], BooleanFormatter);
31
+ export {
32
+ BooleanFormatter
33
+ };
@@ -0,0 +1,48 @@
1
+ import moment from "moment";
2
+ class DateFormatter {
3
+ /**
4
+ * Format date from storage format to display format
5
+ * @param {unknown} input - Date value from loan (typically YYYY-MM-DD or ISO format)
6
+ * @param {object} [options] - Formatting options
7
+ * @param {string} [options.dateFormat] - Target display format (default: MM/DD/YYYY)
8
+ * @returns {string} Formatted date string for UI display
9
+ */
10
+ format(input, options) {
11
+ if (!input || input === "") return "";
12
+ const dateStr = String(input).trim();
13
+ if (!dateStr) return "";
14
+ if (dateStr.includes("_")) return dateStr;
15
+ const displayFormat = options?.dateFormat || "MM/DD/YYYY";
16
+ if (moment(dateStr, displayFormat, true).isValid()) {
17
+ return dateStr;
18
+ }
19
+ const parsed = moment(dateStr);
20
+ if (parsed.isValid()) {
21
+ return parsed.format(displayFormat);
22
+ }
23
+ return "";
24
+ }
25
+ /**
26
+ * Parse date from display format to storage format
27
+ * @param {string} input - Date string from UI (in display format)
28
+ * @param {object} [options] - Parsing options
29
+ * @param {string} [options.dateFormat] - Source display format (default: MM/DD/YYYY)
30
+ * @returns {unknown} Date in storage format (YYYY-MM-DD)
31
+ */
32
+ parse(input, options) {
33
+ if (!input || input === "") return "";
34
+ const displayFormat = options?.dateFormat || "MM/DD/YYYY";
35
+ let parsed = moment(input, displayFormat, true);
36
+ if (parsed.isValid()) {
37
+ return parsed.format("YYYY-MM-DD");
38
+ }
39
+ parsed = moment(input);
40
+ if (parsed.isValid()) {
41
+ return parsed.format("YYYY-MM-DD");
42
+ }
43
+ return input;
44
+ }
45
+ }
46
+ export {
47
+ DateFormatter
48
+ };
@@ -0,0 +1,14 @@
1
+ class DropdownFormatter {
2
+ format(input) {
3
+ if (typeof input === "boolean") {
4
+ return input ? "Y" : "N";
5
+ }
6
+ return input ? String(input) : "";
7
+ }
8
+ parse(input) {
9
+ return input;
10
+ }
11
+ }
12
+ export {
13
+ DropdownFormatter
14
+ };
@@ -0,0 +1,97 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ import { PhoneFormatter } from "../phoneFormatter";
13
+ import { RegexFormatter } from "../regexFormatter";
14
+ import { SSNFormatter } from "../ssnFormatter";
15
+ import { ZipFormatter } from "../zipFormatter";
16
+ import { BooleanFormatter } from "../booleanFormatter";
17
+ import { TextFormatter } from "../textFormatter";
18
+ import { NumberFormatter } from "../numberFormatter";
19
+ import { DateFormatter } from "../dateFormatter";
20
+ import { DropdownFormatter } from "../dropdownFormatter";
21
+ let FormatterFactory = class {
22
+ /**
23
+ * Cache for formatter instances by acronym
24
+ * Since formatters are stateless, we can safely reuse instances
25
+ */
26
+ formatterCache = /* @__PURE__ */ new Map();
27
+ /**
28
+ * Helper to check if acronym is for number formatter
29
+ * @param {string} acronym - Acronym to check
30
+ * @returns {boolean} True if number formatter
31
+ */
32
+ isNumberAcronym(acronym) {
33
+ if (acronym === "DT" || acronym === "DD") return false;
34
+ return acronym === "I" || acronym === "RI" || acronym.startsWith("D") || acronym.startsWith("RD");
35
+ }
36
+ /**
37
+ * Helper to check if acronym is for text formatter
38
+ * @param {string} acronym - Acronym to check
39
+ * @returns {boolean} True if text formatter
40
+ */
41
+ isTextAcronym(acronym) {
42
+ return acronym === "S" || acronym === "ST";
43
+ }
44
+ /**
45
+ * Create specific formatter based on acronym
46
+ * @param {string} upperAcronym - Uppercase acronym
47
+ * @returns Specific formatter instance
48
+ */
49
+ createSpecificFormatter(upperAcronym) {
50
+ switch (upperAcronym) {
51
+ case "SS":
52
+ return new SSNFormatter();
53
+ case "Y":
54
+ return new BooleanFormatter();
55
+ case "P":
56
+ return new PhoneFormatter();
57
+ case "Z":
58
+ return new ZipFormatter();
59
+ case "RS":
60
+ return new RegexFormatter();
61
+ case "DD":
62
+ return new DropdownFormatter();
63
+ case "DT":
64
+ case "TD":
65
+ case "T":
66
+ return new DateFormatter();
67
+ default:
68
+ throw new Error(
69
+ `Unknown acronym "${upperAcronym}". Cannot create formatter.`
70
+ );
71
+ }
72
+ }
73
+ createFormatter(acronym) {
74
+ const upperAcronym = acronym.toUpperCase();
75
+ const cached = this.formatterCache.get(upperAcronym);
76
+ if (cached) {
77
+ return cached;
78
+ }
79
+ let formatter;
80
+ if (this.isNumberAcronym(upperAcronym)) {
81
+ formatter = new NumberFormatter();
82
+ } else if (this.isTextAcronym(upperAcronym)) {
83
+ formatter = new TextFormatter();
84
+ } else {
85
+ formatter = this.createSpecificFormatter(upperAcronym);
86
+ }
87
+ this.formatterCache.set(upperAcronym, formatter);
88
+ return formatter;
89
+ }
90
+ };
91
+ FormatterFactory = __decorateClass([
92
+ decorators.class.Singleton
93
+ ], FormatterFactory);
94
+ const factoryInstance = new FormatterFactory();
95
+ export {
96
+ factoryInstance
97
+ };
@@ -0,0 +1,4 @@
1
+ import { factoryInstance } from "./factory";
2
+ export {
3
+ factoryInstance
4
+ };
@@ -0,0 +1,54 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ import {
13
+ getNumberMaskedValue
14
+ } from "@elliemae/ds-form-helpers-mask-hooks";
15
+ let NumberFormatter = class {
16
+ getMaskOptions(fieldFormat, decimalPlaces) {
17
+ return {
18
+ decimalPlaces: decimalPlaces || 0,
19
+ allowNegative: true,
20
+ decimalRequired: !(fieldFormat === "I" || fieldFormat === "RI"),
21
+ includeThousandsSeparator: true,
22
+ prefix: "",
23
+ suffix: ""
24
+ };
25
+ }
26
+ format(input, { fieldFormat, numberOptions = {} }) {
27
+ if (input === null || input === void 0) return "";
28
+ if (input === "") return "";
29
+ if (!fieldFormat) return String(input);
30
+ const decimalPlaces = parseInt(fieldFormat.match(/\d+$/)?.[0] || "0", 10) || 0;
31
+ const maskOptions = this.getMaskOptions(fieldFormat, decimalPlaces);
32
+ const decimalValue = fieldFormat === "I" || fieldFormat === "RI" ? input : parseFloat(String(input).replace(/[^\d.-]/g, "")).toFixed(
33
+ maskOptions.decimalPlaces || 2
34
+ );
35
+ const maskedValue = getNumberMaskedValue(String(decimalValue), {
36
+ ...maskOptions,
37
+ ...numberOptions
38
+ });
39
+ return maskedValue;
40
+ }
41
+ parse(input, { fieldFormat, unmaskPattern = /[^\d.-]/g }) {
42
+ if (input === void 0 || input === null || input === "") return void 0;
43
+ if (!fieldFormat) return Number(input);
44
+ const decimalPlaces = parseInt(fieldFormat.match(/\d+$/)?.[0] || "0", 10) || 0;
45
+ const unmaskedValue = input.replace(unmaskPattern, "");
46
+ return decimalPlaces > 0 ? Number(parseFloat(unmaskedValue).toFixed(decimalPlaces)) : Number(unmaskedValue);
47
+ }
48
+ };
49
+ NumberFormatter = __decorateClass([
50
+ decorators.class.Singleton
51
+ ], NumberFormatter);
52
+ export {
53
+ NumberFormatter
54
+ };
@@ -0,0 +1,41 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ import {
13
+ getPhoneMaskedValue
14
+ } from "@elliemae/ds-form-helpers-mask-hooks";
15
+ let PhoneFormatter = class {
16
+ format(input, { isInternational }) {
17
+ if (input === null || input === void 0) {
18
+ return "";
19
+ }
20
+ const maskedValue = getPhoneMaskedValue(String(input), { isInternational });
21
+ return maskedValue;
22
+ }
23
+ parse(input, { unmaskPattern = /[()-\s]/g } = {}) {
24
+ if (!input) return void 0;
25
+ const unmaskedValue = input.replace(unmaskPattern, "");
26
+ return unmaskedValue;
27
+ }
28
+ static getPhoneFormatOptions({
29
+ isInternational = false
30
+ }) {
31
+ return {
32
+ isInternational
33
+ };
34
+ }
35
+ };
36
+ PhoneFormatter = __decorateClass([
37
+ decorators.class.Singleton
38
+ ], PhoneFormatter);
39
+ export {
40
+ PhoneFormatter
41
+ };
@@ -0,0 +1,34 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ import { getRegExpMaskedValue } from "@elliemae/ds-form-helpers-mask-hooks";
13
+ let RegexFormatter = class {
14
+ format(input, patterns) {
15
+ if (!patterns || patterns.length === 0) {
16
+ return input;
17
+ }
18
+ const maskedValue = getRegExpMaskedValue(input, patterns);
19
+ return maskedValue;
20
+ }
21
+ parse(input, patterns) {
22
+ if (!patterns || patterns.length === 0) {
23
+ return input;
24
+ }
25
+ const unmaskedValue = getRegExpMaskedValue(input, patterns);
26
+ return unmaskedValue;
27
+ }
28
+ };
29
+ RegexFormatter = __decorateClass([
30
+ decorators.class.Singleton
31
+ ], RegexFormatter);
32
+ export {
33
+ RegexFormatter
34
+ };
@@ -0,0 +1,32 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ import { getSSNMaskedValue } from "@elliemae/ds-form-helpers-mask-hooks";
13
+ let SSNFormatter = class {
14
+ format(input) {
15
+ if (input === null || input === void 0 || input === "") {
16
+ return "";
17
+ }
18
+ const maskedValue = getSSNMaskedValue(String(input));
19
+ return maskedValue;
20
+ }
21
+ parse(input) {
22
+ if (!input) return void 0;
23
+ const unmaskedValue = input.replace(/-/g, "");
24
+ return unmaskedValue;
25
+ }
26
+ };
27
+ SSNFormatter = __decorateClass([
28
+ decorators.class.Singleton
29
+ ], SSNFormatter);
30
+ export {
31
+ SSNFormatter
32
+ };
@@ -0,0 +1,25 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators2, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators2.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators2[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ import { decorators } from "@elliemae/pui-app-sdk";
12
+ let TextFormatter = class {
13
+ format(input) {
14
+ return input ? String(input) : "";
15
+ }
16
+ parse(input) {
17
+ return input;
18
+ }
19
+ };
20
+ TextFormatter = __decorateClass([
21
+ decorators.class.Singleton
22
+ ], TextFormatter);
23
+ export {
24
+ TextFormatter
25
+ };