@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c

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 (170) hide show
  1. package/README.md +1 -1
  2. package/dist/components/AIIcon.d.ts +16 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
  7. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
  8. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
  9. package/dist/components/EntityCollectionView/Board.d.ts +2 -0
  10. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  11. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  12. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  13. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  14. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  15. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  16. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  18. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  19. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  20. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
  21. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  22. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  23. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  24. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  25. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  26. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
  27. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  28. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  29. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  30. package/dist/components/VirtualTable/types.d.ts +2 -0
  31. package/dist/components/index.d.ts +3 -0
  32. package/dist/contexts/index.d.ts +10 -0
  33. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  34. package/dist/core/index.d.ts +1 -0
  35. package/dist/form/validation.d.ts +3 -2
  36. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  37. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  38. package/dist/index.es.js +5239 -1590
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.umd.js +5233 -1585
  41. package/dist/index.umd.js.map +1 -1
  42. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  43. package/dist/preview/components/DatePreview.d.ts +13 -3
  44. package/dist/preview/components/ImagePreview.d.ts +5 -1
  45. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  46. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  47. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  48. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  49. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  50. package/dist/types/collections.d.ts +42 -2
  51. package/dist/types/datasource.d.ts +0 -1
  52. package/dist/types/plugins.d.ts +46 -1
  53. package/dist/types/properties.d.ts +259 -4
  54. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  55. package/dist/util/__tests__/objects.test.d.ts +1 -0
  56. package/dist/util/conditions.d.ts +26 -0
  57. package/dist/util/entities.d.ts +1 -2
  58. package/dist/util/index.d.ts +2 -1
  59. package/dist/util/property_utils.d.ts +2 -1
  60. package/dist/util/resolutions.d.ts +1 -1
  61. package/package.json +10 -7
  62. package/src/app/Scaffold.tsx +14 -15
  63. package/src/components/AIIcon.tsx +39 -0
  64. package/src/components/ArrayContainer.tsx +1 -4
  65. package/src/components/ClearFilterSortButton.tsx +19 -16
  66. package/src/components/ConfirmationDialog.tsx +0 -2
  67. package/src/components/DeleteEntityDialog.tsx +2 -4
  68. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  69. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  70. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  71. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  72. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  73. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  74. package/src/components/EntityCollectionView/Board.tsx +324 -0
  75. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  76. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  77. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  78. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  79. package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
  80. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
  81. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  82. package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
  83. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  84. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  85. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  86. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  87. package/src/components/EntityCollectionView/board_types.ts +113 -0
  88. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  89. package/src/components/ErrorTooltip.tsx +2 -1
  90. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  91. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  92. package/src/components/HomePage/NavigationCard.tsx +20 -18
  93. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  94. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  95. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  96. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  97. package/src/components/ReferenceWidget.tsx +2 -4
  98. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  99. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  100. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  101. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  102. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  103. package/src/components/UnsavedChangesDialog.tsx +0 -2
  104. package/src/components/UserDisplay.tsx +4 -4
  105. package/src/components/VirtualTable/VirtualTable.tsx +170 -19
  106. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  107. package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
  108. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  109. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  110. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  111. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  112. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
  113. package/src/components/VirtualTable/types.tsx +2 -0
  114. package/src/components/common/useColumnsIds.tsx +95 -3
  115. package/src/components/index.tsx +4 -0
  116. package/src/contexts/BreacrumbsContext.tsx +15 -8
  117. package/src/contexts/index.ts +10 -0
  118. package/src/core/DefaultAppBar.tsx +39 -26
  119. package/src/core/DefaultDrawer.tsx +42 -56
  120. package/src/core/DrawerNavigationGroup.tsx +118 -0
  121. package/src/core/DrawerNavigationItem.tsx +4 -3
  122. package/src/core/EntityEditView.tsx +41 -43
  123. package/src/core/SideDialogs.tsx +4 -2
  124. package/src/core/index.tsx +1 -0
  125. package/src/form/PropertyFieldBinding.tsx +58 -43
  126. package/src/form/components/StorageItemPreview.tsx +2 -1
  127. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  128. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  129. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  130. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  131. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
  132. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  133. package/src/form/validation.ts +245 -160
  134. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  135. package/src/hooks/useBuildNavigationController.tsx +42 -19
  136. package/src/hooks/useCollapsedGroups.ts +12 -4
  137. package/src/internal/useBuildDataSource.ts +69 -34
  138. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  139. package/src/internal/useBuildSideEntityController.tsx +2 -4
  140. package/src/internal/useRestoreScroll.tsx +26 -14
  141. package/src/preview/PropertyPreview.tsx +40 -32
  142. package/src/preview/PropertyPreviewProps.tsx +6 -0
  143. package/src/preview/components/DatePreview.tsx +72 -4
  144. package/src/preview/components/EmptyValue.tsx +1 -1
  145. package/src/preview/components/ImagePreview.tsx +37 -21
  146. package/src/preview/components/StorageThumbnail.tsx +16 -12
  147. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  148. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  149. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  150. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  151. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  152. package/src/routes/CustomCMSRoute.tsx +1 -0
  153. package/src/routes/FireCMSRoute.tsx +26 -13
  154. package/src/types/collections.ts +48 -3
  155. package/src/types/datasource.ts +54 -56
  156. package/src/types/plugins.tsx +51 -1
  157. package/src/types/properties.ts +347 -27
  158. package/src/util/__tests__/conditions.test.ts +506 -0
  159. package/src/util/__tests__/objects.test.ts +196 -0
  160. package/src/util/callbacks.ts +6 -3
  161. package/src/util/collections.ts +51 -6
  162. package/src/util/conditions.ts +339 -0
  163. package/src/util/entities.ts +28 -29
  164. package/src/util/entity_cache.ts +2 -1
  165. package/src/util/index.ts +2 -1
  166. package/src/util/objects.ts +31 -13
  167. package/src/util/{references.ts → previews.ts} +14 -0
  168. package/src/util/property_utils.tsx +36 -10
  169. package/src/util/resolutions.ts +57 -55
  170. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -14,8 +14,8 @@ import { enumToObjectEntries, getValueInPath, hydrateRegExp, isPropertyBuilder }
14
14
  // Add custom unique function for array values
15
15
  declare module "yup" {
16
16
  // tslint:disable-next-line
17
- interface ArraySchema<T> {
18
- uniqueInArray(mapper: (a: T) => T, message: string): ArraySchema<T>;
17
+ interface ArraySchema<TIn extends any[] | null | undefined, TContext, TDefault = undefined, TFlags extends yup.Flags = ""> {
18
+ uniqueInArray(mapper: (a: any) => any, message: string): ArraySchema<TIn, TContext, TDefault, TFlags>;
19
19
  }
20
20
  }
21
21
  yup.addMethod(yup.array, "uniqueInArray", function (
@@ -50,12 +50,21 @@ export function getYupEntitySchema<M extends Record<string, any>>(
50
50
  const objectSchema: any = {};
51
51
  Object.entries(properties as Record<string, ResolvedProperty>)
52
52
  .forEach(([name, property]) => {
53
- objectSchema[name] = mapPropertyToYup({
54
- property: property as ResolvedProperty<any>,
55
- customFieldValidator,
56
- name,
57
- entityId
58
- });
53
+ try {
54
+ objectSchema[name] = mapPropertyToYup({
55
+ property: property as ResolvedProperty<any>,
56
+ customFieldValidator,
57
+ name,
58
+ entityId
59
+ });
60
+ } catch (e: any) {
61
+ console.error(`Error creating validation schema for property ${name}:`, e);
62
+ objectSchema[name] = yup.mixed().test(
63
+ "validation-error",
64
+ `Validation error: ${e?.message ?? "Unknown error"}`,
65
+ () => false
66
+ );
67
+ }
59
68
  });
60
69
  return yup.object().shape(objectSchema);
61
70
  }
@@ -65,7 +74,12 @@ export function mapPropertyToYup<T extends CMSType>(propertyContext: PropertyCon
65
74
  const property = propertyContext.property;
66
75
  if (isPropertyBuilder(property)) {
67
76
  console.error("Error in property", propertyContext);
68
- throw Error("Trying to create a yup mapping from a property builder. Please use resolved properties only");
77
+ // Return a permissive schema with an error message instead of crashing
78
+ return yup.mixed().test(
79
+ "property-builder-error",
80
+ "Invalid property configuration: property builder should be resolved",
81
+ () => false
82
+ );
69
83
  }
70
84
 
71
85
  if (property.dataType === "string") {
@@ -85,60 +99,93 @@ export function mapPropertyToYup<T extends CMSType>(propertyContext: PropertyCon
85
99
  } else if (property.dataType === "reference") {
86
100
  return getYupReferenceSchema(propertyContext as PropertyContext<EntityReference>);
87
101
  }
88
- console.error("Unsupported data type in yup mapping", property)
89
- throw Error("Unsupported data type in yup mapping");
102
+
103
+ // Log the error but don't crash the form - return a permissive schema with an error message
104
+ console.error("Unsupported data type in yup mapping", property);
105
+ const dataType = (property as any).dataType ?? "unknown";
106
+ return yup.mixed().test(
107
+ "unsupported-data-type",
108
+ `Unsupported data type: ${dataType}`,
109
+ () => false
110
+ );
90
111
  }
91
112
 
92
113
  export function getYupMapObjectSchema({
93
- property,
94
- entityId,
95
- customFieldValidator,
96
- name
97
- }: PropertyContext<Record<string, any>>): ObjectSchema<any> {
114
+ property,
115
+ entityId,
116
+ customFieldValidator,
117
+ name
118
+ }: PropertyContext<Record<string, any>>): ObjectSchema<any> {
98
119
  const objectSchema: any = {};
99
120
  const validation = property.validation;
100
121
  if (property.properties)
101
122
  Object.entries(property.properties).forEach(([childName, childProperty]: [string, ResolvedProperty]) => {
102
- objectSchema[childName] = mapPropertyToYup<any>({
103
- property: childProperty,
104
- parentProperty: property as ResolvedMapProperty,
105
- customFieldValidator,
106
- name: `${name}[${childName}]`,
107
- entityId
108
- });
123
+ try {
124
+ objectSchema[childName] = mapPropertyToYup<any>({
125
+ property: childProperty,
126
+ parentProperty: property as ResolvedMapProperty,
127
+ customFieldValidator,
128
+ name: `${name}[${childName}]`,
129
+ entityId
130
+ });
131
+ } catch (e: any) {
132
+ console.error(`Error creating validation schema for property ${childName}:`, e);
133
+ objectSchema[childName] = yup.mixed().test(
134
+ "validation-error",
135
+ `Validation error: ${e?.message ?? "Unknown error"}`,
136
+ () => false
137
+ );
138
+ }
109
139
  });
110
140
 
111
141
  const shape = yup.object().shape(objectSchema);
112
142
  if (validation?.required) {
113
- return shape.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true);
143
+ // In yup v0.x, .required().nullable(true) allowed null values
144
+ // To match this behavior: reject undefined but allow null
145
+ return shape.nullable().test(
146
+ "required",
147
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
148
+ (value) => value !== undefined
149
+ ) as any;
114
150
  }
115
- return yup.object().shape(shape.fields).default(undefined).notRequired().nullable(true);
151
+ return yup.object().shape(shape.fields).default(undefined).nullable().optional() as any;
116
152
  }
117
153
 
118
154
  function getYupStringSchema({
119
- property,
120
- parentProperty,
121
- customFieldValidator,
122
- name,
123
- entityId
124
- }: PropertyContext<string>): StringSchema {
125
- let collection: StringSchema<any> = yup.string();
155
+ property,
156
+ parentProperty,
157
+ customFieldValidator,
158
+ name,
159
+ entityId
160
+ }: PropertyContext<string>): StringSchema {
161
+ let schema: StringSchema<any> = yup.string().nullable();
126
162
  const validation = property.validation;
163
+
127
164
  if (property.enumValues) {
128
- if (validation?.required)
129
- collection = collection.required(validation?.requiredMessage ? validation.requiredMessage : "Required");
165
+ if (validation?.required) {
166
+ schema = schema.test(
167
+ "required",
168
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
169
+ (value) => value !== undefined && value !== null && value !== ""
170
+ );
171
+ }
130
172
  const entries = enumToObjectEntries(property.enumValues);
131
- collection = collection.oneOf(
173
+ schema = schema.oneOf(
132
174
  (validation?.required ? entries : [...entries, null])
133
175
  .map((enumValueConfig) => enumValueConfig?.id ?? null)
134
- ).nullable(true);
176
+ );
135
177
  }
178
+
136
179
  if (validation) {
137
- collection = validation.required
138
- ? collection.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true)
139
- : collection.notRequired().nullable(true);
180
+ if (validation.required) {
181
+ schema = schema.test(
182
+ "required",
183
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
184
+ (value) => value !== undefined && value !== null && value !== ""
185
+ );
186
+ }
140
187
  if (validation.unique && customFieldValidator && name)
141
- collection = collection.test("unique", "This value already exists and should be unique",
188
+ schema = schema.test("unique", "This value already exists and should be unique",
142
189
  (value, context) =>
143
190
  customFieldValidator({
144
191
  name,
@@ -147,46 +194,49 @@ function getYupStringSchema({
147
194
  value,
148
195
  entityId
149
196
  }));
150
- if (validation.min || validation.min === 0) collection = collection.min(validation.min, `${property.name} must be min ${validation.min} characters long`);
151
- if (validation.max || validation.max === 0) collection = collection.max(validation.max, `${property.name} must be max ${validation.max} characters long`);
197
+ if (validation.min || validation.min === 0) schema = schema.min(validation.min, `${property.name} must be min ${validation.min} characters long`);
198
+ if (validation.max || validation.max === 0) schema = schema.max(validation.max, `${property.name} must be max ${validation.max} characters long`);
152
199
  if (validation.matches) {
153
200
  const regExp = typeof validation.matches === "string" ? hydrateRegExp(validation.matches) : validation.matches;
154
201
  if (regExp) {
155
- collection = collection.matches(regExp, validation.matchesMessage ? { message: validation.matchesMessage } : undefined)
202
+ schema = schema.matches(regExp, validation.matchesMessage ? { message: validation.matchesMessage } : undefined)
156
203
  }
157
204
  }
158
- if (validation.trim) collection = collection.trim();
159
- if (validation.lowercase) collection = collection.lowercase();
160
- if (validation.uppercase) collection = collection.uppercase();
161
- if (property.email) collection = collection.email(`${property.name} must be an email`);
205
+ if (validation.trim) schema = schema.trim();
206
+ if (validation.lowercase) schema = schema.lowercase();
207
+ if (validation.uppercase) schema = schema.uppercase();
208
+ if (property.email) schema = schema.email(`${property.name} must be an email`);
162
209
  if (property.url) {
163
210
  if (!property.storage || property.storage?.storeUrl) {
164
- collection = collection.url(`${property.name} must be a url`);
211
+ schema = schema.url(`${property.name} must be a url`);
165
212
  } else {
166
213
  console.warn(`Property ${property.name} has a url validation but its storage configuration is not set to store urls`);
167
214
  }
168
215
  }
169
- } else {
170
- collection = collection.notRequired().nullable(true);
171
216
  }
172
- return collection;
217
+ return schema;
173
218
  }
174
219
 
175
220
  function getYupNumberSchema({
176
- property,
177
- parentProperty,
178
- customFieldValidator,
179
- name,
180
- entityId
181
- }: PropertyContext<number>): NumberSchema {
221
+ property,
222
+ parentProperty,
223
+ customFieldValidator,
224
+ name,
225
+ entityId
226
+ }: PropertyContext<number>): NumberSchema {
182
227
  const validation = property.validation;
183
- let collection: NumberSchema<any> = yup.number().typeError("Must be a number");
228
+ let schema: NumberSchema<any> = yup.number().nullable().typeError("Must be a number");
229
+
184
230
  if (validation) {
185
- collection = validation.required
186
- ? collection.required(validation.requiredMessage ? validation.requiredMessage : "Required").nullable(true)
187
- : collection.notRequired().nullable(true);
231
+ if (validation.required) {
232
+ schema = schema.test(
233
+ "required",
234
+ validation.requiredMessage ? validation.requiredMessage : "Required",
235
+ (value) => value !== undefined && value !== null
236
+ );
237
+ }
188
238
  if (validation.unique && customFieldValidator && name)
189
- collection = collection.test("unique",
239
+ schema = schema.test("unique",
190
240
  "This value already exists and should be unique",
191
241
  (value) => customFieldValidator({
192
242
  name,
@@ -195,30 +245,29 @@ function getYupNumberSchema({
195
245
  value,
196
246
  entityId
197
247
  }));
198
- if (validation.min || validation.min === 0) collection = collection.min(validation.min, `${property.name} must be higher or equal to ${validation.min}`);
199
- if (validation.max || validation.max === 0) collection = collection.max(validation.max, `${property.name} must be lower or equal to ${validation.max}`);
200
- if (validation.lessThan || validation.lessThan === 0) collection = collection.lessThan(validation.lessThan, `${property.name} must be higher than ${validation.lessThan}`);
201
- if (validation.moreThan || validation.moreThan === 0) collection = collection.moreThan(validation.moreThan, `${property.name} must be lower than ${validation.moreThan}`);
202
- if (validation.positive) collection = collection.positive(`${property.name} must be positive`);
203
- if (validation.negative) collection = collection.negative(`${property.name} must be negative`);
204
- if (validation.integer) collection = collection.integer(`${property.name} must be an integer`);
205
- } else {
206
- collection = collection.notRequired().nullable(true);
248
+ if (validation.min || validation.min === 0) schema = schema.min(validation.min, `${property.name} must be higher or equal to ${validation.min}`);
249
+ if (validation.max || validation.max === 0) schema = schema.max(validation.max, `${property.name} must be lower or equal to ${validation.max}`);
250
+ if (validation.lessThan || validation.lessThan === 0) schema = schema.lessThan(validation.lessThan, `${property.name} must be higher than ${validation.lessThan}`);
251
+ if (validation.moreThan || validation.moreThan === 0) schema = schema.moreThan(validation.moreThan, `${property.name} must be lower than ${validation.moreThan}`);
252
+ if (validation.positive) schema = schema.positive(`${property.name} must be positive`);
253
+ if (validation.negative) schema = schema.negative(`${property.name} must be negative`);
254
+ if (validation.integer) schema = schema.integer(`${property.name} must be an integer`);
207
255
  }
208
- return collection;
256
+ return schema;
209
257
  }
210
258
 
211
259
  function getYupGeoPointSchema({
212
- property,
213
- parentProperty,
214
- customFieldValidator,
215
- name,
216
- entityId
217
- }: PropertyContext<GeoPoint>): AnySchema {
218
- let collection: ObjectSchema<any> = yup.object();
260
+ property,
261
+ parentProperty,
262
+ customFieldValidator,
263
+ name,
264
+ entityId
265
+ }: PropertyContext<GeoPoint>): AnySchema {
266
+ let schema: ObjectSchema<any> = yup.object().nullable() as ObjectSchema<any>;
219
267
  const validation = property.validation;
268
+
220
269
  if (validation?.unique && customFieldValidator && name)
221
- collection = collection.test("unique",
270
+ schema = schema.test("unique",
222
271
  "This value already exists and should be unique",
223
272
  (value) => customFieldValidator({
224
273
  name,
@@ -228,31 +277,38 @@ function getYupGeoPointSchema({
228
277
  entityId
229
278
  }));
230
279
  if (validation?.required) {
231
- collection = collection.required(validation.requiredMessage).nullable(true);
232
- } else {
233
- collection = collection.notRequired().nullable(true);
280
+ schema = schema.test(
281
+ "required",
282
+ validation.requiredMessage ? validation.requiredMessage : "Required",
283
+ (value) => value !== undefined && value !== null
284
+ );
234
285
  }
235
- return collection;
286
+ return schema;
236
287
  }
237
288
 
238
289
  function getYupDateSchema({
239
- property,
240
- parentProperty,
241
- customFieldValidator,
242
- name,
243
- entityId
244
- }: PropertyContext<Date>): AnySchema | DateSchema {
290
+ property,
291
+ parentProperty,
292
+ customFieldValidator,
293
+ name,
294
+ entityId
295
+ }: PropertyContext<Date>): AnySchema | DateSchema {
245
296
  if (property.autoValue) {
246
- return yup.object().nullable();
297
+ return yup.date().nullable();
247
298
  }
248
- let collection: DateSchema<any> = yup.date();
299
+ let schema: DateSchema<any> = yup.date().nullable();
249
300
  const validation = property.validation;
301
+
250
302
  if (validation) {
251
- collection = validation.required
252
- ? collection.required(validation?.requiredMessage ? validation.requiredMessage : "Required")
253
- : collection.notRequired();
303
+ if (validation.required) {
304
+ schema = schema.test(
305
+ "required",
306
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
307
+ (value) => value !== undefined && value !== null
308
+ );
309
+ }
254
310
  if (validation.unique && customFieldValidator && name)
255
- collection = collection.test("unique",
311
+ schema = schema.test("unique",
256
312
  "This value already exists and should be unique",
257
313
  (value) => customFieldValidator({
258
314
  name,
@@ -261,31 +317,32 @@ function getYupDateSchema({
261
317
  value,
262
318
  entityId
263
319
  }));
264
- if (validation.min) collection = collection.min(validation.min, `${property.name} must be after ${validation.min}`);
265
- if (validation.max) collection = collection.max(validation.max, `${property.name} must be before ${validation.min}`);
266
- } else {
267
- collection = collection.notRequired();
320
+ if (validation.min) schema = schema.min(validation.min, `${property.name} must be after ${validation.min}`);
321
+ if (validation.max) schema = schema.max(validation.max, `${property.name} must be before ${validation.min}`);
268
322
  }
269
- return collection
270
- .transform((v: any) => (v instanceof Date ? v : null))
271
- .nullable();
323
+ return schema.transform((v: any) => (v instanceof Date ? v : null));
272
324
  }
273
325
 
274
326
  function getYupReferenceSchema({
275
- property,
276
- parentProperty,
277
- customFieldValidator,
278
- name,
279
- entityId
280
- }: PropertyContext<EntityReference>): AnySchema {
281
- let collection: ObjectSchema<any> = yup.object();
327
+ property,
328
+ parentProperty,
329
+ customFieldValidator,
330
+ name,
331
+ entityId
332
+ }: PropertyContext<EntityReference>): AnySchema {
333
+ let schema: ObjectSchema<any> = yup.object().nullable() as ObjectSchema<any>;
282
334
  const validation = property.validation;
335
+
283
336
  if (validation) {
284
- collection = validation.required
285
- ? collection.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true)
286
- : collection.notRequired().nullable(true);
337
+ if (validation.required) {
338
+ schema = schema.test(
339
+ "required",
340
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
341
+ (value) => value !== undefined && value !== null
342
+ );
343
+ }
287
344
  if (validation.unique && customFieldValidator && name)
288
- collection = collection.test("unique",
345
+ schema = schema.test("unique",
289
346
  "This value already exists and should be unique",
290
347
  (value) => customFieldValidator({
291
348
  name,
@@ -294,27 +351,30 @@ function getYupReferenceSchema({
294
351
  value,
295
352
  entityId
296
353
  }));
297
- } else {
298
- collection = collection.notRequired().nullable(true);
299
354
  }
300
- return collection;
355
+ return schema;
301
356
  }
302
357
 
303
358
  function getYupBooleanSchema({
304
- property,
305
- parentProperty,
306
- customFieldValidator,
307
- name,
308
- entityId
309
- }: PropertyContext<boolean>): BooleanSchema {
310
- let collection: BooleanSchema<any> = yup.boolean();
359
+ property,
360
+ parentProperty,
361
+ customFieldValidator,
362
+ name,
363
+ entityId
364
+ }: PropertyContext<boolean>): BooleanSchema {
365
+ let schema: BooleanSchema<any> = yup.boolean().nullable();
311
366
  const validation = property.validation;
367
+
312
368
  if (validation) {
313
- collection = validation.required
314
- ? collection.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true)
315
- : collection.notRequired().nullable(true);
369
+ if (validation.required) {
370
+ schema = schema.test(
371
+ "required",
372
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
373
+ (value) => value !== undefined && value !== null
374
+ );
375
+ }
316
376
  if (validation.unique && customFieldValidator && name)
317
- collection = collection.test("unique",
377
+ schema = schema.test("unique",
318
378
  "This value already exists and should be unique",
319
379
  (value) => customFieldValidator({
320
380
  name,
@@ -323,10 +383,8 @@ function getYupBooleanSchema({
323
383
  value,
324
384
  entityId
325
385
  }));
326
- } else {
327
- collection = collection.notRequired().nullable(true);
328
386
  }
329
- return collection;
387
+ return schema;
330
388
  }
331
389
 
332
390
  function hasUniqueInArrayModifier(property: ResolvedProperty): boolean | [string, ResolvedProperty][] {
@@ -340,25 +398,38 @@ function hasUniqueInArrayModifier(property: ResolvedProperty): boolean | [string
340
398
  }
341
399
 
342
400
  function getYupArraySchema({
343
- property,
344
- parentProperty,
345
- customFieldValidator,
346
- name,
347
- entityId
348
- }: PropertyContext<any[]>): AnySchema<any> {
401
+ property,
402
+ parentProperty,
403
+ customFieldValidator,
404
+ name,
405
+ entityId
406
+ }: PropertyContext<any[]>): AnySchema<any> {
349
407
 
350
- let arraySchema: ArraySchema<any> = yup.array();
408
+ let arraySchema: any = yup.array().nullable();
351
409
 
352
410
  if (property.of) {
353
411
  if (Array.isArray(property.of)) {
354
- const yupProperties = (property.of as ResolvedProperty[]).map((p, index) => ({
355
- [`${name}[${index}]`]: mapPropertyToYup({
356
- property: p as ResolvedProperty<any>,
357
- parentProperty: property,
358
- entityId
359
- })
360
- })).reduce((a, b) => ({ ...a, ...b }), {});
361
- return yup.array().of(
412
+ const yupProperties = (property.of as ResolvedProperty[]).map((p, index) => {
413
+ try {
414
+ return {
415
+ [`${name}[${index}]`]: mapPropertyToYup({
416
+ property: p as ResolvedProperty<any>,
417
+ parentProperty: property,
418
+ entityId
419
+ })
420
+ };
421
+ } catch (e: any) {
422
+ console.error(`Error creating validation schema for array item ${index}:`, e);
423
+ return {
424
+ [`${name}[${index}]`]: yup.mixed().test(
425
+ "validation-error",
426
+ `Validation error: ${e?.message ?? "Unknown error"}`,
427
+ () => false
428
+ )
429
+ };
430
+ }
431
+ }).reduce((a, b) => ({ ...a, ...b }), {});
432
+ return yup.array().nullable().of(
362
433
  yup.mixed().test(
363
434
  "Dynamic object validation",
364
435
  "Dynamic object validation error",
@@ -369,20 +440,28 @@ function getYupArraySchema({
369
440
  )
370
441
  );
371
442
  } else {
372
- arraySchema = arraySchema.of(mapPropertyToYup({
373
- property: property.of,
374
- parentProperty: property,
375
- entityId
376
- }));
443
+ try {
444
+ arraySchema = arraySchema.of(mapPropertyToYup({
445
+ property: property.of,
446
+ parentProperty: property,
447
+ entityId
448
+ }));
449
+ } catch (e: any) {
450
+ console.error(`Error creating validation schema for array of property:`, e);
451
+ arraySchema = arraySchema.of(yup.mixed().test(
452
+ "validation-error",
453
+ `Validation error: ${e?.message ?? "Unknown error"}`,
454
+ () => false
455
+ ));
456
+ }
377
457
  const arrayUniqueFields = hasUniqueInArrayModifier(property.of);
378
458
  if (arrayUniqueFields) {
379
459
  if (typeof arrayUniqueFields === "boolean") {
380
- arraySchema = arraySchema.uniqueInArray(v => v, `${property.name} should have unique values within the array`);
460
+ arraySchema = arraySchema.uniqueInArray((v: any) => v, `${property.name} should have unique values within the array`);
381
461
  } else if (Array.isArray(arrayUniqueFields)) {
382
462
  arrayUniqueFields.forEach(([name, childProperty]) => {
383
- arraySchema = arraySchema.uniqueInArray(v => v && v[name], `${property.name} → ${childProperty.name ?? name}: should have unique values within the array`);
384
- }
385
- );
463
+ arraySchema = arraySchema.uniqueInArray((v: any) => v && v[name], `${property.name} → ${childProperty.name ?? name}: should have unique values within the array`);
464
+ });
386
465
  }
387
466
  }
388
467
  }
@@ -390,13 +469,19 @@ function getYupArraySchema({
390
469
  const validation = property.validation;
391
470
 
392
471
  if (validation) {
393
- arraySchema = validation.required
394
- ? arraySchema.required(validation?.requiredMessage ? validation.requiredMessage : "Required").nullable(true)
395
- : arraySchema.notRequired().nullable(true);
472
+ if (validation.required) {
473
+ arraySchema = arraySchema.test(
474
+ "required",
475
+ validation?.requiredMessage ? validation.requiredMessage : "Required",
476
+ (value: any) => value !== undefined && value !== null && value.length > 0
477
+ );
478
+ }
396
479
  if (validation.min || validation.min === 0) arraySchema = arraySchema.min(validation.min, `${property.name} should be min ${validation.min} entries long`);
397
480
  if (validation.max) arraySchema = arraySchema.max(validation.max, `${property.name} should be max ${validation.max} entries long`);
398
- } else {
399
- arraySchema = arraySchema.notRequired().nullable(true);
481
+ // Handle uniqueInArray at the array level (in addition to the of.validation check above)
482
+ if (validation.uniqueInArray) {
483
+ arraySchema = arraySchema.uniqueInArray((v: any) => v, `${property.name} should have unique values within the array`);
484
+ }
400
485
  }
401
486
  return arraySchema;
402
487
  }
@@ -9,16 +9,34 @@ export interface BreadcrumbsController {
9
9
  set: (props: {
10
10
  breadcrumbs: BreadcrumbEntry[];
11
11
  }) => void;
12
+ /**
13
+ * Update the count for a specific breadcrumb by ID.
14
+ */
15
+ updateCount: (id: string, count: number | null | undefined) => void;
12
16
  }
13
17
 
18
+
14
19
  /**
15
20
  * @group Hooks and utilities
16
21
  */
17
22
  export interface BreadcrumbEntry {
18
23
  title: string;
19
24
  url: string;
25
+ /**
26
+ * Optional entity count for collection breadcrumbs.
27
+ * - undefined: not applicable (e.g., entity breadcrumb, custom view)
28
+ * - null: loading
29
+ * - number: loaded count
30
+ */
31
+ count?: number | null;
32
+ /**
33
+ * Unique identifier for this breadcrumb (e.g., collection path).
34
+ * Used to update count without replacing entire breadcrumb array.
35
+ */
36
+ id?: string;
20
37
  }
21
38
 
39
+
22
40
  /**
23
41
  * Hook to retrieve the BreadcrumbsController.
24
42
  *