@ayasofyazilim/ui 0.0.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 (236) hide show
  1. package/__mocks__/canvas.ts +8 -0
  2. package/components.json +21 -0
  3. package/eslint.config.js +4 -0
  4. package/jest-environment.js +37 -0
  5. package/jest.config.ts +47 -0
  6. package/jest.setup.ts +69 -0
  7. package/package.json +124 -0
  8. package/postcss.config.mjs +6 -0
  9. package/src/aria/index.tsx +1 -0
  10. package/src/aria/number-field.tsx +41 -0
  11. package/src/components/.gitkeep +0 -0
  12. package/src/components/accordion.tsx +66 -0
  13. package/src/components/alert-dialog.tsx +157 -0
  14. package/src/components/alert.tsx +70 -0
  15. package/src/components/aspect-ratio.tsx +11 -0
  16. package/src/components/avatar.tsx +53 -0
  17. package/src/components/badge.tsx +67 -0
  18. package/src/components/breadcrumb.tsx +109 -0
  19. package/src/components/button-group.tsx +83 -0
  20. package/src/components/button.tsx +68 -0
  21. package/src/components/calendar.tsx +219 -0
  22. package/src/components/card.tsx +92 -0
  23. package/src/components/carousel.tsx +241 -0
  24. package/src/components/chart.tsx +363 -0
  25. package/src/components/checkbox.tsx +32 -0
  26. package/src/components/collapsible.tsx +33 -0
  27. package/src/components/command.tsx +184 -0
  28. package/src/components/context-menu.tsx +252 -0
  29. package/src/components/dialog.tsx +144 -0
  30. package/src/components/drawer.tsx +135 -0
  31. package/src/components/dropdown-menu.tsx +258 -0
  32. package/src/components/empty.tsx +100 -0
  33. package/src/components/field.tsx +248 -0
  34. package/src/components/form.tsx +169 -0
  35. package/src/components/hover-card.tsx +44 -0
  36. package/src/components/input-group.tsx +170 -0
  37. package/src/components/input-otp.tsx +77 -0
  38. package/src/components/input.tsx +21 -0
  39. package/src/components/item.tsx +193 -0
  40. package/src/components/kbd.tsx +28 -0
  41. package/src/components/label.tsx +24 -0
  42. package/src/components/menubar.tsx +276 -0
  43. package/src/components/navigation-menu.tsx +168 -0
  44. package/src/components/pagination.tsx +130 -0
  45. package/src/components/popover.tsx +88 -0
  46. package/src/components/progress.tsx +31 -0
  47. package/src/components/radio-group.tsx +45 -0
  48. package/src/components/resizable.tsx +56 -0
  49. package/src/components/scroll-area.tsx +58 -0
  50. package/src/components/select.tsx +189 -0
  51. package/src/components/separator.tsx +28 -0
  52. package/src/components/sheet.tsx +140 -0
  53. package/src/components/sidebar.tsx +862 -0
  54. package/src/components/skeleton.tsx +13 -0
  55. package/src/components/slider.tsx +63 -0
  56. package/src/components/sonner.tsx +40 -0
  57. package/src/components/spinner.tsx +16 -0
  58. package/src/components/stepper.tsx +291 -0
  59. package/src/components/switch.tsx +31 -0
  60. package/src/components/table.tsx +133 -0
  61. package/src/components/tabs.tsx +66 -0
  62. package/src/components/textarea.tsx +18 -0
  63. package/src/components/toggle-group.tsx +83 -0
  64. package/src/components/toggle.tsx +47 -0
  65. package/src/components/tooltip.tsx +66 -0
  66. package/src/custom/action-button.tsx +48 -0
  67. package/src/custom/async-select.tsx +287 -0
  68. package/src/custom/awesome-not-found.tsx +116 -0
  69. package/src/custom/charts/area-chart.tsx +147 -0
  70. package/src/custom/charts/bar-chart.tsx +233 -0
  71. package/src/custom/charts/chart-card.tsx +103 -0
  72. package/src/custom/charts/index.tsx +16 -0
  73. package/src/custom/charts/pie-chart.tsx +168 -0
  74. package/src/custom/charts/radar-chart.tsx +126 -0
  75. package/src/custom/checkbox-tree.tsx +100 -0
  76. package/src/custom/combobox.tsx +296 -0
  77. package/src/custom/confirm-dialog.tsx +102 -0
  78. package/src/custom/country-selector.tsx +204 -0
  79. package/src/custom/date-picker/calendar-rac.tsx +109 -0
  80. package/src/custom/date-picker/datefield-rac.tsx +84 -0
  81. package/src/custom/date-picker/index.tsx +273 -0
  82. package/src/custom/date-picker/types/index.ts +4 -0
  83. package/src/custom/date-picker/utils/index.ts +42 -0
  84. package/src/custom/date-picker-old.tsx +50 -0
  85. package/src/custom/date-tooltip.tsx +98 -0
  86. package/src/custom/document-scanner/consts.ts +5 -0
  87. package/src/custom/document-scanner/corner-adjustment/action-buttons.tsx +33 -0
  88. package/src/custom/document-scanner/corner-adjustment/corner-handle.tsx +43 -0
  89. package/src/custom/document-scanner/corner-adjustment/hooks/use-corner-drag.ts +85 -0
  90. package/src/custom/document-scanner/corner-adjustment/index.tsx +125 -0
  91. package/src/custom/document-scanner/corner-adjustment/types.ts +53 -0
  92. package/src/custom/document-scanner/corner-adjustment/utils/clip-path.ts +22 -0
  93. package/src/custom/document-scanner/corner-adjustment/zoom-magnifier.tsx +115 -0
  94. package/src/custom/document-scanner/hooks/use-document-capture.ts +81 -0
  95. package/src/custom/document-scanner/hooks/use-document-scanner.ts +80 -0
  96. package/src/custom/document-scanner/hooks/use-perspective-crop.ts +38 -0
  97. package/src/custom/document-scanner/index.tsx +255 -0
  98. package/src/custom/document-scanner/lib.ts +407 -0
  99. package/src/custom/document-scanner/types.ts +205 -0
  100. package/src/custom/document-scanner/utils/perspective-correction.ts +139 -0
  101. package/src/custom/document-viewer/controllers.tsx +98 -0
  102. package/src/custom/document-viewer/index.tsx +43 -0
  103. package/src/custom/document-viewer/renderers/image.tsx +37 -0
  104. package/src/custom/document-viewer/renderers/index.tsx +2 -0
  105. package/src/custom/document-viewer/renderers/pdf.tsx +105 -0
  106. package/src/custom/email-input/domains.json +159 -0
  107. package/src/custom/email-input/email.tsx +229 -0
  108. package/src/custom/email-input/index.tsx +4 -0
  109. package/src/custom/email-input/types.ts +104 -0
  110. package/src/custom/file-uploader.tsx +541 -0
  111. package/src/custom/filter-component/fields/async-select.tsx +33 -0
  112. package/src/custom/filter-component/fields/date.tsx +60 -0
  113. package/src/custom/filter-component/fields/multi-select.tsx +30 -0
  114. package/src/custom/filter-component/index.tsx +217 -0
  115. package/src/custom/image-canvas.tsx +260 -0
  116. package/src/custom/json-editor.tsx +22 -0
  117. package/src/custom/master-data-grid/components/dialogs/column-settings-dialog.tsx +100 -0
  118. package/src/custom/master-data-grid/components/dialogs/index.ts +1 -0
  119. package/src/custom/master-data-grid/components/filters/client-filter.tsx +368 -0
  120. package/src/custom/master-data-grid/components/filters/filter-input.tsx +256 -0
  121. package/src/custom/master-data-grid/components/filters/index.ts +3 -0
  122. package/src/custom/master-data-grid/components/filters/inline-column-filter.tsx +233 -0
  123. package/src/custom/master-data-grid/components/filters/multi-filter-dialog.tsx +90 -0
  124. package/src/custom/master-data-grid/components/filters/server-filter.tsx +255 -0
  125. package/src/custom/master-data-grid/components/master-data-grid.tsx +472 -0
  126. package/src/custom/master-data-grid/components/pagination/index.ts +1 -0
  127. package/src/custom/master-data-grid/components/pagination/pagination.tsx +178 -0
  128. package/src/custom/master-data-grid/components/table/cell-renderer.tsx +634 -0
  129. package/src/custom/master-data-grid/components/table/header-cell.tsx +162 -0
  130. package/src/custom/master-data-grid/components/table/index.ts +4 -0
  131. package/src/custom/master-data-grid/components/table/table-body-renderer.tsx +113 -0
  132. package/src/custom/master-data-grid/components/table/virtual-body.tsx +138 -0
  133. package/src/custom/master-data-grid/components/toolbar/index.ts +1 -0
  134. package/src/custom/master-data-grid/components/toolbar/toolbar.tsx +314 -0
  135. package/src/custom/master-data-grid/hooks/index.ts +3 -0
  136. package/src/custom/master-data-grid/hooks/use-columns.tsx +332 -0
  137. package/src/custom/master-data-grid/hooks/use-editing.ts +106 -0
  138. package/src/custom/master-data-grid/hooks/use-table-state-reducer.ts +157 -0
  139. package/src/custom/master-data-grid/hooks/use-table-state.ts +31 -0
  140. package/src/custom/master-data-grid/index.ts +16 -0
  141. package/src/custom/master-data-grid/types.ts +466 -0
  142. package/src/custom/master-data-grid/utils/column-generator.tsx +306 -0
  143. package/src/custom/master-data-grid/utils/export-utils.ts +67 -0
  144. package/src/custom/master-data-grid/utils/filter-fns.ts +290 -0
  145. package/src/custom/master-data-grid/utils/index.ts +8 -0
  146. package/src/custom/master-data-grid/utils/pinning-utils.ts +88 -0
  147. package/src/custom/master-data-grid/utils/translation-utils.ts +42 -0
  148. package/src/custom/multi-select.tsx +432 -0
  149. package/src/custom/password-input.tsx +194 -0
  150. package/src/custom/phone-input.tsx +172 -0
  151. package/src/custom/schema-form/custom/index.tsx +1 -0
  152. package/src/custom/schema-form/custom/label.tsx +53 -0
  153. package/src/custom/schema-form/fields/base-input-field.tsx +82 -0
  154. package/src/custom/schema-form/fields/field.tsx +67 -0
  155. package/src/custom/schema-form/fields/index.tsx +5 -0
  156. package/src/custom/schema-form/fields/object.tsx +12 -0
  157. package/src/custom/schema-form/fields/table-array/array-field-item.tsx +90 -0
  158. package/src/custom/schema-form/fields/table-array/array-field-template.tsx +115 -0
  159. package/src/custom/schema-form/index.tsx +259 -0
  160. package/src/custom/schema-form/templates/description.tsx +20 -0
  161. package/src/custom/schema-form/templates/index.tsx +2 -0
  162. package/src/custom/schema-form/templates/submit.tsx +32 -0
  163. package/src/custom/schema-form/types.ts +64 -0
  164. package/src/custom/schema-form/utils/index.ts +4 -0
  165. package/src/custom/schema-form/utils/schema-dependency.ts +655 -0
  166. package/src/custom/schema-form/utils/schemas.ts +289 -0
  167. package/src/custom/schema-form/utils/validation.ts +23 -0
  168. package/src/custom/schema-form/widgets/boolean.tsx +77 -0
  169. package/src/custom/schema-form/widgets/combobox.tsx +274 -0
  170. package/src/custom/schema-form/widgets/date.tsx +59 -0
  171. package/src/custom/schema-form/widgets/email.tsx +34 -0
  172. package/src/custom/schema-form/widgets/index.tsx +10 -0
  173. package/src/custom/schema-form/widgets/password.tsx +40 -0
  174. package/src/custom/schema-form/widgets/phone.tsx +40 -0
  175. package/src/custom/schema-form/widgets/select.tsx +105 -0
  176. package/src/custom/schema-form/widgets/selectable.tsx +25 -0
  177. package/src/custom/schema-form/widgets/string-array.tsx +296 -0
  178. package/src/custom/schema-form/widgets/url.tsx +56 -0
  179. package/src/custom/section-layout-v2.tsx +212 -0
  180. package/src/custom/select-tabs.tsx +109 -0
  181. package/src/custom/selectable.tsx +316 -0
  182. package/src/custom/stepper.tsx +236 -0
  183. package/src/custom/tab-layout.tsx +213 -0
  184. package/src/custom/tanstack-table/fields/index.tsx +12 -0
  185. package/src/custom/tanstack-table/fields/tanstack-table-action-dialogs.tsx +89 -0
  186. package/src/custom/tanstack-table/fields/tanstack-table-column-header.tsx +66 -0
  187. package/src/custom/tanstack-table/fields/tanstack-table-filter-date.tsx +180 -0
  188. package/src/custom/tanstack-table/fields/tanstack-table-filter-faceted.tsx +158 -0
  189. package/src/custom/tanstack-table/fields/tanstack-table-filter-text.tsx +76 -0
  190. package/src/custom/tanstack-table/fields/tanstack-table-pagination.tsx +136 -0
  191. package/src/custom/tanstack-table/fields/tanstack-table-plain-table.tsx +142 -0
  192. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-confirmation.tsx +77 -0
  193. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-custom-dialog.tsx +87 -0
  194. package/src/custom/tanstack-table/fields/tanstack-table-row-actions.tsx +151 -0
  195. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-custom-dialog.tsx +88 -0
  196. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-schemaform-dialog.tsx +47 -0
  197. package/src/custom/tanstack-table/fields/tanstack-table-toolbar.tsx +143 -0
  198. package/src/custom/tanstack-table/fields/tanstack-table-view-options.tsx +171 -0
  199. package/src/custom/tanstack-table/index.tsx +244 -0
  200. package/src/custom/tanstack-table/types/index.ts +328 -0
  201. package/src/custom/tanstack-table/utils/cell-with-actions.tsx +21 -0
  202. package/src/custom/tanstack-table/utils/column-names.ts +26 -0
  203. package/src/custom/tanstack-table/utils/columns-by-row-data.tsx +312 -0
  204. package/src/custom/tanstack-table/utils/editable-columns-by-row-data.tsx +219 -0
  205. package/src/custom/tanstack-table/utils/faceted-boolean-options.tsx +22 -0
  206. package/src/custom/tanstack-table/utils/index.tsx +10 -0
  207. package/src/custom/tanstack-table/utils/pinning-styles.ts +57 -0
  208. package/src/custom/tanstack-table/utils/table.tsx +83 -0
  209. package/src/custom/tanstack-table/utils/test-conditions.ts +17 -0
  210. package/src/custom/timeline.tsx +208 -0
  211. package/src/custom/tree.tsx +200 -0
  212. package/src/custom/tscanify/browser.ts +66 -0
  213. package/src/custom/tscanify/index.ts +51 -0
  214. package/src/custom/tscanify/tscanify-browser.ts +522 -0
  215. package/src/custom/tscanify/tscanify.ts +262 -0
  216. package/src/custom/tscanify/types.ts +22 -0
  217. package/src/custom/webcam.tsx +737 -0
  218. package/src/hooks/.gitkeep +0 -0
  219. package/src/hooks/use-callback-ref.ts +27 -0
  220. package/src/hooks/use-controllable-state.ts +67 -0
  221. package/src/hooks/use-debounce.ts +19 -0
  222. package/src/hooks/use-is-visible.ts +23 -0
  223. package/src/hooks/use-media-query.ts +21 -0
  224. package/src/hooks/use-mobile.ts +21 -0
  225. package/src/hooks/use-on-window-resize.ts +15 -0
  226. package/src/hooks/use-scroll.tsx +22 -0
  227. package/src/lib/utils.ts +61 -0
  228. package/src/lib/zod.ts +2 -0
  229. package/src/styles/core.css +57 -0
  230. package/src/styles/globals.css +130 -0
  231. package/src/test/email-input.test.tsx +217 -0
  232. package/src/test/password-input.test.tsx +92 -0
  233. package/src/test/select-tabs.test.tsx +302 -0
  234. package/src/test/selectable.test.tsx +1093 -0
  235. package/tsconfig.json +13 -0
  236. package/tsconfig.lint.json +8 -0
@@ -0,0 +1,655 @@
1
+ import {
2
+ FormValidation,
3
+ GenericObjectType,
4
+ RJSFValidationError,
5
+ } from "@rjsf/utils";
6
+
7
+ interface JSONSchema {
8
+ type?: string;
9
+ properties?: Record<string, JSONSchema>;
10
+ required?: string[];
11
+ dependencies?: Record<string, JSONSchemaDependency>;
12
+ items?: JSONSchema | JSONSchema[];
13
+ additionalItems?: JSONSchema;
14
+ definitions?: Record<string, JSONSchema>;
15
+ $ref?: string;
16
+ title?: string;
17
+ description?: string;
18
+ enum?: unknown[];
19
+ default?: unknown;
20
+ oneOf?: JSONSchema[];
21
+ nullable?: boolean;
22
+ format?: string;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ interface JSONSchemaDependency {
27
+ properties?: Record<string, JSONSchema>;
28
+ required?: string[];
29
+ oneOf?: JSONSchema[];
30
+ }
31
+
32
+ interface FieldDependencyRule {
33
+ when: (value: unknown) => boolean;
34
+ targets: string[];
35
+ fieldCondition?: JSONSchema;
36
+ }
37
+
38
+ interface FieldDependencies {
39
+ HIDES?: FieldDependencyRule[];
40
+ REQUIRES?: FieldDependencyRule[];
41
+ }
42
+
43
+ type DependencyConfig = Record<string, FieldDependencies>;
44
+
45
+ /**
46
+ * Helper to safely get a nested property in a JSON Schema using dot notation path.
47
+ */
48
+ function getNestedProperty(
49
+ obj: JSONSchema,
50
+ path: string
51
+ ): JSONSchema | undefined {
52
+ const parts = path.split(".");
53
+ let current: JSONSchema | undefined = obj;
54
+
55
+ for (const part of parts) {
56
+ if (!current?.properties?.[part]) return undefined;
57
+ current = current.properties[part];
58
+ }
59
+ return current;
60
+ }
61
+
62
+ /**
63
+ * Get all possible values for a controlling field to create conditional branches.
64
+ */
65
+ function getFieldValues(schema: JSONSchema, fieldPath: string): unknown[] {
66
+ const field = getNestedProperty(schema, fieldPath);
67
+ if (!field) return [];
68
+ if (field.enum) return field.enum;
69
+ if (field.type === "boolean") return [true, false];
70
+ if (field.type === "integer" || field.type === "number")
71
+ return ["__NUMERIC_RANGE__"];
72
+ return [];
73
+ }
74
+
75
+ /**
76
+ * Main function to apply HIDES and REQUIRES field dependencies on a JSON Schema.
77
+ */
78
+ function applyFieldDependencies(
79
+ originalSchema: GenericObjectType,
80
+ dependencies: DependencyConfig
81
+ ): JSONSchema {
82
+ const schema: JSONSchema = JSON.parse(JSON.stringify(originalSchema));
83
+ if (!schema.properties) return schema;
84
+
85
+ for (const [fieldPath, fieldDeps] of Object.entries(dependencies)) {
86
+ const fieldValues = getFieldValues(schema, fieldPath);
87
+ if (fieldValues.length > 0) {
88
+ const parentPath = fieldPath.split(".").slice(0, -1).join(".");
89
+ const parentSchema = parentPath
90
+ ? getNestedProperty(schema, parentPath)
91
+ : schema;
92
+ const fieldName = fieldPath.split(".").pop()!;
93
+
94
+ if (parentSchema) {
95
+ // Collect all HIDES targets globally, to remove from root properties
96
+ const allHidesTargets = new Set<string>();
97
+ if (fieldDeps.HIDES) {
98
+ for (const rule of fieldDeps.HIDES) {
99
+ rule.targets.forEach((t) => allHidesTargets.add(t));
100
+ }
101
+ }
102
+
103
+ // Remove all HIDES targets from root properties immediately
104
+ for (const hiddenTarget of Array.from(allHidesTargets)) {
105
+ if (schema.properties[hiddenTarget]) {
106
+ delete schema.properties[hiddenTarget];
107
+ if (schema.required?.includes(hiddenTarget)) {
108
+ schema.required = schema.required.filter(
109
+ (r) => r !== hiddenTarget
110
+ );
111
+ }
112
+ }
113
+ }
114
+
115
+ const conditionalSchemas: JSONSchema[] = [];
116
+
117
+ for (const value of fieldValues) {
118
+ const requiredFields: string[] = [];
119
+ const visibleFields: string[] = [];
120
+
121
+ if (fieldDeps.REQUIRES) {
122
+ for (const rule of fieldDeps.REQUIRES) {
123
+ if (rule.when(value)) {
124
+ requiredFields.push(...rule.targets);
125
+ visibleFields.push(...rule.targets);
126
+ }
127
+ }
128
+ }
129
+
130
+ if (fieldDeps.HIDES) {
131
+ for (const rule of fieldDeps.HIDES) {
132
+ if (rule.when(value)) {
133
+ // Hidden fields: do NOT add to visibleFields
134
+ // But fields not hidden should be visible
135
+ // So we skip these targets here
136
+ } else {
137
+ // For other values where fields are not hidden, add them visible
138
+ visibleFields.push(...rule.targets);
139
+ }
140
+ }
141
+ }
142
+
143
+ // Also add controlling field itself visible
144
+ visibleFields.push(fieldName);
145
+
146
+ // Deduplicate visibleFields
147
+ const visibleFieldsUnique = Array.from(new Set(visibleFields));
148
+
149
+ // Build properties for conditional schema
150
+ const conditionalProperties: Record<string, JSONSchema> = {};
151
+
152
+ visibleFieldsUnique.forEach((targetField) => {
153
+ // If targetField === fieldName, add enum with current value
154
+ if (targetField === fieldName) {
155
+ conditionalProperties[targetField] = {
156
+ enum: [value],
157
+ };
158
+ } else {
159
+ // Else get original schema for that target field
160
+ const originalFieldSchema = getNestedProperty(
161
+ originalSchema,
162
+ targetField
163
+ );
164
+ if (originalFieldSchema) {
165
+ conditionalProperties[targetField] = { ...originalFieldSchema };
166
+ }
167
+ }
168
+ });
169
+
170
+ const conditionalSchema: JSONSchema = {
171
+ properties: conditionalProperties,
172
+ };
173
+
174
+ if (requiredFields.length > 0) {
175
+ conditionalSchema.required = requiredFields.filter((r) =>
176
+ visibleFieldsUnique.includes(r)
177
+ );
178
+ }
179
+
180
+ conditionalSchemas.push(conditionalSchema);
181
+ }
182
+
183
+ if (!parentSchema.dependencies) parentSchema.dependencies = {};
184
+ parentSchema.dependencies[fieldName] = { oneOf: conditionalSchemas };
185
+ }
186
+ }
187
+ }
188
+
189
+ return schema;
190
+ }
191
+
192
+ function getFormDataValue(
193
+ formData: Record<string, unknown>,
194
+ path: string
195
+ ): unknown {
196
+ const parts = path.split(".");
197
+ let current: unknown = formData;
198
+ for (const part of parts) {
199
+ if (current === null || current === undefined) return undefined;
200
+ current = (current as Record<string, unknown>)[part];
201
+ }
202
+ return current;
203
+ }
204
+
205
+ function deleteFormDataProperty(
206
+ formData: Record<string, unknown>,
207
+ path: string
208
+ ): void {
209
+ const parts = path.split(".");
210
+ if (parts.length === 1) {
211
+ delete formData[path];
212
+ return;
213
+ }
214
+ const parentPath = parts.slice(0, -1);
215
+ const fieldName = parts.at(-1);
216
+ if (!fieldName) return;
217
+ let current: unknown = formData;
218
+ for (const part of parentPath) {
219
+ if (current === null || current === undefined) return;
220
+ current = (current as Record<string, unknown>)[part];
221
+ }
222
+ if (current && typeof current === "object") {
223
+ delete (current as Record<string, unknown>)[fieldName];
224
+ }
225
+ }
226
+
227
+ function cleanHiddenFieldsFromFormData<T extends Record<string, unknown>>(
228
+ formData: T,
229
+ dependencies: DependencyConfig
230
+ ): T {
231
+ const cleanedData = JSON.parse(JSON.stringify(formData)) as T;
232
+
233
+ for (const [fieldPath, fieldDeps] of Object.entries(dependencies)) {
234
+ if (!fieldDeps.HIDES) continue;
235
+
236
+ const controllingValue = getFormDataValue(cleanedData, fieldPath);
237
+
238
+ for (const rule of fieldDeps.HIDES) {
239
+ if (rule.when(controllingValue)) {
240
+ for (const target of rule.targets) {
241
+ deleteFormDataProperty(cleanedData, target);
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ return cleanedData;
248
+ }
249
+
250
+ export { applyFieldDependencies, cleanHiddenFieldsFromFormData };
251
+ export type {
252
+ JSONSchema,
253
+ DependencyConfig,
254
+ FieldDependencies,
255
+ FieldDependencyRule,
256
+ };
257
+
258
+ // ============================================================================
259
+ // RUNTIME CONDITIONAL REQUIREMENTS
260
+ // ============================================================================
261
+ // For fields that need runtime validation (e.g., UUID comparisons, complex conditions)
262
+ // Use this when JSON Schema dependencies can't express the condition.
263
+
264
+ const EMPTY_GUID = "00000000-0000-0000-0000-000000000000";
265
+
266
+ type EmptyCheckFn = (value: unknown) => boolean;
267
+
268
+ interface ConditionalFieldConfig {
269
+ /** Fields that trigger the requirement check when filled */
270
+ triggerFields: string[];
271
+ /** Fields that become required when trigger condition is met */
272
+ requiredFields: string[];
273
+ /** Optional custom empty check per field (default: checks empty string, null, undefined, EMPTY_GUID) */
274
+ emptyChecks?: Record<string, EmptyCheckFn>;
275
+ /** Error message for required fields (default: "Required") */
276
+ errorMessage?: string;
277
+ /** Minimum number of trigger fields that must be filled to activate requirements (default: 2) */
278
+ minFilledCount?: number;
279
+ /** If true, remove this object from form data on submit when requirements are not triggered */
280
+ removeFromSubmit?: boolean;
281
+ }
282
+
283
+ interface RuntimeDependencyConfig {
284
+ /** Path to the nested object (e.g., "address", "telephone") */
285
+ [objectPath: string]: ConditionalFieldConfig;
286
+ }
287
+
288
+ /**
289
+ * Default check for whether a value is considered "empty"
290
+ */
291
+ function isValueEmpty(value: unknown): boolean {
292
+ if (value === null || value === undefined) return true;
293
+ if (typeof value === "string") {
294
+ return value.trim() === "" || value === EMPTY_GUID;
295
+ }
296
+ return false;
297
+ }
298
+
299
+ /**
300
+ * Get a nested value from an object using dot notation path
301
+ */
302
+ function getNestedValue(obj: GenericObjectType, path: string): unknown {
303
+ const parts = path.split(".");
304
+ let current: unknown = obj;
305
+ for (const part of parts) {
306
+ if (current === null || current === undefined) return undefined;
307
+ current = (current as GenericObjectType)[part];
308
+ }
309
+ return current;
310
+ }
311
+
312
+ /**
313
+ * Creates a modified schema where specified nested object fields are not required.
314
+ * The required fields will be validated at runtime instead.
315
+ */
316
+ function applyRuntimeDependencies<T extends GenericObjectType>(
317
+ originalSchema: T,
318
+ config: RuntimeDependencyConfig
319
+ ): T {
320
+ const schema = JSON.parse(JSON.stringify(originalSchema)) as T;
321
+ const schemaAsJson = schema as unknown as JSONSchema;
322
+
323
+ for (const [objectPath, fieldConfig] of Object.entries(config)) {
324
+ const nestedSchema = getNestedProperty(schemaAsJson, objectPath);
325
+ if (nestedSchema) {
326
+ // Remove all conditionally required fields from the nested object's required array
327
+ const fieldsToMakeOptional = [
328
+ ...fieldConfig.triggerFields,
329
+ ...fieldConfig.requiredFields,
330
+ ];
331
+ if (nestedSchema.required) {
332
+ nestedSchema.required = nestedSchema.required.filter(
333
+ (field) => !fieldsToMakeOptional.includes(field)
334
+ );
335
+ }
336
+ }
337
+ }
338
+
339
+ // Also remove the nested object itself from root required if all its fields are optional
340
+ if (schemaAsJson.required) {
341
+ for (const objectPath of Object.keys(config)) {
342
+ const topLevelField = objectPath.split(".")[0];
343
+ if (topLevelField && schemaAsJson.required.includes(topLevelField)) {
344
+ schemaAsJson.required = schemaAsJson.required.filter(
345
+ (field: string) => field !== topLevelField
346
+ );
347
+ }
348
+ }
349
+ }
350
+
351
+ return schema;
352
+ }
353
+
354
+ /**
355
+ * Creates a customValidate function for runtime conditional requirements.
356
+ *
357
+ * Logic: If ANY trigger field is filled, ALL required fields become required.
358
+ *
359
+ * @example
360
+ * // Address validation: if countryId filled AND any other field filled, all required
361
+ * const validateAddress = createRuntimeValidator({
362
+ * address: {
363
+ * triggerFields: ["countryId", "adminAreaLevel1Id", "adminAreaLevel2Id", "addressLine"],
364
+ * requiredFields: ["countryId", "adminAreaLevel1Id", "adminAreaLevel2Id", "addressLine", "type"],
365
+ * }
366
+ * });
367
+ */
368
+ function createRuntimeValidator<TData extends GenericObjectType>(
369
+ config: RuntimeDependencyConfig
370
+ ) {
371
+ return function validate(
372
+ formData: TData | undefined,
373
+ errors: FormValidation<TData>
374
+ ): FormValidation<TData> {
375
+ if (!formData) return errors;
376
+
377
+ for (const [objectPath, fieldConfig] of Object.entries(config)) {
378
+ const nestedObject = getNestedValue(formData, objectPath) as
379
+ | GenericObjectType
380
+ | undefined;
381
+ if (!nestedObject) continue;
382
+
383
+ const errorMessage = fieldConfig.errorMessage || "Required";
384
+
385
+ // Check if any trigger field is filled
386
+ const filledTriggerFields = fieldConfig.triggerFields.filter((field) => {
387
+ const value = nestedObject[field];
388
+ const emptyCheck = fieldConfig.emptyChecks?.[field] || isValueEmpty;
389
+ return !emptyCheck(value);
390
+ });
391
+
392
+ // If at least 2 trigger fields are filled (not just one), all required fields become required
393
+ // This handles the "only countryId filled = optional" case
394
+ if (filledTriggerFields.length >= 2) {
395
+ const nestedErrors = getNestedValue(
396
+ errors as unknown as GenericObjectType,
397
+ objectPath
398
+ ) as GenericObjectType | undefined;
399
+
400
+ for (const requiredField of fieldConfig.requiredFields) {
401
+ const value = nestedObject[requiredField];
402
+ const emptyCheck =
403
+ fieldConfig.emptyChecks?.[requiredField] || isValueEmpty;
404
+
405
+ if (emptyCheck(value)) {
406
+ const fieldError = nestedErrors?.[requiredField] as
407
+ | { addError?: (msg: string) => void }
408
+ | undefined;
409
+ fieldError?.addError?.(errorMessage);
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ return errors;
416
+ };
417
+ }
418
+
419
+ /**
420
+ * Convenience function that applies both schema modifications and returns a validator.
421
+ * Use this for the complete solution.
422
+ *
423
+ * @typeParam TSchema - The schema object type
424
+ * @typeParam TData - The form data type (optional, defaults to GenericObjectType)
425
+ */
426
+ function createConditionalRequirements<
427
+ TSchema extends GenericObjectType,
428
+ TData extends GenericObjectType = GenericObjectType,
429
+ >(
430
+ originalSchema: TSchema,
431
+ config: RuntimeDependencyConfig
432
+ ): {
433
+ schema: TSchema;
434
+ validate: (
435
+ formData: TData | undefined,
436
+ errors: FormValidation<TData>
437
+ ) => FormValidation<TData>;
438
+ } {
439
+ return {
440
+ schema: applyRuntimeDependencies(originalSchema, config),
441
+ validate: createRuntimeValidator<TData>(config),
442
+ };
443
+ }
444
+
445
+ /**
446
+ * Checks if requirements should be active for a given object path.
447
+ * Returns true if 2+ trigger fields are filled.
448
+ */
449
+ function shouldRequireFields<TData extends GenericObjectType>(
450
+ formData: TData | undefined,
451
+ objectPath: string,
452
+ fieldConfig: ConditionalFieldConfig
453
+ ): boolean {
454
+ if (!formData) return false;
455
+
456
+ const nestedObject = getNestedValue(formData, objectPath) as
457
+ | GenericObjectType
458
+ | undefined;
459
+ if (!nestedObject) return false;
460
+
461
+ const filledTriggerFields = fieldConfig.triggerFields.filter((field) => {
462
+ const value = nestedObject[field];
463
+ const emptyCheck = fieldConfig.emptyChecks?.[field] || isValueEmpty;
464
+ return !emptyCheck(value);
465
+ });
466
+
467
+ const minCount = fieldConfig.minFilledCount ?? 2;
468
+ return filledTriggerFields.length >= minCount;
469
+ }
470
+
471
+ /**
472
+ * Creates a schema with required fields dynamically based on current form state.
473
+ * Use this in useMemo to recompute schema when form data changes.
474
+ *
475
+ * @param originalSchema - The original schema
476
+ * @param config - The runtime dependency configuration
477
+ * @param formData - Current form data to determine required state
478
+ * @returns Schema with correct required fields based on form state
479
+ */
480
+ function createDynamicSchema<
481
+ TSchema extends GenericObjectType,
482
+ TData extends GenericObjectType = GenericObjectType,
483
+ >(
484
+ originalSchema: TSchema,
485
+ config: RuntimeDependencyConfig,
486
+ formData: TData | undefined
487
+ ): TSchema {
488
+ const schema = JSON.parse(JSON.stringify(originalSchema)) as TSchema;
489
+ const schemaAsJson = schema as unknown as JSONSchema;
490
+
491
+ for (const [objectPath, fieldConfig] of Object.entries(config)) {
492
+ const nestedSchema = getNestedProperty(schemaAsJson, objectPath);
493
+ if (!nestedSchema) continue;
494
+
495
+ const requirementsActive = shouldRequireFields(
496
+ formData,
497
+ objectPath,
498
+ fieldConfig
499
+ );
500
+
501
+ if (requirementsActive) {
502
+ // Add required fields to the nested object's required array
503
+ const currentRequired = nestedSchema.required || [];
504
+ const newRequired = new Set([
505
+ ...currentRequired,
506
+ ...fieldConfig.requiredFields,
507
+ ]);
508
+ nestedSchema.required = Array.from(newRequired);
509
+ } else {
510
+ // Remove conditionally required fields from required array
511
+ const fieldsToMakeOptional = [
512
+ ...fieldConfig.triggerFields,
513
+ ...fieldConfig.requiredFields,
514
+ ];
515
+ if (nestedSchema.required) {
516
+ nestedSchema.required = nestedSchema.required.filter(
517
+ (field) => !fieldsToMakeOptional.includes(field)
518
+ );
519
+ }
520
+ }
521
+ }
522
+
523
+ // Handle top-level required for nested objects
524
+ if (schemaAsJson.required) {
525
+ for (const [objectPath, fieldConfig] of Object.entries(config)) {
526
+ const topLevelField = objectPath.split(".")[0];
527
+ if (!topLevelField) continue;
528
+
529
+ const requirementsActive = shouldRequireFields(
530
+ formData,
531
+ objectPath,
532
+ fieldConfig
533
+ );
534
+
535
+ if (requirementsActive) {
536
+ // Add to required if not already present
537
+ if (!schemaAsJson.required.includes(topLevelField)) {
538
+ schemaAsJson.required.push(topLevelField);
539
+ }
540
+ } else {
541
+ // Remove from required
542
+ schemaAsJson.required = schemaAsJson.required.filter(
543
+ (field: string) => field !== topLevelField
544
+ );
545
+ }
546
+ }
547
+ }
548
+
549
+ return schema;
550
+ }
551
+
552
+ /**
553
+ * Creates a transformErrors function that filters out format validation errors
554
+ * when the field value is empty. This prevents errors like "must match email format"
555
+ * when an optional email field is empty.
556
+ *
557
+ * @param formData - Current form data to check for empty values
558
+ * @returns TransformErrors function for SchemaForm
559
+ */
560
+ function createEmptyValueTransformErrors<TData extends GenericObjectType>(
561
+ formData: TData | undefined
562
+ ) {
563
+ return function transformErrors(
564
+ errors: RJSFValidationError[]
565
+ ): RJSFValidationError[] {
566
+ return errors.filter((error) => {
567
+ // Only filter format errors
568
+ if (error.name !== "format") return true;
569
+
570
+ // Get the field path from the property (e.g., ".email.emailAddress" -> ["email", "emailAddress"])
571
+ const path = (error.property || "")
572
+ .replace(/^\./, "")
573
+ .split(".")
574
+ .filter(Boolean);
575
+
576
+ if (path.length === 0 || !formData) return true;
577
+
578
+ // Navigate to get the value
579
+ let value: unknown = formData;
580
+ for (const key of path) {
581
+ if (value && typeof value === "object" && key in value) {
582
+ value = (value as Record<string, unknown>)[key];
583
+ } else {
584
+ return true; // Path doesn't exist, keep the error
585
+ }
586
+ }
587
+
588
+ // If value is empty, filter out the format error
589
+ return !isValueEmpty(value);
590
+ });
591
+ };
592
+ }
593
+
594
+ /**
595
+ * Cleans form data for submission by removing objects where requirements are not triggered
596
+ * and removeFromSubmit is true.
597
+ *
598
+ * @param formData - The form data to clean
599
+ * @param config - The runtime dependency configuration
600
+ * @returns Cleaned form data ready for submission
601
+ */
602
+ function cleanFormDataForSubmit<TData extends GenericObjectType>(
603
+ formData: TData,
604
+ config: RuntimeDependencyConfig
605
+ ): Partial<TData> {
606
+ const result = { ...formData } as Record<string, unknown>;
607
+
608
+ for (const [objectPath, fieldConfig] of Object.entries(config)) {
609
+ if (!fieldConfig.removeFromSubmit) continue;
610
+
611
+ // Check if requirements are triggered
612
+ const requirementsActive = shouldRequireFields(
613
+ formData,
614
+ objectPath,
615
+ fieldConfig
616
+ );
617
+
618
+ // If requirements are NOT active and removeFromSubmit is true, remove the object
619
+ if (!requirementsActive) {
620
+ // Handle nested paths (e.g., "contact.address")
621
+ const pathParts = objectPath.split(".");
622
+ if (pathParts.length === 1) {
623
+ delete result[objectPath];
624
+ } else {
625
+ // Navigate to parent and delete the leaf
626
+ let current: Record<string, unknown> = result;
627
+ for (let i = 0; i < pathParts.length - 1; i++) {
628
+ const part = pathParts[i] as string;
629
+ if (current[part] && typeof current[part] === "object") {
630
+ current = current[part] as Record<string, unknown>;
631
+ } else {
632
+ break;
633
+ }
634
+ }
635
+ const leafKey = pathParts[pathParts.length - 1] as string;
636
+ delete current[leafKey];
637
+ }
638
+ }
639
+ }
640
+
641
+ return result as Partial<TData>;
642
+ }
643
+
644
+ export {
645
+ applyRuntimeDependencies,
646
+ createRuntimeValidator,
647
+ createConditionalRequirements,
648
+ createDynamicSchema,
649
+ createEmptyValueTransformErrors,
650
+ cleanFormDataForSubmit,
651
+ shouldRequireFields,
652
+ isValueEmpty,
653
+ EMPTY_GUID,
654
+ };
655
+ export type { RuntimeDependencyConfig, ConditionalFieldConfig };