@rebasepro/admin 0.6.1 → 0.8.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 (187) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-BO_aQ-Z4.js} +236 -155
  2. package/dist/CollectionEditorDialog-BO_aQ-Z4.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-DKzOpMjR.js} +190 -50
  4. package/dist/PropertyEditView-DKzOpMjR.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-BQTIExjA.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-BQTIExjA.js.map +1 -0
  7. package/dist/collection_editor/_cms_internals.d.ts +1 -1
  8. package/dist/collection_editor/api/generateCollectionApi.d.ts +3 -2
  9. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  10. package/dist/collection_editor/index.d.ts +6 -0
  11. package/dist/collection_editor/serializable_types.d.ts +370 -0
  12. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  13. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +3 -3
  14. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  15. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  16. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  17. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  18. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  19. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  20. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  21. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  22. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  23. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  24. package/dist/collection_editor_ui.d.ts +4 -0
  25. package/dist/collection_editor_ui.js +33 -4
  26. package/dist/collection_editor_ui.js.map +1 -0
  27. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  28. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  29. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  30. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  31. package/dist/components/EntityEditView.d.ts +3 -2
  32. package/dist/components/RebaseCMS.d.ts +1 -1
  33. package/dist/components/SelectableTable/SelectionStore.d.ts +12 -21
  34. package/dist/components/field_configs.d.ts +1 -1
  35. package/dist/editor/editor.d.ts +0 -6
  36. package/dist/editor.js +1 -3
  37. package/dist/editor.js.map +1 -1
  38. package/dist/form/EntityForm.d.ts +10 -9
  39. package/dist/form/EntityFormBinding.d.ts +27 -0
  40. package/dist/form/components/StorageUploadProgress.d.ts +6 -1
  41. package/dist/form/form_utils.d.ts +8 -0
  42. package/dist/form/index.d.ts +5 -2
  43. package/dist/{history-BO6VT85a.js → history-B3cqBzEV.js} +2 -2
  44. package/dist/{history-BO6VT85a.js.map → history-B3cqBzEV.js.map} +1 -1
  45. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  46. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  47. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  48. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  49. package/dist/hooks/navigation/utils.d.ts +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +2 -2
  52. package/dist/preview/components/StorageThumbnail.d.ts +3 -1
  53. package/dist/routes/CustomViewRoute.d.ts +2 -1
  54. package/dist/{src-BYniefVu.js → src-cxV1ODRG.js} +1731 -1710
  55. package/dist/src-cxV1ODRG.js.map +1 -0
  56. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  57. package/dist/types/components/EntityFormProps.d.ts +59 -0
  58. package/dist/types/components/PropertyPreviewProps.d.ts +2 -1
  59. package/dist/types/fields.d.ts +10 -7
  60. package/dist/util/dataControllerAdapter.d.ts +30 -0
  61. package/dist/util/navigation_utils.d.ts +1 -1
  62. package/dist/util/propertyConfigMapper.d.ts +17 -0
  63. package/package.json +8 -8
  64. package/src/collection_editor/_cms_internals.ts +1 -1
  65. package/src/collection_editor/api/generateCollectionApi.ts +3 -2
  66. package/src/collection_editor/extensibility_types.ts +168 -0
  67. package/src/collection_editor/index.ts +46 -0
  68. package/src/collection_editor/pgColumnToProperty.ts +2 -2
  69. package/src/collection_editor/serializable_types.ts +474 -0
  70. package/src/collection_editor/serializable_utils.ts +576 -0
  71. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +6 -6
  72. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +170 -89
  73. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  74. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  75. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  76. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  77. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  78. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  79. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +154 -18
  80. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  81. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  82. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  83. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  84. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  85. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +35 -1
  86. package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
  87. package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -1
  88. package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +7 -29
  89. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  90. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  91. package/src/collection_editor_ui.ts +6 -0
  92. package/src/components/DefaultDrawer.tsx +14 -4
  93. package/src/components/DrawerNavigationGroup.tsx +2 -2
  94. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +11 -9
  95. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +2 -1
  96. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +2 -1
  97. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +23 -20
  98. package/src/components/EntityCollectionTable/index.tsx +1 -1
  99. package/src/components/EntityCollectionTable/table_bindings.tsx +24 -44
  100. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  101. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +13 -12
  102. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  103. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +66 -184
  104. package/src/components/EntityCollectionView/EntityCollectionView.tsx +8 -25
  105. package/src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts +1 -1
  106. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  107. package/src/components/EntityCollectionView/useBoardDataController.tsx +12 -40
  108. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +3 -3
  109. package/src/components/EntityDetailView.tsx +2 -2
  110. package/src/components/EntityEditView.tsx +7 -6
  111. package/src/components/EntityEditViewFormActions.tsx +35 -29
  112. package/src/components/HomePage/ContentHomePage.tsx +14 -5
  113. package/src/components/RebaseCMS.tsx +3 -3
  114. package/src/components/RebaseNavigation.tsx +74 -9
  115. package/src/components/RelationSelector.tsx +2 -2
  116. package/src/components/SelectableTable/SelectableTable.tsx +13 -5
  117. package/src/components/SelectableTable/SelectionStore.ts +18 -48
  118. package/src/components/SideDialogs.tsx +0 -7
  119. package/src/components/common/default_entity_actions.tsx +22 -11
  120. package/src/components/field_configs.tsx +0 -14
  121. package/src/contexts/BreacrumbsContext.tsx +15 -6
  122. package/src/editor/editor.tsx +4 -7
  123. package/src/editor/selectors/ai-selector.tsx +0 -1
  124. package/src/form/EntityForm.tsx +191 -428
  125. package/src/form/EntityFormActions.tsx +35 -49
  126. package/src/form/EntityFormBinding.tsx +323 -0
  127. package/src/form/components/FormLayout.tsx +1 -1
  128. package/src/form/components/StorageUploadProgress.tsx +9 -2
  129. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +12 -1
  130. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +4 -4
  131. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  132. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -7
  133. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +19 -15
  134. package/src/form/form_utils.ts +163 -0
  135. package/src/form/index.tsx +14 -6
  136. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  137. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  138. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  139. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  140. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  141. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  142. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  143. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  144. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  145. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  146. package/src/hooks/navigation/utils.ts +7 -4
  147. package/src/index.ts +6 -1
  148. package/src/preview/PropertyPreview.tsx +1 -16
  149. package/src/preview/components/StorageThumbnail.tsx +24 -7
  150. package/src/routes/CustomViewRoute.tsx +14 -6
  151. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  152. package/src/types/components/EntityFormProps.tsx +68 -0
  153. package/src/types/components/PropertyPreviewProps.tsx +1 -1
  154. package/src/types/fields.tsx +9 -8
  155. package/src/util/dataControllerAdapter.ts +93 -0
  156. package/src/util/navigation_utils.ts +1 -1
  157. package/src/util/previews.ts +20 -0
  158. package/src/util/propertyConfigMapper.ts +134 -0
  159. package/src/util/property_utils.tsx +1 -2
  160. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  161. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  162. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  163. package/dist/components/EntityCollectionTable/fields/VirtualTableDateField.d.ts +0 -12
  164. package/dist/components/EntityCollectionTable/fields/VirtualTableInput.d.ts +0 -10
  165. package/dist/components/EntityCollectionTable/fields/VirtualTableNumberInput.d.ts +0 -9
  166. package/dist/components/EntityCollectionTable/fields/VirtualTableSwitch.d.ts +0 -8
  167. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  168. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  169. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  170. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  171. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  172. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +0 -11
  173. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  174. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  175. package/dist/src-BYniefVu.js.map +0 -1
  176. package/src/components/EntityCollectionTable/fields/VirtualTableDateField.tsx +0 -39
  177. package/src/components/EntityCollectionTable/fields/VirtualTableInput.tsx +0 -90
  178. package/src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx +0 -83
  179. package/src/components/EntityCollectionTable/fields/VirtualTableSwitch.tsx +0 -32
  180. package/src/components/EntityCollectionView/Board.tsx +0 -475
  181. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  182. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  183. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  184. package/src/components/EntityCollectionView/board_types.ts +0 -113
  185. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +0 -141
  186. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  187. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -2,14 +2,14 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import * as React$1 from "react";
3
3
  import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { deepEqual } from "fast-equals";
5
- import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Avatar, Badge, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, Container, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KeyRoundIcon, Label, LayoutGridIcon, LinkIcon, ListIcon, ListOrderedIcon, LoadingButton, LogOutIcon, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, StarIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, UploadIcon, UserIcon, VirtualTable, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForKey, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
6
- import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, addInitialSlash, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getLabelOrConfigFrom, getLastSegment, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isHidden, isPropertyBuilder, isReadOnly, normalizeToEntityRelation, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveDefaultSelectedView, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString } from "@rebasepro/common";
7
- import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, ConfirmationDialog, CustomizationControllerContext, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, IconForView, LanguageToggle, LoginView, NotFoundPage, PluginProviderStack, RebaseLogo, RebaseRoutes, SchemaDriftBanner, StudioBridgeRegistryProvider, UIReferenceView, UnsavedChangesDialog, UserDisplay, UserSettingsView, buildCollapsedDefaults, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getFormFieldKeys, getIcon, getRowHeight, iconsSearch, removeEntityFromCache, removeEntityFromMemoryCache, resolveComponentRef, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiConfig, useAuthController, useBridgeRegistration, useBuildLocalConfigurationPersistence, useCollapsedGroups, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useEntityFetch, useLargeLayout, useModeController, usePermissions, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useRestoreScroll, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/core";
5
+ import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Avatar, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, Container, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoaderIcon, LoadingButton, LogOutIcon, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, StarIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, TableView, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, UploadIcon, UserIcon, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
6
+ import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, addInitialSlash, buildRoutedRebaseData, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getLabelOrConfigFrom, getLastSegment, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isHidden, isPropertyBuilder, isReadOnly, normalizeToEntityRelation, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveDataSource, resolveDefaultSelectedView, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
7
+ import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, ConfirmationDialog, CustomizationControllerContext, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, IconForView, LanguageToggle, LoginView, NotFoundPage, PluginProviderStack, RebaseDataContext, RebaseLogo, RebaseRoutes, SchemaDriftBanner, StorageSourceContext, StudioBridgeRegistryProvider, UIReferenceView, UnsavedChangesDialog, UserDisplay, UserSettingsView, buildCollapsedDefaults, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getFormFieldKeys, getIcon, getRowHeight, iconsSearch, removeEntityFromCache, removeEntityFromMemoryCache, resolveComponentRef, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiConfig, useAuthController, useBridgeRegistration, useBuildLocalConfigurationPersistence, useCollapsedGroups, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataSources, useDataTableController, useEntityFetch, useLargeLayout, useModeController, usePermissions, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useRestoreScroll, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/core";
8
8
  import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/formex";
9
9
  import { z } from "zod";
10
10
  import { defaultDateFormat, getArrayValuesCount, getHashValue, getValueInPath, hydrateRegExp, isObject, mergeDeep, prettifyIdentifier, randomString, slugify, toArray } from "@rebasepro/utils";
11
+ import { EntityReference, EntityRelation, GeoPoint, Vector, getCollectionDataPath, getDataSourceCapabilities } from "@rebasepro/types";
11
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
- import { EntityReference, EntityRelation, GeoPoint, Vector, getDataSourceCapabilities } from "@rebasepro/types";
13
13
  import { Link, NavLink, Outlet, Route, useBlocker, useLocation, useNavigate, useSearchParams } from "react-router-dom";
14
14
  import { Highlight, themes } from "prism-react-renderer";
15
15
  import { Command } from "cmdk";
@@ -21,7 +21,6 @@ import { SortableContext, arrayMove, defaultAnimateLayoutChanges, horizontalList
21
21
  import { CSS } from "@dnd-kit/utilities";
22
22
  import ExcelJS from "exceljs";
23
23
  import { buildEntityPropertiesFromData, inferTypeFromValue } from "@rebasepro/schema-inference";
24
- import { createPortal } from "react-dom";
25
24
  import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
26
25
  import Fuse from "fuse.js";
27
26
  import { format } from "date-fns";
@@ -293,16 +292,11 @@ function getZodBinarySchema({ property }) {
293
292
  //#endregion
294
293
  //#region src/form/EntityFormActions.tsx
295
294
  function EntityFormActions({ path, collection, entity, layout, savingError, formex, disabled, status, pluginActions, openEntityMode, navigateBack, formContext }) {
296
- const context = useCMSContext();
297
- const sideEntityController = context.sideEntityController;
298
- const { t } = useTranslation();
299
295
  const bottomActionsProps = {
300
296
  path,
301
297
  savingError,
302
298
  entity,
303
299
  collection,
304
- context,
305
- sideEntityController,
306
300
  disabled,
307
301
  status,
308
302
  pluginActions,
@@ -310,7 +304,6 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
310
304
  navigateBack,
311
305
  formContext,
312
306
  formex,
313
- t,
314
307
  className: layout === "responsive" ? "@6xl:hidden" : void 0
315
308
  };
316
309
  const sideActionsProps = {
@@ -318,8 +311,6 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
318
311
  savingError,
319
312
  entity,
320
313
  collection,
321
- context,
322
- sideEntityController,
323
314
  disabled,
324
315
  status,
325
316
  pluginActions,
@@ -327,7 +318,6 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
327
318
  navigateBack,
328
319
  formContext,
329
320
  formex,
330
- t,
331
321
  className: layout === "responsive" ? "hidden @6xl:flex" : void 0
332
322
  };
333
323
  const bottomActions = buildBottomActions$1(bottomActionsProps);
@@ -335,7 +325,7 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
335
325
  if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
336
326
  return layout === "bottom" ? bottomActions : sideActions;
337
327
  }
338
- function buildBottomActions$1({ savingError, entity, path, formActions, collection, context, sideEntityController, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, t, className }) {
328
+ function buildBottomActions$1({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, className }) {
339
329
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
340
330
  return /* @__PURE__ */ jsxs(DialogActions, {
341
331
  position: "absolute",
@@ -357,10 +347,10 @@ function buildBottomActions$1({ savingError, entity, path, formActions, collecti
357
347
  if (entity) action.onClick({
358
348
  view: "form",
359
349
  entity,
360
- path: path ?? collection.slug,
350
+ path: path ?? getCollectionDataPath(collection),
361
351
  collection,
362
- context,
363
- sideEntityController,
352
+ context: void 0,
353
+ sideEntityController: void 0,
364
354
  openEntityMode,
365
355
  navigateBack,
366
356
  formContext
@@ -375,47 +365,55 @@ function buildBottomActions$1({ savingError, entity, path, formActions, collecti
375
365
  disabled: disabled || formex.isSubmitting,
376
366
  color: "primary",
377
367
  type: "reset",
378
- children: status === "existing" ? t("discard") : t("clear")
368
+ children: status === "existing" ? "Discard" : "Clear"
379
369
  }),
380
- /* @__PURE__ */ jsxs(Button, {
381
- variant: "filled",
382
- color: "primary",
383
- type: "submit",
384
- disabled: disabled || formex.isSubmitting,
385
- startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
386
- children: [
387
- status === "existing" && t("save"),
388
- status === "copy" && t("create_copy"),
389
- status === "new" && t("create")
390
- ]
370
+ /* @__PURE__ */ jsx(Tooltip, {
371
+ title: hasErrors ? "Fix highlighted errors before saving" : void 0,
372
+ children: /* @__PURE__ */ jsxs(LoadingButton, {
373
+ variant: "filled",
374
+ color: "primary",
375
+ type: "submit",
376
+ loading: formex.isSubmitting,
377
+ disabled: disabled || formex.isSubmitting,
378
+ startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
379
+ children: [
380
+ status === "existing" && "Save",
381
+ status === "copy" && "Create copy",
382
+ status === "new" && "Create"
383
+ ]
384
+ })
391
385
  })
392
386
  ]
393
387
  });
394
388
  }
395
- function buildSideActions$1({ savingError, entity, formActions, path, openEntityMode, collection, context, sideEntityController, disabled, status, pluginActions, formex, t, className }) {
389
+ function buildSideActions$1({ savingError, entity, formActions, path, openEntityMode, collection, disabled, status, pluginActions, formex, className }) {
396
390
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
397
391
  return /* @__PURE__ */ jsxs("div", {
398
392
  className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className),
399
393
  children: [
400
- /* @__PURE__ */ jsxs(LoadingButton, {
401
- fullWidth: true,
402
- variant: "filled",
403
- color: "primary",
404
- type: "submit",
405
- startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
406
- disabled: disabled || formex.isSubmitting,
407
- children: [
408
- status === "existing" && t("save"),
409
- status === "copy" && t("create_copy"),
410
- status === "new" && t("create")
411
- ]
394
+ /* @__PURE__ */ jsx(Tooltip, {
395
+ title: hasErrors ? "Fix highlighted errors before saving" : void 0,
396
+ children: /* @__PURE__ */ jsxs(LoadingButton, {
397
+ fullWidth: true,
398
+ variant: "filled",
399
+ color: "primary",
400
+ type: "submit",
401
+ loading: formex.isSubmitting,
402
+ startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
403
+ disabled: disabled || formex.isSubmitting,
404
+ children: [
405
+ status === "existing" && "Save",
406
+ status === "copy" && "Create copy",
407
+ status === "new" && "Create"
408
+ ]
409
+ })
412
410
  }),
413
411
  /* @__PURE__ */ jsx(Button, {
414
412
  fullWidth: true,
415
413
  variant: "text",
416
414
  disabled: disabled || formex.isSubmitting,
417
415
  type: "reset",
418
- children: status === "existing" ? t("discard") : t("clear")
416
+ children: status === "existing" ? "Discard" : "Clear"
419
417
  }),
420
418
  pluginActions,
421
419
  savingError && /* @__PURE__ */ jsx("div", {
@@ -786,26 +784,32 @@ function renderSkeletonIcon() {
786
784
  */
787
785
  var StorageThumbnail = React.memo(StorageThumbnailInternal, areEqual$1);
788
786
  function areEqual$1(prevProps, nextProps) {
789
- return prevProps.size === nextProps.size && prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl && prevProps.storeUrl === nextProps.storeUrl && prevProps.interactive === nextProps.interactive && prevProps.fill === nextProps.fill;
787
+ return prevProps.size === nextProps.size && prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl && prevProps.storeUrl === nextProps.storeUrl && prevProps.interactive === nextProps.interactive && prevProps.fill === nextProps.fill && prevProps.storageSourceKey === nextProps.storageSourceKey;
790
788
  }
791
789
  var URL_CACHE = {};
792
- function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownloadUrl, size, fill }) {
790
+ function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownloadUrl, size, fill, storageSourceKey }) {
793
791
  const [error, setError] = React.useState(void 0);
794
- const storage = useStorageSource();
795
- const [downloadConfig, setDownloadConfig] = React.useState(URL_CACHE[storagePathOrDownloadUrl]);
792
+ const defaultStorage = useStorageSource();
793
+ const storage = resolveStorageSource({
794
+ sourceKey: storageSourceKey,
795
+ sources: useStorageSources().sources,
796
+ defaultSource: defaultStorage
797
+ });
798
+ const cacheKey = `${storageSourceKey ?? ""}::${storagePathOrDownloadUrl}`;
799
+ const [downloadConfig, setDownloadConfig] = React.useState(URL_CACHE[cacheKey]);
796
800
  useEffect(() => {
797
801
  if (!storagePathOrDownloadUrl) return;
798
802
  let unmounted = false;
799
803
  storage.getSignedUrl(storagePathOrDownloadUrl).then(function(downloadConfig) {
800
804
  if (!unmounted) {
801
805
  setDownloadConfig(downloadConfig);
802
- URL_CACHE[storagePathOrDownloadUrl] = downloadConfig;
806
+ URL_CACHE[cacheKey] = downloadConfig;
803
807
  }
804
808
  }).catch(setError);
805
809
  return () => {
806
810
  unmounted = true;
807
811
  };
808
- }, [storagePathOrDownloadUrl]);
812
+ }, [storagePathOrDownloadUrl, cacheKey]);
809
813
  if (!storagePathOrDownloadUrl) return null;
810
814
  const filetype = downloadConfig?.metadata ? getFiletype(downloadConfig?.metadata.contentType) : void 0;
811
815
  const previewType = filetype?.startsWith("image") ? "image" : filetype?.startsWith("video") ? "video" : filetype?.startsWith("audio") ? "audio" : "file";
@@ -953,7 +957,6 @@ function getDefaultIconForProperty(property) {
953
957
  if (property.ui?.url) return GlobeIcon;
954
958
  if (property.email) return MailIcon;
955
959
  if (property.ui?.multiline || property.ui?.markdown) return AlignLeftIcon;
956
- if (property.reference) return LinkIcon;
957
960
  return TextIcon;
958
961
  case "number": return HashIcon;
959
962
  case "boolean": return FlagIcon;
@@ -1056,6 +1059,21 @@ function isHiddenProperty(property) {
1056
1059
  if (!property) return false;
1057
1060
  return Boolean(property.ui?.hideFromCollection);
1058
1061
  }
1062
+ /**
1063
+ * Returns true when the property holds file-storage content (single image,
1064
+ * array of images, generic upload, …). These properties are rendered by the
1065
+ * dedicated image-slot and should NOT appear as regular preview columns.
1066
+ */
1067
+ function isStorageProperty$1(property) {
1068
+ if (!property) return false;
1069
+ if (property.type === "string" && property.storage) return true;
1070
+ if (property.type === "string" && property.ui?.url === "image") return true;
1071
+ if (property.type === "array" && property.of && !Array.isArray(property.of)) {
1072
+ const inner = property.of;
1073
+ if (inner.type === "string" && (inner.storage || inner.ui?.url === "image")) return true;
1074
+ }
1075
+ return false;
1076
+ }
1059
1077
  function getEntityPreviewKeys(authController, targetCollection, fields, previewProperties, limit = 3) {
1060
1078
  const allProperties = Object.keys(targetCollection.properties);
1061
1079
  let listProperties = previewProperties?.filter((p) => allProperties.includes(p));
@@ -1069,7 +1087,7 @@ function getEntityPreviewKeys(authController, targetCollection, fields, previewP
1069
1087
  return !(prop && typeof prop === "object" && "isId" in prop && Boolean(prop.isId)) && key !== "id";
1070
1088
  }).filter((key) => {
1071
1089
  const property = targetCollection.properties[key];
1072
- return property && !isPropertyBuilder(property) && !isReferenceProperty(property) && !isHiddenProperty(property) && (hasExplicitOrder || !isRelationProperty(property));
1090
+ return property && !isPropertyBuilder(property) && !isReferenceProperty(property) && !isHiddenProperty(property) && !isStorageProperty$1(property) && (hasExplicitOrder || !isRelationProperty(property));
1073
1091
  }).slice(0, limit);
1074
1092
  }
1075
1093
  }
@@ -1578,7 +1596,7 @@ var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
1578
1596
  var JSON_TAB_VALUE = "__json";
1579
1597
  //#endregion
1580
1598
  //#region src/components/EntityDetailView.tsx
1581
- var EntityHistoryView$1 = lazy(() => import("./history-BO6VT85a.js").then((m) => ({ default: m.EntityHistoryView })));
1599
+ var EntityHistoryView$1 = lazy(() => import("./history-B3cqBzEV.js").then((m) => ({ default: m.EntityHistoryView })));
1582
1600
  function EntityDetailView({ entityId, ...props }) {
1583
1601
  const { entity, dataLoading, dataLoadingError } = useEntityFetch({
1584
1602
  path: props.path,
@@ -1770,7 +1788,7 @@ function EntityDetailViewInner({ path, entityId, selectedTab: selectedTabProp, c
1770
1788
  }, "history_view") : null;
1771
1789
  const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
1772
1790
  const subcollectionId = subcollection.slug;
1773
- const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.slug)}` : void 0;
1791
+ const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
1774
1792
  if (activeTab !== subcollectionId) return null;
1775
1793
  return /* @__PURE__ */ jsxs("div", {
1776
1794
  className: "relative flex-1 h-full overflow-auto w-full",
@@ -2060,44 +2078,9 @@ function DetailViewSkeleton({ collection }) {
2060
2078
  }
2061
2079
  //#endregion
2062
2080
  //#region src/components/SelectableTable/SelectionStore.ts
2063
- /**
2064
- * A ref-based selection store that avoids React context re-renders.
2065
- *
2066
- * The problem with putting `selectedCell` in a React context value is that
2067
- * ANY cell selection change triggers a context value change, which forces
2068
- * ALL consumers (every PropertyTableCell in the table) to re-render.
2069
- * This causes the DOM to be replaced between mousedown and click events,
2070
- * breaking `alwaysInteractive` cells (like relation selectors) where the
2071
- * user needs to click a button on the very first interaction.
2072
- *
2073
- * This store uses `useSyncExternalStore` so only cells whose `selected`
2074
- * derivation actually changed will re-render.
2075
- */
2076
2081
  function createSelectionStore() {
2077
- let selectedCell = void 0;
2078
- const listeners = /* @__PURE__ */ new Set();
2079
- function getSnapshot() {
2080
- return selectedCell;
2081
- }
2082
- function subscribe(listener) {
2083
- listeners.add(listener);
2084
- return () => listeners.delete(listener);
2085
- }
2086
- function select(cell) {
2087
- selectedCell = cell;
2088
- listeners.forEach((l) => l());
2089
- }
2090
- return {
2091
- getSnapshot,
2092
- subscribe,
2093
- select
2094
- };
2082
+ return createVirtualTableSelectionStore();
2095
2083
  }
2096
- /**
2097
- * Hook that subscribes a cell to the selection store and returns
2098
- * whether THIS cell is selected. Only re-renders when the cell's
2099
- * `selected` boolean actually changes (not on every store update).
2100
- */
2101
2084
  function useCellSelected(store, propertyKey, entityPath, entityId) {
2102
2085
  const selectorRef = useRef({
2103
2086
  propertyKey,
@@ -2118,69 +2101,6 @@ function useCellSelected(store, propertyKey, entityPath, entityId) {
2118
2101
  return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
2119
2102
  }
2120
2103
  //#endregion
2121
- //#region src/components/EntityCollectionTable/fields/VirtualTableInput.tsx
2122
- function VirtualTableInput(props) {
2123
- const ref = React.useRef(null);
2124
- const { disabled, value, multiline, updateValue, focused } = props;
2125
- const prevValue = useRef(value);
2126
- const [internalValue, setInternalValue] = useState(value);
2127
- const focusedState = useRef(false);
2128
- useEffect(() => {
2129
- if (prevValue.current !== value && value !== internalValue) setInternalValue(value);
2130
- prevValue.current = value;
2131
- }, [value]);
2132
- const doUpdate = React.useCallback(() => {
2133
- if (!value && !internalValue) return;
2134
- if (internalValue !== value && internalValue !== prevValue.current) {
2135
- prevValue.current = internalValue;
2136
- updateValue(internalValue);
2137
- }
2138
- }, [
2139
- internalValue,
2140
- updateValue,
2141
- value
2142
- ]);
2143
- useDebouncedCallback(internalValue, doUpdate, !focused, 400);
2144
- useEffect(() => {
2145
- if (ref.current && focused && !focusedState.current) {
2146
- focusedState.current = true;
2147
- ref.current.focus({ preventScroll: true });
2148
- ref.current.selectionStart = ref.current.value.length;
2149
- ref.current.selectionEnd = ref.current.value.length;
2150
- } else focusedState.current = focused;
2151
- }, [focused, ref]);
2152
- return /* @__PURE__ */ jsx(TextareaAutosize, {
2153
- className: focusedDisabled,
2154
- ref,
2155
- style: {
2156
- padding: 0,
2157
- margin: 0,
2158
- width: "100%",
2159
- color: "unset",
2160
- fontWeight: "unset",
2161
- fontSize: "unset",
2162
- fontFamily: "unset",
2163
- background: "unset",
2164
- border: "unset",
2165
- resize: "none",
2166
- outline: "none"
2167
- },
2168
- value: internalValue ?? "",
2169
- onChange: (evt) => {
2170
- const newValue = evt.target.value;
2171
- if (multiline || !newValue.endsWith("\n")) setInternalValue(newValue);
2172
- },
2173
- onFocus: () => {
2174
- focusedState.current = true;
2175
- },
2176
- onBlur: () => {
2177
- focusedState.current = false;
2178
- doUpdate();
2179
- if (props.onBlur) props.onBlur();
2180
- }
2181
- });
2182
- }
2183
- //#endregion
2184
2104
  //#region src/components/EntityCollectionTable/fields/VirtualTableSelect.tsx
2185
2105
  function VirtualTableSelect(props) {
2186
2106
  const { enumValues, internalValue, disabled, small, focused, updateValue, multiple, valueType } = props;
@@ -2254,78 +2174,6 @@ function VirtualTableSelect(props) {
2254
2174
  });
2255
2175
  }
2256
2176
  //#endregion
2257
- //#region src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx
2258
- function VirtualTableNumberInput(props) {
2259
- const { align, value, updateValue, focused } = props;
2260
- const propStringValue = value && typeof value === "number" ? value.toString() : "";
2261
- const [internalValue, setInternalValue] = useState(propStringValue);
2262
- const prevValue = useRef(value);
2263
- useEffect(() => {
2264
- if (prevValue.current !== value && String(value) !== internalValue) setInternalValue(value ? value.toString() : null);
2265
- prevValue.current = value;
2266
- }, [value]);
2267
- useDebouncedCallback(internalValue, React.useCallback(() => {
2268
- if (internalValue !== propStringValue) if (internalValue !== void 0 && internalValue !== null) {
2269
- const numberValue = parseFloat(internalValue);
2270
- if (isNaN(numberValue)) return;
2271
- if (numberValue !== void 0 && numberValue !== null) updateValue(numberValue);
2272
- } else updateValue(null);
2273
- }, [internalValue, value]), !focused, 400);
2274
- useEffect(() => {
2275
- if (!focused && propStringValue !== internalValue) setInternalValue(value !== void 0 && value !== null ? value.toString() : null);
2276
- }, [value, focused]);
2277
- const inputRef = React.useRef(null);
2278
- useEffect(() => {
2279
- if (inputRef.current && focused) inputRef.current.focus({ preventScroll: true });
2280
- }, [focused, inputRef]);
2281
- const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
2282
- return /* @__PURE__ */ jsx(TextField, {
2283
- inputRef,
2284
- invisible: true,
2285
- size: "small",
2286
- className: "w-full",
2287
- inputClassName: cls("p-0 m-0 bg-transparent border-none outline-hidden font-normal leading-normal text-unset", focusedDisabled),
2288
- inputStyle: { textAlign: align },
2289
- value: internalValue ?? "",
2290
- onChange: (evt) => {
2291
- const newValue = evt.target.value.replace(",", ".");
2292
- if (newValue.length === 0) setInternalValue(null);
2293
- if (regexp.test(newValue) || newValue.startsWith("-")) setInternalValue(newValue);
2294
- }
2295
- });
2296
- }
2297
- //#endregion
2298
- //#region src/components/EntityCollectionTable/fields/VirtualTableSwitch.tsx
2299
- function VirtualTableSwitch(props) {
2300
- const { internalValue, updateValue, focused } = props;
2301
- const ref = React.useRef(null);
2302
- useEffect(() => {
2303
- if (ref.current && focused) ref.current.focus({ preventScroll: true });
2304
- }, [focused, ref]);
2305
- return /* @__PURE__ */ jsx(BooleanSwitch, {
2306
- ref,
2307
- size: "small",
2308
- value: Boolean(internalValue),
2309
- onValueChange: updateValue
2310
- });
2311
- }
2312
- //#endregion
2313
- //#region src/components/EntityCollectionTable/fields/VirtualTableDateField.tsx
2314
- function VirtualTableDateField(props) {
2315
- const { locale } = useCustomizationController();
2316
- const { disabled, error, mode, timezone, internalValue, updateValue } = props;
2317
- return /* @__PURE__ */ jsx(DateTimeField, {
2318
- value: internalValue ?? void 0,
2319
- onChange: (dateValue) => updateValue(dateValue ?? null),
2320
- invisible: true,
2321
- inputClassName: cls("w-full h-full", focusedDisabled),
2322
- className: cls("w-full h-full", focusedDisabled),
2323
- mode,
2324
- timezone,
2325
- locale
2326
- });
2327
- }
2328
- //#endregion
2329
2177
  //#region src/components/UserSelector.tsx
2330
2178
  var DEFAULT_PAGE_SIZE$2 = 10;
2331
2179
  /**
@@ -2657,8 +2505,9 @@ function VirtualTableUserSelect(props) {
2657
2505
  }
2658
2506
  //#endregion
2659
2507
  //#region src/form/components/StorageUploadProgress.tsx
2660
- function StorageUploadProgress({ storagePath, entry, metadata, onFileUploadComplete, imageSize, simple }) {
2661
- const storageSource = useStorageSource();
2508
+ function StorageUploadProgress({ storagePath, entry, metadata, onFileUploadComplete, imageSize, simple, storageSource: storageSourceProp }) {
2509
+ const defaultStorageSource = useStorageSource();
2510
+ const storageSource = storageSourceProp ?? defaultStorageSource;
2662
2511
  const snackbarController = useSnackbarController();
2663
2512
  const [error, setError] = React.useState();
2664
2513
  const [loading, setLoading] = React.useState(false);
@@ -2787,7 +2636,7 @@ var rejectDropClasses$1 = "transition-colors duration-200 ease-[cubic-bezier(0,0
2787
2636
  function TableStorageUpload(props) {
2788
2637
  const { propertyKey, error, selected, openPopup, value, disabled, property, entity, path, previewSize, updateValue } = props;
2789
2638
  const storageSource = useStorageSource();
2790
- const { internalValue, setInternalValue, onFilesAdded, storage, onFileUploadComplete, storagePathBuilder, multipleFilesSupported } = useStorageUploadController({
2639
+ const { internalValue, setInternalValue, onFilesAdded, storage, onFileUploadComplete, storagePathBuilder, multipleFilesSupported, resolvedStorageSource } = useStorageUploadController({
2791
2640
  entityValues: entity.values,
2792
2641
  entityId: entity.id,
2793
2642
  path,
@@ -2798,24 +2647,27 @@ function TableStorageUpload(props) {
2798
2647
  value,
2799
2648
  disabled
2800
2649
  });
2801
- return /* @__PURE__ */ jsx(StorageUpload$1, {
2802
- internalValue,
2803
- setInternalValue,
2804
- name: propertyKey,
2805
- disabled,
2806
- autoFocus: false,
2807
- openPopup,
2808
- error,
2809
- selected,
2810
- property,
2811
- onChange: updateValue,
2812
- entity,
2813
- storagePathBuilder,
2814
- storage,
2815
- multipleFilesSupported,
2816
- onFilesAdded,
2817
- onFileUploadComplete,
2818
- previewSize
2650
+ return /* @__PURE__ */ jsx(StorageSourceContext.Provider, {
2651
+ value: resolvedStorageSource,
2652
+ children: /* @__PURE__ */ jsx(StorageUpload$1, {
2653
+ internalValue,
2654
+ setInternalValue,
2655
+ name: propertyKey,
2656
+ disabled,
2657
+ autoFocus: false,
2658
+ openPopup,
2659
+ error,
2660
+ selected,
2661
+ property,
2662
+ onChange: updateValue,
2663
+ entity,
2664
+ storagePathBuilder,
2665
+ storage,
2666
+ multipleFilesSupported,
2667
+ onFilesAdded,
2668
+ onFileUploadComplete,
2669
+ previewSize
2670
+ })
2819
2671
  });
2820
2672
  }
2821
2673
  function StorageUpload$1({ property, name, internalValue, setInternalValue, openPopup, entity, selected, error, onChange, multipleFilesSupported, previewSize: previewSizeInput, disabled, autoFocus, storage, onFilesAdded, onFileUploadComplete, storagePathBuilder }) {
@@ -3086,7 +2938,7 @@ var TableMultipleRelationFieldInternal = React.memo(function TableMultipleRelati
3086
2938
  const selectedEntityIds = value.map((ref) => ref.id);
3087
2939
  const relationDialogController = useEntitySelectionDialog({
3088
2940
  multiselect: true,
3089
- path: collection.slug,
2941
+ path: getCollectionDataPath(collection),
3090
2942
  collection,
3091
2943
  onMultipleEntitiesSelected,
3092
2944
  selectedEntityIds,
@@ -3160,7 +3012,7 @@ var TableRelationFieldInternal = React.memo(function TableRelationFieldInternal(
3160
3012
  const selectedEntityIds = internalValue ? Array.isArray(internalValue) ? internalValue.map((ref) => ref.id) : internalValue.id ? [internalValue.id] : [] : [];
3161
3013
  const relationDialogController = useEntitySelectionDialog({
3162
3014
  multiselect,
3163
- path: collection.slug,
3015
+ path: getCollectionDataPath(collection),
3164
3016
  collection,
3165
3017
  onMultipleEntitiesSelected,
3166
3018
  onSingleEntitySelected,
@@ -3248,7 +3100,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
3248
3100
  const localSelectionIdsRef = useRef(null);
3249
3101
  const pinnedIdsRef = useRef(null);
3250
3102
  const { items: availableItems, isLoading, hasMore, search, loadMore, entityToRelationItem } = useRelationSelector({
3251
- path: collection.slug,
3103
+ path: getCollectionDataPath(collection),
3252
3104
  collection,
3253
3105
  fixedFilter,
3254
3106
  pageSize
@@ -3744,32 +3596,7 @@ function isStorageProperty(property) {
3744
3596
  return false;
3745
3597
  }
3746
3598
  function getTableBindingForProperty(property, selected) {
3747
- const isAStorageProperty = isStorageProperty(property);
3748
- if (property.type === "string" && property.reference?.path) return {
3749
- Component: ({ propertyKey, property, internalValue, updateValue, disabled, size, path }) => {
3750
- const referenceProperty = property.reference;
3751
- if (!referenceProperty) return null;
3752
- return /* @__PURE__ */ jsx(TableReferenceField, {
3753
- name: propertyKey,
3754
- internalValue: internalValue ? new EntityReference({
3755
- id: internalValue,
3756
- path: referenceProperty.path
3757
- }) : void 0,
3758
- updateValue: (v) => updateValue(v ? v.id : null),
3759
- disabled,
3760
- size,
3761
- path: referenceProperty.path,
3762
- multiselect: false,
3763
- previewProperties: referenceProperty.ui?.previewProperties,
3764
- includeId: referenceProperty.includeId,
3765
- includeEntityLink: referenceProperty.includeEntityLink,
3766
- title: property.name,
3767
- fixedFilter: referenceProperty.fixedFilter
3768
- });
3769
- },
3770
- allowScroll: false
3771
- };
3772
- else if (isAStorageProperty) return {
3599
+ if (isStorageProperty(property)) return {
3773
3600
  Component: ({ validationError, error, disabled, selected, openPopup, property, entity, path, internalValue, size, updateValue, propertyKey }) => /* @__PURE__ */ jsx(TableStorageUpload, {
3774
3601
  error: validationError ?? error,
3775
3602
  disabled,
@@ -3847,7 +3674,7 @@ function getTableBindingForProperty(property, selected) {
3847
3674
  }),
3848
3675
  fullHeight: true
3849
3676
  };
3850
- else if (stringProperty.ui?.markdown || !stringProperty.storage || !stringProperty.reference) {
3677
+ else if (stringProperty.ui?.markdown || !stringProperty.storage) {
3851
3678
  const multiline = Boolean(stringProperty.ui?.multiline) || Boolean(stringProperty.ui?.markdown);
3852
3679
  return {
3853
3680
  Component: ({ error, validationError, disabled, selected, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableInput, {
@@ -3869,15 +3696,19 @@ function getTableBindingForProperty(property, selected) {
3869
3696
  updateValue
3870
3697
  }) };
3871
3698
  else if (property.type === "date") return {
3872
- Component: ({ propertyKey, error, validationError, disabled, selected, property, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableDateField, {
3873
- name: propertyKey,
3874
- error: validationError ?? error,
3875
- disabled,
3876
- mode: property.mode,
3877
- focused: selected,
3878
- internalValue,
3879
- updateValue
3880
- }),
3699
+ Component: ({ propertyKey, error, validationError, disabled, selected, property, internalValue, updateValue }) => {
3700
+ const { locale } = useCustomizationController();
3701
+ return /* @__PURE__ */ jsx(VirtualTableDateField, {
3702
+ name: propertyKey,
3703
+ error: validationError ?? error,
3704
+ disabled,
3705
+ mode: property.mode,
3706
+ focused: selected,
3707
+ internalValue,
3708
+ updateValue,
3709
+ locale
3710
+ });
3711
+ },
3881
3712
  fullHeight: true,
3882
3713
  hideOverflow: false,
3883
3714
  allowScroll: false
@@ -5164,7 +4995,12 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
5164
4995
  }, [onEntityClick, inlineEditing]);
5165
4996
  const select = useCallback((cell) => {
5166
4997
  setLocalSelectedCell(cell);
5167
- selectionStore.select(cell);
4998
+ if (cell) selectionStore.select({
4999
+ ...cell,
5000
+ columnKey: cell.propertyKey,
5001
+ rowId: cell.entityId
5002
+ });
5003
+ else selectionStore.select(void 0);
5168
5004
  }, [selectionStore]);
5169
5005
  const unselect = useCallback(() => {
5170
5006
  setLocalSelectedCell(void 0);
@@ -5206,7 +5042,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
5206
5042
  children: /* @__PURE__ */ jsx("div", {
5207
5043
  className: "h-full w-full flex flex-col bg-white dark:bg-surface-900",
5208
5044
  ref,
5209
- children: /* @__PURE__ */ jsx(VirtualTable, {
5045
+ children: /* @__PURE__ */ jsx(TableView, {
5210
5046
  data,
5211
5047
  columns,
5212
5048
  cellRenderer: (props) => cellRenderer(props),
@@ -5350,6 +5186,7 @@ var EntityCollectionTable = function EntityCollectionTable({ className, style, f
5350
5186
  const customFieldValidator = uniqueFieldValidator;
5351
5187
  const propertyCellRenderer = useCallback(({ column, columnIndex, rowData, rowIndex, sortableNodeRef, sortableStyle, sortableAttributes, isDragging, isDraggable, frozen }) => {
5352
5188
  const entity = rowData;
5189
+ if (!entity) return null;
5353
5190
  const propertyKey = column.key;
5354
5191
  const columnCustom = column.custom;
5355
5192
  let disabled = columnCustom?.disabled ?? false;
@@ -5389,6 +5226,7 @@ var EntityCollectionTable = function EntityCollectionTable({ className, style, f
5389
5226
  ]);
5390
5227
  const additionalCellRenderer = useCallback(({ column, rowData, width, sortableNodeRef, sortableStyle, sortableAttributes, isDragging, isDraggable, frozen }) => {
5391
5228
  const entity = rowData;
5229
+ if (!entity) return null;
5392
5230
  const additionalField = additionalFieldsMap[column.key];
5393
5231
  const value = additionalField.dependencies ? Object.entries(entity.values).filter(([key, value]) => additionalField.dependencies.includes(key)).reduce((a, b) => ({
5394
5232
  ...a,
@@ -5697,7 +5535,7 @@ function buildBottomActions({ savingError, entity, formActions, collection, cont
5697
5535
  const props = {
5698
5536
  view: "form",
5699
5537
  entity,
5700
- path: collection.slug,
5538
+ path: getCollectionDataPath(collection),
5701
5539
  collection,
5702
5540
  context,
5703
5541
  sideEntityController,
@@ -5734,19 +5572,22 @@ function buildBottomActions({ savingError, entity, formActions, collection, cont
5734
5572
  onClick: navigateBack,
5735
5573
  children: t("back_to_detail") ?? "Back to details"
5736
5574
  }),
5737
- /* @__PURE__ */ jsxs(Button, {
5738
- variant: canClose ? "text" : "filled",
5739
- color: "primary",
5740
- type: "submit",
5741
- disabled: disabled || formex.isSubmitting,
5742
- onClick: () => {
5743
- sideDialogContext.setPendingClose(false);
5744
- },
5745
- children: [
5746
- status === "existing" && t("save"),
5747
- status === "copy" && t("create_copy"),
5748
- status === "new" && t("create")
5749
- ]
5575
+ /* @__PURE__ */ jsx(Tooltip, {
5576
+ title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
5577
+ children: /* @__PURE__ */ jsxs(Button, {
5578
+ variant: canClose ? "text" : "filled",
5579
+ color: "primary",
5580
+ type: "submit",
5581
+ disabled: disabled || formex.isSubmitting,
5582
+ onClick: () => {
5583
+ sideDialogContext.setPendingClose(false);
5584
+ },
5585
+ children: [
5586
+ status === "existing" && t("save"),
5587
+ status === "copy" && t("create_copy"),
5588
+ status === "new" && t("create")
5589
+ ]
5590
+ })
5750
5591
  }),
5751
5592
  canClose && /* @__PURE__ */ jsxs(LoadingButton, {
5752
5593
  variant: "filled",
@@ -5771,21 +5612,25 @@ function buildSideActions({ savingError, entity, formActions, collection, contex
5771
5612
  return /* @__PURE__ */ jsxs("div", {
5772
5613
  className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className),
5773
5614
  children: [
5774
- /* @__PURE__ */ jsxs(LoadingButton, {
5775
- fullWidth: true,
5776
- variant: "filled",
5777
- color: "primary",
5778
- type: "submit",
5779
- startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
5780
- disabled: disabled || formex.isSubmitting,
5781
- onClick: () => {
5782
- sideDialogContext.setPendingClose?.(false);
5783
- },
5784
- children: [
5785
- status === "existing" && t("save"),
5786
- status === "copy" && t("create_copy"),
5787
- status === "new" && t("create")
5788
- ]
5615
+ /* @__PURE__ */ jsx(Tooltip, {
5616
+ title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
5617
+ children: /* @__PURE__ */ jsxs(LoadingButton, {
5618
+ fullWidth: true,
5619
+ variant: "filled",
5620
+ color: "primary",
5621
+ type: "submit",
5622
+ loading: formex.isSubmitting,
5623
+ startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
5624
+ disabled: disabled || formex.isSubmitting,
5625
+ onClick: () => {
5626
+ sideDialogContext.setPendingClose?.(false);
5627
+ },
5628
+ children: [
5629
+ status === "existing" && t("save"),
5630
+ status === "copy" && t("create_copy"),
5631
+ status === "new" && t("create")
5632
+ ]
5633
+ })
5789
5634
  }),
5790
5635
  /* @__PURE__ */ jsx(Button, {
5791
5636
  fullWidth: true,
@@ -5808,7 +5653,7 @@ function buildSideActions({ savingError, entity, formActions, collection, contex
5808
5653
  const props = {
5809
5654
  view: "form",
5810
5655
  entity,
5811
- path: collection.slug,
5656
+ path: getCollectionDataPath(collection),
5812
5657
  collection,
5813
5658
  context,
5814
5659
  sideEntityController,
@@ -5875,7 +5720,7 @@ function EntityActionButton({ action, enabled, props }) {
5875
5720
  }
5876
5721
  //#endregion
5877
5722
  //#region src/components/EntityEditView.tsx
5878
- var EntityHistoryView = lazy(() => import("./history-BO6VT85a.js").then((m) => ({ default: m.EntityHistoryView })));
5723
+ var EntityHistoryView = lazy(() => import("./history-B3cqBzEV.js").then((m) => ({ default: m.EntityHistoryView })));
5879
5724
  /**
5880
5725
  * This is the default view that is used as the content of a side panel when
5881
5726
  * an entity is opened.
@@ -5927,7 +5772,7 @@ function EntityEditView({ entityId, ...props }) {
5927
5772
  }
5928
5773
  function EntityEditViewInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, status, setStatus, formProps, canEdit }) {
5929
5774
  const ResolvedFormActions = useComponentOverride("Entity.FormActions", EntityEditViewFormActions);
5930
- const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityForm);
5775
+ const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
5931
5776
  const ResolvedCollectionView = useComponentOverride("Collection.View", EntityCollectionView);
5932
5777
  const context = useRebaseContext();
5933
5778
  const urlController = useUrlController();
@@ -6093,7 +5938,7 @@ function EntityEditViewInner({ path, entityId, selectedTab: selectedTabProp, col
6093
5938
  }, "history_view") : null;
6094
5939
  const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
6095
5940
  const subcollectionId = subcollection.slug;
6096
- const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.slug)}` : void 0;
5941
+ const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
6097
5942
  if (activeTab !== subcollectionId) return null;
6098
5943
  return /* @__PURE__ */ jsxs("div", {
6099
5944
  className: "relative flex-1 h-full overflow-auto w-full",
@@ -7070,8 +6915,9 @@ function useEntityHistory(params) {
7070
6915
  //#endregion
7071
6916
  //#region src/hooks/navigation/useBuildCollectionRegistryController.tsx
7072
6917
  function useBuildCollectionRegistryController(props) {
7073
- const { userConfigPersistence } = props;
7074
- const collectionRegistryRef = useRef(new CollectionRegistry());
6918
+ const { userConfigPersistence, dataSources } = props;
6919
+ const collectionRegistryRef = useRef(new CollectionRegistry(void 0, dataSources));
6920
+ if (dataSources) collectionRegistryRef.current.setDataSources(dataSources);
7075
6921
  const [initialised, setInitialised] = useState(false);
7076
6922
  const getCollection = useCallback((slugOrPath, includeUserOverride = false) => {
7077
6923
  const registry = collectionRegistryRef.current;
@@ -7163,7 +7009,9 @@ function useBuildCollectionRegistryController(props) {
7163
7009
  return paths;
7164
7010
  }, []);
7165
7011
  const collections = collectionRegistryRef.current.getCollections();
7166
- if (!initialised && collections.length > 0) setInitialised(true);
7012
+ useEffect(() => {
7013
+ if (!initialised && collections.length > 0) setInitialised(true);
7014
+ }, [initialised, collections.length]);
7167
7015
  return useMemo(() => ({
7168
7016
  collections,
7169
7017
  initialised,
@@ -7491,7 +7339,7 @@ var editEntityAction = {
7491
7339
  if (!entity) throw new Error("INTERNAL: editEntityAction: Entity is undefined");
7492
7340
  if (!sideEntityController) throw new Error("INTERNAL: editEntityAction: sideEntityController is undefined");
7493
7341
  highlightEntity?.(entity);
7494
- context.analyticsController?.onAnalyticsEvent?.("entity_click", {
7342
+ context?.analyticsController?.onAnalyticsEvent?.("entity_click", {
7495
7343
  path: entity.path,
7496
7344
  entityId: entity.id
7497
7345
  });
@@ -7501,6 +7349,7 @@ var editEntityAction = {
7501
7349
  status: "existing",
7502
7350
  entityId: entity.id
7503
7351
  });
7352
+ if (!context?.urlController) throw new Error("INTERNAL: editEntityAction: urlController is undefined");
7504
7353
  navigateToEntity({
7505
7354
  openEntityMode,
7506
7355
  collection,
@@ -7523,11 +7372,12 @@ var copyEntityAction = {
7523
7372
  if (!entity) throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
7524
7373
  if (!sideEntityController) throw new Error("INTERNAL: copyEntityAction: sideEntityController is undefined");
7525
7374
  highlightEntity?.(entity);
7526
- context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
7375
+ context?.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
7527
7376
  path: entity.path,
7528
7377
  entityId: entity.id
7529
7378
  });
7530
7379
  const usedPath = path ?? collection?.slug ?? entity.path;
7380
+ if (!context?.urlController) throw new Error("INTERNAL: copyEntityAction: urlController is undefined");
7531
7381
  navigateToEntity({
7532
7382
  openEntityMode,
7533
7383
  collection,
@@ -7548,7 +7398,7 @@ var deleteEntityAction = {
7548
7398
  isEnabled: ({ entity }) => Boolean(entity),
7549
7399
  onClick({ entity, path, collection, context, selectionController, onCollectionChange, navigateBack, openEntityMode }) {
7550
7400
  if (!entity) throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
7551
- if (!context.dialogsController) throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
7401
+ if (!context?.dialogsController) throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
7552
7402
  const { closeDialog } = context.dialogsController.open({
7553
7403
  key: "delete_entity_dialog_" + entity.id,
7554
7404
  Component: ({ open }) => {
@@ -7560,10 +7410,10 @@ var deleteEntityAction = {
7560
7410
  callbacks: collection.callbacks,
7561
7411
  open,
7562
7412
  onEntityDelete: () => {
7563
- context.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", { path });
7413
+ context?.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", { path });
7564
7414
  selectionController?.setSelectedEntities(selectionController.selectedEntities.filter((e) => e.id !== entity.id));
7565
7415
  onCollectionChange?.();
7566
- if (openEntityMode === "full_screen" && context.urlController) {
7416
+ if (openEntityMode === "full_screen" && context?.urlController) {
7567
7417
  const collectionUrl = context.urlController.buildUrlCollectionPath(path);
7568
7418
  context.urlController.navigate(collectionUrl, { replace: true });
7569
7419
  } else navigateBack?.();
@@ -7643,7 +7493,7 @@ var resetPasswordAction = {
7643
7493
  isEnabled: ({ entity }) => Boolean(entity),
7644
7494
  onClick({ entity, context }) {
7645
7495
  if (!entity) throw new Error("INTERNAL: resetPasswordAction: Entity is undefined");
7646
- if (!context.dialogsController) throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
7496
+ if (!context?.dialogsController) throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
7647
7497
  const user = {
7648
7498
  uid: entity.id,
7649
7499
  email: entity.values?.email,
@@ -7787,12 +7637,34 @@ async function resolveAppViews(baseViews, authController, data, plugins) {
7787
7637
  if (plugins) {
7788
7638
  for (const plugin of plugins) if (plugin.views && plugin.views.length > 0) resolvedViews = [...resolvedViews, ...plugin.views];
7789
7639
  }
7640
+ if (process.env.NODE_ENV !== "production") {
7641
+ const slugCounts = /* @__PURE__ */ new Map();
7642
+ resolvedViews.forEach((v) => {
7643
+ slugCounts.set(v.slug, (slugCounts.get(v.slug) ?? 0) + 1);
7644
+ });
7645
+ slugCounts.forEach((count, slug) => {
7646
+ if (count > 1) console.warn(`[Rebase] Duplicate view slug "${slug}" detected (${count} views). Last-write-wins. Ensure unique slugs across CMS views and plugins.`);
7647
+ });
7648
+ }
7649
+ resolvedViews = filterViewsByRole(resolvedViews, authController);
7790
7650
  return resolvedViews;
7791
7651
  }
7652
+ /**
7653
+ * Filter views by the `roles` field on AppView.
7654
+ * When `roles` is set, the view is only included if the current user
7655
+ * has at least one of the listed roles. Views without `roles` (or with
7656
+ * an empty array) are always included.
7657
+ */
7658
+ function filterViewsByRole(views, authController) {
7659
+ const userRoles = authController.user?.roles ?? [];
7660
+ return views.filter((view) => {
7661
+ if (!view.roles || view.roles.length === 0) return true;
7662
+ return view.roles.some((role) => userRoles.includes(role));
7663
+ });
7664
+ }
7792
7665
  //#endregion
7793
7666
  //#region src/hooks/navigation/utils.ts
7794
7667
  var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
7795
- var NAVIGATION_ADMIN_GROUP_NAME = "Admin";
7796
7668
  function getGroup(collectionOrView) {
7797
7669
  const trimmed = collectionOrView.group?.trim();
7798
7670
  if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
@@ -7801,7 +7673,8 @@ function getGroup(collectionOrView) {
7801
7673
  function computeNavigationGroups({ navigationGroupMappings, collections, views, plugins }) {
7802
7674
  let result = navigationGroupMappings ? navigationGroupMappings.map((g) => ({
7803
7675
  name: g.name,
7804
- entries: [...g.entries]
7676
+ entries: [...g.entries],
7677
+ ...g.collapsedByDefault !== void 0 && { collapsedByDefault: g.collapsedByDefault }
7805
7678
  })) : navigationGroupMappings;
7806
7679
  if (plugins) result = plugins.reduce((acc, plugin) => {
7807
7680
  if (plugin.hooks?.navigationEntries) plugin.hooks.navigationEntries.forEach((entry) => {
@@ -7891,48 +7764,38 @@ function areCollectionsEqual(a, b, visitedSlugs = []) {
7891
7764
  return deepEqual(Object.fromEntries(Object.entries(a).filter(([k, v]) => typeof v !== "function" && k !== "subcollections")), Object.fromEntries(Object.entries(b).filter(([k, v]) => typeof v !== "function" && k !== "subcollections")));
7892
7765
  }
7893
7766
  //#endregion
7894
- //#region src/hooks/navigation/useResolvedCollections.ts
7767
+ //#region src/hooks/navigation/useAsyncResolver.ts
7895
7768
  /**
7896
- * Hook that resolves collection props (which may be async builders or arrays)
7897
- * into concrete EntityCollection[], and registers them with the CollectionRegistry.
7898
- *
7899
- * When userManagement is provided, the default users collection is always
7900
- * prepended. Developer collections override via generic slug-based dedup
7901
- * (Map keyed by slug, last-write-wins). No hardcoded string checks.
7769
+ * Generic hook that resolves an async value with loading/error/refresh
7770
+ * state management, cancellation on unmount or deps change, and
7771
+ * ref-based change detection to prevent unnecessary state updates.
7902
7772
  *
7903
- * Uses refs for potentially-unstable dependencies (driver, authController,
7904
- * plugins) to avoid re-triggering effects when their object identity changes.
7773
+ * Extracted from the common pattern in useResolvedCollections and
7774
+ * useResolvedViews.
7905
7775
  */
7906
- function useResolvedCollections(props) {
7907
- const { authController, collections: collectionsProp, data, plugins, disabled, collectionRegistryController } = props;
7776
+ function useAsyncResolver({ resolver, initialValue, isEqual, deps, disabled }) {
7777
+ const [data, setData] = useState(initialValue);
7908
7778
  const [loading, setLoading] = useState(true);
7909
7779
  const [error, setError] = useState(void 0);
7910
- const [resolvedCollections, setResolvedCollections] = useState([]);
7911
7780
  const [refreshTrigger, setRefreshTrigger] = useState(0);
7912
7781
  const refresh = useCallback(() => {
7913
7782
  setRefreshTrigger((prev) => prev + 1);
7914
7783
  }, []);
7915
- const dataRef = useRef(data);
7916
- dataRef.current = data;
7917
- const authControllerRef = useRef(authController);
7918
- authControllerRef.current = authController;
7919
- const pluginsRef = useRef(plugins);
7920
- pluginsRef.current = plugins;
7921
- const resolvedCollectionsRef = useRef([]);
7922
- const initialLoading = authController.initialLoading;
7923
- const user = authController.user;
7784
+ const dataRef = useRef(initialValue);
7785
+ const resolverRef = useRef(resolver);
7786
+ resolverRef.current = resolver;
7787
+ const isEqualRef = useRef(isEqual);
7788
+ isEqualRef.current = isEqual;
7924
7789
  useEffect(() => {
7925
- if (disabled || initialLoading) return;
7790
+ if (disabled) return;
7926
7791
  let cancelled = false;
7927
7792
  (async () => {
7928
7793
  try {
7929
- const resolved = await resolveCollections(collectionsProp, authControllerRef.current, dataRef.current, pluginsRef.current);
7794
+ const result = await resolverRef.current();
7930
7795
  if (cancelled) return;
7931
- const deduped = [...resolved];
7932
- if (collectionRegistryController.collectionRegistryRef.current.registerMultiple(deduped)) console.debug("Collections have changed", deduped);
7933
- if (!areCollectionListsEqual(resolvedCollectionsRef.current, deduped)) {
7934
- resolvedCollectionsRef.current = deduped;
7935
- setResolvedCollections(deduped);
7796
+ if (!isEqualRef.current(dataRef.current, result)) {
7797
+ dataRef.current = result;
7798
+ setData(result);
7936
7799
  }
7937
7800
  setError(void 0);
7938
7801
  } catch (e) {
@@ -7948,20 +7811,66 @@ function useResolvedCollections(props) {
7948
7811
  cancelled = true;
7949
7812
  };
7950
7813
  }, [
7951
- collectionsProp,
7952
- disabled,
7953
- collectionRegistryController.collectionRegistryRef,
7814
+ ...deps,
7954
7815
  refreshTrigger,
7955
- initialLoading,
7956
- user
7816
+ disabled
7817
+ ]);
7818
+ return useMemo(() => ({
7819
+ data,
7820
+ loading,
7821
+ error,
7822
+ refresh
7823
+ }), [
7824
+ data,
7825
+ loading,
7826
+ error,
7827
+ refresh
7957
7828
  ]);
7829
+ }
7830
+ //#endregion
7831
+ //#region src/hooks/navigation/useResolvedCollections.ts
7832
+ /**
7833
+ * Hook that resolves collection props (which may be async builders or arrays)
7834
+ * into concrete EntityCollection[], and registers them with the CollectionRegistry.
7835
+ *
7836
+ * When userManagement is provided, the default users collection is always
7837
+ * prepended. Developer collections override via generic slug-based dedup
7838
+ * (Map keyed by slug, last-write-wins). No hardcoded string checks.
7839
+ *
7840
+ * Uses refs for potentially-unstable dependencies (driver, authController,
7841
+ * plugins) to avoid re-triggering effects when their object identity changes.
7842
+ */
7843
+ function useResolvedCollections(props) {
7844
+ const { authController, collections: collectionsProp, data, plugins, disabled, collectionRegistryController } = props;
7845
+ const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
7846
+ const dataRef = useRef(data);
7847
+ dataRef.current = data;
7848
+ const authControllerRef = useRef(authController);
7849
+ authControllerRef.current = authController;
7850
+ const pluginsRef = useRef(plugins);
7851
+ pluginsRef.current = plugins;
7852
+ const { data: collections, loading, error, refresh } = useAsyncResolver({
7853
+ resolver: async () => {
7854
+ const deduped = [...await resolveCollections(collectionsProp, authControllerRef.current, dataRef.current, pluginsRef.current)];
7855
+ if (collectionRegistryController.collectionRegistryRef.current.registerMultiple(deduped)) console.debug("Collections have changed", deduped);
7856
+ return deduped;
7857
+ },
7858
+ initialValue: [],
7859
+ isEqual: areCollectionListsEqual,
7860
+ deps: [
7861
+ collectionsProp,
7862
+ userIdentity,
7863
+ disabled
7864
+ ],
7865
+ disabled: disabled || authController.initialLoading
7866
+ });
7958
7867
  return useMemo(() => ({
7959
- collections: resolvedCollections,
7868
+ collections,
7960
7869
  loading,
7961
7870
  error,
7962
7871
  refresh
7963
7872
  }), [
7964
- resolvedCollections,
7873
+ collections,
7965
7874
  loading,
7966
7875
  error,
7967
7876
  refresh
@@ -7982,6 +7891,13 @@ function viewSlugsEqual(a, b) {
7982
7891
  return true;
7983
7892
  }
7984
7893
  /**
7894
+ * Equality check for the combined views data.
7895
+ * Compares both views and adminViews by slug identity.
7896
+ */
7897
+ function areResolvedViewsEqual(a, b) {
7898
+ return viewSlugsEqual(a.views, b.views) && viewSlugsEqual(a.adminViews, b.adminViews);
7899
+ }
7900
+ /**
7985
7901
  * Hook that resolves view and admin view props (which may be async builders or arrays)
7986
7902
  * into concrete AppView[].
7987
7903
  *
@@ -7990,16 +7906,7 @@ function viewSlugsEqual(a, b) {
7990
7906
  */
7991
7907
  function useResolvedViews(props) {
7992
7908
  const { authController, views: viewsProp, adminViews: adminViewsProp, data, plugins, adminMode = "content", effectiveRoleController } = props;
7993
- const [loading, setLoading] = useState(true);
7994
- const [error, setError] = useState(void 0);
7995
- const [resolvedViews, setResolvedViews] = useState(void 0);
7996
- const [resolvedAdminViews, setResolvedAdminViews] = useState(void 0);
7997
- const [refreshTrigger, setRefreshTrigger] = useState(0);
7998
- const refresh = useCallback(() => {
7999
- setRefreshTrigger((prev) => prev + 1);
8000
- }, []);
8001
- const viewsRef = useRef(void 0);
8002
- const adminViewsRef = useRef(void 0);
7909
+ const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
8003
7910
  const dataRef = useRef(data);
8004
7911
  dataRef.current = data;
8005
7912
  const authControllerRef = useRef(authController);
@@ -8023,51 +7930,35 @@ function useResolvedViews(props) {
8023
7930
  const resolvedAuthControllerRef = useRef(resolvedAuthController);
8024
7931
  resolvedAuthControllerRef.current = resolvedAuthController;
8025
7932
  const initialLoading = resolvedAuthController.initialLoading;
8026
- const user = resolvedAuthController.user;
8027
- useEffect(() => {
8028
- if (initialLoading) return;
8029
- let cancelled = false;
8030
- (async () => {
8031
- try {
8032
- const [newViews, newAdminViews] = await Promise.all([resolveAppViews(viewsProp, resolvedAuthControllerRef.current, dataRef.current, pluginsRef.current), resolveAppViews(adminViewsProp, resolvedAuthControllerRef.current, dataRef.current)]);
8033
- if (!viewSlugsEqual(viewsRef.current, newViews)) {
8034
- viewsRef.current = newViews;
8035
- setResolvedViews(newViews);
8036
- }
8037
- if (!viewSlugsEqual(adminViewsRef.current, newAdminViews)) {
8038
- adminViewsRef.current = newAdminViews;
8039
- setResolvedAdminViews(newAdminViews);
8040
- }
8041
- setError(void 0);
8042
- } catch (e) {
8043
- if (!cancelled) {
8044
- console.error("Error resolving views:", e);
8045
- setError(e);
8046
- }
8047
- } finally {
8048
- if (!cancelled) setLoading(false);
8049
- }
8050
- })();
8051
- return () => {
8052
- cancelled = true;
8053
- };
8054
- }, [
8055
- viewsProp,
8056
- adminViewsProp,
8057
- refreshTrigger,
8058
- adminMode,
8059
- initialLoading,
8060
- user
8061
- ]);
7933
+ const { data: resolvedData, loading, error, refresh } = useAsyncResolver({
7934
+ resolver: async () => {
7935
+ const [newViews, newAdminViews] = await Promise.all([resolveAppViews(viewsProp, resolvedAuthControllerRef.current, dataRef.current, pluginsRef.current), resolveAppViews(adminViewsProp, resolvedAuthControllerRef.current, dataRef.current)]);
7936
+ return {
7937
+ views: newViews,
7938
+ adminViews: newAdminViews
7939
+ };
7940
+ },
7941
+ initialValue: {
7942
+ views: void 0,
7943
+ adminViews: void 0
7944
+ },
7945
+ isEqual: areResolvedViewsEqual,
7946
+ deps: [
7947
+ viewsProp,
7948
+ adminViewsProp,
7949
+ adminMode,
7950
+ userIdentity
7951
+ ],
7952
+ disabled: initialLoading
7953
+ });
8062
7954
  return useMemo(() => ({
8063
- views: resolvedViews,
8064
- adminViews: resolvedAdminViews,
7955
+ views: resolvedData.views,
7956
+ adminViews: resolvedData.adminViews,
8065
7957
  loading,
8066
7958
  error,
8067
7959
  refresh
8068
7960
  }), [
8069
- resolvedViews,
8070
- resolvedAdminViews,
7961
+ resolvedData,
8071
7962
  loading,
8072
7963
  error,
8073
7964
  refresh
@@ -8176,21 +8067,24 @@ function useTopLevelNavigation(props) {
8176
8067
  }, [])
8177
8068
  ];
8178
8069
  const groupOrderValue = (groupName) => {
8179
- if (groupName === "Admin") return 1;
8070
+ if (groupName === "Admin" || groupName === "Settings" || groupName === "Admin") return 1;
8180
8071
  return 0;
8181
8072
  };
8182
- navigationEntries = navigationEntries.sort((a, b) => {
8183
- return groupOrderValue(a.group) - groupOrderValue(b.group);
8184
- });
8185
8073
  const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
8186
- if (usedViewsOrder) navigationEntries = navigationEntries.sort((a, b) => {
8187
- const getSortPath = (navEntry) => typeof navEntry.slug === "string" ? navEntry.slug : navEntry.slug[0];
8188
- const aIndex = usedViewsOrder.indexOf(getSortPath(a));
8189
- const bIndex = usedViewsOrder.indexOf(getSortPath(b));
8190
- if (aIndex === -1 && bIndex === -1) return 0;
8191
- if (aIndex === -1) return 1;
8192
- if (bIndex === -1) return -1;
8193
- return aIndex - bIndex;
8074
+ navigationEntries = navigationEntries.sort((a, b) => {
8075
+ const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
8076
+ if (groupDiff !== 0) return groupDiff;
8077
+ if (usedViewsOrder) {
8078
+ const getSortPath = (navEntry) => typeof navEntry.slug === "string" ? navEntry.slug : navEntry.slug[0];
8079
+ const aIndex = usedViewsOrder.indexOf(getSortPath(a));
8080
+ const bIndex = usedViewsOrder.indexOf(getSortPath(b));
8081
+ if (aIndex !== -1 || bIndex !== -1) {
8082
+ if (aIndex === -1) return 1;
8083
+ if (bIndex === -1) return -1;
8084
+ return aIndex - bIndex;
8085
+ }
8086
+ }
8087
+ return 0;
8194
8088
  });
8195
8089
  const collectedGroupsFromEntries = navigationEntries.map((e) => e.group).filter(Boolean);
8196
8090
  const groupsFromMappings = finalNavigationGroupMappings.map((g) => g.name);
@@ -8201,8 +8095,8 @@ function useTopLevelNavigation(props) {
8201
8095
  ...additionalGroups
8202
8096
  ];
8203
8097
  const uniqueGroupsArray = [...new Set(allDefinedGroups)];
8204
- const adminGroups = uniqueGroupsArray.filter((g) => g === NAVIGATION_ADMIN_GROUP_NAME);
8205
- const uniqueGroups = [...uniqueGroupsArray.filter((g) => g !== NAVIGATION_ADMIN_GROUP_NAME), ...adminGroups];
8098
+ const lastGroups = uniqueGroupsArray.filter((g) => g === "Admin" || g === "Settings" || g === "Admin");
8099
+ const uniqueGroups = [...uniqueGroupsArray.filter((g) => g !== "Admin" && g !== "Settings" && g !== "Admin"), ...lastGroups];
8206
8100
  const computedTopLevelNav = {
8207
8101
  allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
8208
8102
  navigationEntries,
@@ -8269,9 +8163,15 @@ function useBuildNavigationStateController(props) {
8269
8163
  adminMode,
8270
8164
  collectionRegistryController
8271
8165
  });
8166
+ const pendingRefreshRef = useRef(false);
8272
8167
  const refreshNavigation = useCallback(() => {
8273
- refreshCollections();
8274
- refreshViews();
8168
+ if (pendingRefreshRef.current) return;
8169
+ pendingRefreshRef.current = true;
8170
+ queueMicrotask(() => {
8171
+ pendingRefreshRef.current = false;
8172
+ refreshCollections();
8173
+ refreshViews();
8174
+ });
8275
8175
  }, [refreshCollections, refreshViews]);
8276
8176
  return useMemo(() => ({
8277
8177
  views,
@@ -8279,8 +8179,7 @@ function useBuildNavigationStateController(props) {
8279
8179
  topLevelNavigation,
8280
8180
  loading: collectionsLoading || viewsLoading,
8281
8181
  navigationLoadingError: collectionsError ?? viewsError,
8282
- refreshNavigation,
8283
- plugins
8182
+ refreshNavigation
8284
8183
  }), [
8285
8184
  views,
8286
8185
  adminViews,
@@ -8289,8 +8188,7 @@ function useBuildNavigationStateController(props) {
8289
8188
  viewsLoading,
8290
8189
  collectionsError,
8291
8190
  viewsError,
8292
- refreshNavigation,
8293
- plugins
8191
+ refreshNavigation
8294
8192
  ]);
8295
8193
  }
8296
8194
  //#endregion
@@ -8352,14 +8250,18 @@ var BreadcrumbContext = React.createContext({
8352
8250
  var BreadcrumbsProvider = ({ children }) => {
8353
8251
  const [breadcrumbs, setBreadcrumbs] = useState([]);
8354
8252
  const set = useCallback((props) => {
8355
- setBreadcrumbs((prev) => props.breadcrumbs.map((newEntry) => {
8356
- const prevEntry = newEntry.id ? prev.find((p) => p.id === newEntry.id) : void 0;
8357
- if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") return {
8358
- ...newEntry,
8359
- count: prevEntry.count
8360
- };
8361
- return newEntry;
8362
- }));
8253
+ setBreadcrumbs((prev) => {
8254
+ const next = props.breadcrumbs.map((newEntry) => {
8255
+ const prevEntry = newEntry.id ? prev.find((p) => p.id === newEntry.id) : void 0;
8256
+ if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") return {
8257
+ ...newEntry,
8258
+ count: prevEntry.count
8259
+ };
8260
+ return newEntry;
8261
+ });
8262
+ if (prev.length === next.length && prev.every((p, i) => p.title === next[i].title && p.url === next[i].url && p.id === next[i].id && p.count === next[i].count)) return prev;
8263
+ return next;
8264
+ });
8363
8265
  }, []);
8364
8266
  const updateCount = useCallback((id, count) => {
8365
8267
  setBreadcrumbs((prev) => prev.map((entry) => entry.id === id ? {
@@ -8502,11 +8404,7 @@ function SideDialogView({ offsetPosition, panel, panelIndex, isTopPanel }) {
8502
8404
  overlayStyle: { zIndex: 40 + panelIndex * 10 },
8503
8405
  style: { zIndex: 45 + panelIndex * 10 },
8504
8406
  onOpenChange: (open) => {
8505
- if (!open) {
8506
- const suggestionMenu = document.querySelector("[data-suggestion-menu=\"true\"]");
8507
- if (suggestionMenu && window.getComputedStyle(suggestionMenu).visibility !== "hidden") return;
8508
- onCloseRequest();
8509
- }
8407
+ if (!open) onCloseRequest();
8510
8408
  },
8511
8409
  onPointerDownOutside: !isTopPanel ? preventDismiss : void 0,
8512
8410
  onInteractOutside: !isTopPanel ? preventDismiss : void 0,
@@ -8736,7 +8634,7 @@ function EntitySelectionDialogActions({ collection, path, onClear, onNewClick })
8736
8634
  //#region src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts
8737
8635
  function useCollectionInlineEditor({ path, collection, dataClient, context }) {
8738
8636
  const uniqueFieldValidator = useCallback(async ({ name, value, property, entityId }) => {
8739
- const conflictingEntities = (await dataClient.collection(path).find({ where: { [name]: "eq." + value } })).data;
8637
+ const conflictingEntities = (await dataClient.collection(path).find({ where: { [name]: ["==", value] } })).data;
8740
8638
  return conflictingEntities.length === 0 || conflictingEntities.length === 1 && conflictingEntities[0].id === entityId;
8741
8639
  }, [path, dataClient]);
8742
8640
  const onValueChange = useCallback(({ value, propertyKey, onValueUpdated, setError, data: entity }) => {
@@ -8969,7 +8867,7 @@ function FormEntry({ propertyKey, widthPercentage = 100, children }) {
8969
8867
  //#region src/form/components/FormLayout.tsx
8970
8868
  function FormLayout({ children, className }) {
8971
8869
  return /* @__PURE__ */ jsx("div", {
8972
- className: cls("flex flex-wrap gap-x-4 w-full space-y-8", className),
8870
+ className: cls("flex flex-wrap gap-x-4 w-full space-y-6", className),
8973
8871
  children
8974
8872
  });
8975
8873
  }
@@ -8994,7 +8892,7 @@ function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalist
8994
8892
  className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"
8995
8893
  }),
8996
8894
  /* @__PURE__ */ jsx("div", {
8997
- className: cls(!skipCardWrapper && paperMixin, "w-full min-h-14 overflow-x-scroll no-scrollbar", !skipCardWrapper && "p-4 md:p-6"),
8895
+ className: cls("w-full overflow-x-scroll no-scrollbar flex items-center", skipCardWrapper ? "" : "rounded-lg border border-surface-200 dark:border-surface-700 px-4 md:px-6 min-h-10 opacity-80"),
8998
8896
  children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
8999
8897
  propertyKey,
9000
8898
  value,
@@ -9281,7 +9179,7 @@ var buildIdsMap = (value) => value && Array.isArray(value) && value.length > 0 ?
9281
9179
  ...a,
9282
9180
  ...b
9283
9181
  }), {}) : {};
9284
- function SortableItem$1({ id, index, size, disabled, buildEntry, remove, copy, addInIndex, canAddElements, sortable, storedProps, updateItemCustomProps }) {
9182
+ function SortableItem({ id, index, size, disabled, buildEntry, remove, copy, addInIndex, canAddElements, sortable, storedProps, updateItemCustomProps }) {
9285
9183
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
9286
9184
  return /* @__PURE__ */ jsx(ArrayContainerItem, {
9287
9185
  nodeRef: setNodeRef,
@@ -9510,7 +9408,7 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
9510
9408
  children: /* @__PURE__ */ jsxs("div", {
9511
9409
  className: cls("space-y-1", className),
9512
9410
  id: droppableId,
9513
- children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem$1, {
9411
+ children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem, {
9514
9412
  id: internalId,
9515
9413
  index,
9516
9414
  size,
@@ -10433,8 +10331,18 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
10433
10331
  const disabled = disabledProp || isSubmitting;
10434
10332
  const highlight = customProps?.highlight;
10435
10333
  const editorProps = customProps?.editorProps;
10436
- const storageSource = useStorageSource();
10334
+ const defaultStorageSource = useStorageSource();
10437
10335
  const storage = property.storage;
10336
+ const storageSources = useStorageSources();
10337
+ const storageSource = useMemo(() => resolveStorageSource({
10338
+ sourceKey: storage?.storageSource,
10339
+ sources: storageSources.sources,
10340
+ defaultSource: defaultStorageSource
10341
+ }), [
10342
+ storage?.storageSource,
10343
+ storageSources.sources,
10344
+ defaultStorageSource
10345
+ ]);
10438
10346
  const entityValues = context.values;
10439
10347
  const entityId = context.entityId;
10440
10348
  const path = context.path;
@@ -10645,91 +10553,6 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
10645
10553
  })] });
10646
10554
  }
10647
10555
  //#endregion
10648
- //#region src/form/field_bindings/ReferenceAsStringFieldBinding.tsx
10649
- /**
10650
- * Field that opens a reference selection dialog and stores the entity ID as a string.
10651
- *
10652
- * This is one of the internal components that get mapped natively inside forms
10653
- * and tables to the specified properties.
10654
- * @group Form fields
10655
- */
10656
- function ReferenceAsStringFieldBinding(props) {
10657
- if (typeof props.property.reference?.path !== "string") return /* @__PURE__ */ jsx(ReadOnlyFieldBinding, { ...props });
10658
- return /* @__PURE__ */ jsx(ReferenceAsStringFieldBindingInternal, { ...props });
10659
- }
10660
- function ReferenceAsStringFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, property, includeDescription, size = "medium" }) {
10661
- if (!property.reference?.path) throw new Error("Property path is required for ReferenceAsStringFieldBinding");
10662
- useClearRestoreValue({
10663
- property,
10664
- value,
10665
- setValue
10666
- });
10667
- const collectionRegistryController = useCollectionRegistryController();
10668
- const path = property.reference.path;
10669
- const collection = useMemo(() => {
10670
- return path ? collectionRegistryController.getCollection(path) : void 0;
10671
- }, [path]);
10672
- const referenceValue = useMemo(() => {
10673
- if (value && path) return new EntityReference({
10674
- id: value,
10675
- path
10676
- });
10677
- }, [value, path]);
10678
- if (!collection) throw Error(`Couldn't find the corresponding collection for the path: ${path}`);
10679
- const referenceDialogController = useEntitySelectionDialog({
10680
- multiselect: false,
10681
- path,
10682
- collection,
10683
- onSingleEntitySelected: useCallback((e) => {
10684
- setValue(e ? String(e.id) : null);
10685
- }, [setValue]),
10686
- selectedEntityIds: value ? [value] : void 0,
10687
- fixedFilter: property.reference?.fixedFilter
10688
- });
10689
- const onEntryClick = (e) => {
10690
- e.preventDefault();
10691
- referenceDialogController.open();
10692
- };
10693
- return /* @__PURE__ */ jsxs(Fragment, { children: [
10694
- !minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
10695
- propertyKey,
10696
- icon: getIconForProperty(property, "small"),
10697
- required: property.validation?.required,
10698
- title: property.name ?? propertyKey,
10699
- className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"
10700
- }),
10701
- !collection && /* @__PURE__ */ jsx(ErrorView, { error: "The specified collection does not exist. Check console" }),
10702
- collection && /* @__PURE__ */ jsxs(Fragment, { children: [referenceValue && /* @__PURE__ */ jsx(ReferencePreview, {
10703
- disabled: !path,
10704
- previewProperties: property.reference?.ui?.previewProperties,
10705
- hover: !disabled,
10706
- size,
10707
- onClick: disabled || isSubmitting ? void 0 : onEntryClick,
10708
- reference: referenceValue,
10709
- includeEntityLink: property.reference?.includeEntityLink,
10710
- includeId: property.reference?.includeId
10711
- }), !value && /* @__PURE__ */ jsx("div", {
10712
- className: "justify-center text-left",
10713
- children: /* @__PURE__ */ jsxs(EntityPreviewContainer, {
10714
- className: cls("px-6 h-16 text-sm font-medium flex items-center gap-6", disabled || isSubmitting ? "text-surface-accent-500" : "cursor-pointer text-surface-accent-700 dark:text-surface-accent-300 hover:bg-surface-accent-50 dark:hover:bg-surface-800 group-hover:bg-surface-accent-50 dark:group-hover:bg-surface-800"),
10715
- onClick: onEntryClick,
10716
- size: "medium",
10717
- children: [/* @__PURE__ */ jsx(IconForView, {
10718
- collectionOrView: collection,
10719
- className: "text-surface-300 dark:text-surface-600"
10720
- }), `Edit ${property.name}`.toUpperCase()]
10721
- })
10722
- })] }),
10723
- /* @__PURE__ */ jsx(FieldHelperText, {
10724
- includeDescription,
10725
- showError,
10726
- error,
10727
- disabled,
10728
- property
10729
- })
10730
- ] });
10731
- }
10732
- //#endregion
10733
10556
  //#region src/form/field_bindings/ReferenceFieldBinding.tsx
10734
10557
  /**
10735
10558
  * Field that opens a reference selection dialog.
@@ -11022,9 +10845,9 @@ var acceptDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0
11022
10845
  var rejectDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-red-500";
11023
10846
  function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, minimalistView, property, includeDescription, context, isSubmitting }) {
11024
10847
  useAuthController();
11025
- const storageSource = useStorageSource(context.collection);
10848
+ const storageSource = useStorageSource();
11026
10849
  const disabled = isReadOnly(property) || !!property.ui?.disabled || isSubmitting || context.disabled;
11027
- const { internalValue, setInternalValue, onFilesAdded, storage, onFileUploadComplete, storagePathBuilder, multipleFilesSupported } = useStorageUploadController({
10850
+ const { internalValue, setInternalValue, onFilesAdded, storage, onFileUploadComplete, storagePathBuilder, multipleFilesSupported, resolvedStorageSource } = useStorageUploadController({
11028
10851
  entityValues: context.values,
11029
10852
  entityId: context.entityId,
11030
10853
  path: context.path,
@@ -11048,19 +10871,22 @@ function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showEr
11048
10871
  title: property.name ?? propertyKey,
11049
10872
  className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"
11050
10873
  }),
11051
- /* @__PURE__ */ jsx(StorageUpload, {
11052
- value: internalValue,
11053
- name: propertyKey,
11054
- disabled: disabled ?? false,
11055
- autoFocus: autoFocus ?? false,
11056
- property,
11057
- onChange: setValue,
11058
- setInternalValue,
11059
- onFilesAdded,
11060
- onFileUploadComplete,
11061
- storagePathBuilder,
11062
- storage,
11063
- multipleFilesSupported
10874
+ /* @__PURE__ */ jsx(StorageSourceContext.Provider, {
10875
+ value: resolvedStorageSource,
10876
+ children: /* @__PURE__ */ jsx(StorageUpload, {
10877
+ value: internalValue,
10878
+ name: propertyKey,
10879
+ disabled: disabled ?? false,
10880
+ autoFocus: autoFocus ?? false,
10881
+ property,
10882
+ onChange: setValue,
10883
+ setInternalValue,
10884
+ onFilesAdded,
10885
+ onFileUploadComplete,
10886
+ storagePathBuilder,
10887
+ storage,
10888
+ multipleFilesSupported
10889
+ })
11064
10890
  }),
11065
10891
  /* @__PURE__ */ jsx(FieldHelperText, {
11066
10892
  includeDescription,
@@ -11567,7 +11393,7 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
11567
11393
  const { t } = useTranslation();
11568
11394
  if (property.type !== "relation") throw Error("RelationFieldBinding expected a property containing a relation");
11569
11395
  const parentCollection = context.collection;
11570
- const capabilities = parentCollection ? getDataSourceCapabilities(parentCollection.driver) : void 0;
11396
+ const capabilities = parentCollection ? getDataSourceCapabilities(parentCollection.engine) : void 0;
11571
11397
  if (!parentCollection || !capabilities?.supportsRelations || !("relations" in parentCollection) || !parentCollection.relations) throw Error("RelationFieldBinding expected a collection with relations support");
11572
11398
  const relation = resolveRelationProperty(property, parentCollection.relations, propertyKey).relation;
11573
11399
  if (!relation) throw Error("Property relation is required for MultipleRelationFieldBinding");
@@ -11578,7 +11404,7 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
11578
11404
  }, [setValue]);
11579
11405
  const referenceDialogController = useEntitySelectionDialog({
11580
11406
  multiselect: true,
11581
- path: collection.slug,
11407
+ path: getCollectionDataPath(collection),
11582
11408
  collection,
11583
11409
  onMultipleEntitiesSelected,
11584
11410
  selectedEntityIds,
@@ -11664,7 +11490,7 @@ function RelationFieldBinding(props) {
11664
11490
  const { property, propertyKey, context } = props;
11665
11491
  if (property.type !== "relation") throw Error("RelationFieldBinding expected a property containing a relation");
11666
11492
  const collection = context.collection;
11667
- const capabilities = collection ? getDataSourceCapabilities(collection.driver) : void 0;
11493
+ const capabilities = collection ? getDataSourceCapabilities(collection.engine) : void 0;
11668
11494
  if (!collection || !capabilities?.supportsRelations || !("relations" in collection) || !collection.relations) throw Error("RelationFieldBinding expected a collection with relations support");
11669
11495
  const relation = resolveRelationProperty(property, collection.relations, propertyKey).relation;
11670
11496
  const manyRelation = relation?.cardinality === "many";
@@ -11724,7 +11550,7 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
11724
11550
  }, [setValue]);
11725
11551
  const referenceDialogController = useEntitySelectionDialog({
11726
11552
  multiselect: false,
11727
- path: collection.slug,
11553
+ path: getCollectionDataPath(collection),
11728
11554
  collection,
11729
11555
  onSingleEntitySelected,
11730
11556
  selectedEntityIds: validValue && normalizedValue ? [normalizedValue.id] : void 0,
@@ -11996,17 +11822,6 @@ var DEFAULT_FIELD_CONFIGS = {
11996
11822
  ui: { Field: StorageUploadFieldBinding }
11997
11823
  }
11998
11824
  },
11999
- reference_as_string: {
12000
- key: "reference_as_string",
12001
- name: "Reference (as string)",
12002
- description: "The value refers to a different collection (it is saved as a string)",
12003
- Icon: LinkIcon,
12004
- color: "#154fb3",
12005
- property: {
12006
- type: "string",
12007
- ui: { Field: ReferenceAsStringFieldBinding }
12008
- }
12009
- },
12010
11825
  reference: {
12011
11826
  key: "reference",
12012
11827
  name: "Reference",
@@ -12152,7 +11967,6 @@ function getDefaultFieldId(property) {
12152
11967
  else if (property.email) return "email";
12153
11968
  else if (property.enum) return "select";
12154
11969
  else if (property.userSelect) return "user_select";
12155
- else if (property.reference) return "reference_as_string";
12156
11970
  else return "text_field";
12157
11971
  else if (property.type === "number") {
12158
11972
  if (property.enum) return "number_select";
@@ -13237,8 +13051,8 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
13237
13051
  const sortedPreviewKeys = hasExplicitOrder ? previewKeys : [...previewKeys].sort((a, b) => {
13238
13052
  const propA = collection.properties[a];
13239
13053
  const propB = collection.properties[b];
13240
- const scoreA = propA?.type === "string" ? propA.multiline ? 2 : 1 : 0;
13241
- return (propB?.type === "string" ? propB.multiline ? 2 : 1 : 0) - scoreA;
13054
+ const scoreA = propA?.type === "string" ? propA.ui?.multiline ? 2 : 1 : 0;
13055
+ return (propB?.type === "string" ? propB.ui?.multiline ? 2 : 1 : 0) - scoreA;
13242
13056
  });
13243
13057
  return {
13244
13058
  titleKey,
@@ -13431,7 +13245,7 @@ function resolveRelationDisplayName(relation, prop) {
13431
13245
  return false;
13432
13246
  };
13433
13247
  const isDisplayCandidate = (p) => {
13434
- return p.type === "string" && !p.multiline && !p.markdown && !p.storage && !("isId" in p && p.isId) && !isHiddenProp(p);
13248
+ return p.type === "string" && !p.ui?.multiline && !p.ui?.markdown && !p.storage && !("isId" in p && p.isId) && !isHiddenProp(p);
13435
13249
  };
13436
13250
  for (const pk of [
13437
13251
  "name",
@@ -13592,189 +13406,57 @@ function EntityCard({ entity, collection, onClick, selected, highlighted, onSele
13592
13406
  //#endregion
13593
13407
  //#region src/components/EntityCollectionView/EntityCollectionCardView.tsx
13594
13408
  /**
13595
- * Get grid column classes based on the size.
13596
- * Smaller size = more columns (smaller cards)
13597
- * Larger size = fewer columns (larger cards)
13598
- */
13599
- function getGridColumnsClass(size) {
13600
- switch (size) {
13601
- case "xs": return "grid-cols-4 sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10";
13602
- case "s": return "grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8";
13603
- case "m": return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
13604
- case "l": return "grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4";
13605
- case "xl": return "grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3";
13606
- default: return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
13607
- }
13608
- }
13609
- function getScrollParent$1(element) {
13610
- if (!element) return null;
13611
- let parent = element.parentElement;
13612
- while (parent) {
13613
- const overflowY = window.getComputedStyle(parent).overflowY;
13614
- if (overflowY === "auto" || overflowY === "scroll") return parent;
13615
- parent = parent.parentElement;
13616
- }
13617
- return document.documentElement;
13618
- }
13619
- /**
13620
13409
  * Card grid view for displaying entities with infinite scroll.
13621
13410
  * Alternative to the EntityCollectionTable for visual browsing.
13622
13411
  */
13623
13412
  function EntityCollectionCardView({ collection, tableController, onEntityClick, selectionController, selectionEnabled = true, highlightedEntities, emptyComponent, onScroll, initialScroll, size = "m" }) {
13624
13413
  const ResolvedEntityCard = useComponentOverride("Collection.Card", EntityCard);
13625
- const containerRef = useRef(null);
13626
- const hasRestoredScroll = useRef(false);
13627
13414
  const { data, dataLoading, noMoreToLoad, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled } = tableController;
13628
- const isLoadingMore = useRef(false);
13629
- const paginationStateRef = useRef({
13630
- paginationEnabled,
13631
- noMoreToLoad,
13632
- dataLoading,
13633
- itemCount,
13634
- pageSize
13635
- });
13636
- useEffect(() => {
13637
- paginationStateRef.current = {
13638
- paginationEnabled,
13639
- noMoreToLoad,
13640
- dataLoading,
13641
- itemCount,
13642
- pageSize
13643
- };
13644
- }, [
13645
- paginationEnabled,
13646
- noMoreToLoad,
13647
- dataLoading,
13648
- itemCount,
13649
- pageSize
13650
- ]);
13651
- useEffect(() => {
13652
- if (!dataLoading) isLoadingMore.current = false;
13653
- }, [dataLoading]);
13654
- useEffect(() => {
13655
- const el = containerRef.current;
13656
- if (!el) return;
13657
- const scrollEl = getScrollParent$1(el);
13658
- if (!scrollEl) return;
13659
- let rafId = null;
13660
- const update = () => {
13661
- rafId = null;
13662
- const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
13663
- if (pe && !nm && !isLoadingMore.current && scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < 400) {
13664
- isLoadingMore.current = true;
13665
- setItemCount?.((ic ?? ps) + ps);
13666
- }
13667
- };
13668
- const onScrollEvent = () => {
13669
- if (rafId === null) rafId = requestAnimationFrame(update);
13670
- };
13671
- scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
13672
- const ro = new ResizeObserver(() => update());
13673
- ro.observe(scrollEl);
13674
- update();
13675
- return () => {
13676
- scrollEl.removeEventListener("scroll", onScrollEvent);
13677
- ro.disconnect();
13678
- if (rafId !== null) cancelAnimationFrame(rafId);
13679
- };
13680
- }, [setItemCount]);
13681
- useEffect(() => {
13682
- if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
13683
- const scrollEl = getScrollParent$1(containerRef.current);
13684
- if (!scrollEl) return;
13685
- let attempts = 0;
13686
- const maxAttempts = 5;
13687
- const tryRestore = () => {
13688
- if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
13689
- scrollEl.scrollTop = initialScroll;
13690
- hasRestoredScroll.current = true;
13691
- } else {
13692
- attempts++;
13693
- requestAnimationFrame(tryRestore);
13694
- }
13695
- };
13696
- requestAnimationFrame(tryRestore);
13697
- }, [initialScroll, data.length]);
13698
- const lastScrollOffset = useRef(0);
13699
- useEffect(() => {
13700
- const el = containerRef.current;
13701
- if (!el || !onScroll) return;
13702
- const scrollEl = getScrollParent$1(el);
13703
- if (!scrollEl) return;
13704
- const handleScroll = () => {
13705
- const currentOffset = scrollEl.scrollTop;
13706
- const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
13707
- lastScrollOffset.current = currentOffset;
13708
- onScroll({
13709
- scrollDirection: direction,
13710
- scrollOffset: currentOffset,
13711
- scrollUpdateWasRequested: false
13712
- });
13713
- };
13714
- scrollEl.addEventListener("scroll", handleScroll, { passive: true });
13715
- return () => scrollEl.removeEventListener("scroll", handleScroll);
13716
- }, [onScroll]);
13717
13415
  const handleEntityClick = useCallback((entity) => {
13718
13416
  onEntityClick?.(entity);
13719
13417
  }, [onEntityClick]);
13720
13418
  const handleSelectionChange = useCallback((entity, selected) => {
13721
13419
  selectionController?.toggleEntitySelection(entity, selected);
13722
13420
  }, [selectionController]);
13723
- const isEntitySelected = useCallback((entity) => {
13724
- return selectionController?.isEntitySelected(entity) ?? false;
13725
- }, [selectionController]);
13726
- const isEntityHighlighted = useCallback((entity) => {
13727
- return highlightedEntities?.some((e) => e.id === entity.id && e.path === entity.path) ?? false;
13728
- }, [highlightedEntities]);
13729
- const gridColumnsClass = getGridColumnsClass(size);
13730
- const isInitialLoading = dataLoading && data.length === 0 && !dataLoadingError;
13731
- const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
13732
- return /* @__PURE__ */ jsx("div", {
13733
- ref: containerRef,
13734
- className: "w-full p-4",
13735
- children: dataLoadingError && data.length === 0 ? /* @__PURE__ */ jsx("div", {
13736
- className: "h-full flex items-center justify-center p-8",
13737
- children: /* @__PURE__ */ jsxs(Typography, {
13738
- className: "text-red-500",
13739
- children: ["Error loading data: ", dataLoadingError.message]
13740
- })
13741
- }) : isInitialLoading ? /* @__PURE__ */ jsx("div", {
13742
- className: "flex items-center justify-center py-12 px-8",
13743
- children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
13744
- }) : isEmpty ? /* @__PURE__ */ jsx("div", {
13745
- className: "w-full flex items-center justify-center py-12 px-8",
13746
- children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
13747
- variant: "label",
13748
- color: "secondary",
13749
- children: "No entries found"
13750
- })
13751
- }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
13752
- className: "max-w-7xl mx-auto",
13753
- children: [/* @__PURE__ */ jsx("div", {
13754
- className: cls("grid gap-4", gridColumnsClass),
13755
- children: data.map((entity) => /* @__PURE__ */ jsx(ResolvedEntityCard, {
13756
- entity,
13757
- collection,
13758
- onClick: handleEntityClick,
13759
- selected: isEntitySelected(entity),
13760
- highlighted: isEntityHighlighted(entity),
13761
- onSelectionChange: handleSelectionChange,
13762
- selectionEnabled,
13763
- size
13764
- }, `${entity.path}_${entity.id}`))
13765
- }), /* @__PURE__ */ jsxs("div", {
13766
- className: "flex items-center justify-center py-8",
13767
- children: [dataLoading && /* @__PURE__ */ jsx(CircularProgress, { size: "small" }), !dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsxs(Typography, {
13768
- variant: "caption",
13769
- color: "secondary",
13770
- children: [
13771
- "All ",
13772
- data.length,
13773
- " entries loaded"
13774
- ]
13775
- })]
13776
- })]
13777
- }) })
13421
+ const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map((e) => e.id)), [selectionController?.selectedEntities]);
13422
+ const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
13423
+ const handleRowSelectionChange = useCallback((entity, selected) => {
13424
+ handleSelectionChange(entity, selected);
13425
+ }, [handleSelectionChange]);
13426
+ return /* @__PURE__ */ jsx(CardView, {
13427
+ data,
13428
+ dataLoading,
13429
+ noMoreToLoad,
13430
+ dataLoadingError,
13431
+ itemCount,
13432
+ setItemCount,
13433
+ pageSize,
13434
+ paginationEnabled,
13435
+ onItemClick: handleEntityClick,
13436
+ selectedIds,
13437
+ highlightedIds,
13438
+ selectionEnabled,
13439
+ onSelectionChange: handleRowSelectionChange,
13440
+ onScroll,
13441
+ initialScroll,
13442
+ size,
13443
+ emptyComponent,
13444
+ renderCard: useCallback((entity, { selected, highlighted, onClick }) => /* @__PURE__ */ jsx(ResolvedEntityCard, {
13445
+ entity,
13446
+ collection,
13447
+ onClick,
13448
+ selected,
13449
+ highlighted,
13450
+ onSelectionChange: handleRowSelectionChange,
13451
+ selectionEnabled,
13452
+ size
13453
+ }, `${entity.path}_${entity.id}`), [
13454
+ collection,
13455
+ selectionEnabled,
13456
+ size,
13457
+ handleRowSelectionChange,
13458
+ ResolvedEntityCard
13459
+ ])
13778
13460
  });
13779
13461
  }
13780
13462
  //#endregion
@@ -13792,38 +13474,9 @@ function getRowClasses(size) {
13792
13474
  default: return "py-3 px-5";
13793
13475
  }
13794
13476
  }
13795
- /**
13796
- * Estimated row height in pixels for virtualization, based on size.
13797
- */
13798
- function getEstimatedRowHeight(size) {
13799
- switch (size) {
13800
- case "xs": return 44;
13801
- case "s": return 52;
13802
- case "m": return 64;
13803
- case "l": return 76;
13804
- case "xl": return 88;
13805
- default: return 64;
13806
- }
13807
- }
13808
- /** Number of extra rows rendered above/below the viewport. */
13809
- var OVERSCAN_COUNT = 8;
13810
- /** Threshold in pixels from the bottom of the scroll area to trigger loading more. */
13811
- var LOAD_MORE_THRESHOLD = 400;
13812
13477
  /** Stable empty array for when no list-view actions are available. */
13813
13478
  var EMPTY_LIST_VIEW_ACTIONS = [];
13814
13479
  /**
13815
- * Walk up the DOM from `element` to find the nearest scrollable ancestor.
13816
- */
13817
- function getScrollParent(element) {
13818
- let parent = element?.parentElement ?? null;
13819
- while (parent) {
13820
- const style = getComputedStyle(parent);
13821
- if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") return parent;
13822
- parent = parent.parentElement;
13823
- }
13824
- return document.documentElement;
13825
- }
13826
- /**
13827
13480
  * Returns true if a property type should NOT be rendered via
13828
13481
  * PropertyPreview in list row columns (because it would blow up height).
13829
13482
  */
@@ -14008,7 +13661,17 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
14008
13661
  subtitleKey,
14009
13662
  "id"
14010
13663
  ]);
14011
- allKeys.filter((k) => !usedKeys.has(k) && resolvedCollection.properties[k]).forEach((key) => {
13664
+ allKeys.filter((k) => {
13665
+ if (usedKeys.has(k)) return false;
13666
+ const prop = resolvedCollection.properties[k];
13667
+ if (!prop) return false;
13668
+ if (prop.type === "string" && (prop.storage || prop.ui?.url === "image")) return false;
13669
+ if (prop.type === "array" && prop.of && !Array.isArray(prop.of)) {
13670
+ const inner = prop.of;
13671
+ if (inner.type === "string" && (inner.storage || inner.ui?.url === "image")) return false;
13672
+ }
13673
+ return true;
13674
+ }).forEach((key) => {
14012
13675
  const prop = resolvedCollection.properties[key];
14013
13676
  cols.push({
14014
13677
  key,
@@ -14077,10 +13740,10 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
14077
13740
  const handleSelectionChange = useCallback((entity, selected) => {
14078
13741
  selectionController?.toggleEntitySelection(entity, selected);
14079
13742
  }, [selectionController]);
14080
- const isEntitySelected = useCallback((entity) => {
13743
+ useCallback((entity) => {
14081
13744
  return selectionController?.isEntitySelected(entity) ?? false;
14082
13745
  }, [selectionController]);
14083
- const isEntityHighlighted = useCallback((entity) => {
13746
+ useCallback((entity) => {
14084
13747
  return highlightedEntities?.some((e) => e.id === entity.id && e.path === entity.path) ?? false;
14085
13748
  }, [highlightedEntities]);
14086
13749
  const getListViewActions = useCallback((entity) => {
@@ -14095,158 +13758,70 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
14095
13758
  customizationController.entityActions
14096
13759
  ]);
14097
13760
  const rowClasses = getRowClasses(size);
14098
- const isInitialLoading = dataLoading && data.length === 0;
14099
- const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
14100
- const estimatedRowHeight = getEstimatedRowHeight(size);
14101
- const [effectiveScrollTop, setEffectiveScrollTop] = useState(0);
14102
- const [viewportHeight, setViewportHeight] = useState(800);
14103
- const paginationStateRef = useRef({
14104
- paginationEnabled,
13761
+ const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map((e) => e.id)), [selectionController?.selectedEntities]);
13762
+ const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
13763
+ const handleRowSelectionChange = useCallback((entity, selected) => {
13764
+ handleSelectionChange(entity, selected);
13765
+ }, [handleSelectionChange]);
13766
+ return /* @__PURE__ */ jsx(ListView, {
13767
+ data,
13768
+ dataLoading,
14105
13769
  noMoreToLoad,
13770
+ dataLoadingError,
14106
13771
  itemCount,
14107
- pageSize
14108
- });
14109
- useEffect(() => {
14110
- paginationStateRef.current = {
14111
- paginationEnabled,
14112
- noMoreToLoad,
14113
- itemCount,
14114
- pageSize
14115
- };
14116
- }, [
13772
+ setItemCount,
13773
+ pageSize,
14117
13774
  paginationEnabled,
14118
- noMoreToLoad,
14119
- itemCount,
14120
- pageSize
14121
- ]);
14122
- useEffect(() => {
14123
- const el = containerRef.current;
14124
- if (!el) return;
14125
- const scrollEl = getScrollParent(el);
14126
- if (!scrollEl) return;
14127
- let rafId = null;
14128
- const update = () => {
14129
- rafId = null;
14130
- const scrollRect = scrollEl.getBoundingClientRect();
14131
- const listTopRelative = el.getBoundingClientRect().top - scrollRect.top;
14132
- setEffectiveScrollTop(Math.max(0, -listTopRelative));
14133
- setViewportHeight(scrollRect.height);
14134
- const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
14135
- if (pe && !nm && !isLoadingMore.current && scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < LOAD_MORE_THRESHOLD) {
14136
- isLoadingMore.current = true;
14137
- setItemCount?.((ic ?? ps) + ps);
14138
- }
14139
- };
14140
- const onScroll = () => {
14141
- if (rafId === null) rafId = requestAnimationFrame(update);
14142
- };
14143
- scrollEl.addEventListener("scroll", onScroll, { passive: true });
14144
- const ro = new ResizeObserver(() => update());
14145
- ro.observe(scrollEl);
14146
- update();
14147
- return () => {
14148
- scrollEl.removeEventListener("scroll", onScroll);
14149
- ro.disconnect();
14150
- if (rafId !== null) cancelAnimationFrame(rafId);
14151
- };
14152
- }, [setItemCount]);
14153
- const totalHeight = data.length * estimatedRowHeight;
14154
- const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
14155
- const endIndex = Math.min(data.length, Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT);
14156
- const visibleData = data.slice(startIndex, endIndex);
14157
- const offsetY = startIndex * estimatedRowHeight;
14158
- const footerHeight = dataLoading ? 48 : !dataLoading && noMoreToLoad && data.length > 0 ? 32 : 0;
14159
- return /* @__PURE__ */ jsx("div", {
14160
- ref: containerRef,
14161
- className: cls("w-full", selectedEntityId === void 0 && "rounded-lg overflow-hidden border " + defaultBorderMixin),
14162
- children: dataLoadingError && data.length === 0 ? /* @__PURE__ */ jsx("div", {
14163
- className: "flex items-center justify-center p-8",
14164
- children: /* @__PURE__ */ jsxs(Typography, {
14165
- className: "text-red-500",
14166
- children: ["Error loading data: ", dataLoadingError.message]
14167
- })
14168
- }) : isInitialLoading ? /* @__PURE__ */ jsx("div", {
14169
- className: "flex items-center justify-center py-12 px-8",
14170
- children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
14171
- }) : isEmpty ? /* @__PURE__ */ jsx("div", {
14172
- className: "w-full flex items-center justify-center py-12 px-8",
14173
- children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
14174
- variant: "label",
14175
- color: "secondary",
14176
- children: "No entries found"
14177
- })
14178
- }) : /* @__PURE__ */ jsxs("div", {
14179
- style: {
14180
- height: totalHeight + footerHeight,
14181
- position: "relative"
14182
- },
14183
- children: [
14184
- /* @__PURE__ */ jsx("div", {
14185
- style: {
14186
- position: "absolute",
14187
- top: offsetY,
14188
- left: 0,
14189
- right: 0
14190
- },
14191
- children: visibleData.map((entity, i) => {
14192
- const isLast = startIndex + i === data.length - 1;
14193
- return /* @__PURE__ */ jsx("div", {
14194
- style: { height: estimatedRowHeight },
14195
- className: cls(!isLast && "border-b", !isLast && defaultBorderMixin),
14196
- children: /* @__PURE__ */ jsx(ListRow, {
14197
- entity,
14198
- collection: resolvedCollection,
14199
- onClick: handleEntityClick,
14200
- selected: isEntitySelected(entity),
14201
- highlighted: isEntityHighlighted(entity),
14202
- onSelectionChange: handleSelectionChange,
14203
- selectionEnabled,
14204
- columns: visibleColumns,
14205
- slotKeys,
14206
- rowClasses,
14207
- showImage,
14208
- size,
14209
- isLast,
14210
- isActive: selectedEntityId !== void 0 && entity.id === selectedEntityId,
14211
- listViewActions: getListViewActions(entity),
14212
- context,
14213
- path,
14214
- selectionController,
14215
- openEntityMode
14216
- })
14217
- }, entity.id);
14218
- })
14219
- }),
14220
- dataLoading && /* @__PURE__ */ jsx("div", {
14221
- className: "flex items-center justify-center py-3",
14222
- style: {
14223
- position: "absolute",
14224
- top: totalHeight,
14225
- left: 0,
14226
- right: 0
14227
- },
14228
- children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
14229
- }),
14230
- !dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsx("div", {
14231
- className: "flex items-center justify-center py-2 dark:bg-surface-900",
14232
- style: {
14233
- position: "absolute",
14234
- top: totalHeight,
14235
- left: 0,
14236
- right: 0
14237
- },
14238
- children: /* @__PURE__ */ jsxs(Typography, {
14239
- variant: "caption",
14240
- color: "secondary",
14241
- children: [
14242
- "All ",
14243
- data.length,
14244
- " entries loaded"
14245
- ]
14246
- })
13775
+ onItemClick: handleEntityClick,
13776
+ selectedIds,
13777
+ highlightedIds,
13778
+ selectionEnabled,
13779
+ emptyComponent,
13780
+ size,
13781
+ selectedEntityId,
13782
+ renderRow: useCallback(({ item: entity, style, className, selected, highlighted, isLast, onClick, onSelectionChange }) => {
13783
+ return /* @__PURE__ */ jsx("div", {
13784
+ style,
13785
+ className,
13786
+ children: /* @__PURE__ */ jsx(ListRow, {
13787
+ entity,
13788
+ collection: resolvedCollection,
13789
+ onClick: handleEntityClick,
13790
+ selected,
13791
+ highlighted,
13792
+ onSelectionChange: handleRowSelectionChange,
13793
+ selectionEnabled,
13794
+ columns: visibleColumns,
13795
+ slotKeys,
13796
+ rowClasses,
13797
+ showImage,
13798
+ size,
13799
+ isLast,
13800
+ isActive: selectedEntityId !== void 0 && entity.id === selectedEntityId,
13801
+ listViewActions: getListViewActions(entity),
13802
+ context,
13803
+ path,
13804
+ selectionController,
13805
+ openEntityMode
14247
13806
  })
14248
- ]
14249
- })
13807
+ }, entity.id);
13808
+ }, [
13809
+ resolvedCollection,
13810
+ selectionEnabled,
13811
+ visibleColumns,
13812
+ slotKeys,
13813
+ rowClasses,
13814
+ showImage,
13815
+ size,
13816
+ selectedEntityId,
13817
+ getListViewActions,
13818
+ context,
13819
+ path,
13820
+ selectionController,
13821
+ openEntityMode,
13822
+ handleRowSelectionChange,
13823
+ handleEntityClick
13824
+ ])
14250
13825
  });
14251
13826
  }
14252
13827
  /**
@@ -14682,457 +14257,6 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
14682
14257
  });
14683
14258
  }
14684
14259
  //#endregion
14685
- //#region src/components/EntityCollectionView/BoardSortableList.tsx
14686
- function BoardSortableList({ columnId, items, ItemComponent, isDragging, isDragOverColumn, loading = false, hasMore = false, onLoadMore }) {
14687
- const { setNodeRef } = useDroppable({
14688
- id: columnId,
14689
- data: { type: "ITEM-LIST" }
14690
- });
14691
- const sentinelRef = useRef(null);
14692
- const isLoadingRef = useRef(false);
14693
- isLoadingRef.current = loading;
14694
- const lastLoadTimeRef = useRef(0);
14695
- useEffect(() => {
14696
- if (!sentinelRef.current || !hasMore || !onLoadMore) return;
14697
- const sentinel = sentinelRef.current;
14698
- const observer = new IntersectionObserver((entries) => {
14699
- const now = Date.now();
14700
- if (entries[0].isIntersecting && hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
14701
- lastLoadTimeRef.current = now;
14702
- onLoadMore();
14703
- }
14704
- }, { threshold: .1 });
14705
- observer.observe(sentinel);
14706
- const rect = sentinel.getBoundingClientRect();
14707
- const containerRect = sentinel.parentElement?.getBoundingClientRect();
14708
- if (containerRect && rect.top < containerRect.bottom && rect.bottom > containerRect.top) {
14709
- const now = Date.now();
14710
- if (hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
14711
- lastLoadTimeRef.current = now;
14712
- onLoadMore();
14713
- }
14714
- }
14715
- return () => observer.disconnect();
14716
- }, [hasMore, onLoadMore]);
14717
- return /* @__PURE__ */ jsx("div", {
14718
- ref: setNodeRef,
14719
- className: useMemo(() => cls("flex flex-col p-2 transition-opacity duration-100 transition-bg ease-linear w-full overflow-y-auto no-scrollbar flex-1 rounded-md", isDragging && isDragOverColumn ? "bg-surface-accent-200 dark:bg-surface-900" : isDragging ? "bg-surface-50 dark:bg-surface-900 hover:bg-surface-accent-100 dark:hover:bg-surface-800" : "bg-surface-50 dark:bg-surface-900"), [isDragging, isDragOverColumn]),
14720
- style: { minHeight: 80 },
14721
- children: /* @__PURE__ */ jsx(SortableContext, {
14722
- items: items.map((i) => i.id),
14723
- strategy: verticalListSortingStrategy,
14724
- children: items.length === 0 && !loading ? /* @__PURE__ */ jsx("div", {
14725
- className: "flex-1 flex items-center justify-center",
14726
- children: /* @__PURE__ */ jsx("span", {
14727
- className: "text-xs text-surface-400 dark:text-surface-500",
14728
- children: "No items"
14729
- })
14730
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [items.map((item, index) => /* @__PURE__ */ jsx(SortableItem, {
14731
- item,
14732
- index,
14733
- columnId,
14734
- ItemComponent
14735
- }, item.id)), (loading || hasMore) && /* @__PURE__ */ jsx("div", {
14736
- ref: sentinelRef,
14737
- className: "flex items-center justify-center py-2 min-h-6",
14738
- children: loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
14739
- })] })
14740
- })
14741
- });
14742
- }
14743
- var SortableItem = memo(function SortableItem({ item, index, columnId, ItemComponent }) {
14744
- const { setNodeRef, attributes, listeners, isDragging: isItemBeingDragged, transform, transition } = useSortable({
14745
- id: item.id,
14746
- data: {
14747
- type: "ITEM",
14748
- columnId
14749
- }
14750
- });
14751
- return /* @__PURE__ */ jsx("div", {
14752
- ref: setNodeRef,
14753
- style: useMemo(() => ({
14754
- transform: CSS.Transform.toString(transform),
14755
- transition,
14756
- zIndex: isItemBeingDragged ? 2 : 1,
14757
- opacity: isItemBeingDragged ? 0 : 1
14758
- }), [
14759
- transform,
14760
- transition,
14761
- isItemBeingDragged
14762
- ]),
14763
- ...attributes,
14764
- ...listeners,
14765
- children: /* @__PURE__ */ jsx(ItemComponent, {
14766
- item,
14767
- isDragging: isItemBeingDragged,
14768
- index
14769
- })
14770
- });
14771
- });
14772
- //#endregion
14773
- //#region src/components/EntityCollectionView/BoardColumnTitle.tsx
14774
- function BoardColumnTitle({ children, className, color, ...props }) {
14775
- const colorScheme = useMemo(() => {
14776
- if (!color) return void 0;
14777
- if (typeof color === "string") return getColorSchemeForKey(color);
14778
- return color;
14779
- }, [color]);
14780
- return /* @__PURE__ */ jsxs(Typography, {
14781
- variant: "subtitle2",
14782
- component: "h4",
14783
- className: cls("py-3 px-3 transition-colors duration-200 flex-grow select-none relative outline-none focus:outline focus:outline-2 focus:outline-offset-2 flex items-center gap-3", className),
14784
- ...props,
14785
- children: [colorScheme && /* @__PURE__ */ jsx("div", {
14786
- className: "w-3 h-3 rounded-full flex-shrink-0",
14787
- style: { backgroundColor: colorScheme.darkColor ?? colorScheme.color }
14788
- }), children]
14789
- });
14790
- }
14791
- //#endregion
14792
- //#region src/components/EntityCollectionView/BoardColumn.tsx
14793
- var BoardColumn = memo(function BoardColumn({ id, title, items, ItemComponent, isDragging, isDragOverColumn, allowReorder = false, loading = false, hasMore = false, onLoadMore, onAddItem, totalCount, color, style }) {
14794
- const { setNodeRef, attributes, listeners, isDragging: isColumnBeingDragged, transform, transition } = useSortable({
14795
- id,
14796
- data: { type: "COLUMN" },
14797
- disabled: !allowReorder
14798
- });
14799
- const combinedStyle = useMemo(() => ({
14800
- ...style,
14801
- transform: CSS.Translate.toString(transform),
14802
- transition,
14803
- zIndex: isColumnBeingDragged ? 2 : 1
14804
- }), [
14805
- style,
14806
- transform,
14807
- transition,
14808
- isColumnBeingDragged
14809
- ]);
14810
- const dragListeners = allowReorder ? listeners : {};
14811
- const columnClassName = useMemo(() => cls("border h-full w-80 min-w-80 mx-2 flex flex-col rounded-md", defaultBorderMixin, isColumnBeingDragged ? "ring-2 ring-primary" : ""), [isColumnBeingDragged]);
14812
- const headerClassName = useMemo(() => cls("flex items-center justify-between px-2 rounded-t-md transition-colors duration-200 ease-in-out", isColumnBeingDragged ? "bg-surface-100 dark:bg-surface-700" : "bg-surface-50 hover:bg-surface-100 dark:bg-surface-800 dark:hover:bg-surface-700", allowReorder ? "cursor-grab" : ""), [isColumnBeingDragged, allowReorder]);
14813
- const itemIds = useMemo(() => items.map((i) => i.id), [items]);
14814
- return /* @__PURE__ */ jsxs("div", {
14815
- ref: setNodeRef,
14816
- style: combinedStyle,
14817
- ...attributes,
14818
- className: columnClassName,
14819
- children: [/* @__PURE__ */ jsxs("div", {
14820
- ...dragListeners,
14821
- className: headerClassName,
14822
- children: [/* @__PURE__ */ jsxs("div", {
14823
- className: "flex items-center gap-2",
14824
- children: [/* @__PURE__ */ jsx(BoardColumnTitle, {
14825
- "aria-label": `${title} item list`,
14826
- color,
14827
- children: title
14828
- }), totalCount !== void 0 && /* @__PURE__ */ jsx("span", {
14829
- className: "text-xs text-surface-500 dark:text-surface-400",
14830
- children: totalCount
14831
- })]
14832
- }), onAddItem && /* @__PURE__ */ jsx(IconButton, {
14833
- size: "small",
14834
- onClick: (e) => {
14835
- e.stopPropagation();
14836
- onAddItem();
14837
- },
14838
- className: "opacity-60 hover:opacity-100",
14839
- children: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.small })
14840
- })]
14841
- }), /* @__PURE__ */ jsx(SortableContext, {
14842
- items: itemIds,
14843
- strategy: verticalListSortingStrategy,
14844
- children: /* @__PURE__ */ jsx(BoardSortableList, {
14845
- columnId: id,
14846
- items,
14847
- ItemComponent,
14848
- isDragging,
14849
- isDragOverColumn,
14850
- loading,
14851
- hasMore,
14852
- onLoadMore
14853
- })
14854
- })]
14855
- });
14856
- });
14857
- //#endregion
14858
- //#region src/components/EntityCollectionView/Board.tsx
14859
- function Board({ data, columns: columnsProp, columnLabels, columnColors, className, assignColumn, allowColumnReorder = false, onColumnReorder, onItemsReorder, ItemComponent, columnLoadingState, onLoadMoreColumn, onAddItemToColumn, AddColumnComponent }) {
14860
- const [activeItem, setActiveItem] = useState(null);
14861
- const [activeColumn, setActiveColumn] = useState(null);
14862
- const [isDragging, setIsDragging] = useState(false);
14863
- const [dragOverColumnId, setDragOverColumnId] = useState(null);
14864
- const grabOffsetRef = useRef({
14865
- x: 0,
14866
- y: 0
14867
- });
14868
- const [overlayPos, setOverlayPos] = useState(null);
14869
- const handleMouseMove = useCallback((e) => {
14870
- setOverlayPos({
14871
- x: e.clientX - grabOffsetRef.current.x,
14872
- y: e.clientY - grabOffsetRef.current.y
14873
- });
14874
- }, []);
14875
- useEffect(() => {
14876
- return () => {
14877
- window.removeEventListener("mousemove", handleMouseMove);
14878
- window.removeEventListener("pointermove", handleMouseMove);
14879
- };
14880
- }, [handleMouseMove]);
14881
- const [itemMapState, setItemMapState] = useState(() => {
14882
- const dataColumnMap = data.reduce((prev, item) => ({
14883
- ...prev,
14884
- [item.id]: assignColumn(item)
14885
- }), {});
14886
- return columnsProp.reduce((previous, column) => ({
14887
- ...previous,
14888
- [column]: data.filter((item) => dataColumnMap[item.id] === column)
14889
- }), {});
14890
- });
14891
- const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
14892
- useEffect(() => {
14893
- if (isDragging) return;
14894
- const dataColumnMap = data.reduce((prev, item) => ({
14895
- ...prev,
14896
- [item.id]: assignColumn(item)
14897
- }), {});
14898
- const newItemMap = columnsProp.reduce((previous, column) => ({
14899
- ...previous,
14900
- [column]: data.filter((item) => dataColumnMap[item.id] === column)
14901
- }), {});
14902
- setItemMapState((prevMap) => {
14903
- let changed = false;
14904
- for (const col of columnsProp) {
14905
- const prevItems = prevMap[col] ?? [];
14906
- const newItems = newItemMap[col] ?? [];
14907
- if (prevItems.length !== newItems.length) {
14908
- changed = true;
14909
- continue;
14910
- }
14911
- for (let i = 0; i < prevItems.length; i++) if (prevItems[i].id !== newItems[i].id || prevItems[i].entity !== newItems[i].entity) {
14912
- changed = true;
14913
- break;
14914
- }
14915
- }
14916
- if (!changed) return prevMap;
14917
- const updated = {};
14918
- for (const col of columnsProp) {
14919
- const prevItems = prevMap[col] ?? [];
14920
- const newItems = newItemMap[col] ?? [];
14921
- const prevById = /* @__PURE__ */ new Map();
14922
- for (const item of prevItems) prevById.set(item.id, item);
14923
- updated[col] = newItems.map((newItem) => {
14924
- const prev = prevById.get(newItem.id);
14925
- if (prev && prev.entity === newItem.entity) return prev;
14926
- return newItem;
14927
- });
14928
- }
14929
- return updated;
14930
- });
14931
- }, [
14932
- data,
14933
- columnsProp,
14934
- assignColumn
14935
- ]);
14936
- const findColumnByItemId = (id) => {
14937
- return Object.keys(itemMapState).find((col) => itemMapState[col]?.some((i) => i.id === id));
14938
- };
14939
- const handleDragStart = (event) => {
14940
- setIsDragging(true);
14941
- setDragOverColumnId(null);
14942
- const { active } = event;
14943
- const activatorEvt = event.activatorEvent;
14944
- if (activatorEvt) {
14945
- const target = activatorEvt.target;
14946
- const rect = (target.closest("[role='button']") ?? target).getBoundingClientRect();
14947
- grabOffsetRef.current = {
14948
- x: activatorEvt.clientX - rect.left,
14949
- y: activatorEvt.clientY - rect.top
14950
- };
14951
- setOverlayPos({
14952
- x: rect.left,
14953
- y: rect.top
14954
- });
14955
- }
14956
- window.addEventListener("mousemove", handleMouseMove);
14957
- window.addEventListener("pointermove", handleMouseMove);
14958
- if (active.data.current?.type === "COLUMN") {
14959
- const columnId = active.id;
14960
- const column = columnsProp.find((col) => String(col) === columnId);
14961
- if (column) setActiveColumn(column);
14962
- } else if (active.data.current?.type === "ITEM") {
14963
- const columnId = findColumnByItemId(active.id);
14964
- if (columnId) {
14965
- const item = itemMapState[columnId]?.find((i) => i.id === active.id);
14966
- setActiveItem(item || null);
14967
- }
14968
- }
14969
- };
14970
- const handleDragOver = (event) => {
14971
- const { active, over } = event;
14972
- if (!over) {
14973
- setDragOverColumnId(null);
14974
- return;
14975
- }
14976
- let currentHoveredColumnId = null;
14977
- const overId = over.id;
14978
- const overDataType = over.data.current?.type;
14979
- if (overDataType === "ITEM-LIST" || overDataType === "COLUMN") currentHoveredColumnId = overId;
14980
- else if (overDataType === "ITEM") currentHoveredColumnId = findColumnByItemId(overId) || null;
14981
- else if (columnsProp.includes(overId)) currentHoveredColumnId = overId;
14982
- setDragOverColumnId(currentHoveredColumnId);
14983
- if (active.data.current?.type !== "ITEM") return;
14984
- const activeId = active.id;
14985
- const activeColumn = findColumnByItemId(activeId);
14986
- let overColumnForMove = findColumnByItemId(overId);
14987
- if (!overColumnForMove && overDataType === "ITEM-LIST") overColumnForMove = overId;
14988
- if (!overColumnForMove && columnsProp.includes(overId)) overColumnForMove = overId;
14989
- if (!activeColumn || !overColumnForMove) return;
14990
- if (activeColumn === overColumnForMove) return;
14991
- if (itemMapState[overColumnForMove]?.some((i) => i.id === activeId)) return;
14992
- setItemMapState((currentMap) => {
14993
- const activeItems = [...currentMap[activeColumn] || []];
14994
- const overItems = [...currentMap[overColumnForMove] || []];
14995
- const activeIndex = activeItems.findIndex((i) => i.id === activeId);
14996
- if (activeIndex === -1) return currentMap;
14997
- let overIndex;
14998
- if (overDataType === "ITEM-LIST" || columnsProp.includes(overId) && !findColumnByItemId(overId)) overIndex = overItems.length;
14999
- else {
15000
- overIndex = overItems.findIndex((i) => i.id === overId);
15001
- if (overIndex !== -1) {
15002
- const modifier = (active.rect.current.translated?.top ?? 0) + (active.rect.current.translated?.height ?? 0) / 2 > (over?.rect.top ?? 0) + (over?.rect.height ?? 0) / 2 ? 1 : 0;
15003
- overIndex = overIndex >= 0 ? overIndex + modifier : overItems.length;
15004
- } else overIndex = overItems.length;
15005
- }
15006
- const newItemMap = { ...currentMap };
15007
- const [moved] = activeItems.splice(activeIndex, 1);
15008
- overItems.splice(overIndex, 0, moved);
15009
- newItemMap[activeColumn] = activeItems;
15010
- newItemMap[overColumnForMove] = overItems;
15011
- return newItemMap;
15012
- });
15013
- };
15014
- const handleDragEnd = (event) => {
15015
- const { active, over } = event;
15016
- window.removeEventListener("mousemove", handleMouseMove);
15017
- window.removeEventListener("pointermove", handleMouseMove);
15018
- setOverlayPos(null);
15019
- setIsDragging(false);
15020
- setActiveItem(null);
15021
- setActiveColumn(null);
15022
- setDragOverColumnId(null);
15023
- if (!over) return;
15024
- const activeId = active.id;
15025
- const overId = over.id;
15026
- if (active.data.current?.type === "COLUMN" && over.data.current?.type === "COLUMN" && activeId !== overId) {
15027
- const oldIndex = columnsProp.findIndex((col) => String(col) === activeId);
15028
- const newIndex = columnsProp.findIndex((col) => String(col) === overId);
15029
- if (oldIndex !== -1 && newIndex !== -1 && onColumnReorder && allowColumnReorder) onColumnReorder(arrayMove([...columnsProp], oldIndex, newIndex));
15030
- } else if (active.data.current?.type === "ITEM" && onItemsReorder) {
15031
- const originalColumn = data.find((item) => item.id === activeId) ? assignColumn(data.find((item) => item.id === activeId)) : void 0;
15032
- const currentColumn = findColumnByItemId(activeId);
15033
- const overColumn = findColumnByItemId(overId) || currentColumn;
15034
- const finalItemMapState = { ...itemMapState };
15035
- if (currentColumn && currentColumn === overColumn) {
15036
- const activeIndex = finalItemMapState[currentColumn].findIndex((i) => i.id === activeId);
15037
- let overIndex = finalItemMapState[overColumn].findIndex((i) => i.id === overId);
15038
- const overType = over.data.current?.type;
15039
- if (overIndex === -1 && (overType === "COLUMN" || overType === "ITEM-LIST")) if (originalColumn === currentColumn) overIndex = finalItemMapState[overColumn].length;
15040
- else overIndex = activeIndex;
15041
- else if (overIndex === -1 && typeof over.data.current?.sortable?.index === "number") overIndex = over.data.current.sortable.index;
15042
- if (activeIndex !== -1 && overIndex !== -1 && activeIndex !== overIndex) {
15043
- finalItemMapState[currentColumn] = arrayMove(finalItemMapState[currentColumn], activeIndex, overIndex);
15044
- setItemMapState(finalItemMapState);
15045
- }
15046
- }
15047
- if (originalColumn !== currentColumn && originalColumn && currentColumn) onItemsReorder(finalItemMapState[currentColumn], {
15048
- itemId: activeId,
15049
- sourceColumn: originalColumn,
15050
- targetColumn: currentColumn
15051
- });
15052
- else if (currentColumn) onItemsReorder(finalItemMapState[currentColumn], {
15053
- itemId: activeId,
15054
- sourceColumn: currentColumn,
15055
- targetColumn: currentColumn
15056
- });
15057
- }
15058
- };
15059
- const customCollisionDetection = (args) => {
15060
- const pointerCollisions = pointerWithin(args);
15061
- if (pointerCollisions.length > 0) return pointerCollisions;
15062
- return rectIntersection(args);
15063
- };
15064
- return /* @__PURE__ */ jsxs(DndContext, {
15065
- sensors,
15066
- collisionDetection: customCollisionDetection,
15067
- onDragStart: handleDragStart,
15068
- onDragOver: handleDragOver,
15069
- onDragEnd: handleDragEnd,
15070
- children: [
15071
- /* @__PURE__ */ jsx(DragOverlay, {
15072
- dropAnimation: null,
15073
- style: { opacity: 0 },
15074
- children: activeItem || activeColumn ? /* @__PURE__ */ jsx("div", { style: {
15075
- width: 1,
15076
- height: 1
15077
- } }) : null
15078
- }),
15079
- overlayPos && (activeItem || activeColumn) && createPortal(/* @__PURE__ */ jsx("div", {
15080
- className: document.documentElement.classList.contains("dark") ? "dark" : "",
15081
- style: {
15082
- position: "fixed",
15083
- top: overlayPos.y,
15084
- left: overlayPos.x,
15085
- zIndex: 9999,
15086
- pointerEvents: "none",
15087
- touchAction: "none"
15088
- },
15089
- children: activeItem ? /* @__PURE__ */ jsx(ItemComponent, {
15090
- item: activeItem,
15091
- isDragging: true,
15092
- index: -1,
15093
- style: {
15094
- boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
15095
- opacity: .9,
15096
- width: "304px"
15097
- }
15098
- }) : activeColumn ? /* @__PURE__ */ jsx(BoardColumn, {
15099
- index: -1,
15100
- id: String(activeColumn),
15101
- title: columnLabels?.[activeColumn] ?? String(activeColumn),
15102
- items: itemMapState[String(activeColumn)] || [],
15103
- ItemComponent,
15104
- isDragging: true,
15105
- isDragOverColumn: false
15106
- }, String(activeColumn)) : null
15107
- }), document.body),
15108
- /* @__PURE__ */ jsx(SortableContext, {
15109
- items: columnsProp.map(String),
15110
- strategy: horizontalListSortingStrategy,
15111
- children: /* @__PURE__ */ jsxs("div", {
15112
- className: cls("p-2 md:p-3 lg:p-4 h-full min-w-full inline-flex", className),
15113
- children: [columnsProp.map((key, index) => /* @__PURE__ */ jsx(BoardColumn, {
15114
- index,
15115
- id: String(key),
15116
- title: columnLabels?.[key] ?? String(key),
15117
- color: columnColors?.[key],
15118
- items: itemMapState[String(key)] || [],
15119
- ItemComponent,
15120
- isDragging,
15121
- isDragOverColumn: String(key) === dragOverColumnId,
15122
- allowReorder: allowColumnReorder,
15123
- loading: columnLoadingState?.[String(key)]?.loading,
15124
- hasMore: columnLoadingState?.[String(key)]?.hasMore,
15125
- totalCount: columnLoadingState?.[String(key)]?.totalCount,
15126
- onLoadMore: onLoadMoreColumn ? () => onLoadMoreColumn(key) : void 0,
15127
- onAddItem: onAddItemToColumn ? () => onAddItemToColumn(key) : void 0,
15128
- style: { opacity: activeColumn === key ? 0 : 1 }
15129
- }, String(key))), AddColumnComponent]
15130
- })
15131
- })
15132
- ]
15133
- });
15134
- }
15135
- //#endregion
15136
14260
  //#region src/components/EntityCollectionView/EntityBoardCard.tsx
15137
14261
  /**
15138
14262
  * Compact card component for displaying an entity in a Kanban board.
@@ -15143,7 +14267,7 @@ function Board({ data, columns: columnsProp, columnLabels, columnColors, classNa
15143
14267
  * layout clean while providing intuitive selection affordance.
15144
14268
  */
15145
14269
  function EntityBoardCardInner({ item, isDragging, isGroupedOver, style, collection, onClick, selected, onSelectionChange, selectionEnabled = false }) {
15146
- const entity = item.entity;
14270
+ const entity = item.data;
15147
14271
  const slotKeys = useCollectionSlotKeys(collection, useAuthController(), useCustomizationController().propertyConfigs);
15148
14272
  const slots = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
15149
14273
  entity,
@@ -15405,18 +14529,10 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
15405
14529
  }));
15406
14530
  return;
15407
14531
  }
15408
- const whereMap = {};
15409
- if (currentFilterValues) Object.entries(currentFilterValues).forEach(([key, value]) => {
15410
- if (value && Array.isArray(value)) {
15411
- const [op, val] = value;
15412
- const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
15413
- let stringVal;
15414
- if (Array.isArray(val)) stringVal = `(${val.join(",")})`;
15415
- else stringVal = String(val);
15416
- whereMap[key] = `${postgrestOp}.${stringVal}`;
15417
- }
15418
- });
15419
- whereMap[currentColumnProperty] = `eq.${column}`;
14532
+ const whereFilter = {
14533
+ ...currentFilterValues ?? {},
14534
+ [currentColumnProperty]: ["==", column]
14535
+ };
15420
14536
  const orderByParam = currentOrderProperty ? `${currentOrderProperty}:asc` : void 0;
15421
14537
  setColumnData((prev) => ({
15422
14538
  ...prev,
@@ -15525,13 +14641,13 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
15525
14641
  const accessor = currentDataClient.collection(currentResolvedPath);
15526
14642
  if (accessor.listen) {
15527
14643
  const unsubscribe = accessor.listen({
15528
- where: whereMap,
14644
+ where: whereFilter,
15529
14645
  limit: itemCount,
15530
14646
  orderBy: orderByParam
15531
14647
  }, (res) => onUpdate(res.data), onError);
15532
14648
  unsubscribersRef.current[column] = unsubscribe;
15533
14649
  } else accessor.find({
15534
- where: whereMap,
14650
+ where: whereFilter,
15535
14651
  limit: itemCount,
15536
14652
  orderBy: orderByParam
15537
14653
  }).then((res) => onUpdate(res.data)).catch(onError);
@@ -15560,19 +14676,11 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
15560
14676
  if (isColumnExcludedByFilter(column, currentFilterValues, currentColumnProperty)) return;
15561
14677
  const accessor = currentDataClient.collection(currentResolvedPath);
15562
14678
  if (accessor.count) {
15563
- const whereMap = {};
15564
- if (currentFilterValues) Object.entries(currentFilterValues).forEach(([key, value]) => {
15565
- if (value && Array.isArray(value)) {
15566
- const [op, val] = value;
15567
- const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
15568
- let stringVal;
15569
- if (Array.isArray(val)) stringVal = `(${val.join(",")})`;
15570
- else stringVal = String(val);
15571
- whereMap[key] = `${postgrestOp}.${stringVal}`;
15572
- }
15573
- });
15574
- whereMap[currentColumnProperty] = `eq.${column}`;
15575
- accessor.count({ where: whereMap }).then((count) => {
14679
+ const whereFilter = {
14680
+ ...currentFilterValues ?? {},
14681
+ [currentColumnProperty]: ["==", column]
14682
+ };
14683
+ accessor.count({ where: whereFilter }).then((count) => {
15576
14684
  if (isCleaningUpRef.current) return;
15577
14685
  setColumnData((prev) => ({
15578
14686
  ...prev,
@@ -15758,7 +14866,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
15758
14866
  //#region src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts
15759
14867
  function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
15760
14868
  const handleItemsReorder = useCallback(async (items, moveInfo) => {
15761
- const entity = items.find((item) => item.id === moveInfo?.itemId)?.entity;
14869
+ const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
15762
14870
  if (!entity || !moveInfo) return;
15763
14871
  analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
15764
14872
  path: fullPath,
@@ -15772,8 +14880,8 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
15772
14880
  if (orderProperty) {
15773
14881
  const targetColumnItems = items;
15774
14882
  const movedIndex = targetColumnItems.findIndex((item) => item.id === moveInfo.itemId);
15775
- const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].entity.values?.[orderProperty] ?? null : null;
15776
- const nextKey = movedIndex < targetColumnItems.length - 1 ? targetColumnItems[movedIndex + 1].entity.values?.[orderProperty] ?? null : null;
14883
+ const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].data.values?.[orderProperty] ?? null : null;
14884
+ const nextKey = movedIndex < targetColumnItems.length - 1 ? targetColumnItems[movedIndex + 1].data.values?.[orderProperty] ?? null : null;
15777
14885
  try {
15778
14886
  const a = prevKey;
15779
14887
  let b = nextKey;
@@ -15980,7 +15088,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
15980
15088
  completed++;
15981
15089
  if (completed === 2) setMissingOrderCount(Math.max(0, totalCount - orderedCount));
15982
15090
  }).catch((e) => console.warn("Failed to get total count:", e));
15983
- accessor.count({ where: { [orderProperty]: "neq.null" } }).then((count) => {
15091
+ accessor.count({ where: { [orderProperty]: ["!=", null] } }).then((count) => {
15984
15092
  orderedCount = count;
15985
15093
  completed++;
15986
15094
  if (completed === 2) setMissingOrderCount(Math.max(0, totalCount - orderedCount));
@@ -16012,7 +15120,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16012
15120
  const boardItems = useMemo(() => {
16013
15121
  return allEntities.map((entity) => ({
16014
15122
  id: String(entity.id),
16015
- entity
15123
+ data: entity
16016
15124
  }));
16017
15125
  }, [allEntities]);
16018
15126
  const columnLoadingState = useMemo(() => {
@@ -16031,7 +15139,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16031
15139
  const assignColumn = useCallback((item) => {
16032
15140
  const column = entityColumnMap[item.id];
16033
15141
  if (column) return column;
16034
- const value = item.entity.values?.[columnProperty];
15142
+ const value = item.data.values?.[columnProperty];
16035
15143
  if (value && columns.includes(String(value))) return String(value);
16036
15144
  return columns[0] || "";
16037
15145
  }, [
@@ -16125,7 +15233,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16125
15233
  ...props,
16126
15234
  collection: collectionRef.current,
16127
15235
  onClick: stableOnClick,
16128
- selected: isEntitySelectedRef.current(props.item.entity),
15236
+ selected: isEntitySelectedRef.current(props.item.data),
16129
15237
  onSelectionChange: stableOnSelectionChange,
16130
15238
  selectionEnabled: selectionEnabledRef.current
16131
15239
  });
@@ -16227,7 +15335,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16227
15335
  }),
16228
15336
  /* @__PURE__ */ jsx("div", {
16229
15337
  className: "flex-1 overflow-auto no-scrollbar",
16230
- children: /* @__PURE__ */ jsx(Board, {
15338
+ children: /* @__PURE__ */ jsx(KanbanView, {
16231
15339
  data: boardItems,
16232
15340
  columns,
16233
15341
  columnLabels,
@@ -17269,7 +16377,7 @@ var EntityCollectionViewInner = React.memo(function EntityCollectionViewInner({
17269
16377
  const collectionRegistry = useCollectionRegistryController();
17270
16378
  const urlController = useUrlController();
17271
16379
  const breadcrumbs = useBreadcrumbsController();
17272
- const path = pathProp ?? collectionProp.slug;
16380
+ const path = pathProp ?? getCollectionDataPath(collectionProp);
17273
16381
  const dataClient = useData();
17274
16382
  const sideEntityController = useSideEntityController();
17275
16383
  useAuthController();
@@ -17808,7 +16916,7 @@ var EntityCollectionViewInner = React.memo(function EntityCollectionViewInner({
17808
16916
  collection,
17809
16917
  tableController,
17810
16918
  path,
17811
- relativePath: collection.slug,
16919
+ relativePath: getCollectionDataPath(collection),
17812
16920
  selectionController: usedSelectionController,
17813
16921
  collectionEntitiesCount: docsCount ?? void 0,
17814
16922
  resolvedProperties: resolvedCollection.properties,
@@ -17824,7 +16932,7 @@ var EntityCollectionViewInner = React.memo(function EntityCollectionViewInner({
17824
16932
  onNewClick,
17825
16933
  openNewDocument,
17826
16934
  path,
17827
- relativePath: collection.slug,
16935
+ relativePath: getCollectionDataPath(collection),
17828
16936
  selectionController: usedSelectionController,
17829
16937
  selectionEnabled: activeSelectionEnabled,
17830
16938
  collectionEntitiesCount: docsCount ?? void 0,
@@ -18089,18 +17197,7 @@ function EntitiesCount({ path, collection, filter, sortBy, onCountChange }) {
18089
17197
  return;
18090
17198
  }
18091
17199
  let cancelled = false;
18092
- const whereMap = {};
18093
- if (filter) Object.entries(filter).forEach(([key, value]) => {
18094
- if (value && Array.isArray(value)) {
18095
- const [op, val] = value;
18096
- const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
18097
- let stringVal;
18098
- if (Array.isArray(val)) stringVal = `(${val.join(",")})`;
18099
- else stringVal = String(val);
18100
- whereMap[key] = `${postgrestOp}.${stringVal}`;
18101
- }
18102
- });
18103
- const whereParams = Object.keys(whereMap).length > 0 ? whereMap : void 0;
17200
+ const whereParams = filter && Object.keys(filter).length > 0 ? filter : void 0;
18104
17201
  const orderByParams = sortByProperty ? `${String(sortByProperty)}:${currentSort}` : void 0;
18105
17202
  const cacheKey = `${path}|${filterKey}|${sortByProperty ?? ""}|${currentSort ?? ""}`;
18106
17203
  let countPromise = inflightCountRequests.get(cacheKey);
@@ -18733,7 +17830,7 @@ function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, d
18733
17830
  })
18734
17831
  ]
18735
17832
  }), /* @__PURE__ */ jsx("div", {
18736
- className: cls("transition-all duration-200 ease-in-out", "overflow-hidden", !hideHeader && "dark:bg-transparent", !hideHeader ? drawerOpen ? "rounded-lg" : "rounded-lg" : "rounded-lg", !hideHeader && collapsed ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"),
17833
+ className: cls("transition-all duration-200 ease-in-out", "overflow-hidden", !hideHeader && "dark:bg-transparent", "rounded-lg", !hideHeader && collapsed ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"),
18737
17834
  children: entries.map((entry) => /* @__PURE__ */ jsx(ResolvedDrawerNavigationItem, {
18738
17835
  icon: /* @__PURE__ */ jsx(IconForView, {
18739
17836
  collectionOrView: entry.collection ?? entry.view,
@@ -18823,7 +17920,13 @@ function CMSNavigationContent() {
18823
17920
  const registry = useRebaseRegistry();
18824
17921
  const ResolvedDrawerNavigationGroup = useComponentOverride("Shell.DrawerNavigationGroup", DrawerNavigationGroup);
18825
17922
  const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
18826
- const filteredEntries = adminModeController.mode === "studio" ? allNavigationEntries.filter((e) => e.type === "view" || e.type === "admin") : allNavigationEntries.filter((e) => e.type !== "view");
17923
+ const studioViewSlugs = useMemo(() => {
17924
+ const slugs = /* @__PURE__ */ new Set();
17925
+ (registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
17926
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
17927
+ return slugs;
17928
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
17929
+ const filteredEntries = adminModeController.mode === "studio" ? allNavigationEntries.filter((e) => e.type === "view" && studioViewSlugs.has(e.slug) || e.type === "admin") : allNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
18827
17930
  const entryGroups = new Set(filteredEntries.map((e) => e.group).filter(Boolean));
18828
17931
  const orderedGroups = navigationState.topLevelNavigation?.groups ?? [];
18829
17932
  const groupsToRender = [...orderedGroups.filter((g) => entryGroups.has(g)), ...[...entryGroups].filter((g) => !orderedGroups.includes(g))];
@@ -19261,15 +18364,15 @@ function DrawerWrapper(props) {
19261
18364
  * is auto-wired as a native feature (slots, provider, Studio view) without
19262
18365
  * needing any external plugin.
19263
18366
  */
19264
- function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
18367
+ function RebaseCMS({ collections, views, homePage, entityViews, entityActions, collectionEditor, navigationGroupMappings }) {
19265
18368
  const dispatch = useRebaseRegistryDispatch();
19266
18369
  useLayoutEffect(() => {
19267
18370
  dispatch.registerCMS({
19268
18371
  collections,
18372
+ views,
19269
18373
  homePage,
19270
18374
  entityViews,
19271
18375
  entityActions,
19272
- plugins,
19273
18376
  collectionEditor,
19274
18377
  navigationGroupMappings
19275
18378
  });
@@ -19277,10 +18380,10 @@ function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins,
19277
18380
  }, [
19278
18381
  dispatch,
19279
18382
  collections,
18383
+ views,
19280
18384
  homePage,
19281
18385
  entityViews,
19282
18386
  entityActions,
19283
- plugins,
19284
18387
  collectionEditor,
19285
18388
  navigationGroupMappings
19286
18389
  ]);
@@ -19416,7 +18519,7 @@ function useLocalCollectionsConfigController(clientOrUrl, baseCollections = [],
19416
18519
  //#region src/components/RebaseNavigation.tsx
19417
18520
  var EMPTY_PLUGINS = [];
19418
18521
  var EMPTY_COLLECTIONS = [];
19419
- var CollectionsStudioView = lazy(() => import("./CollectionsStudioView-B1mN33GB.js").then((n) => n.n).then((m) => ({ default: m.CollectionsStudioView })));
18522
+ var CollectionsStudioView = lazy(() => import("./RouterCollectionsStudioView-BQTIExjA.js").then((n) => n.n).then((m) => ({ default: m.RouterCollectionsStudioView })));
19420
18523
  /**
19421
18524
  * Navigation layer — builds and provides all CMS navigation controllers:
19422
18525
  * collection registry, URL controller, navigation state, side entity,
@@ -19449,7 +18552,19 @@ function RebaseNavigation({ children }) {
19449
18552
  }, [collectionEditorConfig]);
19450
18553
  const cmsCollections = registry.cmsConfig?.collections ?? EMPTY_COLLECTIONS;
19451
18554
  const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
19452
- const collectionRegistryController = useBuildCollectionRegistryController({ userConfigPersistence });
18555
+ const dataSources = useDataSources();
18556
+ const collectionRegistryController = useBuildCollectionRegistryController({
18557
+ userConfigPersistence,
18558
+ dataSources: dataSources.registry
18559
+ });
18560
+ const defaultData = useData();
18561
+ const getCollectionRef = useRef(collectionRegistryController.getCollection);
18562
+ getCollectionRef.current = collectionRegistryController.getCollection;
18563
+ const routedData = useMemo(() => buildRoutedRebaseData({
18564
+ defaultData,
18565
+ sources: dataSources.sources,
18566
+ resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
18567
+ }), [defaultData, dataSources]);
19453
18568
  const urlController = useBuildUrlController({
19454
18569
  basePath: "/",
19455
18570
  baseCollectionPath: "/c",
@@ -19457,7 +18572,14 @@ function RebaseNavigation({ children }) {
19457
18572
  });
19458
18573
  const rebaseClient = useRebaseClient();
19459
18574
  const authController = useAuthController();
19460
- const internalConfigController = useLocalCollectionsConfigController(rebaseClient, useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]), collectionEditorEnabled ? {
18575
+ const resolvedCollections = useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]);
18576
+ useEffect(() => {
18577
+ if (process.env.NODE_ENV === "production") return;
18578
+ const { registry, sources } = dataSources;
18579
+ const missing = Object.values(registry).filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key]).map((d) => d.key);
18580
+ if (missing.length > 0) console.warn(`[Rebase] These data source(s) declare a direct/custom transport but have no client-side driver and will fall back to the default data source: ${missing.map((k) => `"${k}"`).join(", ")}. Provide a \`driver\` for them in \`dataSources\` on <Rebase>.`);
18581
+ }, [dataSources]);
18582
+ const internalConfigController = useLocalCollectionsConfigController(rebaseClient, resolvedCollections, collectionEditorEnabled ? {
19461
18583
  readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
19462
18584
  getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
19463
18585
  } : { readOnly: true });
@@ -19474,6 +18596,7 @@ function RebaseNavigation({ children }) {
19474
18596
  return {
19475
18597
  slug: "schema",
19476
18598
  name: "Edit collections",
18599
+ group: "Database",
19477
18600
  icon: "LayoutList",
19478
18601
  nestedRoutes: true,
19479
18602
  view: /* @__PURE__ */ jsx(Suspense, {
@@ -19491,18 +18614,27 @@ function RebaseNavigation({ children }) {
19491
18614
  if (schemaView) return [...base, schemaView];
19492
18615
  return base;
19493
18616
  }, [registry.studioConfig?.devViews, schemaView]);
18617
+ const cmsViews = registry.cmsConfig?.views;
18618
+ const mergedViews = useMemo(() => {
18619
+ if (!cmsViews) return devViews;
18620
+ if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
18621
+ if (typeof cmsViews === "function") return async (params) => {
18622
+ return [...await cmsViews(params) ?? [], ...devViews];
18623
+ };
18624
+ return [...cmsViews, ...devViews];
18625
+ }, [cmsViews, devViews]);
18626
+ const parentCustomizationController = useCustomizationController();
19494
18627
  const navigationStateController = useBuildNavigationStateController({
19495
- plugins: registry.cmsConfig?.plugins ?? EMPTY_PLUGINS,
18628
+ plugins: parentCustomizationController.plugins ?? EMPTY_PLUGINS,
19496
18629
  collections: collectionsBuilder,
19497
- views: devViews,
18630
+ views: mergedViews,
19498
18631
  navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
19499
18632
  authController: context.authController,
19500
- data: context.data,
18633
+ data: routedData,
19501
18634
  collectionRegistryController,
19502
18635
  urlController,
19503
18636
  adminMode: adminModeController?.mode
19504
18637
  });
19505
- const parentCustomizationController = useCustomizationController();
19506
18638
  const enrichedCustomizationController = useMemo(() => {
19507
18639
  const cmsEntityViews = registry.cmsConfig?.entityViews ?? [];
19508
18640
  const cmsEntityActions = registry.cmsConfig?.entityActions ?? [];
@@ -19517,22 +18649,25 @@ function RebaseNavigation({ children }) {
19517
18649
  registry.cmsConfig?.entityViews,
19518
18650
  registry.cmsConfig?.entityActions
19519
18651
  ]);
19520
- const navigationContent = /* @__PURE__ */ jsx(CustomizationControllerContext.Provider, {
19521
- value: enrichedCustomizationController,
19522
- children: /* @__PURE__ */ jsx(StudioBridgeRegistryProvider, { children: /* @__PURE__ */ jsx(CollectionRegistryContext.Provider, {
19523
- value: collectionRegistryController,
19524
- children: /* @__PURE__ */ jsx(UrlContext.Provider, {
19525
- value: urlController,
19526
- children: /* @__PURE__ */ jsx(NavigationStateContext.Provider, {
19527
- value: navigationStateController,
19528
- children: /* @__PURE__ */ jsxs(SideEntityProvider, { children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar, {
19529
- collectionRegistryController,
19530
- urlController,
19531
- navigationStateController
19532
- }), children] })
18652
+ const navigationContent = /* @__PURE__ */ jsx(RebaseDataContext.Provider, {
18653
+ value: routedData,
18654
+ children: /* @__PURE__ */ jsx(CustomizationControllerContext.Provider, {
18655
+ value: enrichedCustomizationController,
18656
+ children: /* @__PURE__ */ jsx(StudioBridgeRegistryProvider, { children: /* @__PURE__ */ jsx(CollectionRegistryContext.Provider, {
18657
+ value: collectionRegistryController,
18658
+ children: /* @__PURE__ */ jsx(UrlContext.Provider, {
18659
+ value: urlController,
18660
+ children: /* @__PURE__ */ jsx(NavigationStateContext.Provider, {
18661
+ value: navigationStateController,
18662
+ children: /* @__PURE__ */ jsxs(SideEntityProvider, { children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar, {
18663
+ collectionRegistryController,
18664
+ urlController,
18665
+ navigationStateController
18666
+ }), children] })
18667
+ })
19533
18668
  })
19534
- })
19535
- }) })
18669
+ }) })
18670
+ })
19536
18671
  });
19537
18672
  if (collectionEditorEnabled) return /* @__PURE__ */ jsx(ConfigControllerProvider, {
19538
18673
  collectionConfigController: internalConfigController,
@@ -20317,10 +19452,20 @@ function ContentHomePage({ additionalActions, additionalChildrenStart, additiona
20317
19452
  breadcrumbs.set({ breadcrumbs: [] });
20318
19453
  }, [breadcrumbs.set]);
20319
19454
  const { allowDragAndDrop = false, navigationEntries: unFilteredNavigationEntries = [], groups: unFilteredGroupOrder = [], onNavigationEntriesUpdate = () => {} } = navigationStateController.topLevelNavigation || {};
19455
+ const studioViewSlugs = useMemo(() => {
19456
+ const slugs = /* @__PURE__ */ new Set();
19457
+ (registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
19458
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
19459
+ return slugs;
19460
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
20320
19461
  const rawNavigationEntries = useMemo(() => {
20321
- if (adminModeController.mode === "studio") return unFilteredNavigationEntries.filter((e) => e.type === "view" || e.type === "admin");
20322
- return unFilteredNavigationEntries.filter((e) => e.type !== "view");
20323
- }, [unFilteredNavigationEntries, adminModeController.mode]);
19462
+ if (adminModeController.mode === "studio") return unFilteredNavigationEntries.filter((e) => e.type === "view" && studioViewSlugs.has(e.slug) || e.type === "admin");
19463
+ return unFilteredNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
19464
+ }, [
19465
+ unFilteredNavigationEntries,
19466
+ adminModeController.mode,
19467
+ studioViewSlugs
19468
+ ]);
20324
19469
  const groupOrderFromNavController = useMemo(() => {
20325
19470
  const entryGroups = new Set(rawNavigationEntries.map((e) => e.group).filter(Boolean));
20326
19471
  return unFilteredGroupOrder.filter((g) => entryGroups.has(g));
@@ -20850,18 +19995,24 @@ function EntityFullScreenRoute({ pathname, navigationEntries, isNew, isCopy }) {
20850
19995
  function CustomViewRoute({ view }) {
20851
19996
  const breadcrumbs = useBreadcrumbsController();
20852
19997
  const urlController = useUrlController();
19998
+ const breadcrumbsRef = useRef(breadcrumbs);
19999
+ breadcrumbsRef.current = breadcrumbs;
20853
20000
  useEffect(() => {
20854
- breadcrumbs.set({ breadcrumbs: [{
20001
+ breadcrumbsRef.current.set({ breadcrumbs: [{
20855
20002
  title: view.name,
20856
20003
  url: urlController.buildAppUrlPath(view.slug)
20857
20004
  }] });
20858
20005
  }, [view.slug, urlController]);
20859
- return view.view;
20006
+ if (typeof view.view === "function") {
20007
+ const ViewComponent = view.view;
20008
+ return /* @__PURE__ */ jsx(ViewComponent, {});
20009
+ }
20010
+ return /* @__PURE__ */ jsx(Fragment, { children: view.view });
20860
20011
  }
20861
20012
  //#endregion
20862
20013
  //#region src/components/CollectionEditorDialogs.tsx
20863
- var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-HNXGxXdO.js").then((n) => n.r).then((m) => ({ default: m.CollectionEditorDialog })));
20864
- var PropertyFormDialog = lazy(() => import("./PropertyEditView-C2wp8kQ9.js").then((n) => n.t).then((m) => ({ default: m.PropertyFormDialog })));
20014
+ var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-BO_aQ-Z4.js").then((n) => n.r).then((m) => ({ default: m.CollectionEditorDialog })));
20015
+ var PropertyFormDialog = lazy(() => import("./PropertyEditView-DKzOpMjR.js").then((n) => n.t).then((m) => ({ default: m.PropertyFormDialog })));
20865
20016
  /**
20866
20017
  * Renders the CollectionEditorDialog and PropertyFormDialog inside
20867
20018
  * the RebaseShell tree where CMS-internal contexts
@@ -21650,6 +20801,7 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
21650
20801
  content = /* @__PURE__ */ jsx(StorageThumbnail, {
21651
20802
  interactive,
21652
20803
  storeUrl: property.storage?.storeUrl ?? false,
20804
+ storageSourceKey: stringProperty.storage.storageSource,
21653
20805
  size: props.size,
21654
20806
  fill,
21655
20807
  storagePathOrDownloadUrl: filePath
@@ -21677,19 +20829,6 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
21677
20829
  propertyKey,
21678
20830
  size: props.size
21679
20831
  });
21680
- else if (stringProperty.reference) if (typeof stringProperty.reference.path === "string") content = /* @__PURE__ */ jsx(ReferencePreview, {
21681
- disabled: !stringProperty.reference.path,
21682
- previewProperties: stringProperty.reference?.ui?.previewProperties,
21683
- includeId: stringProperty.reference.includeId,
21684
- includeEntityLink: stringProperty.reference.includeEntityLink,
21685
- size: props.size,
21686
- reference: new EntityReference({
21687
- id: value,
21688
- path: stringProperty.reference.path
21689
- }),
21690
- textOnly: props.textOnly
21691
- });
21692
- else content = /* @__PURE__ */ jsx(EmptyValue, {});
21693
20832
  else content = /* @__PURE__ */ jsx(StringPropertyPreview, {
21694
20833
  ...props,
21695
20834
  property: stringProperty,
@@ -22185,7 +21324,7 @@ function LocalChangesMenu({ cachedData, formex, onClearLocalChanges, cacheKey, p
22185
21324
  })] });
22186
21325
  }
22187
21326
  //#endregion
22188
- //#region src/form/EntityForm.tsx
21327
+ //#region src/form/form_utils.ts
22189
21328
  function extractTouchedValues(values, touched) {
22190
21329
  let acc = {};
22191
21330
  if (!touched || typeof touched !== "object") return acc;
@@ -22201,7 +21340,7 @@ function extractTouchedValues(values, touched) {
22201
21340
  * from falsely triggering the unsaved local changes indicator.
22202
21341
  */
22203
21342
  /**
22204
- * CheckIcon if a value is semantically empty (null, undefined, or empty string).
21343
+ * Check if a value is semantically empty (null, undefined, or empty string).
22205
21344
  */
22206
21345
  function isSemanticEmpty(v) {
22207
21346
  return v === null || v === void 0 || v === "";
@@ -22245,23 +21384,51 @@ function getChanges(source, comparison) {
22245
21384
  }
22246
21385
  return changes;
22247
21386
  }
22248
- function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, forceActionsAtTheBottom, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent = EntityFormActions, showDefaultActions = true, showEntityPath = true, navigateBack: navigateBackProp, children }) {
22249
- const { t } = useTranslation();
22250
- const sideEntityController = useSideEntityController();
22251
- const collectionRegistryController = useCollectionRegistryController();
22252
- const navigateBack = useCallback(() => {
22253
- if (navigateBackProp) {
22254
- navigateBackProp();
22255
- return;
21387
+ function getInitialEntityValues(authController, collection, path, status, entity, propertyConfigs) {
21388
+ const properties = collection.properties;
21389
+ if ((status === "existing" || status === "copy") && entity) {
21390
+ let values;
21391
+ if (!collection.alwaysApplyDefaultValues) values = entity.values ?? getDefaultValuesFor(properties);
21392
+ else values = mergeDeep(getDefaultValuesFor(properties), entity.values ?? {});
21393
+ if (status === "copy") {
21394
+ const result = { ...values };
21395
+ for (const [key, property] of Object.entries(properties)) if (property && "isId" in property && property.isId) delete result[key];
21396
+ return result;
22256
21397
  }
22257
- if (openEntityMode === "side_panel" || openEntityMode === "dialog") sideEntityController.close();
22258
- else window.history.back();
22259
- }, [
22260
- navigateBackProp,
22261
- openEntityMode,
22262
- sideEntityController
22263
- ]);
22264
- const authController = useAuthController();
21398
+ return values;
21399
+ } else if (status === "new") return getDefaultValuesFor(properties);
21400
+ else {
21401
+ console.error({
21402
+ status,
21403
+ entity
21404
+ });
21405
+ throw new Error("Form has not been initialised with the correct parameters");
21406
+ }
21407
+ }
21408
+ function zodToFormErrors(zodError) {
21409
+ let errors = {};
21410
+ for (const issue of zodError.issues) {
21411
+ const path = issue.path.join(".");
21412
+ if (path && !getIn(errors, path)) errors = setIn(errors, path, issue.message);
21413
+ }
21414
+ return errors;
21415
+ }
21416
+ //#endregion
21417
+ //#region src/form/EntityForm.tsx
21418
+ /**
21419
+ * Headless entity form component.
21420
+ *
21421
+ * Renders a form for an entity collection without any CMS or backend dependencies.
21422
+ * All backend concerns (save, caching, analytics, plugin slots) are provided via
21423
+ * callback props. For CMS-connected usage, use {@link EntityFormBinding} instead.
21424
+ *
21425
+ * @group Components
21426
+ */
21427
+ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, forceActionsAtTheBottom, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent = EntityFormActions, showDefaultActions = true, showEntityPath = true, navigateBack: navigateBackProp, children, onSubmit: onSubmitProp, onValuesChangeDeferred: onValuesChangeDeferredProp, onReset: onResetProp, uniqueFieldValidator: uniqueFieldValidatorProp, beforeFields, afterFields, pluginActions: pluginActionsProp, computedInitialValues, hasLocalChanges: hasLocalChangesProp, localChangesData, manualApplyLocalChanges, localChangesCacheKey, onClearLocalChanges }) {
21428
+ const customizationController = useCustomizationController();
21429
+ const navigateBack = useCallback(() => {
21430
+ if (navigateBackProp) navigateBackProp();
21431
+ }, [navigateBackProp]);
22265
21432
  const [status, setStatus] = useState(initialStatus);
22266
21433
  const updateStatus = (status) => {
22267
21434
  setStatus(status);
@@ -22269,18 +21436,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22269
21436
  };
22270
21437
  const [valuesToBeSaved, setValuesToBeSaved] = useState(void 0);
22271
21438
  useDebouncedCallback(valuesToBeSaved, () => {
22272
- if (valuesToBeSaved) saveEntity({
22273
- entityId: entityIdProp,
22274
- collection,
22275
- path,
22276
- values: valuesToBeSaved
22277
- });
21439
+ if (valuesToBeSaved && onSubmitProp) {
21440
+ setIsSavingAutoSave(true);
21441
+ Promise.resolve(onSubmitProp(valuesToBeSaved, formex)).finally(() => setIsSavingAutoSave(false));
21442
+ }
22278
21443
  }, false, 2e3);
22279
- const dataClient = useData();
22280
- const snackbarController = useSnackbarController();
22281
- const customizationController = useCustomizationController();
22282
- const context = useCMSContext();
22283
- const analyticsController = useAnalyticsController();
22284
21444
  const [underlyingChanges] = useState({});
22285
21445
  const initialEntityId = useMemo(() => {
22286
21446
  if (status === "new" || status === "copy") return;
@@ -22289,58 +21449,45 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22289
21449
  const [entityId, setEntityId] = useState(initialEntityId);
22290
21450
  const [entityIdError, setEntityIdError] = useState(false);
22291
21451
  const [savingError, setSavingError] = useState();
21452
+ const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
21453
+ const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
22292
21454
  const autoSave = collection.formAutoSave;
22293
- const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [
22294
- authController,
22295
- collection,
22296
- path,
21455
+ const baseInitialValues = useMemo(() => {
21456
+ if (computedInitialValues !== void 0) return computedInitialValues;
21457
+ if ((status === "existing" || status === "copy") && entity) return entity.values ?? getDefaultValuesFor(collection.properties);
21458
+ return getDefaultValuesFor(collection.properties);
21459
+ }, [
21460
+ computedInitialValues,
21461
+ collection.properties,
22297
21462
  status,
22298
- entity,
22299
- customizationController.propertyConfigs
21463
+ entity
22300
21464
  ]);
22301
- const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
22302
21465
  const [localChangesCleared, setLocalChangesCleared] = useState(false);
22303
- const localChangesBackup = getLocalChangesBackup(collection);
22304
- const autoApplyLocalChanges = localChangesBackup === "auto_apply";
22305
- const manualApplyLocalChanges = localChangesBackup === "manual_apply";
21466
+ const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
22306
21467
  const onSubmit = (values, formexController) => {
22307
21468
  setSavingError(void 0);
22308
21469
  setEntityIdError(false);
22309
21470
  if (status === "existing") {
22310
21471
  if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
22311
21472
  } else if (status !== "new" && status !== "copy") throw Error("New FormType added, check EntityForm");
22312
- return save(values)?.then((savedEntity) => {
22313
- if (savedEntity) {
22314
- formexController.resetForm({
22315
- values: savedEntity.values || values,
22316
- submitCount: 0,
22317
- touched: {}
22318
- });
22319
- if (!autoSave) snackbarController.open({
22320
- type: "success",
22321
- message: `${collection.singularName ?? collection.name}: ${t("saved_correctly")}`
22322
- });
22323
- }
21473
+ if (!onSubmitProp) {
21474
+ console.warn("EntityForm: no onSubmit callback provided. Form submission has no effect.");
21475
+ formexController.setSubmitting(false);
21476
+ return;
21477
+ }
21478
+ return Promise.resolve(save(values))?.then((savedEntity) => {
21479
+ if (savedEntity) formexController.resetForm({
21480
+ values: savedEntity.values || values,
21481
+ submitCount: 0,
21482
+ touched: {}
21483
+ });
22324
21484
  }).finally(() => {
22325
21485
  formexController.setSubmitting(false);
22326
21486
  });
22327
21487
  };
22328
21488
  const [initialValues, initialDirty] = useMemo(() => {
22329
- const initialValuesWithLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
22330
- return [initialDirtyValues ? mergeDeep(initialValuesWithLocalChanges, initialDirtyValues) : initialValuesWithLocalChanges, Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0];
22331
- }, [
22332
- autoApplyLocalChanges,
22333
- localChangesDataRaw,
22334
- baseInitialValues,
22335
- initialDirtyValues
22336
- ]);
22337
- const localChangesData = useMemo(() => {
22338
- if (!localChangesDataRaw) return;
22339
- const cleaned = removeEmptyContainers(getChanges(localChangesDataRaw, initialValues));
22340
- if (cleaned && typeof cleaned === "object" && Object.keys(cleaned).length === 0) return;
22341
- return cleaned;
22342
- }, [localChangesDataRaw, initialValues]);
22343
- const hasLocalChanges = !localChangesCleared && !!localChangesData && Object.keys(localChangesData).length > 0;
21489
+ return [initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues, Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0];
21490
+ }, [baseInitialValues, initialDirtyValues]);
22344
21491
  const internalFormex = useCreateFormex({
22345
21492
  initialValues,
22346
21493
  initialDirty,
@@ -22351,17 +21498,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22351
21498
  }), {}) : {},
22352
21499
  onSubmit,
22353
21500
  onReset: () => {
22354
- clearDirtyCache();
21501
+ onResetProp?.();
22355
21502
  onValuesModified?.(false, initialValues);
22356
21503
  },
22357
- onValuesChangeDeferred: (values, controller) => {
22358
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
22359
- if (controller.dirty) {
22360
- const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
22361
- if (touchedValues && Object.keys(touchedValues).length > 0) saveEntityToCache(key, touchedValues);
22362
- else removeEntityFromCache(key);
22363
- }
22364
- },
21504
+ onValuesChangeDeferred: onValuesChangeDeferredProp,
22365
21505
  validation: async (values) => {
22366
21506
  if (!validationSchema) return {};
22367
21507
  const result = await validationSchema.safeParseAsync(values);
@@ -22374,7 +21514,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22374
21514
  const handleKeyDown = (e) => {
22375
21515
  const isUndo = (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z";
22376
21516
  const isRedo = (e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "z" || (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "y";
22377
- if (isUndo && formex.canUndo) {
21517
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "s" && !disabled && formex.dirty) {
21518
+ e.preventDefault();
21519
+ formex.handleSubmit();
21520
+ } else if (isUndo && formex.canUndo) {
22378
21521
  e.preventDefault();
22379
21522
  formex.undo();
22380
21523
  } else if (isRedo && formex.canRedo) {
@@ -22385,31 +21528,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22385
21528
  window.addEventListener("keydown", handleKeyDown);
22386
21529
  return () => window.removeEventListener("keydown", handleKeyDown);
22387
21530
  }, [formex]);
22388
- useCallback((e) => {
22389
- snackbarController.open({
22390
- type: "error",
22391
- message: `${t("error_before_saving")}: ${e?.message}`
22392
- });
22393
- console.error(e);
22394
- }, [snackbarController]);
22395
- useCallback((e) => {
22396
- snackbarController.open({
22397
- type: "error",
22398
- message: `${t("error_after_saving")}: ${e?.message}`
22399
- });
22400
- console.error(e);
22401
- }, [snackbarController]);
22402
- function clearDirtyCache() {
22403
- if (status === "new" || status === "copy") {
22404
- removeEntityFromMemoryCache(path + "#new");
22405
- removeEntityFromCache(path + "#new");
22406
- } else {
22407
- removeEntityFromMemoryCache(path + "/" + entityId);
22408
- removeEntityFromCache(path + "/" + entityId);
22409
- }
22410
- }
22411
21531
  const afterSave = (updatedEntity) => {
22412
- clearDirtyCache();
21532
+ onResetProp?.();
22413
21533
  onValuesModified?.(false, updatedEntity.values);
22414
21534
  onEntityChange?.(updatedEntity);
22415
21535
  updateStatus("existing");
@@ -22422,62 +21542,19 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22422
21542
  collection
22423
21543
  });
22424
21544
  };
22425
- const afterSaveError = useCallback((e) => {
22426
- snackbarController.open({
22427
- type: "error",
22428
- title: t("error_saving_entity"),
22429
- message: e?.message
22430
- });
22431
- console.error("Error saving entity", path, entityId, e);
22432
- console.error(e);
22433
- }, [
22434
- entityId,
22435
- path,
22436
- snackbarController
22437
- ]);
22438
- const saveEntity = ({ values, previousValues, entityId, collection, path }) => {
22439
- return saveEntityWithCallbacks({
22440
- path,
22441
- entityId,
22442
- values,
22443
- previousValues,
22444
- collection,
22445
- status,
22446
- data: dataClient,
22447
- context,
22448
- afterSave,
22449
- afterSaveError
22450
- });
22451
- };
22452
- const onSaveEntityRequest = async ({ collection, path, entityId, values, previousValues, autoSave }) => {
22453
- if (!status) return;
22454
- if (autoSave) {
22455
- setValuesToBeSaved(values);
22456
- return Promise.resolve();
22457
- } else return saveEntity({
22458
- collection,
22459
- path,
22460
- entityId,
22461
- values,
22462
- previousValues
22463
- });
22464
- };
22465
21545
  const lastSavedValues = useRef(entity?.values);
22466
21546
  const save = async (values) => {
22467
21547
  const valuesToSave = status === "existing" ? getChanges(values, entity?.values || {}) : values;
22468
21548
  if (status === "existing" && Object.keys(valuesToSave).length === 0 && entity) return Promise.resolve(entity);
22469
21549
  lastSavedValues.current = values;
22470
- return onSaveEntityRequest({
22471
- collection,
22472
- path,
22473
- entityId,
22474
- values: valuesToSave,
22475
- previousValues: entity?.values,
22476
- autoSave: autoSave ?? false
22477
- }).then((savedEntity) => {
22478
- const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
22479
- analyticsController.onAnalyticsEvent?.(eventName, { path });
22480
- return savedEntity;
21550
+ if (!onSubmitProp) return;
21551
+ if (autoSave ?? false) {
21552
+ setValuesToBeSaved(values);
21553
+ return Promise.resolve();
21554
+ }
21555
+ return Promise.resolve(onSubmitProp(valuesToSave, formex)).then((result) => {
21556
+ if (result) afterSave(result);
21557
+ return result;
22481
21558
  }).catch((e) => {
22482
21559
  console.error(e);
22483
21560
  setSavingError(e);
@@ -22508,44 +21585,18 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22508
21585
  path
22509
21586
  ]);
22510
21587
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
22511
- const formActionProps = {
22512
- entityId,
22513
- parentCollectionSlugs: collectionRegistryController.getParentCollectionSlugs(path),
22514
- parentEntityIds: collectionRegistryController.getParentEntityIds(path),
22515
- path,
22516
- status,
22517
- collection,
22518
- context,
22519
- formContext,
22520
- openEntityMode,
22521
- disabled: actionsDisabled
22522
- };
22523
- const pluginFormActions = useSlot("form.actions", formActionProps);
22524
- const pluginFormBefore = useSlot("form.before", formActionProps);
22525
- const pluginFormAfter = useSlot("form.after", formActionProps);
22526
21588
  const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
22527
21589
  const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
22528
21590
  const title = rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
21591
+ const modified = formex.dirty;
22529
21592
  useEffect(() => {
22530
21593
  if (!autoSave) onValuesModified?.(modified, formex.values);
22531
21594
  }, [formex.dirty]);
22532
- const modified = formex.dirty;
22533
- const uniqueFieldValidator = useCallback(async ({ name, value }) => {
22534
- try {
22535
- const { data } = await dataClient.collection(path).find({
22536
- where: { [name]: `eq.${value}` },
22537
- limit: 2
22538
- });
22539
- return (entityId ? data.filter((e) => e.id !== entityId) : data).length === 0;
22540
- } catch (e) {
22541
- console.error("Error checking unique field", e);
22542
- return true;
22543
- }
22544
- }, [
22545
- dataClient,
22546
- path,
22547
- entityId
22548
- ]);
21595
+ const defaultUniqueFieldValidator = useCallback(async () => true, []);
21596
+ const uniqueFieldValidator = useMemo(() => {
21597
+ if (uniqueFieldValidatorProp) return uniqueFieldValidatorProp;
21598
+ return defaultUniqueFieldValidator;
21599
+ }, [uniqueFieldValidatorProp, defaultUniqueFieldValidator]);
22549
21600
  const validationSchema = useMemo(() => getEntitySchema(entityId, collection.properties, uniqueFieldValidator), [
22550
21601
  entityId,
22551
21602
  collection.properties,
@@ -22577,6 +21628,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22577
21628
  modifiedValues: formex.values,
22578
21629
  formContext
22579
21630
  });
21631
+ const isNewEntity = status === "new" || status === "copy";
21632
+ let firstFocusableIndex = -1;
22580
21633
  return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key) => {
22581
21634
  const property = collection.properties?.[key];
22582
21635
  if (property) {
@@ -22585,9 +21638,12 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22585
21638
  const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
22586
21639
  const disabled = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.ui?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto;
22587
21640
  if (isHidden(property)) return null;
21641
+ const widthPercentage = property.ui?.widthPercentage ?? 100;
21642
+ const shouldAutoFocus = isNewEntity && !disabled && firstFocusableIndex === -1;
21643
+ if (shouldAutoFocus) firstFocusableIndex = 0;
22588
21644
  return /* @__PURE__ */ jsx(FormEntry, {
22589
21645
  propertyKey: key,
22590
- widthPercentage: property.ui?.widthPercentage ?? 100,
21646
+ widthPercentage,
22591
21647
  children: /* @__PURE__ */ jsx(PropertyFieldBinding, {
22592
21648
  propertyKey: key,
22593
21649
  disabled,
@@ -22597,22 +21653,23 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22597
21653
  context: formContext,
22598
21654
  partOfArray: false,
22599
21655
  minimalistView: false,
22600
- autoFocus: false
21656
+ autoFocus: shouldAutoFocus
22601
21657
  })
22602
21658
  }, `field_${key}`);
22603
21659
  }
22604
21660
  const additionalField = collection.additionalFields?.find((f) => f.key === key);
22605
21661
  if (additionalField && entity) {
22606
- const Builder = additionalField.Builder;
22607
- if (!Builder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
22608
- const child = Builder ? /* @__PURE__ */ jsx(Builder, {
21662
+ const AdditionalFieldBuilder = additionalField.Builder;
21663
+ if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
21664
+ const additionalFieldContext = formContext;
21665
+ const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
22609
21666
  entity,
22610
- context
21667
+ context: additionalFieldContext
22611
21668
  }) : /* @__PURE__ */ jsx("div", {
22612
21669
  className: "w-full",
22613
21670
  children: additionalField.value?.({
22614
21671
  entity,
22615
- context
21672
+ context: additionalFieldContext
22616
21673
  })?.toString()
22617
21674
  });
22618
21675
  return /* @__PURE__ */ jsxs("div", {
@@ -22633,8 +21690,9 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22633
21690
  }).filter(Boolean) });
22634
21691
  };
22635
21692
  const formRef = useRef(null);
21693
+ const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22636
21694
  const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
22637
- pluginFormBefore,
21695
+ beforeFields,
22638
21696
  !Builder && /* @__PURE__ */ jsxs("div", {
22639
21697
  className: "w-full flex flex-col items-start my-4 lg:my-6",
22640
21698
  children: [
@@ -22647,7 +21705,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22647
21705
  color: "warning",
22648
21706
  size: "small",
22649
21707
  outerClassName: "w-full mb-4 text-xs",
22650
- children: t("entity_does_not_exist")
21708
+ children: "This entity does not exist yet"
22651
21709
  }),
22652
21710
  showEntityPath && /* @__PURE__ */ jsx(Alert, {
22653
21711
  color: "base",
@@ -22668,15 +21726,43 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22668
21726
  initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
22669
21727
  color: "info",
22670
21728
  size: "small",
22671
- children: t("entity_does_not_exist")
21729
+ children: "This entity does not exist yet"
21730
+ }),
21731
+ hasFormErrors && /* @__PURE__ */ jsx(Alert, {
21732
+ color: "error",
21733
+ size: "small",
21734
+ outerClassName: "w-full mt-2",
21735
+ children: "Please fix the highlighted errors before saving."
22672
21736
  }),
22673
21737
  formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
22674
21738
  className: "mt-12 flex flex-col gap-8",
22675
21739
  ref: formRef,
22676
21740
  children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
22677
21741
  }) }),
22678
- pluginFormAfter,
22679
- forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" })
21742
+ afterFields,
21743
+ forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" }),
21744
+ /* @__PURE__ */ jsxs(Dialog, {
21745
+ open: discardDialogOpen,
21746
+ onOpenChange: setDiscardDialogOpen,
21747
+ maxWidth: "sm",
21748
+ children: [
21749
+ /* @__PURE__ */ jsx(DialogTitle, { children: status === "existing" ? "Discard changes?" : "Clear form?" }),
21750
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { children: status === "existing" ? "All unsaved changes will be lost. This cannot be undone." : "All entered values will be cleared. This cannot be undone." }) }),
21751
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
21752
+ variant: "text",
21753
+ onClick: () => setDiscardDialogOpen(false),
21754
+ children: "Cancel"
21755
+ }), /* @__PURE__ */ jsx(Button, {
21756
+ variant: "filled",
21757
+ color: "error",
21758
+ onClick: () => {
21759
+ setDiscardDialogOpen(false);
21760
+ formex.resetForm({ values: baseInitialValues });
21761
+ },
21762
+ children: status === "existing" ? "Discard" : "Clear"
21763
+ })] })
21764
+ ]
21765
+ })
22680
21766
  ] }) });
22681
21767
  useEffect(() => {
22682
21768
  if (entityId && onIdChange) onIdChange(entityId);
@@ -22691,7 +21777,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22691
21777
  formex,
22692
21778
  disabled: actionsDisabled,
22693
21779
  status,
22694
- pluginActions: pluginFormActions ?? [],
21780
+ pluginActions: pluginActionsProp ?? [],
22695
21781
  openEntityMode,
22696
21782
  showDefaultActions,
22697
21783
  navigateBack,
@@ -22701,7 +21787,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22701
21787
  value: formex,
22702
21788
  children: /* @__PURE__ */ jsxs("form", {
22703
21789
  onSubmit: formex.handleSubmit,
22704
- onReset: () => formex.resetForm({ values: baseInitialValues }),
21790
+ onReset: (e) => {
21791
+ e.preventDefault();
21792
+ if (formex.dirty) setDiscardDialogOpen(true);
21793
+ else formex.resetForm({ values: baseInitialValues });
21794
+ },
22705
21795
  noValidate: true,
22706
21796
  className: cls("@container flex-1 flex flex-row w-full overflow-y-auto justify-center", className),
22707
21797
  children: [/* @__PURE__ */ jsx("div", {
@@ -22710,15 +21800,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22710
21800
  children: /* @__PURE__ */ jsxs("div", {
22711
21801
  className: cls("flex flex-col w-full", openEntityMode === "dialog" ? "pt-4 pb-12 px-6 sm:px-8" : "pt-12 pb-16 px-4 sm:px-8 md:px-10"),
22712
21802
  children: [/* @__PURE__ */ jsxs("div", {
22713
- className: "flex flex-row gap-4 self-end sticky top-4 z-10",
22714
- children: [manualApplyLocalChanges && hasLocalChanges && /* @__PURE__ */ jsx(LocalChangesMenu, {
22715
- cacheKey: status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId,
21803
+ className: "flex flex-row gap-4 justify-end h-0 overflow-visible sticky top-4 z-10",
21804
+ children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx(LocalChangesMenu, {
21805
+ cacheKey: localChangesCacheKey,
22716
21806
  properties: collection.properties,
22717
21807
  cachedData: localChangesData,
22718
21808
  formex,
22719
- onClearLocalChanges: () => setLocalChangesCleared(true)
22720
- }), formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
22721
- title: t("form_modified"),
21809
+ onClearLocalChanges: () => {
21810
+ setLocalChangesCleared(true);
21811
+ onClearLocalChanges?.();
21812
+ }
21813
+ }), isSavingAutoSave ? /* @__PURE__ */ jsx(Tooltip, {
21814
+ title: "Saving…",
21815
+ children: /* @__PURE__ */ jsx(Chip, {
21816
+ size: "small",
21817
+ className: "py-1",
21818
+ colorScheme: "blueDarker",
21819
+ children: /* @__PURE__ */ jsx(LoaderIcon, {
21820
+ size: iconSize.smallest,
21821
+ className: "animate-spin"
21822
+ })
21823
+ })
21824
+ }) : formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
21825
+ title: "Form has been modified",
22722
21826
  children: /* @__PURE__ */ jsx(Chip, {
22723
21827
  size: "small",
22724
21828
  className: "py-1",
@@ -22726,7 +21830,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22726
21830
  children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
22727
21831
  })
22728
21832
  }) : /* @__PURE__ */ jsx(Tooltip, {
22729
- title: t("form_in_sync"),
21833
+ title: "Form is in sync",
22730
21834
  children: /* @__PURE__ */ jsx(Chip, {
22731
21835
  size: "small",
22732
21836
  className: "py-1",
@@ -22739,40 +21843,401 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22739
21843
  })
22740
21844
  });
22741
21845
  }
22742
- function getInitialEntityValues(authController, collection, path, status, entity, propertyConfigs) {
22743
- const properties = collection.properties;
22744
- if ((status === "existing" || status === "copy") && entity) {
22745
- let values;
22746
- if (!collection.alwaysApplyDefaultValues) values = entity.values ?? getDefaultValuesFor(properties);
22747
- else values = mergeDeep(getDefaultValuesFor(properties), entity.values ?? {});
22748
- if (status === "copy") {
22749
- const result = { ...values };
22750
- for (const [key, property] of Object.entries(properties)) if (property && "isId" in property && property.isId) delete result[key];
22751
- return result;
22752
- }
22753
- return values;
22754
- } else if (status === "new") return getDefaultValuesFor(properties);
22755
- else {
22756
- console.error({
22757
- status,
22758
- entity
22759
- });
22760
- throw new Error("Form has not been initialised with the correct parameters");
21846
+ function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
21847
+ useEffect(() => {
21848
+ if (!autoSave) return;
21849
+ if (formex.values && !deepEqual(formex.values, lastSavedValues.current)) save(formex.values);
21850
+ }, [autoSave, formex.values]);
21851
+ }
21852
+ //#endregion
21853
+ //#region src/form/EntityFormBinding.tsx
21854
+ /**
21855
+ * CMS-connected wrapper around EntityForm.
21856
+ *
21857
+ * Provides all backend concerns that the headless EntityForm delegates via callbacks:
21858
+ * - Save via the data layer (`saveEntityWithCallbacks`)
21859
+ * - Unique field validation via the data layer
21860
+ * - Entity caching (local changes backup/restore)
21861
+ * - Plugin slots (form.before, form.after, form.actions)
21862
+ * - Snackbar notifications on save success/error
21863
+ * - Analytics events
21864
+ * - Navigation via side entity controller
21865
+ *
21866
+ * Use this component inside the CMS (`<RebaseShell>`).
21867
+ * For headless usage outside the CMS, use `EntityForm` directly.
21868
+ *
21869
+ * @group Components
21870
+ */
21871
+ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, initialStatus, onSaved, onValuesModified, onEntityChange, onStatusChange, openEntityMode = "full_screen", navigateBack: navigateBackProp, initialDirtyValues, ...restProps }) {
21872
+ const { t } = useTranslation();
21873
+ const sideEntityController = useSideEntityController();
21874
+ const collectionRegistryController = useCollectionRegistryController();
21875
+ const authController = useAuthController();
21876
+ const dataClient = useData();
21877
+ const snackbarController = useSnackbarController();
21878
+ const customizationController = useCustomizationController();
21879
+ const context = useCMSContext();
21880
+ const analyticsController = useAnalyticsController();
21881
+ const [status, setStatus] = useState(initialStatus);
21882
+ const [entityId, setEntityId] = useState(() => {
21883
+ if (status === "new" || status === "copy") return void 0;
21884
+ return entityIdProp;
21885
+ });
21886
+ const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [
21887
+ authController,
21888
+ collection,
21889
+ path,
21890
+ status,
21891
+ entity,
21892
+ customizationController.propertyConfigs
21893
+ ]);
21894
+ const localChangesBackup = getLocalChangesBackup(collection);
21895
+ const autoApplyLocalChanges = localChangesBackup === "auto_apply";
21896
+ const manualApplyLocalChanges = localChangesBackup === "manual_apply";
21897
+ const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
21898
+ const [localChangesCleared, setLocalChangesCleared] = useState(false);
21899
+ const computedInitialValues = useMemo(() => {
21900
+ const withLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
21901
+ return initialDirtyValues ? mergeDeep(withLocalChanges, initialDirtyValues) : withLocalChanges;
21902
+ }, [
21903
+ autoApplyLocalChanges,
21904
+ localChangesDataRaw,
21905
+ baseInitialValues,
21906
+ initialDirtyValues
21907
+ ]);
21908
+ const localChangesData = useMemo(() => {
21909
+ if (!localChangesDataRaw) return void 0;
21910
+ const cleaned = removeEmptyContainers(getChanges(localChangesDataRaw, computedInitialValues));
21911
+ if (cleaned && typeof cleaned === "object" && Object.keys(cleaned).length === 0) return;
21912
+ return cleaned;
21913
+ }, [localChangesDataRaw, computedInitialValues]);
21914
+ const hasLocalChanges = !localChangesCleared && !!localChangesData && Object.keys(localChangesData).length > 0;
21915
+ const localChangesCacheKey = useMemo(() => {
21916
+ return status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
21917
+ }, [
21918
+ status,
21919
+ path,
21920
+ entityId
21921
+ ]);
21922
+ const navigateBack = useCallback(() => {
21923
+ if (navigateBackProp) {
21924
+ navigateBackProp();
21925
+ return;
21926
+ }
21927
+ if (openEntityMode === "side_panel" || openEntityMode === "dialog") sideEntityController.close();
21928
+ else window.history.back();
21929
+ }, [
21930
+ navigateBackProp,
21931
+ openEntityMode,
21932
+ sideEntityController
21933
+ ]);
21934
+ const clearDirtyCache = useCallback(() => {
21935
+ if (status === "new" || status === "copy") {
21936
+ removeEntityFromMemoryCache(path + "#new");
21937
+ removeEntityFromCache(path + "#new");
21938
+ } else {
21939
+ removeEntityFromMemoryCache(path + "/" + entityId);
21940
+ removeEntityFromCache(path + "/" + entityId);
21941
+ }
21942
+ }, [
21943
+ status,
21944
+ path,
21945
+ entityId
21946
+ ]);
21947
+ const onReset = useCallback(() => {
21948
+ clearDirtyCache();
21949
+ }, [clearDirtyCache]);
21950
+ const onValuesChangeDeferred = useCallback((values, controller) => {
21951
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
21952
+ if (controller.dirty) {
21953
+ const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
21954
+ if (touchedValues && Object.keys(touchedValues).length > 0) saveEntityToCache(key, touchedValues);
21955
+ else removeEntityFromCache(key);
21956
+ }
21957
+ }, [
21958
+ status,
21959
+ path,
21960
+ entityId
21961
+ ]);
21962
+ const onSubmit = useCallback(async (values, formex) => {
21963
+ try {
21964
+ const savedEntity = await saveEntityWithCallbacks({
21965
+ path,
21966
+ entityId,
21967
+ values,
21968
+ previousValues: entity?.values,
21969
+ collection,
21970
+ status,
21971
+ data: dataClient,
21972
+ context,
21973
+ afterSave: (updatedEntity) => {
21974
+ clearDirtyCache();
21975
+ onValuesModified?.(false, updatedEntity.values);
21976
+ onEntityChange?.(updatedEntity);
21977
+ setStatus("existing");
21978
+ setEntityId(updatedEntity.id);
21979
+ onStatusChange?.("existing");
21980
+ if (onSaved) onSaved({
21981
+ entity: updatedEntity,
21982
+ status,
21983
+ path,
21984
+ entityId: updatedEntity.id,
21985
+ collection
21986
+ });
21987
+ },
21988
+ afterSaveError: (e) => {
21989
+ snackbarController.open({
21990
+ type: "error",
21991
+ title: t("error_saving_entity"),
21992
+ message: e?.message
21993
+ });
21994
+ console.error("Error saving entity", path, entityId, e);
21995
+ }
21996
+ });
21997
+ if (!collection.formAutoSave) snackbarController.open({
21998
+ type: "success",
21999
+ message: `${collection.singularName ?? collection.name}: ${t("saved_correctly")}`
22000
+ });
22001
+ const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
22002
+ analyticsController.onAnalyticsEvent?.(eventName, { path });
22003
+ return savedEntity;
22004
+ } catch (e) {
22005
+ console.error(e);
22006
+ throw e;
22007
+ }
22008
+ }, [
22009
+ path,
22010
+ entityId,
22011
+ entity,
22012
+ collection,
22013
+ status,
22014
+ dataClient,
22015
+ context,
22016
+ clearDirtyCache,
22017
+ onValuesModified,
22018
+ onEntityChange,
22019
+ onSaved,
22020
+ onStatusChange,
22021
+ snackbarController,
22022
+ analyticsController,
22023
+ t
22024
+ ]);
22025
+ const uniqueFieldValidator = useCallback(async ({ name, value }) => {
22026
+ try {
22027
+ const { data } = await dataClient.collection(path).find({
22028
+ where: { [name]: ["==", value] },
22029
+ limit: 2
22030
+ });
22031
+ return (entityId ? data.filter((e) => e.id !== entityId) : data).length === 0;
22032
+ } catch (e) {
22033
+ console.error("Error checking unique field", e);
22034
+ return true;
22035
+ }
22036
+ }, [
22037
+ dataClient,
22038
+ path,
22039
+ entityId
22040
+ ]);
22041
+ const parentCollectionSlugs = collectionRegistryController.getParentCollectionSlugs(path);
22042
+ const parentEntityIds = collectionRegistryController.getParentEntityIds(path);
22043
+ const formActionProps = useMemo(() => ({
22044
+ entityId,
22045
+ parentCollectionSlugs,
22046
+ parentEntityIds,
22047
+ path,
22048
+ status,
22049
+ collection,
22050
+ context,
22051
+ formContext: void 0,
22052
+ openEntityMode,
22053
+ disabled: false
22054
+ }), [
22055
+ entityId,
22056
+ parentCollectionSlugs,
22057
+ parentEntityIds,
22058
+ path,
22059
+ status,
22060
+ collection,
22061
+ context,
22062
+ openEntityMode
22063
+ ]);
22064
+ const pluginFormActions = useSlot("form.actions", formActionProps);
22065
+ const pluginFormBefore = useSlot("form.before", formActionProps);
22066
+ const pluginFormAfter = useSlot("form.after", formActionProps);
22067
+ return /* @__PURE__ */ jsx(EntityForm, {
22068
+ ...restProps,
22069
+ path,
22070
+ entityId: entityIdProp,
22071
+ collection,
22072
+ entity,
22073
+ initialStatus,
22074
+ initialDirtyValues,
22075
+ onSaved,
22076
+ onValuesModified,
22077
+ onEntityChange,
22078
+ onStatusChange,
22079
+ openEntityMode,
22080
+ navigateBack,
22081
+ onSubmit,
22082
+ onValuesChangeDeferred,
22083
+ onReset,
22084
+ uniqueFieldValidator,
22085
+ beforeFields: pluginFormBefore,
22086
+ afterFields: pluginFormAfter,
22087
+ pluginActions: pluginFormActions ?? [],
22088
+ computedInitialValues,
22089
+ hasLocalChanges,
22090
+ localChangesData,
22091
+ manualApplyLocalChanges,
22092
+ localChangesCacheKey,
22093
+ onClearLocalChanges: () => setLocalChangesCleared(true)
22094
+ });
22095
+ }
22096
+ //#endregion
22097
+ //#region src/util/propertyConfigMapper.ts
22098
+ /**
22099
+ * Map Rebase property types to headless-supported types.
22100
+ * `vector` and `binary` are not directly renderable, so we fall back to `string`.
22101
+ */
22102
+ var TYPE_MAP = {
22103
+ string: "string",
22104
+ number: "number",
22105
+ boolean: "boolean",
22106
+ date: "date",
22107
+ geopoint: "geopoint",
22108
+ reference: "reference",
22109
+ relation: "relation",
22110
+ array: "array",
22111
+ map: "map",
22112
+ vector: "string",
22113
+ binary: "string"
22114
+ };
22115
+ /**
22116
+ * Convert a single Property to a CollectionPropertyConfig.
22117
+ */
22118
+ function mapPropertyToConfig(property) {
22119
+ const base = {
22120
+ type: TYPE_MAP[property.type] ?? "string",
22121
+ name: property.name,
22122
+ description: property.description,
22123
+ columnWidth: property.ui?.columnWidth,
22124
+ hideFromCollection: property.ui?.hideFromCollection
22125
+ };
22126
+ if (property.type === "string") {
22127
+ if (property.enum) base.enum = mapEnumValues(property.enum);
22128
+ if (property.ui?.multiline) base.multiline = true;
22129
+ if (property.ui?.previewAsTag) base.previewAsTag = true;
22130
+ if (property.ui?.url) base.url = property.ui.url;
22131
+ if (property.ui?.markdown) base.markdown = true;
22132
+ if (property.storage) base.storage = true;
22133
+ if (property.email) base.email = true;
22134
+ }
22135
+ if (property.type === "number" && property.enum) base.enum = mapEnumValues(property.enum);
22136
+ if (property.type === "array" && property.of && !Array.isArray(property.of)) base.of = mapPropertyToConfig(property.of);
22137
+ if (property.type === "map" && property.properties) {
22138
+ base.properties = mapPropertiesToConfigs(property.properties);
22139
+ if (property.propertiesOrder) base.propertiesOrder = property.propertiesOrder;
22761
22140
  }
22141
+ if (property.type === "date" && property.mode) base.mode = property.mode;
22142
+ if (property.ui?.Preview) {}
22143
+ return base;
22762
22144
  }
22763
- function zodToFormErrors(zodError) {
22764
- let errors = {};
22765
- for (const issue of zodError.issues) {
22766
- const path = issue.path.join(".");
22767
- if (path && !getIn(errors, path)) errors = setIn(errors, path, issue.message);
22145
+ /**
22146
+ * Convert a Properties record to a CollectionPropertyConfig record.
22147
+ */
22148
+ function mapPropertiesToConfigs(properties) {
22149
+ const result = {};
22150
+ for (const [key, property] of Object.entries(properties)) result[key] = mapPropertyToConfig(property);
22151
+ return result;
22152
+ }
22153
+ /**
22154
+ * Convert Rebase EnumValues to CollectionEnumValueConfig format.
22155
+ * Handles both array form (EnumValueConfig[]) and record form.
22156
+ */
22157
+ function mapEnumValues(enumValues) {
22158
+ if (!enumValues) return void 0;
22159
+ if (Array.isArray(enumValues)) {
22160
+ const result = {};
22161
+ for (const item of enumValues) result[String(item.id)] = normalizeEnumValueConfig(item);
22162
+ return result;
22768
22163
  }
22769
- return errors;
22164
+ const result = {};
22165
+ for (const [key, value] of Object.entries(enumValues)) result[key] = typeof value === "string" ? value : normalizeEnumValueConfig(value);
22166
+ return result;
22770
22167
  }
22771
- function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
22772
- useEffect(() => {
22773
- if (!autoSave) return;
22774
- if (formex.values && !deepEqual(formex.values, lastSavedValues.current)) save(formex.values);
22775
- }, [autoSave, formex.values]);
22168
+ function normalizeEnumValueConfig(config) {
22169
+ return {
22170
+ label: config.label,
22171
+ color: typeof config.color === "string" ? config.color : void 0,
22172
+ disabled: config.disabled
22173
+ };
22174
+ }
22175
+ //#endregion
22176
+ //#region src/util/dataControllerAdapter.ts
22177
+ /**
22178
+ * Adapts an `EntityTableController<M>` (from @rebasepro/types) to a
22179
+ * `CollectionDataController<T>` (from @rebasepro/ui), bridging the
22180
+ * entity-aware data layer to the headless collection view.
22181
+ */
22182
+ /**
22183
+ * Flatten Entity<M> objects to plain row objects.
22184
+ * The headless view works with flat `Record<string, unknown>` rows,
22185
+ * while the entity system uses `{ id, path, values: M }`.
22186
+ *
22187
+ * The flattened row includes `id` and `path` at the top level,
22188
+ * plus all values spread.
22189
+ */
22190
+ function flattenEntities(entities) {
22191
+ return entities.map((entity) => ({
22192
+ id: entity.id,
22193
+ path: entity.path,
22194
+ ...entity.values
22195
+ }));
22196
+ }
22197
+ /**
22198
+ * React hook that adapts an EntityTableController to a CollectionDataController.
22199
+ *
22200
+ * Usage:
22201
+ * ```tsx
22202
+ * const tableController = useDataTableController({ path, collection });
22203
+ * const dataController = useCollectionDataController(tableController);
22204
+ *
22205
+ * <CollectionView dataController={dataController} ... />
22206
+ * ```
22207
+ */
22208
+ function useCollectionDataController(tableController) {
22209
+ const flatData = useMemo(() => flattenEntities(tableController.data), [tableController.data]);
22210
+ return useMemo(() => ({
22211
+ data: flatData,
22212
+ loading: tableController.dataLoading,
22213
+ noMoreToLoad: tableController.noMoreToLoad,
22214
+ error: tableController.dataLoadingError,
22215
+ filterValues: tableController.filterValues,
22216
+ setFilterValues: tableController.setFilterValues,
22217
+ sortBy: tableController.sortBy,
22218
+ setSortBy: tableController.setSortBy,
22219
+ searchString: tableController.searchString,
22220
+ setSearchString: tableController.setSearchString,
22221
+ clearFilter: tableController.clearFilter,
22222
+ initialScroll: tableController.initialScroll,
22223
+ onScroll: tableController.onScroll,
22224
+ paginationEnabled: tableController.paginationEnabled,
22225
+ pageSize: tableController.pageSize,
22226
+ itemCount: tableController.itemCount,
22227
+ setItemCount: tableController.setItemCount
22228
+ }), [flatData, tableController]);
22229
+ }
22230
+ /**
22231
+ * Non-hook version for cases where you already have the data
22232
+ * and just need to build a static controller.
22233
+ */
22234
+ function createStaticDataController(data, options) {
22235
+ return {
22236
+ data,
22237
+ loading: options?.loading ?? false,
22238
+ noMoreToLoad: true,
22239
+ error: options?.error
22240
+ };
22776
22241
  }
22777
22242
  //#endregion
22778
22243
  //#region src/data_import/import/ImportCollectionAction.tsx
@@ -23606,6 +23071,562 @@ var export_exports = /* @__PURE__ */ __exportAll({
23606
23071
  */
23607
23072
  var useCollectionsConfigController = () => useContext(ConfigControllerContext);
23608
23073
  //#endregion
23074
+ //#region src/collection_editor/serializable_utils.ts
23075
+ /**
23076
+ * Strip non-serializable fields from a `BaseUIConfig`.
23077
+ * Removes `Field` and `Preview` (ComponentRef).
23078
+ */
23079
+ function toSerializableUIConfig(ui) {
23080
+ if (!ui) return void 0;
23081
+ const { Field, Preview, ...rest } = ui;
23082
+ const result = {};
23083
+ for (const [key, value] of Object.entries(rest)) if (key !== "Field" && key !== "Preview" && value !== void 0) result[key] = value;
23084
+ return Object.keys(result).length > 0 ? result : void 0;
23085
+ }
23086
+ /**
23087
+ * Convert a `StorageConfig` to its serializable form.
23088
+ * Drops function variants of `storagePath`, `fileName`, and all function fields.
23089
+ */
23090
+ function toSerializableStorageConfig(storage) {
23091
+ if (!storage) return void 0;
23092
+ const storagePath = typeof storage.storagePath === "string" ? storage.storagePath : void 0;
23093
+ if (!storagePath) return void 0;
23094
+ const result = { storagePath };
23095
+ if (storage.acceptedFiles) result.acceptedFiles = storage.acceptedFiles;
23096
+ if (storage.imageResize) result.imageResize = storage.imageResize;
23097
+ if (storage.metadata) result.metadata = storage.metadata;
23098
+ if (typeof storage.fileName === "string") result.fileName = storage.fileName;
23099
+ if (storage.includeBucketUrl !== void 0) result.includeBucketUrl = storage.includeBucketUrl;
23100
+ if (storage.storeUrl !== void 0) result.storeUrl = storage.storeUrl;
23101
+ if (storage.maxSize !== void 0) result.maxSize = storage.maxSize;
23102
+ if (storage.storageSource !== void 0) result.storageSource = storage.storageSource;
23103
+ return result;
23104
+ }
23105
+ /**
23106
+ * Convert a RegExp or string `matches` validation to a plain string.
23107
+ */
23108
+ function serializeMatches(matches) {
23109
+ if (matches === void 0) return void 0;
23110
+ if (typeof matches === "string") return matches;
23111
+ return matches.source;
23112
+ }
23113
+ /**
23114
+ * Resolve a relation target to a string slug.
23115
+ * Functions are called to extract the target; EntityCollection objects
23116
+ * use their slug.
23117
+ */
23118
+ function resolveRelationTarget(target) {
23119
+ if (!target) return void 0;
23120
+ if (typeof target === "string") return target;
23121
+ if (typeof target === "function") try {
23122
+ const resolved = target();
23123
+ if (typeof resolved === "string") return resolved;
23124
+ if (resolved && typeof resolved === "object" && "slug" in resolved) return resolved.slug;
23125
+ } catch {}
23126
+ }
23127
+ /**
23128
+ * Strip non-serializable base property fields.
23129
+ * Returns a clean object with only the common serializable fields from BaseProperty.
23130
+ * Validation is intentionally excluded — each property type handles its own.
23131
+ */
23132
+ function toSerializableBaseFields(property) {
23133
+ const result = { name: property.name };
23134
+ if (property.description) result.description = property.description;
23135
+ if (property.propertyConfig) result.propertyConfig = property.propertyConfig;
23136
+ if (property.columnName) result.columnName = property.columnName;
23137
+ if (property.defaultValue !== void 0) result.defaultValue = property.defaultValue;
23138
+ if (property.conditions) result.conditions = property.conditions;
23139
+ if (property.metadata) result.metadata = property.metadata;
23140
+ const ui = toSerializableUIConfig(property.ui);
23141
+ if (ui) result.ui = ui;
23142
+ return result;
23143
+ }
23144
+ /**
23145
+ * Convert a single `Property` to its JSON-serializable form.
23146
+ * Strips functions, converts RegExp to string, Date to ISO string.
23147
+ */
23148
+ function toSerializableProperty(property) {
23149
+ const base = toSerializableBaseFields(property);
23150
+ switch (property.type) {
23151
+ case "string": {
23152
+ const sp = property;
23153
+ const result = {
23154
+ ...base,
23155
+ type: "string"
23156
+ };
23157
+ if (sp.columnType) result.columnType = sp.columnType;
23158
+ if (sp.isId !== void 0) result.isId = sp.isId;
23159
+ if (sp.enum) result.enum = sp.enum;
23160
+ if (sp.userSelect) result.userSelect = sp.userSelect;
23161
+ if (sp.email) result.email = sp.email;
23162
+ if (sp.validation) {
23163
+ const { matches, ...restValidation } = sp.validation;
23164
+ const serializedValidation = { ...restValidation };
23165
+ const matchStr = serializeMatches(matches);
23166
+ if (matchStr !== void 0) serializedValidation.matches = matchStr;
23167
+ result.validation = serializedValidation;
23168
+ }
23169
+ const storage = toSerializableStorageConfig(sp.storage);
23170
+ if (storage) result.storage = storage;
23171
+ return result;
23172
+ }
23173
+ case "number": {
23174
+ const np = property;
23175
+ const result = {
23176
+ ...base,
23177
+ type: "number"
23178
+ };
23179
+ if (np.columnType) result.columnType = np.columnType;
23180
+ if (np.validation) result.validation = np.validation;
23181
+ if (np.isId !== void 0) result.isId = np.isId;
23182
+ if (np.enum) result.enum = np.enum;
23183
+ return result;
23184
+ }
23185
+ case "boolean": {
23186
+ const bp = property;
23187
+ const result = {
23188
+ ...base,
23189
+ type: "boolean"
23190
+ };
23191
+ if (bp.validation) result.validation = bp.validation;
23192
+ return result;
23193
+ }
23194
+ case "date": {
23195
+ const dp = property;
23196
+ const result = {
23197
+ ...base,
23198
+ type: "date"
23199
+ };
23200
+ if (dp.columnType) result.columnType = dp.columnType;
23201
+ if (dp.mode) result.mode = dp.mode;
23202
+ if (dp.timezone) result.timezone = dp.timezone;
23203
+ if (dp.autoValue) result.autoValue = dp.autoValue;
23204
+ if (dp.validation) {
23205
+ const serializedValidation = {};
23206
+ if (dp.validation.required !== void 0) serializedValidation.required = dp.validation.required;
23207
+ if (dp.validation.requiredMessage) serializedValidation.requiredMessage = dp.validation.requiredMessage;
23208
+ if (dp.validation.unique) serializedValidation.unique = dp.validation.unique;
23209
+ if (dp.validation.uniqueInArray) serializedValidation.uniqueInArray = dp.validation.uniqueInArray;
23210
+ if (dp.validation.min instanceof Date) serializedValidation.min = dp.validation.min.toISOString();
23211
+ else if (typeof dp.validation.min === "string") serializedValidation.min = dp.validation.min;
23212
+ if (dp.validation.max instanceof Date) serializedValidation.max = dp.validation.max.toISOString();
23213
+ else if (typeof dp.validation.max === "string") serializedValidation.max = dp.validation.max;
23214
+ result.validation = serializedValidation;
23215
+ }
23216
+ return result;
23217
+ }
23218
+ case "geopoint": {
23219
+ const gp = property;
23220
+ const result = {
23221
+ ...base,
23222
+ type: "geopoint"
23223
+ };
23224
+ if (gp.validation) result.validation = gp.validation;
23225
+ return result;
23226
+ }
23227
+ case "reference": {
23228
+ const rp = property;
23229
+ const result = {
23230
+ ...base,
23231
+ type: "reference"
23232
+ };
23233
+ if (rp.isId !== void 0) result.isId = rp.isId;
23234
+ if (rp.path) result.path = rp.path;
23235
+ if (rp.fixedFilter) result.fixedFilter = rp.fixedFilter;
23236
+ if (rp.includeId !== void 0) result.includeId = rp.includeId;
23237
+ if (rp.includeEntityLink !== void 0) result.includeEntityLink = rp.includeEntityLink;
23238
+ return result;
23239
+ }
23240
+ case "relation": {
23241
+ const rl = property;
23242
+ const result = {
23243
+ ...base,
23244
+ type: "relation"
23245
+ };
23246
+ if (rl.isId !== void 0) result.isId = rl.isId;
23247
+ const target = resolveRelationTarget(rl.target);
23248
+ if (target) result.target = target;
23249
+ if (rl.cardinality) result.cardinality = rl.cardinality;
23250
+ if (rl.direction) result.direction = rl.direction;
23251
+ if (rl.inverseRelationName) result.inverseRelationName = rl.inverseRelationName;
23252
+ if (rl.localKey) result.localKey = rl.localKey;
23253
+ if (rl.foreignKeyOnTarget) result.foreignKeyOnTarget = rl.foreignKeyOnTarget;
23254
+ if (rl.through) result.through = rl.through;
23255
+ if (rl.joinPath) result.joinPath = rl.joinPath;
23256
+ if (rl.onUpdate) result.onUpdate = rl.onUpdate;
23257
+ if (rl.onDelete) result.onDelete = rl.onDelete;
23258
+ if (rl.relationName) result.relationName = rl.relationName;
23259
+ if (rl.fixedFilter) result.fixedFilter = rl.fixedFilter;
23260
+ if (rl.includeId !== void 0) result.includeId = rl.includeId;
23261
+ if (rl.includeEntityLink !== void 0) result.includeEntityLink = rl.includeEntityLink;
23262
+ if (rl.widget) result.widget = rl.widget;
23263
+ return result;
23264
+ }
23265
+ case "array": {
23266
+ const ap = property;
23267
+ const result = {
23268
+ ...base,
23269
+ type: "array"
23270
+ };
23271
+ if (ap.columnType) result.columnType = ap.columnType;
23272
+ if (ap.validation) result.validation = ap.validation;
23273
+ if (ap.sortable !== void 0) result.sortable = ap.sortable;
23274
+ if (ap.canAddElements !== void 0) result.canAddElements = ap.canAddElements;
23275
+ if (ap.of) if (Array.isArray(ap.of)) result.of = ap.of.map(toSerializableProperty);
23276
+ else result.of = toSerializableProperty(ap.of);
23277
+ if (ap.oneOf) {
23278
+ result.oneOf = { properties: toSerializableProperties(ap.oneOf.properties) };
23279
+ if (ap.oneOf.propertiesOrder) result.oneOf.propertiesOrder = ap.oneOf.propertiesOrder;
23280
+ if (ap.oneOf.typeField) result.oneOf.typeField = ap.oneOf.typeField;
23281
+ if (ap.oneOf.valueField) result.oneOf.valueField = ap.oneOf.valueField;
23282
+ }
23283
+ return result;
23284
+ }
23285
+ case "map": {
23286
+ const mp = property;
23287
+ const result = {
23288
+ ...base,
23289
+ type: "map"
23290
+ };
23291
+ if (mp.columnType) result.columnType = mp.columnType;
23292
+ if (mp.validation) result.validation = mp.validation;
23293
+ if (mp.propertiesOrder) result.propertiesOrder = mp.propertiesOrder;
23294
+ if (mp.previewProperties) result.previewProperties = mp.previewProperties;
23295
+ if (mp.keyValue) result.keyValue = mp.keyValue;
23296
+ if (mp.properties) result.properties = toSerializableProperties(mp.properties);
23297
+ return result;
23298
+ }
23299
+ case "vector": {
23300
+ const vp = property;
23301
+ const result = {
23302
+ ...base,
23303
+ type: "vector",
23304
+ dimensions: vp.dimensions
23305
+ };
23306
+ if (vp.validation) result.validation = vp.validation;
23307
+ return result;
23308
+ }
23309
+ case "binary": {
23310
+ const bp = property;
23311
+ const result = {
23312
+ ...base,
23313
+ type: "binary"
23314
+ };
23315
+ if (bp.validation) result.validation = bp.validation;
23316
+ return result;
23317
+ }
23318
+ default: return base;
23319
+ }
23320
+ }
23321
+ /**
23322
+ * Convert a `Properties` record to its serializable form.
23323
+ */
23324
+ function toSerializableProperties(properties) {
23325
+ const result = {};
23326
+ for (const [key, property] of Object.entries(properties)) result[key] = toSerializableProperty(property);
23327
+ return result;
23328
+ }
23329
+ /**
23330
+ * Convert an `EntityCollection` to its JSON-serializable form.
23331
+ *
23332
+ * Strips all non-serializable fields (functions, React nodes, class instances)
23333
+ * while preserving the structural schema that the collection editor works with.
23334
+ *
23335
+ * The result is safe for `JSON.stringify()` and database storage.
23336
+ */
23337
+ function toSerializableCollection(collection) {
23338
+ const result = {
23339
+ slug: collection.slug,
23340
+ name: collection.name,
23341
+ properties: toSerializableProperties(collection.properties)
23342
+ };
23343
+ if (collection.singularName) result.singularName = collection.singularName;
23344
+ if (collection.description) result.description = collection.description;
23345
+ if (collection.group) result.group = collection.group;
23346
+ if (collection.engine) result.engine = collection.engine;
23347
+ if (collection.dataSource) result.dataSource = collection.dataSource;
23348
+ if (collection.databaseId) result.databaseId = collection.databaseId;
23349
+ if (collection.titleProperty) result.titleProperty = collection.titleProperty;
23350
+ if (collection.ownerId) result.ownerId = collection.ownerId;
23351
+ if (collection.metadata) result.metadata = collection.metadata;
23352
+ if (collection.table) result.table = collection.table;
23353
+ if ("schema" in collection && collection.schema) result.schema = collection.schema;
23354
+ if (collection.orderProperty) result.orderProperty = collection.orderProperty;
23355
+ if (typeof collection.icon === "string") result.icon = collection.icon;
23356
+ if (collection.propertiesOrder) result.propertiesOrder = collection.propertiesOrder;
23357
+ if (collection.previewProperties) result.previewProperties = collection.previewProperties;
23358
+ if (collection.listProperties) result.listProperties = collection.listProperties;
23359
+ if (collection.enabledViews) result.enabledViews = collection.enabledViews;
23360
+ if (collection.disableDefaultActions) result.disableDefaultActions = collection.disableDefaultActions;
23361
+ if (collection.securityRules) result.securityRules = collection.securityRules;
23362
+ if (collection.openEntityMode) result.openEntityMode = collection.openEntityMode;
23363
+ if (collection.defaultEntityAction) result.defaultEntityAction = collection.defaultEntityAction;
23364
+ if (collection.defaultViewMode) result.defaultViewMode = collection.defaultViewMode;
23365
+ if (collection.defaultSize) result.defaultSize = collection.defaultSize;
23366
+ if (collection.localChangesBackup !== void 0) result.localChangesBackup = collection.localChangesBackup;
23367
+ if (collection.kanban) result.kanban = collection.kanban;
23368
+ if (collection.fixedFilter) result.fixedFilter = collection.fixedFilter;
23369
+ if (collection.defaultFilter) result.defaultFilter = collection.defaultFilter;
23370
+ if (collection.filterPresets) result.filterPresets = collection.filterPresets;
23371
+ if (collection.sort) result.sort = collection.sort;
23372
+ if (collection.pagination !== void 0) result.pagination = collection.pagination;
23373
+ if (collection.selectionEnabled !== void 0) result.selectionEnabled = collection.selectionEnabled;
23374
+ if (collection.inlineEditing !== void 0) result.inlineEditing = collection.inlineEditing;
23375
+ if (collection.hideFromNavigation !== void 0) result.hideFromNavigation = collection.hideFromNavigation;
23376
+ if (collection.hideIdFromForm !== void 0) result.hideIdFromForm = collection.hideIdFromForm;
23377
+ if (collection.hideIdFromCollection !== void 0) result.hideIdFromCollection = collection.hideIdFromCollection;
23378
+ if (collection.formAutoSave !== void 0) result.formAutoSave = collection.formAutoSave;
23379
+ if (collection.alwaysApplyDefaultValues !== void 0) result.alwaysApplyDefaultValues = collection.alwaysApplyDefaultValues;
23380
+ if (collection.includeJsonView !== void 0) result.includeJsonView = collection.includeJsonView;
23381
+ if (collection.history !== void 0) result.history = collection.history;
23382
+ if (collection.sideDialogWidth !== void 0) result.sideDialogWidth = collection.sideDialogWidth;
23383
+ if (typeof collection.exportable === "boolean") result.exportable = collection.exportable;
23384
+ if (typeof collection.auth === "boolean") result.auth = collection.auth;
23385
+ if (typeof collection.defaultSelectedView === "string") result.defaultSelectedView = collection.defaultSelectedView;
23386
+ return result;
23387
+ }
23388
+ /**
23389
+ * Convert a serializable property back to the original `Property` type.
23390
+ *
23391
+ * This is a mostly pass-through operation since serializable properties
23392
+ * are a subset of the original types. The main conversions are:
23393
+ * - `validation.matches` (string) stays as string (compatible with Property)
23394
+ * - `validation.min/max` on dates: ISO strings → Date objects
23395
+ */
23396
+ function fromSerializableProperty(serialized) {
23397
+ switch (serialized.type) {
23398
+ case "date": {
23399
+ const { validation: dateValidation, ...dateRest } = serialized;
23400
+ const result = {
23401
+ ...dateRest,
23402
+ type: "date"
23403
+ };
23404
+ if (dateValidation) {
23405
+ const convertedValidation = { ...dateValidation };
23406
+ if (typeof dateValidation.min === "string") convertedValidation.min = new Date(dateValidation.min);
23407
+ if (typeof dateValidation.max === "string") convertedValidation.max = new Date(dateValidation.max);
23408
+ result.validation = convertedValidation;
23409
+ }
23410
+ return result;
23411
+ }
23412
+ case "array": {
23413
+ const sp = serialized;
23414
+ const result = { ...sp };
23415
+ if (sp.of) if (Array.isArray(sp.of)) result.of = sp.of.map(fromSerializableProperty);
23416
+ else result.of = fromSerializableProperty(sp.of);
23417
+ if (sp.oneOf) result.oneOf = {
23418
+ ...sp.oneOf,
23419
+ properties: fromSerializableProperties(sp.oneOf.properties)
23420
+ };
23421
+ return result;
23422
+ }
23423
+ case "map": {
23424
+ const sp = serialized;
23425
+ const result = { ...sp };
23426
+ if (sp.properties) result.properties = fromSerializableProperties(sp.properties);
23427
+ return result;
23428
+ }
23429
+ case "string": return { ...serialized };
23430
+ default: return serialized;
23431
+ }
23432
+ }
23433
+ /**
23434
+ * Convert a `SerializableProperties` record back to `Properties`.
23435
+ */
23436
+ function fromSerializableProperties(serialized) {
23437
+ const result = {};
23438
+ for (const [key, property] of Object.entries(serialized)) result[key] = fromSerializableProperty(property);
23439
+ return result;
23440
+ }
23441
+ /**
23442
+ * Convert a `SerializableCollection` back to an `EntityCollection`.
23443
+ *
23444
+ * The result will NOT contain any of the non-serializable fields
23445
+ * (callbacks, entityActions, etc.) — those must be re-attached by the
23446
+ * consumer if needed.
23447
+ */
23448
+ function fromSerializableCollection(serialized) {
23449
+ const { properties, ...rest } = serialized;
23450
+ return {
23451
+ ...rest,
23452
+ properties: fromSerializableProperties(properties)
23453
+ };
23454
+ }
23455
+ //#endregion
23456
+ //#region src/collection_editor/useJsonCollectionsConfigController.tsx
23457
+ /**
23458
+ * A `CollectionsConfigController` implementation that persists collection
23459
+ * configurations as JSON via a pluggable `JsonCollectionStore` adapter.
23460
+ *
23461
+ * Use this when you want to store collection schemas in a database, API,
23462
+ * localStorage, or any other JSON-compatible backend — as opposed to the
23463
+ * default code-based persistence.
23464
+ *
23465
+ * @example
23466
+ * ```tsx
23467
+ * const store: JsonCollectionStore = {
23468
+ * load: async () => { const res = await fetch('/api/schemas'); return res.json(); },
23469
+ * save: async (slug, data) => { await fetch(`/api/schemas/${slug}`, { method: 'PUT', body: JSON.stringify(data) }); },
23470
+ * delete: async (slug) => { await fetch(`/api/schemas/${slug}`, { method: 'DELETE' }); },
23471
+ * };
23472
+ *
23473
+ * const configController = useJsonCollectionsConfigController({ store });
23474
+ * ```
23475
+ */
23476
+ function useJsonCollectionsConfigController({ store, readOnly = false, autoLoad = true }) {
23477
+ const [collections, setCollections] = useState([]);
23478
+ const [loading, setLoading] = useState(autoLoad);
23479
+ const [navigationEntries, setNavigationEntries] = useState([]);
23480
+ const storeRef = useRef(store);
23481
+ storeRef.current = store;
23482
+ useEffect(() => {
23483
+ if (!autoLoad) return;
23484
+ let cancelled = false;
23485
+ (async () => {
23486
+ try {
23487
+ const [serialized, navEntries] = await Promise.all([storeRef.current.load(), storeRef.current.loadNavigationEntries?.() ?? Promise.resolve([])]);
23488
+ if (cancelled) return;
23489
+ setCollections(serialized.map(fromSerializableCollection));
23490
+ setNavigationEntries(navEntries);
23491
+ } catch (e) {
23492
+ console.error("useJsonCollectionsConfigController: failed to load collections", e);
23493
+ } finally {
23494
+ if (!cancelled) setLoading(false);
23495
+ }
23496
+ })();
23497
+ return () => {
23498
+ cancelled = true;
23499
+ };
23500
+ }, [autoLoad]);
23501
+ /**
23502
+ * Update local state and persist a collection to the store.
23503
+ */
23504
+ const persistCollection = useCallback(async (collection) => {
23505
+ const serializable = toSerializableCollection(collection);
23506
+ await storeRef.current.save(collection.slug, serializable);
23507
+ }, []);
23508
+ /**
23509
+ * Find collection by id (slug).
23510
+ */
23511
+ const getCollection = useCallback((id) => {
23512
+ const found = collections.find((c) => c.slug === id);
23513
+ if (found) return found;
23514
+ throw new Error(`Collection "${id}" not found`);
23515
+ }, [collections]);
23516
+ const saveCollection = useCallback(async ({ id, collectionData }) => {
23517
+ const collection = collectionData;
23518
+ await persistCollection(collection);
23519
+ setCollections((prev) => {
23520
+ const idx = prev.findIndex((c) => c.slug === id);
23521
+ if (idx >= 0) {
23522
+ const next = [...prev];
23523
+ next[idx] = collection;
23524
+ return next;
23525
+ }
23526
+ return [...prev, collection];
23527
+ });
23528
+ }, [persistCollection]);
23529
+ const updateCollection = useCallback(async ({ id, collectionData, previousId }) => {
23530
+ setCollections((prev) => {
23531
+ const lookupId = previousId ?? id;
23532
+ const idx = prev.findIndex((c) => c.slug === lookupId);
23533
+ if (idx < 0) return prev;
23534
+ const merged = {
23535
+ ...prev[idx],
23536
+ ...collectionData
23537
+ };
23538
+ const next = [...prev];
23539
+ next[idx] = merged;
23540
+ persistCollection(merged).catch((e) => console.error("useJsonCollectionsConfigController: failed to update collection", e));
23541
+ if (previousId && previousId !== id) storeRef.current.delete(previousId).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete old slug", e));
23542
+ return next;
23543
+ });
23544
+ }, [persistCollection]);
23545
+ const deleteCollection = useCallback(async ({ id }) => {
23546
+ await storeRef.current.delete(id);
23547
+ setCollections((prev) => prev.filter((c) => c.slug !== id));
23548
+ }, []);
23549
+ const saveProperty = useCallback(async ({ path, propertyKey, property, newPropertiesOrder }) => {
23550
+ setCollections((prev) => {
23551
+ const idx = prev.findIndex((c) => c.slug === path);
23552
+ if (idx < 0) return prev;
23553
+ const collection = { ...prev[idx] };
23554
+ collection.properties = {
23555
+ ...collection.properties,
23556
+ [propertyKey]: property
23557
+ };
23558
+ if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
23559
+ const next = [...prev];
23560
+ next[idx] = collection;
23561
+ persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to save property", e));
23562
+ return next;
23563
+ });
23564
+ }, [persistCollection]);
23565
+ const deleteProperty = useCallback(async ({ path, propertyKey, newPropertiesOrder }) => {
23566
+ setCollections((prev) => {
23567
+ const idx = prev.findIndex((c) => c.slug === path);
23568
+ if (idx < 0) return prev;
23569
+ const collection = { ...prev[idx] };
23570
+ const { [propertyKey]: _removed, ...remaining } = collection.properties;
23571
+ collection.properties = remaining;
23572
+ if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
23573
+ const next = [...prev];
23574
+ next[idx] = collection;
23575
+ persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete property", e));
23576
+ return next;
23577
+ });
23578
+ }, [persistCollection]);
23579
+ const updatePropertiesOrder = useCallback(async ({ collection, newPropertiesOrder }) => {
23580
+ const updated = {
23581
+ ...collection,
23582
+ propertiesOrder: newPropertiesOrder
23583
+ };
23584
+ setCollections((prev) => {
23585
+ const idx = prev.findIndex((c) => c.slug === collection.slug);
23586
+ if (idx < 0) return prev;
23587
+ const next = [...prev];
23588
+ next[idx] = updated;
23589
+ return next;
23590
+ });
23591
+ await persistCollection(updated);
23592
+ }, [persistCollection]);
23593
+ const updateKanbanColumnsOrder = useCallback(async ({ collection, kanbanColumnProperty, newColumnsOrder }) => {}, []);
23594
+ const saveNavigationEntriesHandler = useCallback(async (entries) => {
23595
+ setNavigationEntries(entries);
23596
+ if (storeRef.current.saveNavigationEntries) await storeRef.current.saveNavigationEntries(entries);
23597
+ }, []);
23598
+ return useMemo(() => ({
23599
+ loading,
23600
+ readOnly,
23601
+ readOnlyReason: readOnly ? "Configuration is read-only." : void 0,
23602
+ collections,
23603
+ getCollection,
23604
+ saveCollection,
23605
+ updateCollection,
23606
+ deleteCollection,
23607
+ saveProperty,
23608
+ deleteProperty,
23609
+ updatePropertiesOrder,
23610
+ updateKanbanColumnsOrder,
23611
+ navigationEntries,
23612
+ saveNavigationEntries: saveNavigationEntriesHandler
23613
+ }), [
23614
+ loading,
23615
+ readOnly,
23616
+ collections,
23617
+ getCollection,
23618
+ saveCollection,
23619
+ updateCollection,
23620
+ deleteCollection,
23621
+ saveProperty,
23622
+ deleteProperty,
23623
+ updatePropertiesOrder,
23624
+ updateKanbanColumnsOrder,
23625
+ navigationEntries,
23626
+ saveNavigationEntriesHandler
23627
+ ]);
23628
+ }
23629
+ //#endregion
23609
23630
  //#region src/collection_editor/validateCollectionJson.ts
23610
23631
  /**
23611
23632
  * Valid dataType values for properties
@@ -24006,6 +24027,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
24006
24027
  return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
24007
24028
  }
24008
24029
  //#endregion
24009
- export { useApp as $, getBracketNotation as $n, SideDialogs as $t, KeyValuePreview as A, useSideDialogsController as An, RepeatFieldBinding as At, RebaseRouteDefs as B, SelectableTableContext as Bn, ArrayContainer as Bt, zodToFormErrors as C, useCMSContext as Cn, getFieldConfig as Ct, NumberPropertyPreview as D, useNavigationStateController as Dn, SwitchFieldBinding as Dt, UserPreview as E, NavigationStateContext as En, TextFieldBinding as Et, ArrayEnumPreview as F, useSelectionController as Fn, MapFieldBinding as Ft, useLocalCollectionsConfigController as G, resolveEntityAction as Gn, FormEntry as Gt, RebaseRoute as H, useSideEntityController as Hn, PropertyFieldBinding as Ht, ArrayOfStorageComponentsPreview as I, EntityCollectionTable as In, KeyValueFieldBinding as It, Scaffold as J, ArrayOfReferencesPreview as Jn, LabelWithIcon as Jt, RebaseAuthGate as K, resolveEntityView as Kn, LabelWithIconAndTooltip as Kt, RelationPreview as L, SelectableTable as Ln, DateTimeFieldBinding as Lt, ArrayOneOfPreview as M, getEntityViewWidth as Mn, ReferenceAsStringFieldBinding as Mt, ArrayOfStringsPreview as N, useBuildSideEntityController as Nn, MultiSelectFieldBinding as Nt, BooleanPreview as O, CollectionRegistryContext as On, StorageUploadFieldBinding as Ot, ArrayPropertyEnumPreview as P, mergeEntityActions as Pn, MarkdownEditorFieldBinding as Pt, AppBar as Q, getEntityTitlePropertyKey as Qn, EntitySelectionTable as Qt, CollectionPanel as R, EntityCollectionRowActions as Rn, BlockFieldBinding as Rt, EntityForm as S, useResolvedNavigationFrom as Sn, getDefaultFieldId as St, PropertyPreview as T, useUrlController as Tn, VectorFieldBinding as Tt, RebaseLayout as U, useEntitySelectionDialog as Un, ReadOnlyFieldBinding as Ut, CustomViewRoute as V, SideEntityControllerContext as Vn, ArrayCustomShapedFieldBinding as Vt, RebaseNavigation as W, VirtualTableInput as Wn, FormLayout as Wt, DefaultDrawer as X, EntityPreview as Xn, SearchIconsView as Xt, Drawer as Y, ReferencePreview as Yn, FieldHelperText as Yt, DrawerFooterActions as Z, getEntityPreviewKeys as Zn, FieldCaption as Zt, getEntityCSVExportableData as _, removeTrailingSlash as _n, renderSkeletonText as _r, convertFileToJson as _t, namespaceToPropertiesPath as a, useResolvedCollections as an, getResolvedPropertyInPath as ar, EntityCard as at, ImportDataPreview as b, useEntityHistory as bn, ImagePreview as br, DEFAULT_FIELD_CONFIGS as bt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as c, editEntityAction as cn, ArrayPropertyPreview as cr, ImportNewPropertyFieldPreview as ct, useCollectionsConfigController as d, addInitialSlash as dn, StorageThumbnail as dr, flattenEntry as dt, useBreadcrumbsController as en, getDefaultPropertiesOrder as er, AdminModeSyncer as et, ExportCollectionAction as f, getCollectionBySlugWithin as fn, StorageThumbnailInternal as fr, processValueMapping as ft, downloadEntitiesExport as g, removeInitialSlash as gn, renderSkeletonImageThumbnail as gr, ImportFileUpload as gt, downloadDataAsCsv as h, removeInitialAndTrailingSlashes$1 as hn, renderSkeletonIcon as hr, ImportSaveInProgress as ht, namespaceToPropertiesOrderPath as i, useResolvedViews as in, getPropertyInPath as ir, EntityCollectionCardView as it, MapPropertyPreview as j, buildSidePanelsFromUrl as jn, ReferenceFieldBinding as jt, DatePreview as k, useCollectionRegistryController as kn, SelectFieldBinding as kt, buildCollectionGenerationCallback as l, resetPasswordAction as ln, StringPropertyPreview as lr, DataNewPropertiesMapping as lt, downloadBlob as m, getLastSegment$1 as mn, renderSkeletonCaptionText as mr, useImportConfig as mt, getFullIdPath as n, useBuildNavigationStateController as nn, getIconForWidget as nr, ReferenceWidget as nt, MissingReferenceWidget as o, copyEntityAction as on, isReferenceProperty as or, EntityCollectionViewActions as ot, BasicExportAction as p, getCollectionPathsCombinations as pn, SkeletonPropertyComponent as pr, getInferenceType as pt, RebaseCMS as q, EntityView as qn, PropertyIdCopyTooltip as qt, idToPropertiesPath as r, useTopLevelNavigation as rn, getPropertiesWithPropertiesOrder as rr, EntityCollectionView as rt, CollectionGenerationApiError as s, deleteEntityAction as sn, isRelationProperty as sr, useCollectionEditorController as st, getFullId as t, useBuildUrlController as tn, getIconForProperty as tr, SideEntityProvider as tt, validateCollectionJson as u, CreationResultDialog as un, EnumValuesChip as ur, convertDataToEntity as ut, getEntityJsonExportableData as v, resolveCollectionPathIds$1 as vn, UrlComponentPreview as vr, unflattenObject as vt, ArrayOfMapsPreview as w, UrlContext as wn, getFieldId as wt, PropertySelectEntry as x, resolveNavigationFrom as xn, EmptyValue as xr, getDefaultFieldConfig as xt, ImportCollectionAction as y, useBuildCollectionRegistryController as yn, sanitizeUrl as yr, PropertyConfigBadge as yt, RebaseShell as z, useClearRestoreValue as zn, ArrayOfReferencesFieldBinding as zt };
24030
+ export { RelationPreview as $, SelectableTable as $n, BlockFieldBinding as $t, useCollectionDataController as A, removeInitialAndTrailingSlashes$1 as An, renderSkeletonIcon as Ar, useImportConfig as At, ArrayOfMapsPreview as B, useUrlController as Bn, getFieldId as Bt, downloadEntitiesExport as C, editEntityAction as Cn, ArrayPropertyPreview as Cr, useCollectionEditorController as Ct, ImportDataPreview as D, getCollectionBySlugWithin as Dn, StorageThumbnailInternal as Dr, flattenEntry as Dt, ImportCollectionAction as E, addInitialSlash as En, StorageThumbnail as Er, convertDataToEntity as Et, extractTouchedValues as F, useEntityHistory as Fn, ImagePreview as Fr, PropertyConfigBadge as Ft, DatePreview as G, useSideDialogsController as Gn, SelectFieldBinding as Gt, UserPreview as H, useNavigationStateController as Hn, TextFieldBinding as Ht, getChanges as I, resolveNavigationFrom as In, EmptyValue as Ir, DEFAULT_FIELD_CONFIGS as It, ArrayOneOfPreview as J, useBuildSideEntityController as Jn, MultiSelectFieldBinding as Jt, KeyValuePreview as K, buildSidePanelsFromUrl as Kn, RepeatFieldBinding as Kt, getInitialEntityValues as L, useResolvedNavigationFrom as Ln, getDefaultFieldConfig as Lt, mapPropertyToConfig as M, removeTrailingSlash as Mn, renderSkeletonText as Mr, ImportFileUpload as Mt, EntityFormBinding as N, resolveCollectionPathIds$1 as Nn, UrlComponentPreview as Nr, convertFileToJson as Nt, PropertySelectEntry as O, getCollectionPathsCombinations as On, SkeletonPropertyComponent as Or, processValueMapping as Ot, EntityForm as P, useBuildCollectionRegistryController as Pn, sanitizeUrl as Pr, unflattenObject as Pt, ArrayOfStorageComponentsPreview as Q, EntityCollectionTable as Qn, DateTimeFieldBinding as Qt, removeEmptyContainers as R, useCMSContext as Rn, getDefaultFieldId as Rt, downloadDataAsCsv as S, deleteEntityAction as Sn, isRelationProperty as Sr, EntityCollectionViewActions as St, getEntityJsonExportableData as T, CreationResultDialog as Tn, EnumValuesChip as Tr, DataNewPropertiesMapping as Tt, NumberPropertyPreview as U, CollectionRegistryContext as Un, SwitchFieldBinding as Ut, PropertyPreview as V, NavigationStateContext as Vn, VectorFieldBinding as Vt, BooleanPreview as W, useCollectionRegistryController as Wn, StorageUploadFieldBinding as Wt, ArrayPropertyEnumPreview as X, useSelectionController as Xn, MapFieldBinding as Xt, ArrayOfStringsPreview as Y, mergeEntityActions as Yn, MarkdownEditorFieldBinding as Yt, ArrayEnumPreview as Z, VirtualTableInput$1 as Zn, KeyValueFieldBinding as Zt, toSerializableProperty as _, useBuildNavigationStateController as _n, getIconForWidget as _r, SideEntityProvider as _t, namespaceToPropertiesPath as a, FormLayout as an, useEntitySelectionDialog as ar, RebaseLayout as at, BasicExportAction as b, useResolvedCollections as bn, getResolvedPropertyInPath as br, EntityCollectionCardView as bt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as c, PropertyIdCopyTooltip as cn, EntityView as cr, RebaseAuthGate as ct, useJsonCollectionsConfigController as d, SearchIconsView as dn, EntityPreview as dr, Drawer as dt, ArrayOfReferencesFieldBinding as en, EntityCollectionRowActions as er, CollectionPanel as et, fromSerializableCollection as f, FieldCaption as fn, getEntityPreviewKeys as fr, DefaultDrawer as ft, toSerializableProperties as g, useBuildUrlController as gn, getIconForProperty as gr, AdminModeSyncer as gt, toSerializableCollection as h, useBreadcrumbsController as hn, getDefaultPropertiesOrder as hr, useApp as ht, namespaceToPropertiesOrderPath as i, ReadOnlyFieldBinding as in, useSideEntityController as ir, RebaseRoute as it, mapPropertiesToConfigs as j, removeInitialSlash as jn, renderSkeletonImageThumbnail as jr, ImportSaveInProgress as jt, createStaticDataController as k, getLastSegment$1 as kn, renderSkeletonCaptionText as kr, getInferenceType as kt, buildCollectionGenerationCallback as l, LabelWithIcon as ln, ArrayOfReferencesPreview as lr, RebaseCMS as lt, fromSerializableProperty as m, SideDialogs as mn, getBracketNotation as mr, AppBar as mt, getFullIdPath as n, ArrayCustomShapedFieldBinding as nn, SelectableTableContext as nr, RebaseRouteDefs as nt, MissingReferenceWidget as o, FormEntry as on, resolveEntityAction as or, RebaseNavigation as ot, fromSerializableProperties as p, EntitySelectionTable as pn, getEntityTitlePropertyKey as pr, DrawerFooterActions as pt, MapPropertyPreview as q, getEntityViewWidth as qn, ReferenceFieldBinding as qt, idToPropertiesPath as r, PropertyFieldBinding as rn, SideEntityControllerContext as rr, CustomViewRoute as rt, CollectionGenerationApiError as s, LabelWithIconAndTooltip as sn, resolveEntityView as sr, useLocalCollectionsConfigController as st, getFullId as t, ArrayContainer as tn, useClearRestoreValue as tr, RebaseShell as tt, validateCollectionJson as u, FieldHelperText as un, ReferencePreview as ur, Scaffold as ut, useCollectionsConfigController as v, useTopLevelNavigation as vn, getPropertiesWithPropertiesOrder as vr, ReferenceWidget as vt, getEntityCSVExportableData as w, resetPasswordAction as wn, StringPropertyPreview as wr, ImportNewPropertyFieldPreview as wt, downloadBlob as x, copyEntityAction as xn, isReferenceProperty as xr, EntityCard as xt, ExportCollectionAction as y, useResolvedViews as yn, getPropertyInPath as yr, EntityCollectionView as yt, zodToFormErrors as z, UrlContext as zn, getFieldConfig as zt };
24010
24031
 
24011
- //# sourceMappingURL=src-BYniefVu.js.map
24032
+ //# sourceMappingURL=src-cxV1ODRG.js.map