@firecms/core 3.0.1 → 3.1.0-canary.9e89e98

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 +44 -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 +5185 -1561
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.umd.js +5179 -1556
  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 +50 -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 +490 -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 +199 -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 +57 -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,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,84 @@ export const EntityCollectionView = React.memo(
394
501
  authController,
395
502
  }), [collection, fullPath]);
396
503
 
504
+ // Check if Kanban view is possible (collection has at least one string enum property)
505
+ const hasEnumProperty = useMemo(() => {
506
+ const properties = resolvedCollection.properties;
507
+ return Object.values(properties).some((prop: any) =>
508
+ prop && prop.dataType === "string" && prop.enumValues
509
+ );
510
+ }, [resolvedCollection.properties]);
511
+
512
+ // Compute the effective enabled views:
513
+ // - Start from collection.enabledViews (defaults to all three)
514
+ // - Filter out kanban if no enum properties exist
515
+ const enabledViews: ViewMode[] = useMemo(() => {
516
+ const configured = collection.enabledViews ?? ["table", "cards", "kanban"];
517
+ if (!hasEnumProperty) {
518
+ return configured.filter(v => v !== "kanban");
519
+ }
520
+ return configured;
521
+ }, [collection.enabledViews, hasEnumProperty]);
522
+
523
+ // Compute available enum properties for kanban column selection
524
+ const kanbanPropertyOptions: KanbanPropertyOption[] = useMemo(() => {
525
+ const options: KanbanPropertyOption[] = [];
526
+ const properties = resolvedCollection.properties;
527
+
528
+ for (const [key, property] of Object.entries(properties)) {
529
+ const prop = property as any;
530
+ if (prop && prop.dataType === "string" && prop.enumValues) {
531
+ options.push({
532
+ key,
533
+ label: prop.name || key
534
+ });
535
+ }
536
+ }
537
+
538
+ return options;
539
+ }, [resolvedCollection.properties]);
540
+
541
+ // Get saved kanban property from user config
542
+ const getSavedKanbanProperty = useCallback((): string | undefined => {
543
+ const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath);
544
+ return (saved as any)?.kanbanColumnProperty;
545
+ }, [userConfigPersistence, fullPath]);
546
+
547
+ // Selected kanban property state - priority: saved config > collection default > first available
548
+ const [selectedKanbanProperty, setSelectedKanbanProperty] = useState<string>(() => {
549
+ const saved = getSavedKanbanProperty();
550
+ if (saved && kanbanPropertyOptions.some(o => o.key === saved)) return saved;
551
+ if (collection.kanban?.columnProperty) return collection.kanban.columnProperty;
552
+ return kanbanPropertyOptions[0]?.key ?? "";
553
+ });
554
+
555
+ // Update selected property if options change and current selection is no longer valid
556
+ useEffect(() => {
557
+ if (kanbanPropertyOptions.length > 0 && !kanbanPropertyOptions.some(o => o.key === selectedKanbanProperty)) {
558
+ const saved = getSavedKanbanProperty();
559
+ if (saved && kanbanPropertyOptions.some(o => o.key === saved)) {
560
+ setSelectedKanbanProperty(saved);
561
+ } else if (collection.kanban?.columnProperty && kanbanPropertyOptions.some(o => o.key === collection.kanban?.columnProperty)) {
562
+ setSelectedKanbanProperty(collection.kanban.columnProperty);
563
+ } else {
564
+ setSelectedKanbanProperty(kanbanPropertyOptions[0]?.key ?? "");
565
+ }
566
+ }
567
+ }, [kanbanPropertyOptions, selectedKanbanProperty, getSavedKanbanProperty, collection.kanban?.columnProperty]);
568
+
569
+ // Handle kanban property change
570
+ const onKanbanPropertyChange = useCallback((property: string) => {
571
+ setSelectedKanbanProperty(property);
572
+ // Save to local persistence
573
+ if (userConfigPersistence) {
574
+ onCollectionModifiedForUser(fullPath, { kanbanColumnProperty: property } as any);
575
+ }
576
+ }, [userConfigPersistence, onCollectionModifiedForUser, fullPath]);
577
+
397
578
  const getPropertyFor = useCallback(({
398
- propertyKey,
399
- entity
400
- }: GetPropertyForProps<M>) => {
579
+ propertyKey,
580
+ entity
581
+ }: GetPropertyForProps<M>) => {
401
582
  let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
402
583
 
403
584
  // we might not find the property in the collection if combining property builders and map spread
@@ -417,7 +598,18 @@ export const EntityCollectionView = React.memo(
417
598
  });
418
599
  }, [collection.properties, customizationController.propertyConfigs, resolvedCollection.properties]);
419
600
 
420
- const displayedColumnIds = useColumnIds(resolvedCollection, true);
601
+ // Use a collection with local propertiesOrder for optimistic UI updates
602
+ const collectionWithLocalOrder = useMemo(() => {
603
+ if (localPropertiesOrder && localPropertiesOrder !== resolvedCollection.propertiesOrder) {
604
+ return {
605
+ ...resolvedCollection,
606
+ propertiesOrder: localPropertiesOrder
607
+ };
608
+ }
609
+ return resolvedCollection;
610
+ }, [resolvedCollection, localPropertiesOrder]);
611
+
612
+ const displayedColumnIds = useColumnIds(collectionWithLocalOrder, true);
421
613
 
422
614
  const additionalFields = useMemo(() => {
423
615
  const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
@@ -427,23 +619,22 @@ export const EntityCollectionView = React.memo(
427
619
  width: 200,
428
620
  dependencies: [],
429
621
  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
- }}>
622
+ <Button
623
+ className={"max-w-full truncate justify-start"}
624
+ startIcon={<KeyboardTabIcon size={"small"} />}
625
+ onClick={(event: any) => {
626
+ event.stopPropagation();
627
+ navigateToEntity({
628
+ openEntityMode,
629
+ collection,
630
+ entityId: entity.id,
631
+ selectedTab: subcollection.id ?? subcollection.path,
632
+ path: fullPath,
633
+ fullIdPath,
634
+ navigation,
635
+ sideEntityController
636
+ })
637
+ }}>
447
638
  {subcollection.name}
448
639
  </Button>
449
640
  )
@@ -465,7 +656,7 @@ export const EntityCollectionView = React.memo(
465
656
  <ReferencePreview
466
657
  key={reference.path + "/" + reference.id}
467
658
  reference={reference}
468
- size={"small"}/>
659
+ size={"small"} />
469
660
  );
470
661
  })}
471
662
  </div>
@@ -488,9 +679,9 @@ export const EntityCollectionView = React.memo(
488
679
  const largeLayout = useLargeLayout();
489
680
 
490
681
  const getActionsForEntity = ({
491
- entity,
492
- customEntityActions
493
- }: {
682
+ entity,
683
+ customEntityActions
684
+ }: {
494
685
  entity?: Entity<M>,
495
686
  customEntityActions?: EntityAction[]
496
687
  }): EntityAction[] => {
@@ -514,11 +705,11 @@ export const EntityCollectionView = React.memo(
514
705
  };
515
706
 
516
707
  const tableRowActionsBuilder = useCallback(({
517
- entity,
518
- size,
519
- width,
520
- frozen
521
- }: {
708
+ entity,
709
+ size,
710
+ width,
711
+ frozen
712
+ }: {
522
713
  entity: Entity<any>,
523
714
  size: CollectionSize,
524
715
  width: number,
@@ -558,45 +749,28 @@ export const EntityCollectionView = React.memo(
558
749
 
559
750
  }, [updateLastDeleteTimestamp, usedSelectionController]);
560
751
 
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
- >
752
+ // Update breadcrumb count when count changes (only if loaded)
753
+ useEffect(() => {
754
+ if (docsCount !== undefined) {
755
+ breadcrumbs.updateCount(fullPath, docsCount);
756
+ }
757
+ }, [docsCount, fullPath, breadcrumbs.updateCount]);
588
758
 
589
- {collection.description && <div className="m-4 text-surface-900 dark:text-white">
590
- <Markdown source={collection.description}/>
591
- </div>}
759
+ // EntitiesCount fetches count and updates breadcrumb - no visual rendering needed here
760
+ const countFetcher = <EntitiesCount
761
+ fullPath={fullPath}
762
+ collection={collection}
763
+ filter={tableController.filterValues}
764
+ sortBy={tableController.sortBy}
765
+ onCountChange={setDocsCount}
766
+ />;
592
767
 
593
- </Popover>;
594
768
 
595
769
  const buildAdditionalHeaderWidget = useCallback(({
596
- property,
597
- propertyKey,
598
- onHover
599
- }: {
770
+ property,
771
+ propertyKey,
772
+ onHover
773
+ }: {
600
774
  property: ResolvedProperty,
601
775
  propertyKey: string,
602
776
  onHover: boolean
@@ -616,7 +790,7 @@ export const EntityCollectionView = React.memo(
616
790
  fullPath={fullPath}
617
791
  collection={collection}
618
792
  tableController={tableController}
619
- parentCollectionIds={parentCollectionIds ?? []}/>;
793
+ parentCollectionIds={parentCollectionIds ?? []} />;
620
794
  })}
621
795
  </>;
622
796
  }, [customizationController.plugins, fullPath, parentCollectionIds]);
@@ -625,9 +799,9 @@ export const EntityCollectionView = React.memo(
625
799
  ? function () {
626
800
  if (typeof AddColumnComponent === "function")
627
801
  return <AddColumnComponent fullPath={fullPath}
628
- parentCollectionIds={parentCollectionIds ?? []}
629
- collection={collection}
630
- tableController={tableController}/>;
802
+ parentCollectionIds={parentCollectionIds ?? []}
803
+ collection={collection}
804
+ tableController={tableController} />;
631
805
  return null;
632
806
  }
633
807
  : undefined;
@@ -643,32 +817,37 @@ export const EntityCollectionView = React.memo(
643
817
  parentCollectionIds
644
818
  });
645
819
 
820
+ // Popover open state managed at parent level to prevent closing when view changes
821
+ const [viewModePopoverOpen, setViewModePopoverOpen] = useState(false);
822
+
823
+ // Create ViewModeToggle once to prevent remounting when view changes
824
+ const viewModeToggleElement = (
825
+ <ViewModeToggle
826
+ viewMode={viewMode}
827
+ onViewModeChange={onViewModeChange}
828
+ enabledViews={enabledViews}
829
+ size={viewMode === "table" ? tableSize : viewMode === "cards" ? cardSize : undefined}
830
+ onSizeChanged={viewMode === "table" ? onTableSizeChanged : viewMode === "cards" ? setCardSize : undefined}
831
+ open={viewModePopoverOpen}
832
+ onOpenChange={setViewModePopoverOpen}
833
+ kanbanPropertyOptions={kanbanPropertyOptions}
834
+ selectedKanbanProperty={selectedKanbanProperty}
835
+ onKanbanPropertyChange={onKanbanPropertyChange}
836
+ />
837
+ );
838
+
646
839
  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}
840
+ <div className={cls("overflow-hidden h-full w-full rounded-md flex flex-col", className)}
841
+ ref={containerRef}>
842
+
843
+ {/* Unified toolbar - rendered once, outside view conditionals */}
844
+ {countFetcher}
845
+ <CollectionTableToolbar
846
+ loading={tableController.dataLoading}
847
+ onTextSearch={textSearchEnabled && textSearchInitialised ? tableController.setSearchString : undefined}
848
+ onTextSearchClick={textSearchEnabled && !textSearchInitialised ? onTextSearchClick : undefined}
670
849
  textSearchLoading={textSearchLoading}
671
- textSearchEnabled={textSearchEnabled}
850
+ viewModeToggle={viewModeToggleElement}
672
851
  actionsStart={<EntityCollectionViewStartActions
673
852
  parentCollectionIds={parentCollectionIds ?? []}
674
853
  collection={collection}
@@ -676,7 +855,8 @@ export const EntityCollectionView = React.memo(
676
855
  path={fullPath}
677
856
  relativePath={collection.path}
678
857
  selectionController={usedSelectionController}
679
- collectionEntitiesCount={docsCount}/>}
858
+ collectionEntitiesCount={docsCount}
859
+ resolvedProperties={resolvedCollection.properties} />}
680
860
  actions={<EntityCollectionViewActions
681
861
  parentCollectionIds={parentCollectionIds ?? []}
682
862
  collection={collection}
@@ -689,33 +869,143 @@ export const EntityCollectionView = React.memo(
689
869
  selectionEnabled={selectionEnabled}
690
870
  collectionEntitiesCount={docsCount}
691
871
  />}
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
872
  />
718
873
 
874
+ {/* View content - only the view-specific content changes */}
875
+ {viewMode === "kanban" && enabledViews.includes("kanban") ? (
876
+ <EntityCollectionBoardView
877
+ key={`kanban-view-${fullPath}-${selectedKanbanProperty}`}
878
+ collection={collection}
879
+ tableController={tableController}
880
+ fullPath={fullPath}
881
+ parentCollectionIds={parentCollectionIds}
882
+ columnProperty={selectedKanbanProperty}
883
+ onEntityClick={onEntityClick}
884
+ selectionController={usedSelectionController}
885
+ selectionEnabled={selectionEnabled}
886
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
887
+ deletedEntities={deletedEntities}
888
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
889
+ ? <div className="flex flex-col items-center justify-center">
890
+ <Typography variant={"subtitle2"}>So empty...</Typography>
891
+ <Button
892
+ onClick={onNewClick}
893
+ className="mt-4"
894
+ >
895
+ <AddIcon />
896
+ Create your first entry
897
+ </Button>
898
+ </div>
899
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
900
+ }
901
+ />
902
+ ) : viewMode === "cards" ? (
903
+ <EntityCollectionCardView
904
+ key={`cards-view-${fullPath}`}
905
+ collection={collection}
906
+ tableController={tableController}
907
+ onEntityClick={onEntityClick}
908
+ selectionController={usedSelectionController}
909
+ selectionEnabled={selectionEnabled}
910
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
911
+ onScroll={tableController.onScroll}
912
+ initialScroll={tableController.initialScroll}
913
+ size={cardSize}
914
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
915
+ ? <div className="flex flex-col items-center justify-center">
916
+ <Typography variant={"subtitle2"}>So empty...</Typography>
917
+ <Button
918
+ onClick={onNewClick}
919
+ className="mt-4"
920
+ >
921
+ <AddIcon />
922
+ Create your first entry
923
+ </Button>
924
+ </div>
925
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
926
+ }
927
+ />
928
+ ) : (
929
+ <EntityCollectionTable
930
+ key={`collection_table_${fullPath}`}
931
+ hideToolbar={true}
932
+ additionalFields={additionalFields}
933
+ tableController={tableController}
934
+ enablePopupIcon={true}
935
+ displayedColumnIds={displayedColumnIds}
936
+ onSizeChanged={onTableSizeChanged}
937
+ onEntityClick={onEntityClick}
938
+ onColumnResize={onColumnResize}
939
+ onValueChange={onValueChange}
940
+ tableRowActionsBuilder={tableRowActionsBuilder}
941
+ uniqueFieldValidator={uniqueFieldValidator}
942
+ selectionController={usedSelectionController}
943
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
944
+ defaultSize={tableSize}
945
+ properties={resolvedCollection.properties}
946
+ getPropertyFor={getPropertyFor}
947
+ onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
948
+ onScroll={tableController.onScroll}
949
+ initialScroll={tableController.initialScroll}
950
+ textSearchLoading={textSearchLoading}
951
+ textSearchEnabled={textSearchEnabled}
952
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
953
+ ? <div className="flex flex-col items-center justify-center">
954
+ <Typography variant={"subtitle2"}>So empty...</Typography>
955
+ <Button
956
+ onClick={onNewClick}
957
+ className="mt-4"
958
+ >
959
+ <AddIcon />
960
+ Create your first entry
961
+ </Button>
962
+ </div>
963
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
964
+ }
965
+ hoverRow={hoverRow}
966
+ inlineEditing={checkInlineEditing()}
967
+ AdditionalHeaderWidget={buildAdditionalHeaderWidget}
968
+ AddColumnComponent={addColumnComponentInternal}
969
+ getIdColumnWidth={getIdColumnWidth}
970
+ additionalIDHeaderWidget={<EntityIdHeaderWidget
971
+ path={fullPath}
972
+ fullIdPath={fullIdPath ?? fullPath}
973
+ collection={collection} />}
974
+ openEntityMode={openEntityMode}
975
+ onColumnsOrderChange={(newColumns) => {
976
+ // Extract property keys from the new column order
977
+ // Filter to only include actual property columns (not frozen columns, not additional fields, etc.)
978
+ // Deduplicate to clean up any previously duplicated keys
979
+ const seenKeys = new Set<string>();
980
+ const newPropertiesOrder = newColumns
981
+ .filter(col => !col.frozen && getPropertyInPath(collection.properties, col.key))
982
+ .map(col => col.key)
983
+ .filter(key => {
984
+ if (seenKeys.has(key)) return false;
985
+ seenKeys.add(key);
986
+ return true;
987
+ });
988
+
989
+ // Optimistically update local state to prevent UI flickering
990
+ setLocalPropertiesOrder(newPropertiesOrder);
991
+
992
+ // Call each plugin's onColumnsReorder callback
993
+ if (customizationController?.plugins) {
994
+ customizationController.plugins
995
+ .filter(plugin => plugin.collectionView?.onColumnsReorder)
996
+ .forEach(plugin => {
997
+ plugin.collectionView!.onColumnsReorder!({
998
+ fullPath,
999
+ parentCollectionIds: parentCollectionIds ?? [],
1000
+ collection,
1001
+ newPropertiesOrder
1002
+ });
1003
+ });
1004
+ }
1005
+ }}
1006
+ />
1007
+ )}
1008
+
719
1009
  {popupCell && <PopupFormField
720
1010
  key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`}
721
1011
  open={Boolean(popupCell)}
@@ -728,7 +1018,7 @@ export const EntityCollectionView = React.memo(
728
1018
  customFieldValidator={uniqueFieldValidator}
729
1019
  path={resolvedFullPath}
730
1020
  onCellValueChange={onValueChange}
731
- container={containerRef.current}/>}
1021
+ container={containerRef.current} />}
732
1022
 
733
1023
  {deleteEntityClicked &&
734
1024
  <DeleteEntityDialog
@@ -739,7 +1029,7 @@ export const EntityCollectionView = React.memo(
739
1029
  open={Boolean(deleteEntityClicked)}
740
1030
  onEntityDelete={internalOnEntityDelete}
741
1031
  onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
742
- onClose={() => setDeleteEntityClicked(undefined)}/>}
1032
+ onClose={() => setDeleteEntityClicked(undefined)} />}
743
1033
 
744
1034
  </div>
745
1035
  );
@@ -769,12 +1059,12 @@ export const EntityCollectionView = React.memo(
769
1059
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
770
1060
 
771
1061
  function EntitiesCount({
772
- fullPath,
773
- collection,
774
- filter,
775
- sortBy,
776
- onCountChange
777
- }: {
1062
+ fullPath,
1063
+ collection,
1064
+ filter,
1065
+ sortBy,
1066
+ onCountChange
1067
+ }: {
778
1068
  fullPath: string,
779
1069
  collection: EntityCollection,
780
1070
  filter?: FilterValues<any>,
@@ -803,9 +1093,9 @@ function EntitiesCount({
803
1093
  }, [fullPath, dataSource.countEntities, resolvedPath, collection, filter, sortByProperty, currentSort]);
804
1094
 
805
1095
  useEffect(() => {
806
- if (onCountChange) {
1096
+ if (onCountChange && count !== undefined) {
807
1097
  setError(undefined);
808
- onCountChange(count ?? 0);
1098
+ onCountChange(count);
809
1099
  }
810
1100
  }, [onCountChange, count]);
811
1101
 
@@ -813,14 +1103,11 @@ function EntitiesCount({
813
1103
  return null;
814
1104
  }
815
1105
 
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>;
1106
+ // Count is now displayed in the breadcrumb bar, this component only fetches and reports
1107
+ return null;
822
1108
  }
823
1109
 
1110
+
824
1111
  function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityCollection {
825
1112
  if (key.includes(".")) {
826
1113
  const [parentKey, ...childKey] = key.split(".");
@@ -830,10 +1117,10 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
830
1117
  }
831
1118
 
832
1119
  function EntityIdHeaderWidget({
833
- collection,
834
- path,
835
- fullIdPath
836
- }: {
1120
+ collection,
1121
+ path,
1122
+ fullIdPath
1123
+ }: {
837
1124
  collection: EntityCollection,
838
1125
  path: string,
839
1126
  fullIdPath: string
@@ -857,29 +1144,29 @@ function EntityIdHeaderWidget({
857
1144
  alignOffset={-117}
858
1145
  trigger={
859
1146
  <IconButton size={"small"}>
860
- <SearchIcon size={"small"}/>
1147
+ <SearchIcon size={"small"} />
861
1148
  </IconButton>
862
1149
  }>
863
1150
  <div
864
1151
  className={cls("my-2 rounded-lg bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-white")}>
865
1152
  <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"}>
1153
+ onSubmit={(e) => {
1154
+ e.preventDefault();
1155
+ if (!searchString) return;
1156
+ setOpenPopup(false);
1157
+ const entityId = searchString.trim();
1158
+ setRecentIds(addRecentId(collection.id, entityId));
1159
+ navigateToEntity({
1160
+ openEntityMode,
1161
+ collection,
1162
+ entityId,
1163
+ path,
1164
+ fullIdPath,
1165
+ sideEntityController,
1166
+ navigation
1167
+ })
1168
+ }}
1169
+ className={"w-96 max-w-full"}>
883
1170
 
884
1171
  <div className="flex p-2 w-full gap-2">
885
1172
  <input
@@ -890,32 +1177,32 @@ function EntityIdHeaderWidget({
890
1177
  setSearchString(e.target.value);
891
1178
  }}
892
1179
  value={searchString}
893
- className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled}/>
1180
+ className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled} />
894
1181
  <Button variant={"text"}
895
- disabled={!(searchString.trim())}
896
- type={"submit"}
897
- ><KeyboardTabIcon/></Button>
1182
+ disabled={!(searchString.trim())}
1183
+ type={"submit"}
1184
+ ><KeyboardTabIcon /></Button>
898
1185
  </div>
899
1186
  </form>
900
1187
  {recentIds && recentIds.length > 0 && <div className="flex flex-col gap-2 p-2">
901
1188
  {recentIds.map(id => (
902
1189
  <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"}/>
1190
+ key={id}
1191
+ hover={true}
1192
+ onClick={() => {
1193
+ setOpenPopup(false);
1194
+ navigateToEntity({
1195
+ openEntityMode,
1196
+ collection,
1197
+ entityId: id,
1198
+ path,
1199
+ fullIdPath,
1200
+ sideEntityController,
1201
+ navigation
1202
+ })
1203
+ }}
1204
+ includeEntityLink={false}
1205
+ size={"small"} />
919
1206
  ))}
920
1207
  </div>}
921
1208
  </div>