@firecms/core 3.0.0 → 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 (185) 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/VirtualTable.performance.test.d.ts +1 -0
  26. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  27. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
  28. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  29. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  30. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  31. package/dist/components/VirtualTable/types.d.ts +2 -0
  32. package/dist/components/index.d.ts +3 -0
  33. package/dist/contexts/index.d.ts +10 -0
  34. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  35. package/dist/core/index.d.ts +1 -0
  36. package/dist/form/components/LocalChangesMenu.d.ts +2 -2
  37. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  38. package/dist/form/validation.d.ts +3 -2
  39. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  40. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  41. package/dist/index.es.js +5397 -1768
  42. package/dist/index.es.js.map +1 -1
  43. package/dist/index.umd.js +5391 -1763
  44. package/dist/index.umd.js.map +1 -1
  45. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  46. package/dist/preview/components/DatePreview.d.ts +13 -3
  47. package/dist/preview/components/ImagePreview.d.ts +5 -1
  48. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  49. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  50. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  51. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  52. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  53. package/dist/types/collections.d.ts +42 -2
  54. package/dist/types/datasource.d.ts +0 -1
  55. package/dist/types/entities.d.ts +1 -0
  56. package/dist/types/plugins.d.ts +46 -1
  57. package/dist/types/properties.d.ts +268 -4
  58. package/dist/types/storage.d.ts +8 -0
  59. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  60. package/dist/util/__tests__/objects.test.d.ts +1 -0
  61. package/dist/util/conditions.d.ts +26 -0
  62. package/dist/util/entities.d.ts +1 -2
  63. package/dist/util/index.d.ts +2 -1
  64. package/dist/util/property_utils.d.ts +2 -1
  65. package/dist/util/resolutions.d.ts +1 -1
  66. package/dist/util/useStorageUploadController.d.ts +1 -1
  67. package/package.json +11 -7
  68. package/src/app/Scaffold.tsx +14 -15
  69. package/src/components/AIIcon.tsx +39 -0
  70. package/src/components/ArrayContainer.tsx +1 -4
  71. package/src/components/ClearFilterSortButton.tsx +19 -16
  72. package/src/components/ConfirmationDialog.tsx +0 -2
  73. package/src/components/DeleteEntityDialog.tsx +2 -4
  74. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  75. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  76. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  77. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  78. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  79. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  80. package/src/components/EntityCollectionView/Board.tsx +324 -0
  81. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  82. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  83. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  84. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  85. package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
  86. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
  87. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  88. package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
  89. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  90. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  91. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  92. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  93. package/src/components/EntityCollectionView/board_types.ts +113 -0
  94. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  95. package/src/components/EntityPreview.tsx +1 -1
  96. package/src/components/ErrorTooltip.tsx +2 -1
  97. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  98. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  99. package/src/components/HomePage/NavigationCard.tsx +20 -18
  100. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  101. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  102. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  103. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  104. package/src/components/ReferenceWidget.tsx +2 -4
  105. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  106. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  107. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  108. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  109. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  110. package/src/components/UnsavedChangesDialog.tsx +0 -2
  111. package/src/components/UserDisplay.tsx +4 -4
  112. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +386 -0
  113. package/src/components/VirtualTable/VirtualTable.tsx +172 -21
  114. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  115. package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
  116. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  117. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  118. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  119. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  120. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
  121. package/src/components/VirtualTable/types.tsx +2 -0
  122. package/src/components/common/useColumnsIds.tsx +95 -3
  123. package/src/components/index.tsx +4 -0
  124. package/src/contexts/BreacrumbsContext.tsx +15 -8
  125. package/src/contexts/index.ts +10 -0
  126. package/src/core/DefaultAppBar.tsx +39 -26
  127. package/src/core/DefaultDrawer.tsx +42 -56
  128. package/src/core/DrawerNavigationGroup.tsx +118 -0
  129. package/src/core/DrawerNavigationItem.tsx +4 -3
  130. package/src/core/EntityEditView.tsx +41 -43
  131. package/src/core/SideDialogs.tsx +4 -2
  132. package/src/core/index.tsx +1 -0
  133. package/src/form/EntityForm.tsx +3 -10
  134. package/src/form/PropertyFieldBinding.tsx +58 -43
  135. package/src/form/components/LocalChangesMenu.tsx +6 -6
  136. package/src/form/components/StorageItemPreview.tsx +2 -1
  137. package/src/form/components/StorageUploadProgress.tsx +4 -3
  138. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  139. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  140. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  141. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  142. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
  143. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  144. package/src/form/validation.ts +245 -160
  145. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  146. package/src/hooks/useBuildNavigationController.tsx +42 -19
  147. package/src/hooks/useCollapsedGroups.ts +12 -4
  148. package/src/internal/useBuildDataSource.ts +69 -34
  149. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  150. package/src/internal/useBuildSideEntityController.tsx +2 -4
  151. package/src/internal/useRestoreScroll.tsx +26 -14
  152. package/src/preview/PropertyPreview.tsx +40 -32
  153. package/src/preview/PropertyPreviewProps.tsx +6 -0
  154. package/src/preview/components/DatePreview.tsx +72 -4
  155. package/src/preview/components/EmptyValue.tsx +1 -1
  156. package/src/preview/components/ImagePreview.tsx +37 -21
  157. package/src/preview/components/ReferencePreview.tsx +6 -1
  158. package/src/preview/components/StorageThumbnail.tsx +16 -12
  159. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  160. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  161. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  162. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  163. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  164. package/src/routes/CustomCMSRoute.tsx +1 -0
  165. package/src/routes/FireCMSRoute.tsx +26 -13
  166. package/src/types/collections.ts +48 -3
  167. package/src/types/datasource.ts +54 -56
  168. package/src/types/entities.ts +10 -0
  169. package/src/types/plugins.tsx +51 -1
  170. package/src/types/properties.ts +357 -27
  171. package/src/types/storage.ts +9 -0
  172. package/src/util/__tests__/conditions.test.ts +506 -0
  173. package/src/util/__tests__/objects.test.ts +196 -0
  174. package/src/util/callbacks.ts +6 -3
  175. package/src/util/collections.ts +51 -6
  176. package/src/util/conditions.ts +339 -0
  177. package/src/util/entities.ts +28 -29
  178. package/src/util/entity_cache.ts +2 -1
  179. package/src/util/index.ts +2 -1
  180. package/src/util/objects.ts +31 -13
  181. package/src/util/{references.ts → previews.ts} +14 -0
  182. package/src/util/property_utils.tsx +36 -10
  183. package/src/util/resolutions.ts +57 -55
  184. package/src/util/useStorageUploadController.tsx +11 -1
  185. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -14,13 +14,15 @@ import {
14
14
  PartialEntityCollection,
15
15
  PropertyOrBuilder,
16
16
  ResolvedProperty,
17
- SaveEntityProps
17
+ SaveEntityProps,
18
+ ViewMode
18
19
  } from "../../types";
19
20
  import {
20
21
  EntityCollectionRowActions,
21
22
  EntityCollectionTable,
22
23
  useDataSourceTableController
23
24
  } from "../EntityCollectionTable";
25
+ import { CollectionTableToolbar } from "../EntityCollectionTable/internal/CollectionTableToolbar";
24
26
 
25
27
  import {
26
28
  canCreateEntity,
@@ -45,8 +47,12 @@ import {
45
47
  useNavigationController,
46
48
  useSideEntityController
47
49
  } from "../../hooks";
50
+ import { useBreadcrumbsController } from "../../hooks/useBreadcrumbsController";
48
51
  import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
49
52
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
53
+ import { EntityCollectionCardView } from "./EntityCollectionCardView";
54
+ import { EntityCollectionBoardView } from "./EntityCollectionBoardView";
55
+ import { ViewModeToggle, KanbanPropertyOption } from "./ViewModeToggle";
50
56
  import {
51
57
  AddIcon,
52
58
  Button,
@@ -142,18 +148,19 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
142
148
  */
143
149
  export const EntityCollectionView = React.memo(
144
150
  function EntityCollectionView<M extends Record<string, any>>({
145
- fullPath: fullPathProp,
146
- fullIdPath,
147
- parentCollectionIds,
148
- isSubCollection,
149
- className,
150
- updateUrl,
151
- ...collectionProp
152
- }: EntityCollectionViewProps<M>
151
+ fullPath: fullPathProp,
152
+ fullIdPath,
153
+ parentCollectionIds,
154
+ isSubCollection,
155
+ className,
156
+ updateUrl,
157
+ ...collectionProp
158
+ }: EntityCollectionViewProps<M>
153
159
  ) {
154
160
 
155
161
  const context = useFireCMSContext();
156
162
  const navigation = useNavigationController();
163
+ const breadcrumbs = useBreadcrumbsController();
157
164
  const fullPath = fullPathProp ?? collectionProp.path;
158
165
  const dataSource = useDataSource(collectionProp);
159
166
  const sideEntityController = useSideEntityController();
@@ -184,8 +191,19 @@ export const EntityCollectionView = React.memo(
184
191
 
185
192
  const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
186
193
 
187
- // number of entities in the collection
188
- const [docsCount, setDocsCount] = useState<number>(0);
194
+ // Track recently deleted entities for optimistic Kanban count updates
195
+ const [deletedEntities, setDeletedEntities] = React.useState<Entity<M>[]>([]);
196
+
197
+ // number of entities in the collection (undefined = loading)
198
+ const [docsCount, setDocsCount] = useState<number | undefined>(undefined);
199
+
200
+ // Optimistic state for column order to prevent UI flickering during persistence
201
+ const [localPropertiesOrder, setLocalPropertiesOrder] = useState<string[] | undefined>(collection.propertiesOrder);
202
+
203
+ // Sync local state with collection's propertiesOrder when it changes from external sources
204
+ useEffect(() => {
205
+ setLocalPropertiesOrder(collection.propertiesOrder);
206
+ }, [collection.propertiesOrder]);
189
207
 
190
208
  const unselectNavigatedEntity = useCallback(() => {
191
209
  const currentSelection = highlightedEntity;
@@ -208,6 +226,83 @@ export const EntityCollectionView = React.memo(
208
226
 
209
227
  const [popOverOpen, setPopOverOpen] = useState(false);
210
228
 
229
+ // View mode priority: URL > saved user config > collection.defaultViewMode
230
+ const defaultViewMode = collection.defaultViewMode ?? "table";
231
+
232
+ // Parse view from URL
233
+ const getViewFromUrl = useCallback((): ViewMode | null => {
234
+ const params = new URLSearchParams(window.location.search);
235
+ const urlView = params.get("__view");
236
+ if (urlView && ["table", "kanban", "cards"].includes(urlView)) {
237
+ return urlView as ViewMode;
238
+ }
239
+ return null;
240
+ }, []);
241
+
242
+ // Get saved view from local persistence
243
+ const getSavedView = useCallback((): ViewMode | null => {
244
+ const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath)?.defaultViewMode;
245
+ return (saved as ViewMode) ?? null;
246
+ }, [userConfigPersistence, fullPath]);
247
+
248
+ const [viewMode, setViewModeState] = useState<ViewMode>(() => {
249
+ // Priority: URL > saved config > collection default
250
+ const urlView = getViewFromUrl();
251
+ if (urlView) return urlView;
252
+ const savedView = getSavedView();
253
+ if (savedView) return savedView;
254
+ return defaultViewMode;
255
+ });
256
+
257
+ // Sync URL with current view on init (if view came from saved config)
258
+ useEffect(() => {
259
+ const urlView = getViewFromUrl();
260
+ if (!urlView && viewMode !== "table") {
261
+ // View came from saved config but URL doesn't have it - update URL without push
262
+ const url = new URL(window.location.href);
263
+ url.searchParams.set("__view", viewMode);
264
+ window.history.replaceState({}, "", url.toString());
265
+ }
266
+ }, []); // Only on mount
267
+
268
+ // Update URL when view mode changes (user action)
269
+ const setViewMode = useCallback((newMode: ViewMode) => {
270
+ setViewModeState(newMode);
271
+
272
+ // Update URL with __view param
273
+ const url = new URL(window.location.href);
274
+ if (newMode === "table") {
275
+ url.searchParams.delete("__view");
276
+ } else {
277
+ url.searchParams.set("__view", newMode);
278
+ }
279
+ window.history.pushState({}, "", url.toString());
280
+ }, []);
281
+
282
+ // Listen for browser back/forward
283
+ useEffect(() => {
284
+ const handlePopState = () => {
285
+ const urlView = getViewFromUrl();
286
+ if (urlView) {
287
+ // URL has explicit view - use it
288
+ setViewModeState(urlView);
289
+ } else {
290
+ // No URL param - fallback to saved config or collection default
291
+ const savedView = getSavedView();
292
+ setViewModeState(savedView ?? defaultViewMode);
293
+ }
294
+ };
295
+
296
+ window.addEventListener("popstate", handlePopState);
297
+ return () => window.removeEventListener("popstate", handlePopState);
298
+ }, [getViewFromUrl, getSavedView, defaultViewMode]);
299
+
300
+ // Card view size state - controls the grid column count
301
+ const [cardSize, setCardSize] = useState<CollectionSize>(collection.defaultSize ?? "m");
302
+
303
+ // Table view size state - controls row height
304
+ const [tableSize, setTableSize] = useState<CollectionSize>(collection.defaultSize ?? "m");
305
+
211
306
  const selectionController = useSelectionController<M>();
212
307
  const usedSelectionController = collection.selectionController ?? selectionController;
213
308
  const {
@@ -284,6 +379,7 @@ export const EntityCollectionView = React.memo(
284
379
  path: fullPath
285
380
  });
286
381
  setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
382
+ setDeletedEntities(prev => [...prev, entity]);
287
383
  setLastDeleteTimestamp(Date.now());
288
384
  };
289
385
 
@@ -293,6 +389,7 @@ export const EntityCollectionView = React.memo(
293
389
  });
294
390
  setSelectedEntities([]);
295
391
  setDeleteEntityClicked(undefined);
392
+ setDeletedEntities(prev => [...prev, ...entities]);
296
393
  setLastDeleteTimestamp(Date.now());
297
394
  };
298
395
 
@@ -317,9 +414,9 @@ export const EntityCollectionView = React.memo(
317
414
  }, [userConfigPersistence]);
318
415
 
319
416
  const onColumnResize = useCallback(({
320
- width,
321
- key
322
- }: OnColumnResizeParams) => {
417
+ width,
418
+ key
419
+ }: OnColumnResizeParams) => {
323
420
 
324
421
  const collection = collectionRef.current;
325
422
  // Only for property columns
@@ -328,29 +425,39 @@ export const EntityCollectionView = React.memo(
328
425
  onCollectionModifiedForUser(fullPath, localCollection);
329
426
  }, [onCollectionModifiedForUser, fullPath]);
330
427
 
331
- const onSizeChanged = useCallback((size: CollectionSize) => {
428
+ const onTableSizeChanged = useCallback((size: CollectionSize) => {
429
+ setTableSize(size);
332
430
  if (userConfigPersistence)
333
431
  onCollectionModifiedForUser(fullPath, { defaultSize: size })
334
432
  }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
335
433
 
434
+ // View mode change: update URL + save to local persistence
435
+ const onViewModeChange = useCallback((mode: ViewMode) => {
436
+ setViewMode(mode);
437
+ // Save to local persistence for next visit
438
+ if (userConfigPersistence) {
439
+ onCollectionModifiedForUser(fullPath, { defaultViewMode: mode } as PartialEntityCollection<M>);
440
+ }
441
+ }, [setViewMode, userConfigPersistence, onCollectionModifiedForUser, fullPath]);
442
+
336
443
  const createEnabled = canCreateEntity(collection, authController, fullPath, null);
337
444
 
338
445
  const uniqueFieldValidator: UniqueFieldValidator = useCallback(
339
446
  ({
340
- name,
341
- value,
342
- property,
343
- entityId
344
- }) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection),
447
+ name,
448
+ value,
449
+ property,
450
+ entityId
451
+ }) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection),
345
452
  [fullPath]);
346
453
 
347
454
  const onValueChange: OnCellValueChange<any, any> = ({
348
- value,
349
- propertyKey,
350
- onValueUpdated,
351
- setError,
352
- data: entity,
353
- }) => {
455
+ value,
456
+ propertyKey,
457
+ onValueUpdated,
458
+ setError,
459
+ data: entity,
460
+ }) => {
354
461
 
355
462
  const updatedValues = setIn({ ...entity.values }, propertyKey, value);
356
463
 
@@ -394,10 +501,78 @@ export const EntityCollectionView = React.memo(
394
501
  authController,
395
502
  }), [collection, fullPath]);
396
503
 
504
+ // Check if Kanban view is available (needs kanban.columnProperty with enumValues)
505
+ const kanbanEnabled = useMemo(() => {
506
+ if (!collection.kanban?.columnProperty) return false;
507
+ const property = getPropertyInPath(resolvedCollection.properties, collection.kanban.columnProperty);
508
+ if (!property || !("dataType" in property) || property.dataType !== "string") return false;
509
+ return Boolean(property.enumValues);
510
+ }, [collection.kanban?.columnProperty, resolvedCollection.properties]);
511
+
512
+ // Check if a plugin can configure Kanban (has KanbanSetupComponent)
513
+ const hasKanbanConfigPlugin = useMemo(() => {
514
+ return customizationController.plugins?.some(plugin => plugin.collectionView?.KanbanSetupComponent) ?? false;
515
+ }, [customizationController.plugins]);
516
+
517
+ // Compute available enum properties for kanban column selection
518
+ const kanbanPropertyOptions: KanbanPropertyOption[] = useMemo(() => {
519
+ const options: KanbanPropertyOption[] = [];
520
+ const properties = resolvedCollection.properties;
521
+
522
+ for (const [key, property] of Object.entries(properties)) {
523
+ const prop = property as any;
524
+ if (prop && prop.dataType === "string" && prop.enumValues) {
525
+ options.push({
526
+ key,
527
+ label: prop.name || key
528
+ });
529
+ }
530
+ }
531
+
532
+ return options;
533
+ }, [resolvedCollection.properties]);
534
+
535
+ // Get saved kanban property from user config
536
+ const getSavedKanbanProperty = useCallback((): string | undefined => {
537
+ const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath);
538
+ return (saved as any)?.kanbanColumnProperty;
539
+ }, [userConfigPersistence, fullPath]);
540
+
541
+ // Selected kanban property state - priority: saved config > collection default > first available
542
+ const [selectedKanbanProperty, setSelectedKanbanProperty] = useState<string>(() => {
543
+ const saved = getSavedKanbanProperty();
544
+ if (saved && kanbanPropertyOptions.some(o => o.key === saved)) return saved;
545
+ if (collection.kanban?.columnProperty) return collection.kanban.columnProperty;
546
+ return kanbanPropertyOptions[0]?.key ?? "";
547
+ });
548
+
549
+ // Update selected property if options change and current selection is no longer valid
550
+ useEffect(() => {
551
+ if (kanbanPropertyOptions.length > 0 && !kanbanPropertyOptions.some(o => o.key === selectedKanbanProperty)) {
552
+ const saved = getSavedKanbanProperty();
553
+ if (saved && kanbanPropertyOptions.some(o => o.key === saved)) {
554
+ setSelectedKanbanProperty(saved);
555
+ } else if (collection.kanban?.columnProperty && kanbanPropertyOptions.some(o => o.key === collection.kanban?.columnProperty)) {
556
+ setSelectedKanbanProperty(collection.kanban.columnProperty);
557
+ } else {
558
+ setSelectedKanbanProperty(kanbanPropertyOptions[0]?.key ?? "");
559
+ }
560
+ }
561
+ }, [kanbanPropertyOptions, selectedKanbanProperty, getSavedKanbanProperty, collection.kanban?.columnProperty]);
562
+
563
+ // Handle kanban property change
564
+ const onKanbanPropertyChange = useCallback((property: string) => {
565
+ setSelectedKanbanProperty(property);
566
+ // Save to local persistence
567
+ if (userConfigPersistence) {
568
+ onCollectionModifiedForUser(fullPath, { kanbanColumnProperty: property } as any);
569
+ }
570
+ }, [userConfigPersistence, onCollectionModifiedForUser, fullPath]);
571
+
397
572
  const getPropertyFor = useCallback(({
398
- propertyKey,
399
- entity
400
- }: GetPropertyForProps<M>) => {
573
+ propertyKey,
574
+ entity
575
+ }: GetPropertyForProps<M>) => {
401
576
  let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
402
577
 
403
578
  // we might not find the property in the collection if combining property builders and map spread
@@ -417,7 +592,18 @@ export const EntityCollectionView = React.memo(
417
592
  });
418
593
  }, [collection.properties, customizationController.propertyConfigs, resolvedCollection.properties]);
419
594
 
420
- const displayedColumnIds = useColumnIds(resolvedCollection, true);
595
+ // Use a collection with local propertiesOrder for optimistic UI updates
596
+ const collectionWithLocalOrder = useMemo(() => {
597
+ if (localPropertiesOrder && localPropertiesOrder !== resolvedCollection.propertiesOrder) {
598
+ return {
599
+ ...resolvedCollection,
600
+ propertiesOrder: localPropertiesOrder
601
+ };
602
+ }
603
+ return resolvedCollection;
604
+ }, [resolvedCollection, localPropertiesOrder]);
605
+
606
+ const displayedColumnIds = useColumnIds(collectionWithLocalOrder, true);
421
607
 
422
608
  const additionalFields = useMemo(() => {
423
609
  const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
@@ -427,23 +613,22 @@ export const EntityCollectionView = React.memo(
427
613
  width: 200,
428
614
  dependencies: [],
429
615
  Builder: ({ entity }) => (
430
- <Button color={"primary"}
431
- variant={"outlined"}
432
- className={"max-w-full truncate justify-start"}
433
- startIcon={<KeyboardTabIcon size={"small"}/>}
434
- onClick={(event: any) => {
435
- event.stopPropagation();
436
- navigateToEntity({
437
- openEntityMode,
438
- collection,
439
- entityId: entity.id,
440
- selectedTab: subcollection.id ?? subcollection.path,
441
- path: fullPath,
442
- fullIdPath,
443
- navigation,
444
- sideEntityController
445
- })
446
- }}>
616
+ <Button
617
+ className={"max-w-full truncate justify-start"}
618
+ startIcon={<KeyboardTabIcon size={"small"} />}
619
+ onClick={(event: any) => {
620
+ event.stopPropagation();
621
+ navigateToEntity({
622
+ openEntityMode,
623
+ collection,
624
+ entityId: entity.id,
625
+ selectedTab: subcollection.id ?? subcollection.path,
626
+ path: fullPath,
627
+ fullIdPath,
628
+ navigation,
629
+ sideEntityController
630
+ })
631
+ }}>
447
632
  {subcollection.name}
448
633
  </Button>
449
634
  )
@@ -465,7 +650,7 @@ export const EntityCollectionView = React.memo(
465
650
  <ReferencePreview
466
651
  key={reference.path + "/" + reference.id}
467
652
  reference={reference}
468
- size={"small"}/>
653
+ size={"small"} />
469
654
  );
470
655
  })}
471
656
  </div>
@@ -488,9 +673,9 @@ export const EntityCollectionView = React.memo(
488
673
  const largeLayout = useLargeLayout();
489
674
 
490
675
  const getActionsForEntity = ({
491
- entity,
492
- customEntityActions
493
- }: {
676
+ entity,
677
+ customEntityActions
678
+ }: {
494
679
  entity?: Entity<M>,
495
680
  customEntityActions?: EntityAction[]
496
681
  }): EntityAction[] => {
@@ -514,11 +699,11 @@ export const EntityCollectionView = React.memo(
514
699
  };
515
700
 
516
701
  const tableRowActionsBuilder = useCallback(({
517
- entity,
518
- size,
519
- width,
520
- frozen
521
- }: {
702
+ entity,
703
+ size,
704
+ width,
705
+ frozen
706
+ }: {
522
707
  entity: Entity<any>,
523
708
  size: CollectionSize,
524
709
  width: number,
@@ -558,45 +743,28 @@ export const EntityCollectionView = React.memo(
558
743
 
559
744
  }, [updateLastDeleteTimestamp, usedSelectionController]);
560
745
 
561
- const title = <Popover
562
- open={popOverOpen}
563
- onOpenChange={setPopOverOpen}
564
- enabled={Boolean(collection.description)}
565
- trigger={<div className="flex flex-col items-start">
566
- <Typography
567
- variant={"subtitle1"}
568
- className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
569
- onClick={collection.description
570
- ? (e) => {
571
- setPopOverOpen(true);
572
- e.stopPropagation();
573
- }
574
- : undefined}>
575
- {`${collection.name}`}
576
- </Typography>
577
-
578
- <EntitiesCount
579
- fullPath={fullPath}
580
- collection={collection}
581
- filter={tableController.filterValues}
582
- sortBy={tableController.sortBy}
583
- onCountChange={setDocsCount}
584
- />
585
-
586
- </div>}
587
- >
746
+ // Update breadcrumb count when count changes (only if loaded)
747
+ useEffect(() => {
748
+ if (docsCount !== undefined) {
749
+ breadcrumbs.updateCount(fullPath, docsCount);
750
+ }
751
+ }, [docsCount, fullPath, breadcrumbs.updateCount]);
588
752
 
589
- {collection.description && <div className="m-4 text-surface-900 dark:text-white">
590
- <Markdown source={collection.description}/>
591
- </div>}
753
+ // EntitiesCount fetches count and updates breadcrumb - no visual rendering needed here
754
+ const countFetcher = <EntitiesCount
755
+ fullPath={fullPath}
756
+ collection={collection}
757
+ filter={tableController.filterValues}
758
+ sortBy={tableController.sortBy}
759
+ onCountChange={setDocsCount}
760
+ />;
592
761
 
593
- </Popover>;
594
762
 
595
763
  const buildAdditionalHeaderWidget = useCallback(({
596
- property,
597
- propertyKey,
598
- onHover
599
- }: {
764
+ property,
765
+ propertyKey,
766
+ onHover
767
+ }: {
600
768
  property: ResolvedProperty,
601
769
  propertyKey: string,
602
770
  onHover: boolean
@@ -616,7 +784,7 @@ export const EntityCollectionView = React.memo(
616
784
  fullPath={fullPath}
617
785
  collection={collection}
618
786
  tableController={tableController}
619
- parentCollectionIds={parentCollectionIds ?? []}/>;
787
+ parentCollectionIds={parentCollectionIds ?? []} />;
620
788
  })}
621
789
  </>;
622
790
  }, [customizationController.plugins, fullPath, parentCollectionIds]);
@@ -625,9 +793,9 @@ export const EntityCollectionView = React.memo(
625
793
  ? function () {
626
794
  if (typeof AddColumnComponent === "function")
627
795
  return <AddColumnComponent fullPath={fullPath}
628
- parentCollectionIds={parentCollectionIds ?? []}
629
- collection={collection}
630
- tableController={tableController}/>;
796
+ parentCollectionIds={parentCollectionIds ?? []}
797
+ collection={collection}
798
+ tableController={tableController} />;
631
799
  return null;
632
800
  }
633
801
  : undefined;
@@ -643,32 +811,38 @@ export const EntityCollectionView = React.memo(
643
811
  parentCollectionIds
644
812
  });
645
813
 
814
+ // Popover open state managed at parent level to prevent closing when view changes
815
+ const [viewModePopoverOpen, setViewModePopoverOpen] = useState(false);
816
+
817
+ // Create ViewModeToggle once to prevent remounting when view changes
818
+ const viewModeToggleElement = (
819
+ <ViewModeToggle
820
+ viewMode={viewMode}
821
+ onViewModeChange={onViewModeChange}
822
+ kanbanEnabled={kanbanEnabled}
823
+ hasKanbanConfigPlugin={hasKanbanConfigPlugin}
824
+ size={viewMode === "table" ? tableSize : viewMode === "cards" ? cardSize : undefined}
825
+ onSizeChanged={viewMode === "table" ? onTableSizeChanged : viewMode === "cards" ? setCardSize : undefined}
826
+ open={viewModePopoverOpen}
827
+ onOpenChange={setViewModePopoverOpen}
828
+ kanbanPropertyOptions={kanbanPropertyOptions}
829
+ selectedKanbanProperty={selectedKanbanProperty}
830
+ onKanbanPropertyChange={onKanbanPropertyChange}
831
+ />
832
+ );
833
+
646
834
  return (
647
- <div className={cls("overflow-hidden h-full w-full rounded-md", className)}
648
- ref={containerRef}>
649
- <EntityCollectionTable
650
- key={`collection_table_${fullPath}`}
651
- additionalFields={additionalFields}
652
- tableController={tableController}
653
- enablePopupIcon={true}
654
- displayedColumnIds={displayedColumnIds}
655
- onSizeChanged={onSizeChanged}
656
- onEntityClick={onEntityClick}
657
- onColumnResize={onColumnResize}
658
- onValueChange={onValueChange}
659
- tableRowActionsBuilder={tableRowActionsBuilder}
660
- uniqueFieldValidator={uniqueFieldValidator}
661
- title={title}
662
- selectionController={usedSelectionController}
663
- highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
664
- defaultSize={collection.defaultSize}
665
- properties={resolvedCollection.properties}
666
- getPropertyFor={getPropertyFor}
667
- onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
668
- onScroll={tableController.onScroll}
669
- initialScroll={tableController.initialScroll}
835
+ <div className={cls("overflow-hidden h-full w-full rounded-md flex flex-col", className)}
836
+ ref={containerRef}>
837
+
838
+ {/* Unified toolbar - rendered once, outside view conditionals */}
839
+ {countFetcher}
840
+ <CollectionTableToolbar
841
+ loading={tableController.dataLoading}
842
+ onTextSearch={textSearchEnabled && textSearchInitialised ? tableController.setSearchString : undefined}
843
+ onTextSearchClick={textSearchEnabled && !textSearchInitialised ? onTextSearchClick : undefined}
670
844
  textSearchLoading={textSearchLoading}
671
- textSearchEnabled={textSearchEnabled}
845
+ viewModeToggle={viewModeToggleElement}
672
846
  actionsStart={<EntityCollectionViewStartActions
673
847
  parentCollectionIds={parentCollectionIds ?? []}
674
848
  collection={collection}
@@ -676,7 +850,8 @@ export const EntityCollectionView = React.memo(
676
850
  path={fullPath}
677
851
  relativePath={collection.path}
678
852
  selectionController={usedSelectionController}
679
- collectionEntitiesCount={docsCount}/>}
853
+ collectionEntitiesCount={docsCount}
854
+ resolvedProperties={resolvedCollection.properties} />}
680
855
  actions={<EntityCollectionViewActions
681
856
  parentCollectionIds={parentCollectionIds ?? []}
682
857
  collection={collection}
@@ -689,33 +864,143 @@ export const EntityCollectionView = React.memo(
689
864
  selectionEnabled={selectionEnabled}
690
865
  collectionEntitiesCount={docsCount}
691
866
  />}
692
- emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
693
- ? <div className="flex flex-col items-center justify-center">
694
- <Typography variant={"subtitle2"}>So empty...</Typography>
695
- <Button
696
- color={"primary"}
697
- variant={"outlined"}
698
- onClick={onNewClick}
699
- className="mt-4"
700
- >
701
- <AddIcon/>
702
- Create your first entry
703
- </Button>
704
- </div>
705
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
706
- }
707
- hoverRow={hoverRow}
708
- inlineEditing={checkInlineEditing()}
709
- AdditionalHeaderWidget={buildAdditionalHeaderWidget}
710
- AddColumnComponent={addColumnComponentInternal}
711
- getIdColumnWidth={getIdColumnWidth}
712
- additionalIDHeaderWidget={<EntityIdHeaderWidget
713
- path={fullPath}
714
- fullIdPath={fullIdPath ?? fullPath}
715
- collection={collection}/>}
716
- openEntityMode={openEntityMode}
717
867
  />
718
868
 
869
+ {/* View content - only the view-specific content changes */}
870
+ {viewMode === "kanban" && (kanbanEnabled || hasKanbanConfigPlugin) ? (
871
+ <EntityCollectionBoardView
872
+ key={`kanban-view-${fullPath}-${selectedKanbanProperty}`}
873
+ collection={collection}
874
+ tableController={tableController}
875
+ fullPath={fullPath}
876
+ parentCollectionIds={parentCollectionIds}
877
+ columnProperty={selectedKanbanProperty}
878
+ onEntityClick={onEntityClick}
879
+ selectionController={usedSelectionController}
880
+ selectionEnabled={selectionEnabled}
881
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
882
+ deletedEntities={deletedEntities}
883
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
884
+ ? <div className="flex flex-col items-center justify-center">
885
+ <Typography variant={"subtitle2"}>So empty...</Typography>
886
+ <Button
887
+ onClick={onNewClick}
888
+ className="mt-4"
889
+ >
890
+ <AddIcon />
891
+ Create your first entry
892
+ </Button>
893
+ </div>
894
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
895
+ }
896
+ />
897
+ ) : viewMode === "cards" ? (
898
+ <EntityCollectionCardView
899
+ key={`cards-view-${fullPath}`}
900
+ collection={collection}
901
+ tableController={tableController}
902
+ onEntityClick={onEntityClick}
903
+ selectionController={usedSelectionController}
904
+ selectionEnabled={selectionEnabled}
905
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
906
+ onScroll={tableController.onScroll}
907
+ initialScroll={tableController.initialScroll}
908
+ size={cardSize}
909
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
910
+ ? <div className="flex flex-col items-center justify-center">
911
+ <Typography variant={"subtitle2"}>So empty...</Typography>
912
+ <Button
913
+ onClick={onNewClick}
914
+ className="mt-4"
915
+ >
916
+ <AddIcon />
917
+ Create your first entry
918
+ </Button>
919
+ </div>
920
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
921
+ }
922
+ />
923
+ ) : (
924
+ <EntityCollectionTable
925
+ key={`collection_table_${fullPath}`}
926
+ hideToolbar={true}
927
+ additionalFields={additionalFields}
928
+ tableController={tableController}
929
+ enablePopupIcon={true}
930
+ displayedColumnIds={displayedColumnIds}
931
+ onSizeChanged={onTableSizeChanged}
932
+ onEntityClick={onEntityClick}
933
+ onColumnResize={onColumnResize}
934
+ onValueChange={onValueChange}
935
+ tableRowActionsBuilder={tableRowActionsBuilder}
936
+ uniqueFieldValidator={uniqueFieldValidator}
937
+ selectionController={usedSelectionController}
938
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
939
+ defaultSize={tableSize}
940
+ properties={resolvedCollection.properties}
941
+ getPropertyFor={getPropertyFor}
942
+ onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
943
+ onScroll={tableController.onScroll}
944
+ initialScroll={tableController.initialScroll}
945
+ textSearchLoading={textSearchLoading}
946
+ textSearchEnabled={textSearchEnabled}
947
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
948
+ ? <div className="flex flex-col items-center justify-center">
949
+ <Typography variant={"subtitle2"}>So empty...</Typography>
950
+ <Button
951
+ onClick={onNewClick}
952
+ className="mt-4"
953
+ >
954
+ <AddIcon />
955
+ Create your first entry
956
+ </Button>
957
+ </div>
958
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
959
+ }
960
+ hoverRow={hoverRow}
961
+ inlineEditing={checkInlineEditing()}
962
+ AdditionalHeaderWidget={buildAdditionalHeaderWidget}
963
+ AddColumnComponent={addColumnComponentInternal}
964
+ getIdColumnWidth={getIdColumnWidth}
965
+ additionalIDHeaderWidget={<EntityIdHeaderWidget
966
+ path={fullPath}
967
+ fullIdPath={fullIdPath ?? fullPath}
968
+ collection={collection} />}
969
+ openEntityMode={openEntityMode}
970
+ onColumnsOrderChange={(newColumns) => {
971
+ // Extract property keys from the new column order
972
+ // Filter to only include actual property columns (not frozen columns, not additional fields, etc.)
973
+ // Deduplicate to clean up any previously duplicated keys
974
+ const seenKeys = new Set<string>();
975
+ const newPropertiesOrder = newColumns
976
+ .filter(col => !col.frozen && getPropertyInPath(collection.properties, col.key))
977
+ .map(col => col.key)
978
+ .filter(key => {
979
+ if (seenKeys.has(key)) return false;
980
+ seenKeys.add(key);
981
+ return true;
982
+ });
983
+
984
+ // Optimistically update local state to prevent UI flickering
985
+ setLocalPropertiesOrder(newPropertiesOrder);
986
+
987
+ // Call each plugin's onColumnsReorder callback
988
+ if (customizationController?.plugins) {
989
+ customizationController.plugins
990
+ .filter(plugin => plugin.collectionView?.onColumnsReorder)
991
+ .forEach(plugin => {
992
+ plugin.collectionView!.onColumnsReorder!({
993
+ fullPath,
994
+ parentCollectionIds: parentCollectionIds ?? [],
995
+ collection,
996
+ newPropertiesOrder
997
+ });
998
+ });
999
+ }
1000
+ }}
1001
+ />
1002
+ )}
1003
+
719
1004
  {popupCell && <PopupFormField
720
1005
  key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`}
721
1006
  open={Boolean(popupCell)}
@@ -728,7 +1013,7 @@ export const EntityCollectionView = React.memo(
728
1013
  customFieldValidator={uniqueFieldValidator}
729
1014
  path={resolvedFullPath}
730
1015
  onCellValueChange={onValueChange}
731
- container={containerRef.current}/>}
1016
+ container={containerRef.current} />}
732
1017
 
733
1018
  {deleteEntityClicked &&
734
1019
  <DeleteEntityDialog
@@ -739,7 +1024,7 @@ export const EntityCollectionView = React.memo(
739
1024
  open={Boolean(deleteEntityClicked)}
740
1025
  onEntityDelete={internalOnEntityDelete}
741
1026
  onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
742
- onClose={() => setDeleteEntityClicked(undefined)}/>}
1027
+ onClose={() => setDeleteEntityClicked(undefined)} />}
743
1028
 
744
1029
  </div>
745
1030
  );
@@ -769,12 +1054,12 @@ export const EntityCollectionView = React.memo(
769
1054
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
770
1055
 
771
1056
  function EntitiesCount({
772
- fullPath,
773
- collection,
774
- filter,
775
- sortBy,
776
- onCountChange
777
- }: {
1057
+ fullPath,
1058
+ collection,
1059
+ filter,
1060
+ sortBy,
1061
+ onCountChange
1062
+ }: {
778
1063
  fullPath: string,
779
1064
  collection: EntityCollection,
780
1065
  filter?: FilterValues<any>,
@@ -803,9 +1088,9 @@ function EntitiesCount({
803
1088
  }, [fullPath, dataSource.countEntities, resolvedPath, collection, filter, sortByProperty, currentSort]);
804
1089
 
805
1090
  useEffect(() => {
806
- if (onCountChange) {
1091
+ if (onCountChange && count !== undefined) {
807
1092
  setError(undefined);
808
- onCountChange(count ?? 0);
1093
+ onCountChange(count);
809
1094
  }
810
1095
  }, [onCountChange, count]);
811
1096
 
@@ -813,14 +1098,11 @@ function EntitiesCount({
813
1098
  return null;
814
1099
  }
815
1100
 
816
- return <Typography
817
- className="w-full text-ellipsis block overflow-hidden whitespace-nowrap max-w-xs text-left w-fit-content"
818
- variant={"caption"}
819
- color={"secondary"}>
820
- {count !== undefined ? `${count} entities` : <Skeleton className={"w-full max-w-[80px] mt-1"}/>}
821
- </Typography>;
1101
+ // Count is now displayed in the breadcrumb bar, this component only fetches and reports
1102
+ return null;
822
1103
  }
823
1104
 
1105
+
824
1106
  function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityCollection {
825
1107
  if (key.includes(".")) {
826
1108
  const [parentKey, ...childKey] = key.split(".");
@@ -830,10 +1112,10 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
830
1112
  }
831
1113
 
832
1114
  function EntityIdHeaderWidget({
833
- collection,
834
- path,
835
- fullIdPath
836
- }: {
1115
+ collection,
1116
+ path,
1117
+ fullIdPath
1118
+ }: {
837
1119
  collection: EntityCollection,
838
1120
  path: string,
839
1121
  fullIdPath: string
@@ -857,29 +1139,29 @@ function EntityIdHeaderWidget({
857
1139
  alignOffset={-117}
858
1140
  trigger={
859
1141
  <IconButton size={"small"}>
860
- <SearchIcon size={"small"}/>
1142
+ <SearchIcon size={"small"} />
861
1143
  </IconButton>
862
1144
  }>
863
1145
  <div
864
1146
  className={cls("my-2 rounded-lg bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-white")}>
865
1147
  <form noValidate={true}
866
- onSubmit={(e) => {
867
- e.preventDefault();
868
- if (!searchString) return;
869
- setOpenPopup(false);
870
- const entityId = searchString.trim();
871
- setRecentIds(addRecentId(collection.id, entityId));
872
- navigateToEntity({
873
- openEntityMode,
874
- collection,
875
- entityId,
876
- path,
877
- fullIdPath,
878
- sideEntityController,
879
- navigation
880
- })
881
- }}
882
- className={"w-96 max-w-full"}>
1148
+ onSubmit={(e) => {
1149
+ e.preventDefault();
1150
+ if (!searchString) return;
1151
+ setOpenPopup(false);
1152
+ const entityId = searchString.trim();
1153
+ setRecentIds(addRecentId(collection.id, entityId));
1154
+ navigateToEntity({
1155
+ openEntityMode,
1156
+ collection,
1157
+ entityId,
1158
+ path,
1159
+ fullIdPath,
1160
+ sideEntityController,
1161
+ navigation
1162
+ })
1163
+ }}
1164
+ className={"w-96 max-w-full"}>
883
1165
 
884
1166
  <div className="flex p-2 w-full gap-2">
885
1167
  <input
@@ -890,32 +1172,32 @@ function EntityIdHeaderWidget({
890
1172
  setSearchString(e.target.value);
891
1173
  }}
892
1174
  value={searchString}
893
- className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled}/>
1175
+ className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled} />
894
1176
  <Button variant={"text"}
895
- disabled={!(searchString.trim())}
896
- type={"submit"}
897
- ><KeyboardTabIcon/></Button>
1177
+ disabled={!(searchString.trim())}
1178
+ type={"submit"}
1179
+ ><KeyboardTabIcon /></Button>
898
1180
  </div>
899
1181
  </form>
900
1182
  {recentIds && recentIds.length > 0 && <div className="flex flex-col gap-2 p-2">
901
1183
  {recentIds.map(id => (
902
1184
  <ReferencePreview reference={new EntityReference(id, path)}
903
- key={id}
904
- hover={true}
905
- onClick={() => {
906
- setOpenPopup(false);
907
- navigateToEntity({
908
- openEntityMode,
909
- collection,
910
- entityId: id,
911
- path,
912
- fullIdPath,
913
- sideEntityController,
914
- navigation
915
- })
916
- }}
917
- includeEntityLink={false}
918
- size={"small"}/>
1185
+ key={id}
1186
+ hover={true}
1187
+ onClick={() => {
1188
+ setOpenPopup(false);
1189
+ navigateToEntity({
1190
+ openEntityMode,
1191
+ collection,
1192
+ entityId: id,
1193
+ path,
1194
+ fullIdPath,
1195
+ sideEntityController,
1196
+ navigation
1197
+ })
1198
+ }}
1199
+ includeEntityLink={false}
1200
+ size={"small"} />
919
1201
  ))}
920
1202
  </div>}
921
1203
  </div>