@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
@@ -13,6 +13,13 @@ type VirtualTableCellProps<T extends any> = {
13
13
  rowIndex: any;
14
14
  columnIndex: number;
15
15
  cellRenderer: React.ComponentType<CellRendererParams<T>>;
16
+ // Sortable props
17
+ sortableNodeRef?: (node: HTMLElement | null) => void;
18
+ sortableStyle?: React.CSSProperties;
19
+ sortableAttributes?: Record<string, any>;
20
+ isDragging?: boolean;
21
+ isDraggable?: boolean;
22
+ frozen?: boolean;
16
23
  };
17
24
 
18
25
  export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
@@ -27,7 +34,13 @@ export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
27
34
  column: props.column,
28
35
  columns: props.columns,
29
36
  columnIndex: props.columnIndex,
30
- width: props.column.width
37
+ width: props.column.width,
38
+ sortableNodeRef: props.sortableNodeRef,
39
+ sortableStyle: props.sortableStyle,
40
+ sortableAttributes: props.sortableAttributes,
41
+ isDragging: props.isDragging,
42
+ isDraggable: props.isDraggable,
43
+ frozen: props.frozen
31
44
  } as CellRendererParams<T>
32
45
  );
33
46
  },
@@ -37,6 +50,9 @@ export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
37
50
  equal(a.cellData, b.cellData) &&
38
51
  equal(a.rowIndex, b.rowIndex) &&
39
52
  equal(a.cellRenderer, b.cellRenderer) &&
40
- equal(a.columnIndex, b.columnIndex)
53
+ equal(a.columnIndex, b.columnIndex) &&
54
+ a.isDragging === b.isDragging &&
55
+ a.isDraggable === b.isDraggable &&
56
+ a.frozen === b.frozen
41
57
  }
42
58
  );
@@ -45,6 +45,8 @@ type VirtualTableHeaderProps<M extends Record<string, any>> = {
45
45
  onClickResizeColumn?: (columnIndex: number, column: VirtualTableColumn) => void;
46
46
  createFilterField?: (props: FilterFormFieldProps<any>) => React.ReactNode;
47
47
  AdditionalHeaderWidget?: (props: { onHover: boolean }) => React.ReactNode;
48
+ isDragging?: boolean;
49
+ isDraggable?: boolean;
48
50
  };
49
51
 
50
52
  export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
@@ -59,7 +61,9 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
59
61
  column,
60
62
  onClickResizeColumn,
61
63
  createFilterField,
62
- AdditionalHeaderWidget
64
+ AdditionalHeaderWidget,
65
+ isDragging,
66
+ isDraggable
63
67
  }: VirtualTableHeaderProps<M>) {
64
68
 
65
69
  const [onHover, setOnHover] = useState(false);
@@ -85,14 +89,16 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
85
89
  return (
86
90
  <ErrorBoundary>
87
91
  <div
88
- className={cls("flex py-0 px-3 h-full text-xs uppercase font-semibold relative select-none items-center bg-surface-50 dark:bg-surface-900",
92
+ className={cls("flex py-0 px-3 h-full text-xs uppercase font-semibold relative select-none items-center",
93
+ isDragging
94
+ ? "bg-primary-bg dark:bg-primary-bg-dark"
95
+ : "bg-surface-50 dark:bg-surface-900",
89
96
  "text-text-secondary hover:text-text-primary dark:text-text-secondary-dark dark:hover:text-text-primary-dark",
90
- "hover:bg-surface-100 dark:hover:bg-surface-800 hover:bg-opacity-50 dark:hover:bg-opacity-50",
91
- column.frozen ? "sticky left-0 z-10" : "relative z-0"
97
+ !isDragging && "hover:bg-surface-100 dark:hover:bg-surface-800 hover:bg-opacity-50 hover:bg-surface-100/50 dark:hover:bg-opacity-50 dark:hover:bg-surface-800/50",
98
+ column.frozen ? "sticky left-0 z-10" : "relative z-0",
99
+ isDraggable && "cursor-grab"
92
100
  )}
93
101
  style={{
94
- // transition: "color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms",
95
- // fontSize: "0.750rem",
96
102
  left: column.frozen ? 0 : undefined,
97
103
  minWidth: column.width,
98
104
  maxWidth: column.width
@@ -179,11 +185,17 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
179
185
 
180
186
  {column.resizable && <div
181
187
  ref={resizeHandleRef}
188
+ data-no-dnd="true"
182
189
  className={cls(
183
190
  "absolute h-full w-[6px] top-0 right-0 cursor-col-resize",
184
191
  hovered && "bg-surface-300 dark:bg-surface-700"
185
192
  )}
186
- onMouseDown={onClickResizeColumn ? () => onClickResizeColumn(columnIndex, column) : undefined}
193
+ onPointerDown={(e) => {
194
+ e.stopPropagation();
195
+ if (onClickResizeColumn) {
196
+ onClickResizeColumn(columnIndex, column);
197
+ }
198
+ }}
187
199
  />}
188
200
  </div>
189
201
 
@@ -243,7 +255,7 @@ function FilterForm<M>({
243
255
  className={cls(defaultBorderMixin, "py-4 px-6 typography-label border-b")}>
244
256
  {column.title ?? id}
245
257
  </div>
246
- {filterField && <div className="m-4">
258
+ {filterField && <div className="m-4 w-[400px]">
247
259
  {filterField}
248
260
  </div>}
249
261
  <div className="flex justify-end m-4">
@@ -251,13 +263,10 @@ function FilterForm<M>({
251
263
  className="mr-4"
252
264
  disabled={!filterIsSet}
253
265
  variant={"text"}
254
- color="primary"
255
266
  type="reset"
256
267
  aria-label="filter clear"
257
268
  onClick={reset}>Clear</Button>
258
269
  <Button
259
- variant="outlined"
260
- color="primary"
261
270
  type="submit">Filter</Button>
262
271
  </div>
263
272
  </form>
@@ -1,25 +1,132 @@
1
- import React, { createRef, useCallback, useEffect, useState } from "react";
1
+ import React, { createRef, useCallback, useEffect, useMemo, useState } from "react";
2
2
 
3
3
  import { VirtualTableColumn, VirtualTableWhereFilterOp } from "./VirtualTableProps";
4
4
  import { ErrorBoundary } from "../ErrorBoundary";
5
5
  import { VirtualTableHeader } from "./VirtualTableHeader";
6
6
  import { VirtualTableContextProps } from "./types";
7
7
  import { cls, defaultBorderMixin } from "@firecms/ui";
8
+ import { useSortable } from "@dnd-kit/sortable";
9
+ import { CSS } from "@dnd-kit/utilities";
10
+
11
+ // Sortable column header wrapper
12
+ const SortableColumnHeader = ({
13
+ column,
14
+ columnIndex,
15
+ columnRefs,
16
+ isResizing,
17
+ onFilterUpdate,
18
+ filter,
19
+ sortByProperty,
20
+ currentSort,
21
+ onColumnSort,
22
+ onClickResizeColumn,
23
+ createFilterField,
24
+ isDragging,
25
+ isDraggable
26
+ }: {
27
+ column: VirtualTableColumn;
28
+ columnIndex: number;
29
+ columnRefs: React.RefObject<HTMLDivElement>[];
30
+ isResizing: number;
31
+ onFilterUpdate: any;
32
+ filter: [VirtualTableWhereFilterOp, any] | undefined;
33
+ sortByProperty: string | undefined;
34
+ currentSort: "asc" | "desc" | undefined;
35
+ onColumnSort: any;
36
+ onClickResizeColumn: (index: number) => void;
37
+ createFilterField: any;
38
+ isDragging: boolean;
39
+ isDraggable: boolean;
40
+ }) => {
41
+ const [isPressing, setIsPressing] = useState(false);
42
+
43
+ const {
44
+ attributes,
45
+ listeners,
46
+ setNodeRef,
47
+ transform,
48
+ transition,
49
+ } = useSortable({
50
+ id: column.key,
51
+ disabled: !isDraggable || column.frozen
52
+ });
53
+
54
+ const style = {
55
+ // Only use translate, ignore any scale transforms
56
+ transform: transform ? `translateX(${transform.x}px)` : undefined,
57
+ // Don't transition the dragged item - only other items should animate
58
+ transition: isDragging ? undefined : transition,
59
+ minWidth: column.width,
60
+ maxWidth: column.width,
61
+ width: column.width,
62
+ };
63
+
64
+ // Combine our press handlers with dnd-kit listeners
65
+ const combinedListeners = isDraggable ? {
66
+ ...listeners,
67
+ onPointerDown: (e: React.PointerEvent) => {
68
+ setIsPressing(true);
69
+ listeners?.onPointerDown?.(e);
70
+ },
71
+ onPointerUp: () => setIsPressing(false),
72
+ onPointerCancel: () => setIsPressing(false),
73
+ } : {};
74
+
75
+ // Reset pressing state when drag ends
76
+ React.useEffect(() => {
77
+ if (!isDragging) {
78
+ setIsPressing(false);
79
+ }
80
+ }, [isDragging]);
81
+
82
+ return (
83
+ <div
84
+ ref={setNodeRef}
85
+ style={style}
86
+ className={cls(
87
+ "flex-shrink-0 h-full",
88
+ column.frozen && "sticky left-0 z-10"
89
+ )}
90
+ {...attributes}
91
+ {...combinedListeners}
92
+ >
93
+ <VirtualTableHeader
94
+ resizeHandleRef={columnRefs[columnIndex]}
95
+ columnIndex={columnIndex}
96
+ isResizingIndex={isResizing}
97
+ onFilterUpdate={onFilterUpdate}
98
+ filter={filter}
99
+ sort={sortByProperty === column.key ? currentSort : undefined}
100
+ onColumnSort={onColumnSort}
101
+ onClickResizeColumn={onClickResizeColumn}
102
+ column={column}
103
+ createFilterField={createFilterField}
104
+ AdditionalHeaderWidget={column.AdditionalHeaderWidget}
105
+ isDragging={isDragging || isPressing}
106
+ isDraggable={isDraggable} />
107
+ </div>
108
+ );
109
+ };
8
110
 
9
111
  export const VirtualTableHeaderRow = ({
10
- columns,
11
- currentSort,
12
- onColumnSort,
13
- onFilterUpdate,
14
- sortByProperty,
15
- filter,
16
- onColumnResize,
17
- onColumnResizeEnd,
18
- createFilterField,
19
- AddColumnComponent
20
- }: VirtualTableContextProps<any>) => {
21
-
22
- const columnRefs = columns.map(() => createRef<HTMLDivElement>());
112
+ columns,
113
+ currentSort,
114
+ onColumnSort,
115
+ onFilterUpdate,
116
+ sortByProperty,
117
+ filter,
118
+ onColumnResize,
119
+ onColumnResizeEnd,
120
+ createFilterField,
121
+ AddColumnComponent,
122
+ onColumnsOrderChange,
123
+ data,
124
+ cellRenderer: CellRenderer,
125
+ rowHeight = 54,
126
+ draggingColumnId
127
+ }: VirtualTableContextProps<any>) => {
128
+
129
+ const columnRefs = useMemo(() => columns.map(() => createRef<HTMLDivElement>()), [columns.length]);
23
130
  const [isResizing, setIsResizing] = useState(-1);
24
131
 
25
132
  const adjustWidthColumn = useCallback((index: number, width: number, end: boolean) => {
@@ -99,33 +206,42 @@ export const VirtualTableHeaderRow = ({
99
206
  }, [setCursorDocument]);
100
207
 
101
208
  return (
102
- <div
103
- className={cls(defaultBorderMixin, "z-20 sticky min-w-full flex w-fit flex-row top-0 left-0 h-12 border-b bg-surface-50 dark:bg-surface-900")}>
104
- {columns.map((c, columnIndex) => {
105
- const column = columns[columnIndex];
106
-
107
- const filterForThisProperty: [VirtualTableWhereFilterOp, any] | undefined =
108
- column && filter && filter[column.key]
109
- ? filter[column.key]
110
- : undefined;
111
- return <ErrorBoundary key={"header_" + column.key}>
112
- <VirtualTableHeader
113
- resizeHandleRef={columnRefs[columnIndex]}
114
- columnIndex={columnIndex}
115
- isResizingIndex={isResizing}
116
- onFilterUpdate={onFilterUpdate}
117
- filter={filterForThisProperty}
118
- sort={sortByProperty === column.key ? currentSort : undefined}
119
- onColumnSort={onColumnSort}
120
- onClickResizeColumn={onClickResizeColumn}
121
- column={column}
122
- createFilterField={createFilterField}
123
- AdditionalHeaderWidget={column.AdditionalHeaderWidget}/>
124
- </ErrorBoundary>;
125
- })}
126
-
127
- {AddColumnComponent && <AddColumnComponent/>}
128
-
129
- </div>
209
+ <>
210
+ <div
211
+ className={cls(defaultBorderMixin, "z-20 sticky min-w-full flex w-fit flex-row top-0 left-0 h-12 border-b bg-surface-50 dark:bg-surface-900")}>
212
+ {columns.map((column, columnIndex) => {
213
+ const filterForThisProperty: [VirtualTableWhereFilterOp, any] | undefined =
214
+ column && filter && filter[column.key]
215
+ ? filter[column.key]
216
+ : undefined;
217
+
218
+ const isDraggable = !column.frozen && !!onColumnsOrderChange;
219
+ const isDragging = draggingColumnId === column.key;
220
+
221
+ return (
222
+ <ErrorBoundary key={"header_" + column.key}>
223
+ <SortableColumnHeader
224
+ column={column}
225
+ columnIndex={columnIndex}
226
+ columnRefs={columnRefs}
227
+ isResizing={isResizing}
228
+ onFilterUpdate={onFilterUpdate}
229
+ filter={filterForThisProperty}
230
+ sortByProperty={sortByProperty}
231
+ currentSort={currentSort}
232
+ onColumnSort={onColumnSort}
233
+ onClickResizeColumn={onClickResizeColumn}
234
+ createFilterField={createFilterField}
235
+ isDragging={isDragging}
236
+ isDraggable={isDraggable}
237
+ />
238
+ </ErrorBoundary>
239
+ );
240
+ })}
241
+
242
+ {AddColumnComponent && <AddColumnComponent />}
243
+
244
+ </div>
245
+ </>
130
246
  );
131
247
  };
@@ -38,7 +38,7 @@ export interface VirtualTableProps<T extends Record<string, any>> {
38
38
  * @param sortBy
39
39
  */
40
40
  checkFilterCombination?: (filterValues: VirtualTableFilterValues<Extract<keyof T, string>>,
41
- sortBy?: [string, "asc" | "desc"]) => boolean;
41
+ sortBy?: [string, "asc" | "desc"]) => boolean;
42
42
 
43
43
  /**
44
44
  * A callback function when scrolling the table to near the end
@@ -162,6 +162,12 @@ export interface VirtualTableProps<T extends Record<string, any>> {
162
162
  */
163
163
  initialScroll?: number;
164
164
 
165
+ /**
166
+ * Callback when columns are reordered via drag-and-drop.
167
+ * @param columns The new column order
168
+ */
169
+ onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
170
+
165
171
  }
166
172
 
167
173
  export type CellRendererParams<T = any> = {
@@ -172,6 +178,13 @@ export type CellRendererParams<T = any> = {
172
178
  rowIndex: number;
173
179
  width: number;
174
180
  isScrolling?: boolean;
181
+ // Sortable props for dnd-kit integration
182
+ sortableNodeRef?: (node: HTMLElement | null) => void;
183
+ sortableStyle?: React.CSSProperties;
184
+ sortableAttributes?: Record<string, any>;
185
+ isDragging?: boolean;
186
+ isDraggable?: boolean;
187
+ frozen?: boolean;
175
188
  };
176
189
 
177
190
  /**
@@ -29,7 +29,7 @@ export const VirtualTableRow = React.memo<VirtualTableRowProps<any>>(
29
29
  return (
30
30
  <div
31
31
  className={cls(
32
- "flex min-w-full text-sm border-b border-surface-200 dark:border-surface-800 border-opacity-40 dark:border-opacity-40",
32
+ "flex min-w-full text-sm border-b border-surface-200 dark:border-surface-800 border-opacity-40 border-surface-200/40 dark:border-opacity-40 dark:border-surface-800/40",
33
33
  rowClassName ? rowClassName(rowData) : "",
34
34
  {
35
35
  "hover:bg-opacity-95": hoverRow,
@@ -6,6 +6,7 @@ export function VirtualTableDateField(props: {
6
6
  name: string;
7
7
  error: Error | undefined;
8
8
  mode?: "date" | "date_time";
9
+ timezone?: string;
9
10
  internalValue: Date | undefined | null;
10
11
  updateValue: (newValue: (Date | null)) => void;
11
12
  focused: boolean;
@@ -18,6 +19,7 @@ export function VirtualTableDateField(props: {
18
19
  disabled,
19
20
  error,
20
21
  mode,
22
+ timezone,
21
23
  internalValue,
22
24
  updateValue
23
25
  } = props;
@@ -31,6 +33,7 @@ export function VirtualTableDateField(props: {
31
33
  inputClassName={cls("w-full h-full", focusedDisabled)}
32
34
  className={cls("w-full h-full", focusedDisabled)}
33
35
  mode={mode}
36
+ timezone={timezone}
34
37
  locale={locale}
35
38
  />
36
39
  );
@@ -62,9 +62,19 @@ export function VirtualTableSelect(props: {
62
62
  key={`${enumKey}`}
63
63
  enumKey={String(enumKey)}
64
64
  enumValues={enumValues}
65
- size={small ? "small" : "medium"}/>;
65
+ size={small ? "small" : "medium"} />;
66
66
  };
67
67
 
68
+ // When the dropdown closes (including on escape), restore focus to the trigger
69
+ const handleOpenChange = useCallback((open: boolean) => {
70
+ if (!open && ref.current) {
71
+ // Use setTimeout to ensure focus is restored after Radix finishes its cleanup
72
+ setTimeout(() => {
73
+ ref.current?.focus({ preventScroll: true });
74
+ }, 0);
75
+ }
76
+ }, []);
77
+
68
78
  return (
69
79
  multiple
70
80
  ? <MultiSelect
@@ -77,7 +87,8 @@ export function VirtualTableSelect(props: {
77
87
  value={validValue
78
88
  ? ((internalValue as any[]).map(v => v.toString()))
79
89
  : ([])}
80
- onValueChange={onChange}>
90
+ onValueChange={onChange}
91
+ onOpenChange={handleOpenChange}>
81
92
  {enumValues?.map((enumConfig) => (
82
93
  <MultiSelectItem
83
94
  key={enumConfig.id}
@@ -85,7 +96,7 @@ export function VirtualTableSelect(props: {
85
96
  <EnumValuesChip
86
97
  enumKey={enumConfig.id}
87
98
  enumValues={enumValues}
88
- size={small ? "small" : "medium"}/>
99
+ size={small ? "small" : "medium"} />
89
100
  </MultiSelectItem>
90
101
  ))}
91
102
  </MultiSelect>
@@ -94,6 +105,7 @@ export function VirtualTableSelect(props: {
94
105
  size={"large"}
95
106
  fullWidth={true}
96
107
  className="w-full h-full p-0 bg-transparent"
108
+ inputClassName="focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none"
97
109
  position={"item-aligned"}
98
110
  disabled={disabled}
99
111
  padding={false}
@@ -101,6 +113,7 @@ export function VirtualTableSelect(props: {
101
113
  ? internalValue?.toString()
102
114
  : ""}
103
115
  onValueChange={onChange}
116
+ onOpenChange={handleOpenChange}
104
117
  renderValue={renderValue}>
105
118
  {enumValues?.map((enumConfig) => (
106
119
  <SelectItem
@@ -109,7 +122,7 @@ export function VirtualTableSelect(props: {
109
122
  <EnumValuesChip
110
123
  enumKey={enumConfig.id}
111
124
  enumValues={enumValues}
112
- size={small ? "small" : "medium"}/>
125
+ size={small ? "small" : "medium"} />
113
126
  </SelectItem>
114
127
  ))}
115
128
  </Select>
@@ -40,4 +40,6 @@ export type VirtualTableContextProps<T extends any> = {
40
40
  rowClassName?: (rowData: T) => string | undefined;
41
41
  endAdornment?: React.ReactNode;
42
42
  AddColumnComponent?: React.ComponentType;
43
+ onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
44
+ draggingColumnId?: string | null;
43
45
  };
@@ -1,5 +1,6 @@
1
1
  import { useMemo } from "react";
2
2
  import { EntityCollection, ResolvedEntityCollection, ResolvedProperty } from "../../types";
3
+ import { getPropertyInPath } from "../../util";
3
4
  import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
4
5
  import { PropertyColumnConfig } from "../EntityCollectionTable/EntityCollectionTableProps";
5
6
 
@@ -16,15 +17,45 @@ export function useColumnIds<M extends Record<string, any>>(collection: Resolved
16
17
 
17
18
  function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEntityCollection<M>, keys: string[]): PropertyColumnConfig[] {
18
19
 
19
- return keys.flatMap((key) => {
20
+ // First, figure out which spread map roots have individual child keys in the order
21
+ // If so, we should NOT auto-expand them - just use the explicit child keys
22
+ const rootsWithExplicitChildren = new Set<string>();
23
+ for (const key of keys) {
24
+ if (key.includes(".")) {
25
+ const rootKey = key.split(".")[0];
26
+ const rootProperty = collection.properties[rootKey];
27
+ if (rootProperty && rootProperty.dataType === "map" && rootProperty.spreadChildren && rootProperty.properties) {
28
+ rootsWithExplicitChildren.add(rootKey);
29
+ }
30
+ }
31
+ }
32
+
33
+ // Track processed keys to avoid duplicates
34
+ const processedPropertyKeys = new Set<string>();
35
+
36
+ const result = keys.flatMap((key) => {
37
+ // Skip if already processed (handles duplicates in propertiesOrder)
38
+ if (processedPropertyKeys.has(key)) return [null];
39
+
40
+ // Check if it's a top-level property
20
41
  const property = collection.properties[key];
21
42
  if (property) {
43
+ processedPropertyKeys.add(key);
22
44
  if (property.hideFromCollection)
23
45
  return [null];
24
46
  if (property.disabled && typeof property.disabled === "object" && property.disabled.hidden)
25
47
  return [null];
48
+
26
49
  if (property.dataType === "map" && property.spreadChildren && property.properties) {
27
- return getColumnKeysForProperty(property, key);
50
+ // Check if this spread map has explicit child keys in propertiesOrder
51
+ if (rootsWithExplicitChildren.has(key)) {
52
+ // DON'T auto-expand - the children are explicitly listed elsewhere
53
+ return [null];
54
+ }
55
+ // Auto-expand all children
56
+ const childConfigs = getColumnKeysForProperty(property, key);
57
+ childConfigs.forEach(c => processedPropertyKeys.add(c.key));
58
+ return childConfigs;
28
59
  }
29
60
  return [{
30
61
  key,
@@ -32,6 +63,33 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
32
63
  }];
33
64
  }
34
65
 
66
+ // Check if it's a nested key like "data.mode" (for spread map properties)
67
+ if (key.includes(".")) {
68
+ const rootKey = key.split(".")[0];
69
+ const rootProperty = collection.properties[rootKey];
70
+
71
+ if (rootProperty && rootProperty.dataType === "map" && rootProperty.properties) {
72
+ const nestedProperty = getPropertyInPath(collection.properties, key) as ResolvedProperty | undefined;
73
+ if (nestedProperty) {
74
+ processedPropertyKeys.add(key);
75
+ // Mark root as seen
76
+ processedPropertyKeys.add(rootKey);
77
+
78
+ if (nestedProperty.hideFromCollection)
79
+ return [null];
80
+ if (nestedProperty.disabled && typeof nestedProperty.disabled === "object" && nestedProperty.disabled.hidden)
81
+ return [null];
82
+
83
+ return [{
84
+ key,
85
+ disabled: Boolean(rootProperty.disabled) || Boolean(rootProperty.readOnly) ||
86
+ Boolean(nestedProperty.disabled) || Boolean(nestedProperty.readOnly)
87
+ }];
88
+ }
89
+ }
90
+ }
91
+
92
+ // Check additional fields
35
93
  const additionalField = collection.additionalFields?.find(field => field.key === key);
36
94
  if (additionalField) {
37
95
  return [{
@@ -40,6 +98,7 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
40
98
  }];
41
99
  }
42
100
 
101
+ // Check subcollections
43
102
  if (collection.subcollections) {
44
103
  const subCollection = collection.subcollections.find(subCol => getSubcollectionColumnId(subCol) === key);
45
104
  if (subCollection) {
@@ -50,6 +109,7 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
50
109
  }
51
110
  }
52
111
 
112
+ // Check collection group parent
53
113
  if (collection.collectionGroup && key === COLLECTION_GROUP_PARENT_ID) {
54
114
  return [{
55
115
  key,
@@ -59,6 +119,37 @@ function hideAndExpandKeys<M extends Record<string, any>>(collection: ResolvedEn
59
119
 
60
120
  return [null];
61
121
  }).filter(Boolean) as PropertyColumnConfig[];
122
+
123
+ // Add any missing properties that weren't in propertiesOrder
124
+ // This ensures properties NEVER disappear
125
+ for (const propKey of Object.keys(collection.properties)) {
126
+ // Skip if already processed
127
+ if (processedPropertyKeys.has(propKey)) continue;
128
+
129
+ const property = collection.properties[propKey];
130
+ if (!property) continue;
131
+ if (property.hideFromCollection) continue;
132
+ if (property.disabled && typeof property.disabled === "object" && property.disabled.hidden) continue;
133
+
134
+ if (property.dataType === "map" && property.spreadChildren && property.properties) {
135
+ // For spread maps, add all children that weren't already added
136
+ const allChildConfigs = getColumnKeysForProperty(property, propKey);
137
+ for (const childConfig of allChildConfigs) {
138
+ if (!processedPropertyKeys.has(childConfig.key)) {
139
+ result.push(childConfig);
140
+ processedPropertyKeys.add(childConfig.key);
141
+ }
142
+ }
143
+ } else {
144
+ result.push({
145
+ key: propKey,
146
+ disabled: Boolean(property.disabled) || Boolean(property.readOnly)
147
+ });
148
+ processedPropertyKeys.add(propKey);
149
+ }
150
+ }
151
+
152
+ return result;
62
153
  }
63
154
 
64
155
  function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: ResolvedEntityCollection<M>, includeSubCollections: boolean) {
@@ -69,7 +160,8 @@ function getDefaultColumnKeys<M extends Record<string, any> = any>(collection: R
69
160
 
70
161
  const columnIds: string[] = [
71
162
  ...propertyKeys,
72
- ...additionalFields.map((field) => field.key)
163
+ // Filter out additional fields whose key already exists in propertyKeys to avoid duplicate column keys
164
+ ...additionalFields.filter((field) => !propertyKeys.includes(field.key)).map((field) => field.key)
73
165
  ];
74
166
 
75
167
  if (includeSubCollections) {
@@ -16,6 +16,8 @@ export * from "./SelectableTable/SelectableTable";
16
16
  export * from "./SelectableTable/SelectableTableContext";
17
17
  export * from "./EntityCollectionView/EntityCollectionView";
18
18
  export * from "./EntityCollectionView/EntityCollectionViewActions";
19
+ export * from "./EntityCollectionView/EntityCollectionCardView";
20
+ export * from "./EntityCollectionView/EntityCard";
19
21
  export * from "./EntityCollectionView/useSelectionController";
20
22
 
21
23
  export * from "./PropertyConfigBadge";
@@ -38,3 +40,5 @@ export * from "./SearchIconsView";
38
40
  export * from "./FieldCaption";
39
41
 
40
42
  export * from "./EntityPreview";
43
+
44
+ export * from "./AIIcon";