@rebasepro/admin 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
  2. package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-C9yhLyja.js} +129 -15
  4. package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
  7. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  8. package/dist/collection_editor/index.d.ts +6 -0
  9. package/dist/collection_editor/serializable_types.d.ts +374 -0
  10. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  11. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
  12. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  13. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  14. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  15. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  16. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  17. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  18. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  19. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  20. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  21. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  22. package/dist/collection_editor_ui.d.ts +4 -0
  23. package/dist/collection_editor_ui.js +33 -4
  24. package/dist/collection_editor_ui.js.map +1 -0
  25. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  26. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  27. package/dist/components/EntityEditView.d.ts +3 -2
  28. package/dist/components/RebaseCMS.d.ts +1 -1
  29. package/dist/form/EntityForm.d.ts +10 -9
  30. package/dist/form/EntityFormBinding.d.ts +27 -0
  31. package/dist/form/form_utils.d.ts +8 -0
  32. package/dist/form/index.d.ts +5 -1
  33. package/dist/{history-BO6VT85a.js → history-ChHgyyvM.js} +2 -2
  34. package/dist/{history-BO6VT85a.js.map → history-ChHgyyvM.js.map} +1 -1
  35. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  36. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  37. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  38. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  39. package/dist/hooks/navigation/utils.d.ts +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +2 -2
  42. package/dist/routes/CustomViewRoute.d.ts +2 -1
  43. package/dist/{src-BYniefVu.js → src-DOX2nOyh.js} +1586 -1283
  44. package/dist/src-DOX2nOyh.js.map +1 -0
  45. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  46. package/dist/types/components/EntityFormProps.d.ts +59 -0
  47. package/dist/types/fields.d.ts +6 -1
  48. package/dist/util/dataControllerAdapter.d.ts +30 -0
  49. package/dist/util/navigation_utils.d.ts +1 -1
  50. package/dist/util/propertyConfigMapper.d.ts +17 -0
  51. package/package.json +18 -18
  52. package/src/collection_editor/extensibility_types.ts +168 -0
  53. package/src/collection_editor/index.ts +46 -0
  54. package/src/collection_editor/serializable_types.ts +487 -0
  55. package/src/collection_editor/serializable_utils.ts +588 -0
  56. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
  57. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
  58. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  59. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  60. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  61. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  62. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  63. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  64. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +155 -10
  65. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  66. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  67. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  68. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  69. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  70. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  71. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  72. package/src/collection_editor_ui.ts +6 -0
  73. package/src/components/DefaultDrawer.tsx +13 -3
  74. package/src/components/DrawerNavigationGroup.tsx +2 -2
  75. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  76. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -11
  77. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  78. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
  79. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  80. package/src/components/EntityEditView.tsx +5 -4
  81. package/src/components/EntityEditViewFormActions.tsx +32 -26
  82. package/src/components/HomePage/ContentHomePage.tsx +13 -4
  83. package/src/components/RebaseCMS.tsx +3 -2
  84. package/src/components/RebaseNavigation.tsx +71 -7
  85. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  86. package/src/components/SideDialogs.tsx +0 -7
  87. package/src/components/common/default_entity_actions.tsx +9 -9
  88. package/src/contexts/BreacrumbsContext.tsx +15 -6
  89. package/src/editor/selectors/ai-selector.tsx +0 -1
  90. package/src/form/EntityForm.tsx +191 -428
  91. package/src/form/EntityFormActions.tsx +33 -47
  92. package/src/form/EntityFormBinding.tsx +323 -0
  93. package/src/form/components/FormLayout.tsx +1 -1
  94. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  95. package/src/form/form_utils.ts +163 -0
  96. package/src/form/index.tsx +14 -5
  97. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  98. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  99. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  100. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  101. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  102. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  103. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  104. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  105. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  106. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  107. package/src/hooks/navigation/utils.ts +7 -4
  108. package/src/index.ts +5 -0
  109. package/src/routes/CustomViewRoute.tsx +14 -6
  110. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  111. package/src/types/components/EntityFormProps.tsx +68 -0
  112. package/src/types/fields.tsx +7 -1
  113. package/src/util/dataControllerAdapter.ts +93 -0
  114. package/src/util/navigation_utils.ts +1 -1
  115. package/src/util/propertyConfigMapper.ts +134 -0
  116. package/LICENSE +0 -21
  117. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  118. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  119. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  120. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  121. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  122. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  123. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  124. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  125. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  126. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  127. package/dist/src-BYniefVu.js.map +0 -1
  128. package/src/components/EntityCollectionView/Board.tsx +0 -475
  129. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  130. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  131. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  132. package/src/components/EntityCollectionView/board_types.ts +0 -113
  133. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  134. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -1,11 +1,12 @@
1
1
  import type { EntityCollection } from "@rebasepro/types";
2
- import React, { useCallback, useEffect, useRef } from "react";
2
+ import React, { useCallback, useEffect, useMemo, useRef } from "react";
3
3
  import { CollectionSize, Entity, EntityTableController, SelectionController } from "@rebasepro/types";
4
4
  import { EntityCard } from "./EntityCard";
5
5
  import {
6
6
  cls,
7
7
  CircularProgress,
8
- Typography
8
+ Typography,
9
+ CardView
9
10
  } from "@rebasepro/ui";
10
11
  import { useComponentOverride } from "@rebasepro/core";
11
12
 
@@ -92,8 +93,6 @@ export function EntityCollectionCardView<M extends Record<string, unknown> = Rec
92
93
  }: EntityCollectionCardViewProps<M>) {
93
94
 
94
95
  const ResolvedEntityCard = useComponentOverride("Collection.Card", EntityCard) as typeof EntityCard;
95
- const containerRef = useRef<HTMLDivElement>(null);
96
- const hasRestoredScroll = useRef(false);
97
96
 
98
97
  const {
99
98
  data,
@@ -106,116 +105,6 @@ export function EntityCollectionCardView<M extends Record<string, unknown> = Rec
106
105
  paginationEnabled
107
106
  } = tableController;
108
107
 
109
- // Track if we're currently loading to prevent multiple simultaneous load requests
110
- const isLoadingMore = useRef(false);
111
-
112
- // Keep mutable refs for values used in the scroll listener callback
113
- // to avoid re-attaching the listener every time pagination state changes.
114
- const paginationStateRef = useRef({ paginationEnabled,
115
- noMoreToLoad,
116
- dataLoading,
117
- itemCount,
118
- pageSize });
119
- useEffect(() => {
120
- paginationStateRef.current = { paginationEnabled,
121
- noMoreToLoad,
122
- dataLoading,
123
- itemCount,
124
- pageSize };
125
- }, [paginationEnabled, noMoreToLoad, dataLoading, itemCount, pageSize]);
126
-
127
- // Reset loading flag when new data arrives (separate effect, like list view)
128
- useEffect(() => {
129
- if (!dataLoading) isLoadingMore.current = false;
130
- }, [dataLoading]);
131
-
132
- // Infinite scroll and resize observer
133
- useEffect(() => {
134
- const el = containerRef.current;
135
- if (!el) return;
136
- const scrollEl = getScrollParent(el);
137
- if (!scrollEl) return;
138
-
139
- let rafId: number | null = null;
140
-
141
- const update = () => {
142
- rafId = null;
143
-
144
- // Infinite scroll: trigger load-more when near the bottom
145
- const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
146
- if (
147
- pe &&
148
- !nm &&
149
- !isLoadingMore.current &&
150
- scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < 400
151
- ) {
152
- isLoadingMore.current = true;
153
- setItemCount?.((ic ?? ps) + ps);
154
- }
155
- };
156
-
157
- const onScrollEvent = () => {
158
- if (rafId === null) rafId = requestAnimationFrame(update);
159
- };
160
-
161
- scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
162
- const ro = new ResizeObserver(() => update());
163
- ro.observe(scrollEl);
164
- update(); // initial measurement
165
-
166
- return () => {
167
- scrollEl.removeEventListener("scroll", onScrollEvent);
168
- ro.disconnect();
169
- if (rafId !== null) cancelAnimationFrame(rafId);
170
- };
171
- }, [setItemCount]);
172
-
173
- // Scroll restoration — deferred to after layout paint
174
- useEffect(() => {
175
- if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
176
-
177
- const scrollEl = getScrollParent(containerRef.current);
178
- if (!scrollEl) return;
179
-
180
- let attempts = 0;
181
- const maxAttempts = 5;
182
-
183
- const tryRestore = () => {
184
- if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
185
- scrollEl.scrollTop = initialScroll;
186
- hasRestoredScroll.current = true;
187
- } else {
188
- attempts++;
189
- requestAnimationFrame(tryRestore);
190
- }
191
- };
192
-
193
- requestAnimationFrame(tryRestore);
194
- }, [initialScroll, data.length]);
195
-
196
- // Scroll tracking: call onScroll when user scrolls
197
- const lastScrollOffset = useRef(0);
198
- useEffect(() => {
199
- const el = containerRef.current;
200
- if (!el || !onScroll) return;
201
- const scrollEl = getScrollParent(el);
202
- if (!scrollEl) return;
203
-
204
- const handleScroll = () => {
205
- const currentOffset = scrollEl.scrollTop;
206
- const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
207
- lastScrollOffset.current = currentOffset;
208
- onScroll({
209
- scrollDirection: direction,
210
- scrollOffset: currentOffset,
211
- scrollUpdateWasRequested: false
212
- });
213
- };
214
-
215
- scrollEl.addEventListener("scroll", handleScroll, { passive: true });
216
- return () => scrollEl.removeEventListener("scroll", handleScroll);
217
- }, [onScroll]);
218
-
219
108
  const handleEntityClick = useCallback((entity: Entity<M>) => {
220
109
  onEntityClick?.(entity);
221
110
  }, [onEntityClick]);
@@ -224,84 +113,45 @@ pageSize };
224
113
  selectionController?.toggleEntitySelection(entity, selected);
225
114
  }, [selectionController]);
226
115
 
227
- const isEntitySelected = useCallback((entity: Entity<M>) => {
228
- return selectionController?.isEntitySelected(entity) ?? false;
229
- }, [selectionController]);
230
-
231
- const isEntityHighlighted = useCallback((entity: Entity<M>) => {
232
- return highlightedEntities?.some(e => e.id === entity.id && e.path === entity.path) ?? false;
233
- }, [highlightedEntities]);
116
+ const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map(e => e.id)), [selectionController?.selectedEntities]);
117
+ const highlightedIds = useMemo(() => new Set(highlightedEntities?.map(e => e.id)), [highlightedEntities]);
234
118
 
235
- const gridColumnsClass = getGridColumnsClass(size);
236
-
237
- // Initial loading state (no data yet)
238
- const isInitialLoading = dataLoading && data.length === 0 && !dataLoadingError;
239
- // Empty state
240
- const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
119
+ const handleRowSelectionChange = useCallback((entity: Entity<M>, selected: boolean) => {
120
+ handleSelectionChange(entity, selected);
121
+ }, [handleSelectionChange]);
241
122
 
242
123
  return (
243
- <div
244
- ref={containerRef}
245
- className="w-full p-4"
246
- >
247
- {/* Error state */}
248
- {dataLoadingError && data.length === 0 ? (
249
- <div className="h-full flex items-center justify-center p-8">
250
- <Typography className="text-red-500">
251
- Error loading data: {dataLoadingError.message}
252
- </Typography>
253
- </div>
254
- ) : isInitialLoading ? (
255
- <div className="flex items-center justify-center py-12 px-8">
256
- <CircularProgress size="small"/>
257
- </div>
258
- ) : isEmpty ? (
259
- <div className="w-full flex items-center justify-center py-12 px-8">
260
- {emptyComponent ?? (
261
- <Typography variant="label" color="secondary">
262
- No entries found
263
- </Typography>
264
- )}
265
- </div>
266
- ) : (
267
- <>
268
- {/* Card Grid with max-width container */}
269
- <div className="max-w-7xl mx-auto">
270
- <div className={cls(
271
- "grid gap-4",
272
- gridColumnsClass
273
- )}>
274
- {data.map((entity) => (
275
- <ResolvedEntityCard
276
- key={`${entity.path}_${entity.id}`}
277
- entity={entity}
278
- collection={collection}
279
- onClick={handleEntityClick}
280
- selected={isEntitySelected(entity)}
281
- highlighted={isEntityHighlighted(entity)}
282
- onSelectionChange={handleSelectionChange}
283
- selectionEnabled={selectionEnabled}
284
- size={size}
285
- />
286
- ))}
287
- </div>
288
-
289
- {/* Load more trigger / Loading indicator */}
290
- <div
291
- className="flex items-center justify-center py-8"
292
- >
293
- {dataLoading && (
294
- <CircularProgress size="small"/>
295
- )}
296
- {!dataLoading && noMoreToLoad && data.length > 0 && (
297
- <Typography variant="caption" color="secondary">
298
- All {data.length} entries loaded
299
- </Typography>
300
- )}
301
- </div>
302
- </div>
303
- </>
304
- )}
305
- </div>
124
+ <CardView<Entity<M>>
125
+ data={data}
126
+ dataLoading={dataLoading}
127
+ noMoreToLoad={noMoreToLoad}
128
+ dataLoadingError={dataLoadingError}
129
+ itemCount={itemCount}
130
+ setItemCount={setItemCount}
131
+ pageSize={pageSize}
132
+ paginationEnabled={paginationEnabled}
133
+ onItemClick={handleEntityClick}
134
+ selectedIds={selectedIds}
135
+ highlightedIds={highlightedIds}
136
+ selectionEnabled={selectionEnabled}
137
+ onSelectionChange={handleRowSelectionChange}
138
+ onScroll={onScroll}
139
+ initialScroll={initialScroll}
140
+ size={size}
141
+ emptyComponent={emptyComponent}
142
+ renderCard={useCallback((entity, { selected, highlighted, onClick }) => (
143
+ <ResolvedEntityCard
144
+ key={`${entity.path}_${entity.id}`}
145
+ entity={entity}
146
+ collection={collection}
147
+ onClick={onClick as any}
148
+ selected={selected}
149
+ highlighted={highlighted}
150
+ onSelectionChange={handleRowSelectionChange}
151
+ selectionEnabled={selectionEnabled}
152
+ size={size}
153
+ />
154
+ ), [collection, selectionEnabled, size, handleRowSelectionChange, ResolvedEntityCard])}
155
+ />
306
156
  );
307
157
  }
@@ -10,7 +10,8 @@ import {
10
10
  defaultBorderMixin,
11
11
  IconButton,
12
12
  Tooltip,
13
- Typography
13
+ Typography,
14
+ ListView
14
15
  } from "@rebasepro/ui";
15
16
  import { PropertyPreview } from "../../preview";
16
17
  import {
@@ -510,192 +511,62 @@ customEntityActions });
510
511
 
511
512
  const rowClasses = getRowClasses(size);
512
513
 
513
- // Initial loading state (no data yet)
514
- const isInitialLoading = dataLoading && data.length === 0;
515
- // Empty state
516
- const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
517
-
518
- // ── Virtualization: scroll-parent windowing ──
519
- const estimatedRowHeight = getEstimatedRowHeight(size);
520
- const [effectiveScrollTop, setEffectiveScrollTop] = useState(0);
521
- const [viewportHeight, setViewportHeight] = useState(800);
522
-
523
- // Keep mutable refs for values used in the scroll handler to avoid
524
- // re-attaching the listener every time pagination state changes.
525
- const paginationStateRef = useRef({ paginationEnabled,
526
- noMoreToLoad,
527
- itemCount,
528
- pageSize });
529
- useEffect(() => {
530
- paginationStateRef.current = { paginationEnabled,
531
- noMoreToLoad,
532
- itemCount,
533
- pageSize };
534
- }, [paginationEnabled, noMoreToLoad, itemCount, pageSize]);
535
-
536
- useEffect(() => {
537
- const el = containerRef.current;
538
- if (!el) return;
539
- const scrollEl = getScrollParent(el);
540
- if (!scrollEl) return;
541
-
542
- let rafId: number | null = null;
543
-
544
- const update = () => {
545
- rafId = null;
546
- const scrollRect = scrollEl.getBoundingClientRect();
547
- const listRect = el.getBoundingClientRect();
548
-
549
- // How much of the list has scrolled past the viewport top
550
- const listTopRelative = listRect.top - scrollRect.top;
551
- setEffectiveScrollTop(Math.max(0, -listTopRelative));
552
- setViewportHeight(scrollRect.height);
553
-
554
- // Infinite scroll: trigger load-more when near the bottom
555
- const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
556
- if (
557
- pe &&
558
- !nm &&
559
- !isLoadingMore.current &&
560
- scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < LOAD_MORE_THRESHOLD
561
- ) {
562
- isLoadingMore.current = true;
563
- setItemCount?.((ic ?? ps) + ps);
564
- }
565
- };
566
-
567
- const onScroll = () => {
568
- if (rafId === null) rafId = requestAnimationFrame(update);
569
- };
514
+ const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map(e => e.id)), [selectionController?.selectedEntities]);
515
+ const highlightedIds = useMemo(() => new Set(highlightedEntities?.map(e => e.id)), [highlightedEntities]);
570
516
 
571
- scrollEl.addEventListener("scroll", onScroll, { passive: true });
572
- const ro = new ResizeObserver(() => update());
573
- ro.observe(scrollEl);
574
- update(); // initial measurement
575
-
576
- return () => {
577
- scrollEl.removeEventListener("scroll", onScroll);
578
- ro.disconnect();
579
- if (rafId !== null) cancelAnimationFrame(rafId);
580
- };
581
- }, [setItemCount]); // stable deps only — mutable state via refs
582
-
583
- // Compute the visible window of rows
584
- const totalHeight = data.length * estimatedRowHeight;
585
- const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
586
- const endIndex = Math.min(
587
- data.length,
588
- Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT
589
- );
590
- const visibleData = data.slice(startIndex, endIndex);
591
- const offsetY = startIndex * estimatedRowHeight;
592
-
593
- // Footer height for loading/end indicators
594
- const footerHeight = dataLoading ? 48 : (!dataLoading && noMoreToLoad && data.length > 0) ? 32 : 0;
517
+ const handleRowSelectionChange = useCallback((entity: Entity<M>, selected: boolean) => {
518
+ handleSelectionChange(entity, selected);
519
+ }, [handleSelectionChange]);
595
520
 
596
521
  return (
597
- <div
598
- ref={containerRef}
599
- className={cls(
600
- "w-full",
601
- selectedEntityId === undefined && "rounded-lg overflow-hidden border " + defaultBorderMixin
602
- )}
603
- >
604
- {/* Error state */}
605
- {dataLoadingError && data.length === 0 ? (
606
- <div className="flex items-center justify-center p-8">
607
- <Typography className="text-red-500">
608
- Error loading data: {dataLoadingError.message}
609
- </Typography>
610
- </div>
611
- ) : isInitialLoading ? (
612
- <div className="flex items-center justify-center py-12 px-8">
613
- <CircularProgress size="small"/>
614
- </div>
615
- ) : isEmpty ? (
616
- <div className="w-full flex items-center justify-center py-12 px-8">
617
- {emptyComponent ?? (
618
- <Typography variant="label" color="secondary">
619
- No entries found
620
- </Typography>
621
- )}
622
- </div>
623
- ) : (
624
- /* Spacer with total height — no internal scroll.
625
- The nearest scrollable ancestor provides the scrollbar. */
626
- <div style={{ height: totalHeight + footerHeight,
627
- position: "relative" }}>
628
- {/* Windowed rows */}
629
- <div style={{ position: "absolute",
630
- top: offsetY,
631
- left: 0,
632
- right: 0 }}>
633
- {visibleData.map((entity, i) => {
634
- const actualIndex = startIndex + i;
635
- const isLast = actualIndex === data.length - 1;
636
- return (
637
- <div
638
- key={entity.id}
639
- style={{ height: estimatedRowHeight }}
640
- className={cls(
641
- !isLast && "border-b",
642
- !isLast && defaultBorderMixin
643
- )}
644
- >
645
- <ListRow
646
- entity={entity}
647
- collection={resolvedCollection}
648
- onClick={handleEntityClick}
649
- selected={isEntitySelected(entity)}
650
- highlighted={isEntityHighlighted(entity)}
651
- onSelectionChange={handleSelectionChange}
652
- selectionEnabled={selectionEnabled}
653
- columns={visibleColumns}
654
- slotKeys={slotKeys}
655
- rowClasses={rowClasses}
656
- showImage={showImage}
657
- size={size}
658
- isLast={isLast}
659
- isActive={selectedEntityId !== undefined && entity.id === selectedEntityId}
660
- listViewActions={getListViewActions(entity)}
661
- context={context}
662
- path={path}
663
- selectionController={selectionController}
664
- openEntityMode={openEntityMode}
665
- />
666
- </div>
667
- );
668
- })}
522
+ <ListView<Entity<M>>
523
+ data={data}
524
+ dataLoading={dataLoading}
525
+ noMoreToLoad={noMoreToLoad}
526
+ dataLoadingError={dataLoadingError}
527
+ itemCount={itemCount}
528
+ setItemCount={setItemCount}
529
+ pageSize={pageSize}
530
+ paginationEnabled={paginationEnabled}
531
+ onItemClick={handleEntityClick}
532
+ selectedIds={selectedIds}
533
+ highlightedIds={highlightedIds}
534
+ selectionEnabled={selectionEnabled}
535
+ emptyComponent={emptyComponent}
536
+ size={size}
537
+ selectedEntityId={selectedEntityId}
538
+ renderRow={useCallback(({ item: entity, style, className, selected, highlighted, isLast, onClick, onSelectionChange }) => {
539
+ return (
540
+ <div
541
+ key={entity.id}
542
+ style={style}
543
+ className={className}
544
+ >
545
+ <ListRow
546
+ entity={entity}
547
+ collection={resolvedCollection}
548
+ onClick={handleEntityClick}
549
+ selected={selected}
550
+ highlighted={highlighted}
551
+ onSelectionChange={handleRowSelectionChange}
552
+ selectionEnabled={selectionEnabled}
553
+ columns={visibleColumns}
554
+ slotKeys={slotKeys}
555
+ rowClasses={rowClasses}
556
+ showImage={showImage}
557
+ size={size}
558
+ isLast={isLast}
559
+ isActive={selectedEntityId !== undefined && entity.id === selectedEntityId}
560
+ listViewActions={getListViewActions(entity)}
561
+ context={context}
562
+ path={path}
563
+ selectionController={selectionController}
564
+ openEntityMode={openEntityMode}
565
+ />
669
566
  </div>
670
-
671
- {/* Loading / end indicators pinned at the bottom */}
672
- {dataLoading && (
673
- <div
674
- className="flex items-center justify-center py-3"
675
- style={{ position: "absolute",
676
- top: totalHeight,
677
- left: 0,
678
- right: 0 }}
679
- >
680
- <CircularProgress size="small"/>
681
- </div>
682
- )}
683
- {!dataLoading && noMoreToLoad && data.length > 0 && (
684
- <div
685
- className="flex items-center justify-center py-2 dark:bg-surface-900"
686
- style={{ position: "absolute",
687
- top: totalHeight,
688
- left: 0,
689
- right: 0 }}
690
- >
691
- <Typography variant="caption" color="secondary">
692
- All {data.length} entries loaded
693
- </Typography>
694
- </div>
695
- )}
696
- </div>
697
- )}
698
- </div>
567
+ );
568
+ }, [resolvedCollection, selectionEnabled, visibleColumns, slotKeys, rowClasses, showImage, size, selectedEntityId, getListViewActions, context, path, selectionController, openEntityMode, handleRowSelectionChange, handleEntityClick])}
569
+ />
699
570
  );
700
571
  }
701
572
 
@@ -1,8 +1,8 @@
1
1
  import { useCallback, useMemo } from "react";
2
2
  import { setIn } from "@rebasepro/formex";
3
- import { EntityCollection, SaveEntityProps, RebaseData, RebaseContext, AnalyticsController } from "@rebasepro/types";
3
+ import { EntityCollection, SaveEntityProps, RebaseData, RebaseContext, AnalyticsController, Entity } from "@rebasepro/types";
4
4
  import { saveEntityWithCallbacks } from "@rebasepro/core";
5
- import { BoardItem } from "../board_types";
5
+ import { BoardItem } from "@rebasepro/ui";
6
6
  import { BoardDataController } from "../useBoardDataController";
7
7
  import { generateKeyBetween } from "fractional-indexing";
8
8
 
@@ -33,10 +33,10 @@ export function useKanbanDragAndDrop<M extends Record<string, unknown>>({
33
33
  // to compute a single new sort key for the moved item.
34
34
  // Only one DB write per drag — the moved item gets its new key.
35
35
  const handleItemsReorder = useCallback(async (
36
- items: BoardItem<M>[],
36
+ items: BoardItem<Entity<M>>[],
37
37
  moveInfo?: { itemId: string; sourceColumn: string; targetColumn: string; }
38
38
  ) => {
39
- const entity = items.find(item => item.id === moveInfo?.itemId)?.entity;
39
+ const entity = items.find(item => item.id === moveInfo?.itemId)?.data;
40
40
  if (!entity || !moveInfo) return;
41
41
 
42
42
  analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
@@ -64,10 +64,10 @@ export function useKanbanDragAndDrop<M extends Record<string, unknown>>({
64
64
 
65
65
  // Get the order keys of the neighbours
66
66
  const prevKey = movedIndex > 0
67
- ? (targetColumnItems[movedIndex - 1].entity.values?.[orderProperty] as string | null) ?? null
67
+ ? (targetColumnItems[movedIndex - 1].data.values?.[orderProperty] as string | null) ?? null
68
68
  : null;
69
69
  const nextKey = movedIndex < targetColumnItems.length - 1
70
- ? (targetColumnItems[movedIndex + 1].entity.values?.[orderProperty] as string | null) ?? null
70
+ ? (targetColumnItems[movedIndex + 1].data.values?.[orderProperty] as string | null) ?? null
71
71
  : null;
72
72
 
73
73
  try {
@@ -39,8 +39,9 @@ import {
39
39
  getIcon
40
40
  } from "@rebasepro/core";
41
41
  import { getEntityFromMemoryCache } from "@rebasepro/core";
42
- import { EntityForm } from "../form";
43
- import type { EntityFormProps, OnUpdateParams } from "../types/components/EntityFormProps";
42
+ import { EntityFormBinding } from "../form";
43
+ import type { EntityFormBindingProps } from "../form";
44
+ import type { OnUpdateParams } from "../types/components/EntityFormProps";
44
45
  import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
45
46
  import { EntityJsonPreview } from "../components/EntityJsonPreview";
46
47
  // Lazy-load history view — only loaded when user clicks the HistoryIcon tab
@@ -85,7 +86,7 @@ export interface EntityEditViewProps<M extends Record<string, unknown> = Record<
85
86
  navigateBack?: () => void;
86
87
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
87
88
  barActions?: (params: BarActionsParams) => React.ReactNode;
88
- formProps?: Partial<EntityFormProps<M>>,
89
+ formProps?: Partial<EntityFormBindingProps<M>>,
89
90
  /**
90
91
  * Pre-populate the form with these values when creating a new entity.
91
92
  * Only applied when the form is in "new" mode (no entityId).
@@ -197,7 +198,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
197
198
  }) {
198
199
 
199
200
  const ResolvedFormActions = useComponentOverride("Entity.FormActions", EntityEditViewFormActions);
200
- const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityForm) as typeof EntityForm;
201
+ const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding) as typeof EntityFormBinding;
201
202
  const ResolvedCollectionView = useComponentOverride("Collection.View", EntityCollectionView);
202
203
 
203
204
  const context = useRebaseContext();