@rebasepro/admin 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
  2. package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-C9yhLyja.js} +129 -15
  4. package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
  7. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  8. package/dist/collection_editor/index.d.ts +6 -0
  9. package/dist/collection_editor/serializable_types.d.ts +374 -0
  10. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  11. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
  12. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  13. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  14. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  15. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  16. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  17. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  18. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  19. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  20. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  21. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  22. package/dist/collection_editor_ui.d.ts +4 -0
  23. package/dist/collection_editor_ui.js +33 -4
  24. package/dist/collection_editor_ui.js.map +1 -0
  25. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  26. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  27. package/dist/components/EntityEditView.d.ts +3 -2
  28. package/dist/components/RebaseCMS.d.ts +1 -1
  29. package/dist/form/EntityForm.d.ts +10 -9
  30. package/dist/form/EntityFormBinding.d.ts +27 -0
  31. package/dist/form/form_utils.d.ts +8 -0
  32. package/dist/form/index.d.ts +5 -1
  33. package/dist/{history-BO6VT85a.js → history-ChHgyyvM.js} +2 -2
  34. package/dist/{history-BO6VT85a.js.map → history-ChHgyyvM.js.map} +1 -1
  35. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  36. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  37. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  38. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  39. package/dist/hooks/navigation/utils.d.ts +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +2 -2
  42. package/dist/routes/CustomViewRoute.d.ts +2 -1
  43. package/dist/{src-BYniefVu.js → src-DOX2nOyh.js} +1586 -1283
  44. package/dist/src-DOX2nOyh.js.map +1 -0
  45. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  46. package/dist/types/components/EntityFormProps.d.ts +59 -0
  47. package/dist/types/fields.d.ts +6 -1
  48. package/dist/util/dataControllerAdapter.d.ts +30 -0
  49. package/dist/util/navigation_utils.d.ts +1 -1
  50. package/dist/util/propertyConfigMapper.d.ts +17 -0
  51. package/package.json +18 -18
  52. package/src/collection_editor/extensibility_types.ts +168 -0
  53. package/src/collection_editor/index.ts +46 -0
  54. package/src/collection_editor/serializable_types.ts +487 -0
  55. package/src/collection_editor/serializable_utils.ts +588 -0
  56. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
  57. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
  58. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  59. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  60. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  61. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  62. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  63. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  64. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +155 -10
  65. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  66. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  67. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  68. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  69. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  70. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  71. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  72. package/src/collection_editor_ui.ts +6 -0
  73. package/src/components/DefaultDrawer.tsx +13 -3
  74. package/src/components/DrawerNavigationGroup.tsx +2 -2
  75. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  76. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -11
  77. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  78. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
  79. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  80. package/src/components/EntityEditView.tsx +5 -4
  81. package/src/components/EntityEditViewFormActions.tsx +32 -26
  82. package/src/components/HomePage/ContentHomePage.tsx +13 -4
  83. package/src/components/RebaseCMS.tsx +3 -2
  84. package/src/components/RebaseNavigation.tsx +71 -7
  85. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  86. package/src/components/SideDialogs.tsx +0 -7
  87. package/src/components/common/default_entity_actions.tsx +9 -9
  88. package/src/contexts/BreacrumbsContext.tsx +15 -6
  89. package/src/editor/selectors/ai-selector.tsx +0 -1
  90. package/src/form/EntityForm.tsx +191 -428
  91. package/src/form/EntityFormActions.tsx +33 -47
  92. package/src/form/EntityFormBinding.tsx +323 -0
  93. package/src/form/components/FormLayout.tsx +1 -1
  94. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  95. package/src/form/form_utils.ts +163 -0
  96. package/src/form/index.tsx +14 -5
  97. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  98. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  99. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  100. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  101. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  102. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  103. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  104. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  105. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  106. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  107. package/src/hooks/navigation/utils.ts +7 -4
  108. package/src/index.ts +5 -0
  109. package/src/routes/CustomViewRoute.tsx +14 -6
  110. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  111. package/src/types/components/EntityFormProps.tsx +68 -0
  112. package/src/types/fields.tsx +7 -1
  113. package/src/util/dataControllerAdapter.ts +93 -0
  114. package/src/util/navigation_utils.ts +1 -1
  115. package/src/util/propertyConfigMapper.ts +134 -0
  116. package/LICENSE +0 -21
  117. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  118. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  119. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  120. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  121. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  122. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  123. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  124. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  125. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  126. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  127. package/dist/src-BYniefVu.js.map +0 -1
  128. package/src/components/EntityCollectionView/Board.tsx +0 -475
  129. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  130. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  131. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  132. package/src/components/EntityCollectionView/board_types.ts +0 -113
  133. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  134. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -2,9 +2,9 @@ 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, BooleanSwitch, 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, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, 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 } 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, 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, 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";
@@ -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",
@@ -359,8 +349,8 @@ function buildBottomActions$1({ savingError, entity, path, formActions, collecti
359
349
  entity,
360
350
  path: path ?? collection.slug,
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", {
@@ -1578,7 +1576,7 @@ var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
1578
1576
  var JSON_TAB_VALUE = "__json";
1579
1577
  //#endregion
1580
1578
  //#region src/components/EntityDetailView.tsx
1581
- var EntityHistoryView$1 = lazy(() => import("./history-BO6VT85a.js").then((m) => ({ default: m.EntityHistoryView })));
1579
+ var EntityHistoryView$1 = lazy(() => import("./history-ChHgyyvM.js").then((m) => ({ default: m.EntityHistoryView })));
1582
1580
  function EntityDetailView({ entityId, ...props }) {
1583
1581
  const { entity, dataLoading, dataLoadingError } = useEntityFetch({
1584
1582
  path: props.path,
@@ -5206,7 +5204,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
5206
5204
  children: /* @__PURE__ */ jsx("div", {
5207
5205
  className: "h-full w-full flex flex-col bg-white dark:bg-surface-900",
5208
5206
  ref,
5209
- children: /* @__PURE__ */ jsx(VirtualTable, {
5207
+ children: /* @__PURE__ */ jsx(TableView, {
5210
5208
  data,
5211
5209
  columns,
5212
5210
  cellRenderer: (props) => cellRenderer(props),
@@ -5734,19 +5732,22 @@ function buildBottomActions({ savingError, entity, formActions, collection, cont
5734
5732
  onClick: navigateBack,
5735
5733
  children: t("back_to_detail") ?? "Back to details"
5736
5734
  }),
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
- ]
5735
+ /* @__PURE__ */ jsx(Tooltip, {
5736
+ title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
5737
+ children: /* @__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
+ ]
5750
+ })
5750
5751
  }),
5751
5752
  canClose && /* @__PURE__ */ jsxs(LoadingButton, {
5752
5753
  variant: "filled",
@@ -5771,21 +5772,25 @@ function buildSideActions({ savingError, entity, formActions, collection, contex
5771
5772
  return /* @__PURE__ */ jsxs("div", {
5772
5773
  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
5774
  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
- ]
5775
+ /* @__PURE__ */ jsx(Tooltip, {
5776
+ title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
5777
+ children: /* @__PURE__ */ jsxs(LoadingButton, {
5778
+ fullWidth: true,
5779
+ variant: "filled",
5780
+ color: "primary",
5781
+ type: "submit",
5782
+ loading: formex.isSubmitting,
5783
+ startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
5784
+ disabled: disabled || formex.isSubmitting,
5785
+ onClick: () => {
5786
+ sideDialogContext.setPendingClose?.(false);
5787
+ },
5788
+ children: [
5789
+ status === "existing" && t("save"),
5790
+ status === "copy" && t("create_copy"),
5791
+ status === "new" && t("create")
5792
+ ]
5793
+ })
5789
5794
  }),
5790
5795
  /* @__PURE__ */ jsx(Button, {
5791
5796
  fullWidth: true,
@@ -5875,7 +5880,7 @@ function EntityActionButton({ action, enabled, props }) {
5875
5880
  }
5876
5881
  //#endregion
5877
5882
  //#region src/components/EntityEditView.tsx
5878
- var EntityHistoryView = lazy(() => import("./history-BO6VT85a.js").then((m) => ({ default: m.EntityHistoryView })));
5883
+ var EntityHistoryView = lazy(() => import("./history-ChHgyyvM.js").then((m) => ({ default: m.EntityHistoryView })));
5879
5884
  /**
5880
5885
  * This is the default view that is used as the content of a side panel when
5881
5886
  * an entity is opened.
@@ -5927,7 +5932,7 @@ function EntityEditView({ entityId, ...props }) {
5927
5932
  }
5928
5933
  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
5934
  const ResolvedFormActions = useComponentOverride("Entity.FormActions", EntityEditViewFormActions);
5930
- const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityForm);
5935
+ const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
5931
5936
  const ResolvedCollectionView = useComponentOverride("Collection.View", EntityCollectionView);
5932
5937
  const context = useRebaseContext();
5933
5938
  const urlController = useUrlController();
@@ -7070,8 +7075,9 @@ function useEntityHistory(params) {
7070
7075
  //#endregion
7071
7076
  //#region src/hooks/navigation/useBuildCollectionRegistryController.tsx
7072
7077
  function useBuildCollectionRegistryController(props) {
7073
- const { userConfigPersistence } = props;
7074
- const collectionRegistryRef = useRef(new CollectionRegistry());
7078
+ const { userConfigPersistence, dataSources } = props;
7079
+ const collectionRegistryRef = useRef(new CollectionRegistry(void 0, dataSources));
7080
+ if (dataSources) collectionRegistryRef.current.setDataSources(dataSources);
7075
7081
  const [initialised, setInitialised] = useState(false);
7076
7082
  const getCollection = useCallback((slugOrPath, includeUserOverride = false) => {
7077
7083
  const registry = collectionRegistryRef.current;
@@ -7163,7 +7169,9 @@ function useBuildCollectionRegistryController(props) {
7163
7169
  return paths;
7164
7170
  }, []);
7165
7171
  const collections = collectionRegistryRef.current.getCollections();
7166
- if (!initialised && collections.length > 0) setInitialised(true);
7172
+ useEffect(() => {
7173
+ if (!initialised && collections.length > 0) setInitialised(true);
7174
+ }, [initialised, collections.length]);
7167
7175
  return useMemo(() => ({
7168
7176
  collections,
7169
7177
  initialised,
@@ -7491,7 +7499,7 @@ var editEntityAction = {
7491
7499
  if (!entity) throw new Error("INTERNAL: editEntityAction: Entity is undefined");
7492
7500
  if (!sideEntityController) throw new Error("INTERNAL: editEntityAction: sideEntityController is undefined");
7493
7501
  highlightEntity?.(entity);
7494
- context.analyticsController?.onAnalyticsEvent?.("entity_click", {
7502
+ context?.analyticsController?.onAnalyticsEvent?.("entity_click", {
7495
7503
  path: entity.path,
7496
7504
  entityId: entity.id
7497
7505
  });
@@ -7508,7 +7516,7 @@ var editEntityAction = {
7508
7516
  path: newFullIdPath,
7509
7517
  sideEntityController,
7510
7518
  onClose: () => unhighlightEntity?.(entity),
7511
- navigation: context.urlController,
7519
+ navigation: context?.urlController,
7512
7520
  selectedTab: defaultSelectedView
7513
7521
  });
7514
7522
  return Promise.resolve(void 0);
@@ -7523,7 +7531,7 @@ var copyEntityAction = {
7523
7531
  if (!entity) throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
7524
7532
  if (!sideEntityController) throw new Error("INTERNAL: copyEntityAction: sideEntityController is undefined");
7525
7533
  highlightEntity?.(entity);
7526
- context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
7534
+ context?.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
7527
7535
  path: entity.path,
7528
7536
  entityId: entity.id
7529
7537
  });
@@ -7536,7 +7544,7 @@ var copyEntityAction = {
7536
7544
  copy: true,
7537
7545
  sideEntityController,
7538
7546
  onClose: () => unhighlightEntity?.(entity),
7539
- navigation: context.urlController
7547
+ navigation: context?.urlController
7540
7548
  });
7541
7549
  return Promise.resolve(void 0);
7542
7550
  }
@@ -7548,7 +7556,7 @@ var deleteEntityAction = {
7548
7556
  isEnabled: ({ entity }) => Boolean(entity),
7549
7557
  onClick({ entity, path, collection, context, selectionController, onCollectionChange, navigateBack, openEntityMode }) {
7550
7558
  if (!entity) throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
7551
- if (!context.dialogsController) throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
7559
+ if (!context?.dialogsController) throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
7552
7560
  const { closeDialog } = context.dialogsController.open({
7553
7561
  key: "delete_entity_dialog_" + entity.id,
7554
7562
  Component: ({ open }) => {
@@ -7560,10 +7568,10 @@ var deleteEntityAction = {
7560
7568
  callbacks: collection.callbacks,
7561
7569
  open,
7562
7570
  onEntityDelete: () => {
7563
- context.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", { path });
7571
+ context?.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", { path });
7564
7572
  selectionController?.setSelectedEntities(selectionController.selectedEntities.filter((e) => e.id !== entity.id));
7565
7573
  onCollectionChange?.();
7566
- if (openEntityMode === "full_screen" && context.urlController) {
7574
+ if (openEntityMode === "full_screen" && context?.urlController) {
7567
7575
  const collectionUrl = context.urlController.buildUrlCollectionPath(path);
7568
7576
  context.urlController.navigate(collectionUrl, { replace: true });
7569
7577
  } else navigateBack?.();
@@ -7643,7 +7651,7 @@ var resetPasswordAction = {
7643
7651
  isEnabled: ({ entity }) => Boolean(entity),
7644
7652
  onClick({ entity, context }) {
7645
7653
  if (!entity) throw new Error("INTERNAL: resetPasswordAction: Entity is undefined");
7646
- if (!context.dialogsController) throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
7654
+ if (!context?.dialogsController) throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
7647
7655
  const user = {
7648
7656
  uid: entity.id,
7649
7657
  email: entity.values?.email,
@@ -7787,12 +7795,34 @@ async function resolveAppViews(baseViews, authController, data, plugins) {
7787
7795
  if (plugins) {
7788
7796
  for (const plugin of plugins) if (plugin.views && plugin.views.length > 0) resolvedViews = [...resolvedViews, ...plugin.views];
7789
7797
  }
7798
+ if (process.env.NODE_ENV !== "production") {
7799
+ const slugCounts = /* @__PURE__ */ new Map();
7800
+ resolvedViews.forEach((v) => {
7801
+ slugCounts.set(v.slug, (slugCounts.get(v.slug) ?? 0) + 1);
7802
+ });
7803
+ slugCounts.forEach((count, slug) => {
7804
+ if (count > 1) console.warn(`[Rebase] Duplicate view slug "${slug}" detected (${count} views). Last-write-wins. Ensure unique slugs across CMS views and plugins.`);
7805
+ });
7806
+ }
7807
+ resolvedViews = filterViewsByRole(resolvedViews, authController);
7790
7808
  return resolvedViews;
7791
7809
  }
7810
+ /**
7811
+ * Filter views by the `roles` field on AppView.
7812
+ * When `roles` is set, the view is only included if the current user
7813
+ * has at least one of the listed roles. Views without `roles` (or with
7814
+ * an empty array) are always included.
7815
+ */
7816
+ function filterViewsByRole(views, authController) {
7817
+ const userRoles = authController.user?.roles ?? [];
7818
+ return views.filter((view) => {
7819
+ if (!view.roles || view.roles.length === 0) return true;
7820
+ return view.roles.some((role) => userRoles.includes(role));
7821
+ });
7822
+ }
7792
7823
  //#endregion
7793
7824
  //#region src/hooks/navigation/utils.ts
7794
7825
  var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
7795
- var NAVIGATION_ADMIN_GROUP_NAME = "Admin";
7796
7826
  function getGroup(collectionOrView) {
7797
7827
  const trimmed = collectionOrView.group?.trim();
7798
7828
  if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
@@ -7801,7 +7831,8 @@ function getGroup(collectionOrView) {
7801
7831
  function computeNavigationGroups({ navigationGroupMappings, collections, views, plugins }) {
7802
7832
  let result = navigationGroupMappings ? navigationGroupMappings.map((g) => ({
7803
7833
  name: g.name,
7804
- entries: [...g.entries]
7834
+ entries: [...g.entries],
7835
+ ...g.collapsedByDefault !== void 0 && { collapsedByDefault: g.collapsedByDefault }
7805
7836
  })) : navigationGroupMappings;
7806
7837
  if (plugins) result = plugins.reduce((acc, plugin) => {
7807
7838
  if (plugin.hooks?.navigationEntries) plugin.hooks.navigationEntries.forEach((entry) => {
@@ -7891,48 +7922,38 @@ function areCollectionsEqual(a, b, visitedSlugs = []) {
7891
7922
  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
7923
  }
7893
7924
  //#endregion
7894
- //#region src/hooks/navigation/useResolvedCollections.ts
7925
+ //#region src/hooks/navigation/useAsyncResolver.ts
7895
7926
  /**
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.
7927
+ * Generic hook that resolves an async value with loading/error/refresh
7928
+ * state management, cancellation on unmount or deps change, and
7929
+ * ref-based change detection to prevent unnecessary state updates.
7902
7930
  *
7903
- * Uses refs for potentially-unstable dependencies (driver, authController,
7904
- * plugins) to avoid re-triggering effects when their object identity changes.
7931
+ * Extracted from the common pattern in useResolvedCollections and
7932
+ * useResolvedViews.
7905
7933
  */
7906
- function useResolvedCollections(props) {
7907
- const { authController, collections: collectionsProp, data, plugins, disabled, collectionRegistryController } = props;
7934
+ function useAsyncResolver({ resolver, initialValue, isEqual, deps, disabled }) {
7935
+ const [data, setData] = useState(initialValue);
7908
7936
  const [loading, setLoading] = useState(true);
7909
7937
  const [error, setError] = useState(void 0);
7910
- const [resolvedCollections, setResolvedCollections] = useState([]);
7911
7938
  const [refreshTrigger, setRefreshTrigger] = useState(0);
7912
7939
  const refresh = useCallback(() => {
7913
7940
  setRefreshTrigger((prev) => prev + 1);
7914
7941
  }, []);
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;
7942
+ const dataRef = useRef(initialValue);
7943
+ const resolverRef = useRef(resolver);
7944
+ resolverRef.current = resolver;
7945
+ const isEqualRef = useRef(isEqual);
7946
+ isEqualRef.current = isEqual;
7924
7947
  useEffect(() => {
7925
- if (disabled || initialLoading) return;
7948
+ if (disabled) return;
7926
7949
  let cancelled = false;
7927
7950
  (async () => {
7928
7951
  try {
7929
- const resolved = await resolveCollections(collectionsProp, authControllerRef.current, dataRef.current, pluginsRef.current);
7952
+ const result = await resolverRef.current();
7930
7953
  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);
7954
+ if (!isEqualRef.current(dataRef.current, result)) {
7955
+ dataRef.current = result;
7956
+ setData(result);
7936
7957
  }
7937
7958
  setError(void 0);
7938
7959
  } catch (e) {
@@ -7948,20 +7969,66 @@ function useResolvedCollections(props) {
7948
7969
  cancelled = true;
7949
7970
  };
7950
7971
  }, [
7951
- collectionsProp,
7952
- disabled,
7953
- collectionRegistryController.collectionRegistryRef,
7972
+ ...deps,
7954
7973
  refreshTrigger,
7955
- initialLoading,
7956
- user
7974
+ disabled
7975
+ ]);
7976
+ return useMemo(() => ({
7977
+ data,
7978
+ loading,
7979
+ error,
7980
+ refresh
7981
+ }), [
7982
+ data,
7983
+ loading,
7984
+ error,
7985
+ refresh
7957
7986
  ]);
7987
+ }
7988
+ //#endregion
7989
+ //#region src/hooks/navigation/useResolvedCollections.ts
7990
+ /**
7991
+ * Hook that resolves collection props (which may be async builders or arrays)
7992
+ * into concrete EntityCollection[], and registers them with the CollectionRegistry.
7993
+ *
7994
+ * When userManagement is provided, the default users collection is always
7995
+ * prepended. Developer collections override via generic slug-based dedup
7996
+ * (Map keyed by slug, last-write-wins). No hardcoded string checks.
7997
+ *
7998
+ * Uses refs for potentially-unstable dependencies (driver, authController,
7999
+ * plugins) to avoid re-triggering effects when their object identity changes.
8000
+ */
8001
+ function useResolvedCollections(props) {
8002
+ const { authController, collections: collectionsProp, data, plugins, disabled, collectionRegistryController } = props;
8003
+ const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
8004
+ const dataRef = useRef(data);
8005
+ dataRef.current = data;
8006
+ const authControllerRef = useRef(authController);
8007
+ authControllerRef.current = authController;
8008
+ const pluginsRef = useRef(plugins);
8009
+ pluginsRef.current = plugins;
8010
+ const { data: collections, loading, error, refresh } = useAsyncResolver({
8011
+ resolver: async () => {
8012
+ const deduped = [...await resolveCollections(collectionsProp, authControllerRef.current, dataRef.current, pluginsRef.current)];
8013
+ if (collectionRegistryController.collectionRegistryRef.current.registerMultiple(deduped)) console.debug("Collections have changed", deduped);
8014
+ return deduped;
8015
+ },
8016
+ initialValue: [],
8017
+ isEqual: areCollectionListsEqual,
8018
+ deps: [
8019
+ collectionsProp,
8020
+ userIdentity,
8021
+ disabled
8022
+ ],
8023
+ disabled: disabled || authController.initialLoading
8024
+ });
7958
8025
  return useMemo(() => ({
7959
- collections: resolvedCollections,
8026
+ collections,
7960
8027
  loading,
7961
8028
  error,
7962
8029
  refresh
7963
8030
  }), [
7964
- resolvedCollections,
8031
+ collections,
7965
8032
  loading,
7966
8033
  error,
7967
8034
  refresh
@@ -7982,6 +8049,13 @@ function viewSlugsEqual(a, b) {
7982
8049
  return true;
7983
8050
  }
7984
8051
  /**
8052
+ * Equality check for the combined views data.
8053
+ * Compares both views and adminViews by slug identity.
8054
+ */
8055
+ function areResolvedViewsEqual(a, b) {
8056
+ return viewSlugsEqual(a.views, b.views) && viewSlugsEqual(a.adminViews, b.adminViews);
8057
+ }
8058
+ /**
7985
8059
  * Hook that resolves view and admin view props (which may be async builders or arrays)
7986
8060
  * into concrete AppView[].
7987
8061
  *
@@ -7990,16 +8064,7 @@ function viewSlugsEqual(a, b) {
7990
8064
  */
7991
8065
  function useResolvedViews(props) {
7992
8066
  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);
8067
+ const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
8003
8068
  const dataRef = useRef(data);
8004
8069
  dataRef.current = data;
8005
8070
  const authControllerRef = useRef(authController);
@@ -8023,51 +8088,35 @@ function useResolvedViews(props) {
8023
8088
  const resolvedAuthControllerRef = useRef(resolvedAuthController);
8024
8089
  resolvedAuthControllerRef.current = resolvedAuthController;
8025
8090
  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
- ]);
8091
+ const { data: resolvedData, loading, error, refresh } = useAsyncResolver({
8092
+ resolver: async () => {
8093
+ const [newViews, newAdminViews] = await Promise.all([resolveAppViews(viewsProp, resolvedAuthControllerRef.current, dataRef.current, pluginsRef.current), resolveAppViews(adminViewsProp, resolvedAuthControllerRef.current, dataRef.current)]);
8094
+ return {
8095
+ views: newViews,
8096
+ adminViews: newAdminViews
8097
+ };
8098
+ },
8099
+ initialValue: {
8100
+ views: void 0,
8101
+ adminViews: void 0
8102
+ },
8103
+ isEqual: areResolvedViewsEqual,
8104
+ deps: [
8105
+ viewsProp,
8106
+ adminViewsProp,
8107
+ adminMode,
8108
+ userIdentity
8109
+ ],
8110
+ disabled: initialLoading
8111
+ });
8062
8112
  return useMemo(() => ({
8063
- views: resolvedViews,
8064
- adminViews: resolvedAdminViews,
8113
+ views: resolvedData.views,
8114
+ adminViews: resolvedData.adminViews,
8065
8115
  loading,
8066
8116
  error,
8067
8117
  refresh
8068
8118
  }), [
8069
- resolvedViews,
8070
- resolvedAdminViews,
8119
+ resolvedData,
8071
8120
  loading,
8072
8121
  error,
8073
8122
  refresh
@@ -8176,21 +8225,24 @@ function useTopLevelNavigation(props) {
8176
8225
  }, [])
8177
8226
  ];
8178
8227
  const groupOrderValue = (groupName) => {
8179
- if (groupName === "Admin") return 1;
8228
+ if (groupName === "Admin" || groupName === "Settings" || groupName === "Admin") return 1;
8180
8229
  return 0;
8181
8230
  };
8182
- navigationEntries = navigationEntries.sort((a, b) => {
8183
- return groupOrderValue(a.group) - groupOrderValue(b.group);
8184
- });
8185
8231
  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;
8232
+ navigationEntries = navigationEntries.sort((a, b) => {
8233
+ const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
8234
+ if (groupDiff !== 0) return groupDiff;
8235
+ if (usedViewsOrder) {
8236
+ const getSortPath = (navEntry) => typeof navEntry.slug === "string" ? navEntry.slug : navEntry.slug[0];
8237
+ const aIndex = usedViewsOrder.indexOf(getSortPath(a));
8238
+ const bIndex = usedViewsOrder.indexOf(getSortPath(b));
8239
+ if (aIndex !== -1 || bIndex !== -1) {
8240
+ if (aIndex === -1) return 1;
8241
+ if (bIndex === -1) return -1;
8242
+ return aIndex - bIndex;
8243
+ }
8244
+ }
8245
+ return 0;
8194
8246
  });
8195
8247
  const collectedGroupsFromEntries = navigationEntries.map((e) => e.group).filter(Boolean);
8196
8248
  const groupsFromMappings = finalNavigationGroupMappings.map((g) => g.name);
@@ -8201,8 +8253,8 @@ function useTopLevelNavigation(props) {
8201
8253
  ...additionalGroups
8202
8254
  ];
8203
8255
  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];
8256
+ const lastGroups = uniqueGroupsArray.filter((g) => g === "Admin" || g === "Settings" || g === "Admin");
8257
+ const uniqueGroups = [...uniqueGroupsArray.filter((g) => g !== "Admin" && g !== "Settings" && g !== "Admin"), ...lastGroups];
8206
8258
  const computedTopLevelNav = {
8207
8259
  allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
8208
8260
  navigationEntries,
@@ -8269,9 +8321,15 @@ function useBuildNavigationStateController(props) {
8269
8321
  adminMode,
8270
8322
  collectionRegistryController
8271
8323
  });
8324
+ const pendingRefreshRef = useRef(false);
8272
8325
  const refreshNavigation = useCallback(() => {
8273
- refreshCollections();
8274
- refreshViews();
8326
+ if (pendingRefreshRef.current) return;
8327
+ pendingRefreshRef.current = true;
8328
+ queueMicrotask(() => {
8329
+ pendingRefreshRef.current = false;
8330
+ refreshCollections();
8331
+ refreshViews();
8332
+ });
8275
8333
  }, [refreshCollections, refreshViews]);
8276
8334
  return useMemo(() => ({
8277
8335
  views,
@@ -8279,8 +8337,7 @@ function useBuildNavigationStateController(props) {
8279
8337
  topLevelNavigation,
8280
8338
  loading: collectionsLoading || viewsLoading,
8281
8339
  navigationLoadingError: collectionsError ?? viewsError,
8282
- refreshNavigation,
8283
- plugins
8340
+ refreshNavigation
8284
8341
  }), [
8285
8342
  views,
8286
8343
  adminViews,
@@ -8289,8 +8346,7 @@ function useBuildNavigationStateController(props) {
8289
8346
  viewsLoading,
8290
8347
  collectionsError,
8291
8348
  viewsError,
8292
- refreshNavigation,
8293
- plugins
8349
+ refreshNavigation
8294
8350
  ]);
8295
8351
  }
8296
8352
  //#endregion
@@ -8352,14 +8408,18 @@ var BreadcrumbContext = React.createContext({
8352
8408
  var BreadcrumbsProvider = ({ children }) => {
8353
8409
  const [breadcrumbs, setBreadcrumbs] = useState([]);
8354
8410
  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
- }));
8411
+ setBreadcrumbs((prev) => {
8412
+ const next = props.breadcrumbs.map((newEntry) => {
8413
+ const prevEntry = newEntry.id ? prev.find((p) => p.id === newEntry.id) : void 0;
8414
+ if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") return {
8415
+ ...newEntry,
8416
+ count: prevEntry.count
8417
+ };
8418
+ return newEntry;
8419
+ });
8420
+ 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;
8421
+ return next;
8422
+ });
8363
8423
  }, []);
8364
8424
  const updateCount = useCallback((id, count) => {
8365
8425
  setBreadcrumbs((prev) => prev.map((entry) => entry.id === id ? {
@@ -8502,11 +8562,7 @@ function SideDialogView({ offsetPosition, panel, panelIndex, isTopPanel }) {
8502
8562
  overlayStyle: { zIndex: 40 + panelIndex * 10 },
8503
8563
  style: { zIndex: 45 + panelIndex * 10 },
8504
8564
  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
- }
8565
+ if (!open) onCloseRequest();
8510
8566
  },
8511
8567
  onPointerDownOutside: !isTopPanel ? preventDismiss : void 0,
8512
8568
  onInteractOutside: !isTopPanel ? preventDismiss : void 0,
@@ -8969,7 +9025,7 @@ function FormEntry({ propertyKey, widthPercentage = 100, children }) {
8969
9025
  //#region src/form/components/FormLayout.tsx
8970
9026
  function FormLayout({ children, className }) {
8971
9027
  return /* @__PURE__ */ jsx("div", {
8972
- className: cls("flex flex-wrap gap-x-4 w-full space-y-8", className),
9028
+ className: cls("flex flex-wrap gap-x-4 w-full space-y-6", className),
8973
9029
  children
8974
9030
  });
8975
9031
  }
@@ -8994,7 +9050,7 @@ function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalist
8994
9050
  className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"
8995
9051
  }),
8996
9052
  /* @__PURE__ */ jsx("div", {
8997
- className: cls(!skipCardWrapper && paperMixin, "w-full min-h-14 overflow-x-scroll no-scrollbar", !skipCardWrapper && "p-4 md:p-6"),
9053
+ 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
9054
  children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
8999
9055
  propertyKey,
9000
9056
  value,
@@ -9281,7 +9337,7 @@ var buildIdsMap = (value) => value && Array.isArray(value) && value.length > 0 ?
9281
9337
  ...a,
9282
9338
  ...b
9283
9339
  }), {}) : {};
9284
- function SortableItem$1({ id, index, size, disabled, buildEntry, remove, copy, addInIndex, canAddElements, sortable, storedProps, updateItemCustomProps }) {
9340
+ function SortableItem({ id, index, size, disabled, buildEntry, remove, copy, addInIndex, canAddElements, sortable, storedProps, updateItemCustomProps }) {
9285
9341
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
9286
9342
  return /* @__PURE__ */ jsx(ArrayContainerItem, {
9287
9343
  nodeRef: setNodeRef,
@@ -9510,7 +9566,7 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
9510
9566
  children: /* @__PURE__ */ jsxs("div", {
9511
9567
  className: cls("space-y-1", className),
9512
9568
  id: droppableId,
9513
- children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem$1, {
9569
+ children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem, {
9514
9570
  id: internalId,
9515
9571
  index,
9516
9572
  size,
@@ -13592,189 +13648,57 @@ function EntityCard({ entity, collection, onClick, selected, highlighted, onSele
13592
13648
  //#endregion
13593
13649
  //#region src/components/EntityCollectionView/EntityCollectionCardView.tsx
13594
13650
  /**
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
13651
  * Card grid view for displaying entities with infinite scroll.
13621
13652
  * Alternative to the EntityCollectionTable for visual browsing.
13622
13653
  */
13623
13654
  function EntityCollectionCardView({ collection, tableController, onEntityClick, selectionController, selectionEnabled = true, highlightedEntities, emptyComponent, onScroll, initialScroll, size = "m" }) {
13624
13655
  const ResolvedEntityCard = useComponentOverride("Collection.Card", EntityCard);
13625
- const containerRef = useRef(null);
13626
- const hasRestoredScroll = useRef(false);
13627
13656
  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
13657
  const handleEntityClick = useCallback((entity) => {
13718
13658
  onEntityClick?.(entity);
13719
13659
  }, [onEntityClick]);
13720
13660
  const handleSelectionChange = useCallback((entity, selected) => {
13721
13661
  selectionController?.toggleEntitySelection(entity, selected);
13722
13662
  }, [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
- }) })
13663
+ const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map((e) => e.id)), [selectionController?.selectedEntities]);
13664
+ const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
13665
+ const handleRowSelectionChange = useCallback((entity, selected) => {
13666
+ handleSelectionChange(entity, selected);
13667
+ }, [handleSelectionChange]);
13668
+ return /* @__PURE__ */ jsx(CardView, {
13669
+ data,
13670
+ dataLoading,
13671
+ noMoreToLoad,
13672
+ dataLoadingError,
13673
+ itemCount,
13674
+ setItemCount,
13675
+ pageSize,
13676
+ paginationEnabled,
13677
+ onItemClick: handleEntityClick,
13678
+ selectedIds,
13679
+ highlightedIds,
13680
+ selectionEnabled,
13681
+ onSelectionChange: handleRowSelectionChange,
13682
+ onScroll,
13683
+ initialScroll,
13684
+ size,
13685
+ emptyComponent,
13686
+ renderCard: useCallback((entity, { selected, highlighted, onClick }) => /* @__PURE__ */ jsx(ResolvedEntityCard, {
13687
+ entity,
13688
+ collection,
13689
+ onClick,
13690
+ selected,
13691
+ highlighted,
13692
+ onSelectionChange: handleRowSelectionChange,
13693
+ selectionEnabled,
13694
+ size
13695
+ }, `${entity.path}_${entity.id}`), [
13696
+ collection,
13697
+ selectionEnabled,
13698
+ size,
13699
+ handleRowSelectionChange,
13700
+ ResolvedEntityCard
13701
+ ])
13778
13702
  });
13779
13703
  }
13780
13704
  //#endregion
@@ -13792,38 +13716,9 @@ function getRowClasses(size) {
13792
13716
  default: return "py-3 px-5";
13793
13717
  }
13794
13718
  }
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
13719
  /** Stable empty array for when no list-view actions are available. */
13813
13720
  var EMPTY_LIST_VIEW_ACTIONS = [];
13814
13721
  /**
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
13722
  * Returns true if a property type should NOT be rendered via
13828
13723
  * PropertyPreview in list row columns (because it would blow up height).
13829
13724
  */
@@ -14077,10 +13972,10 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
14077
13972
  const handleSelectionChange = useCallback((entity, selected) => {
14078
13973
  selectionController?.toggleEntitySelection(entity, selected);
14079
13974
  }, [selectionController]);
14080
- const isEntitySelected = useCallback((entity) => {
13975
+ useCallback((entity) => {
14081
13976
  return selectionController?.isEntitySelected(entity) ?? false;
14082
13977
  }, [selectionController]);
14083
- const isEntityHighlighted = useCallback((entity) => {
13978
+ useCallback((entity) => {
14084
13979
  return highlightedEntities?.some((e) => e.id === entity.id && e.path === entity.path) ?? false;
14085
13980
  }, [highlightedEntities]);
14086
13981
  const getListViewActions = useCallback((entity) => {
@@ -14095,158 +13990,70 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
14095
13990
  customizationController.entityActions
14096
13991
  ]);
14097
13992
  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,
13993
+ const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map((e) => e.id)), [selectionController?.selectedEntities]);
13994
+ const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
13995
+ const handleRowSelectionChange = useCallback((entity, selected) => {
13996
+ handleSelectionChange(entity, selected);
13997
+ }, [handleSelectionChange]);
13998
+ return /* @__PURE__ */ jsx(ListView, {
13999
+ data,
14000
+ dataLoading,
14105
14001
  noMoreToLoad,
14002
+ dataLoadingError,
14106
14003
  itemCount,
14107
- pageSize
14108
- });
14109
- useEffect(() => {
14110
- paginationStateRef.current = {
14111
- paginationEnabled,
14112
- noMoreToLoad,
14113
- itemCount,
14114
- pageSize
14115
- };
14116
- }, [
14004
+ setItemCount,
14005
+ pageSize,
14117
14006
  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
- })
14007
+ onItemClick: handleEntityClick,
14008
+ selectedIds,
14009
+ highlightedIds,
14010
+ selectionEnabled,
14011
+ emptyComponent,
14012
+ size,
14013
+ selectedEntityId,
14014
+ renderRow: useCallback(({ item: entity, style, className, selected, highlighted, isLast, onClick, onSelectionChange }) => {
14015
+ return /* @__PURE__ */ jsx("div", {
14016
+ style,
14017
+ className,
14018
+ children: /* @__PURE__ */ jsx(ListRow, {
14019
+ entity,
14020
+ collection: resolvedCollection,
14021
+ onClick: handleEntityClick,
14022
+ selected,
14023
+ highlighted,
14024
+ onSelectionChange: handleRowSelectionChange,
14025
+ selectionEnabled,
14026
+ columns: visibleColumns,
14027
+ slotKeys,
14028
+ rowClasses,
14029
+ showImage,
14030
+ size,
14031
+ isLast,
14032
+ isActive: selectedEntityId !== void 0 && entity.id === selectedEntityId,
14033
+ listViewActions: getListViewActions(entity),
14034
+ context,
14035
+ path,
14036
+ selectionController,
14037
+ openEntityMode
14247
14038
  })
14248
- ]
14249
- })
14039
+ }, entity.id);
14040
+ }, [
14041
+ resolvedCollection,
14042
+ selectionEnabled,
14043
+ visibleColumns,
14044
+ slotKeys,
14045
+ rowClasses,
14046
+ showImage,
14047
+ size,
14048
+ selectedEntityId,
14049
+ getListViewActions,
14050
+ context,
14051
+ path,
14052
+ selectionController,
14053
+ openEntityMode,
14054
+ handleRowSelectionChange,
14055
+ handleEntityClick
14056
+ ])
14250
14057
  });
14251
14058
  }
14252
14059
  /**
@@ -14682,457 +14489,6 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
14682
14489
  });
14683
14490
  }
14684
14491
  //#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
14492
  //#region src/components/EntityCollectionView/EntityBoardCard.tsx
15137
14493
  /**
15138
14494
  * Compact card component for displaying an entity in a Kanban board.
@@ -15143,7 +14499,7 @@ function Board({ data, columns: columnsProp, columnLabels, columnColors, classNa
15143
14499
  * layout clean while providing intuitive selection affordance.
15144
14500
  */
15145
14501
  function EntityBoardCardInner({ item, isDragging, isGroupedOver, style, collection, onClick, selected, onSelectionChange, selectionEnabled = false }) {
15146
- const entity = item.entity;
14502
+ const entity = item.data;
15147
14503
  const slotKeys = useCollectionSlotKeys(collection, useAuthController(), useCustomizationController().propertyConfigs);
15148
14504
  const slots = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
15149
14505
  entity,
@@ -15758,7 +15114,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
15758
15114
  //#region src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts
15759
15115
  function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
15760
15116
  const handleItemsReorder = useCallback(async (items, moveInfo) => {
15761
- const entity = items.find((item) => item.id === moveInfo?.itemId)?.entity;
15117
+ const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
15762
15118
  if (!entity || !moveInfo) return;
15763
15119
  analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
15764
15120
  path: fullPath,
@@ -15772,8 +15128,8 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
15772
15128
  if (orderProperty) {
15773
15129
  const targetColumnItems = items;
15774
15130
  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;
15131
+ const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].data.values?.[orderProperty] ?? null : null;
15132
+ const nextKey = movedIndex < targetColumnItems.length - 1 ? targetColumnItems[movedIndex + 1].data.values?.[orderProperty] ?? null : null;
15777
15133
  try {
15778
15134
  const a = prevKey;
15779
15135
  let b = nextKey;
@@ -16012,7 +15368,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16012
15368
  const boardItems = useMemo(() => {
16013
15369
  return allEntities.map((entity) => ({
16014
15370
  id: String(entity.id),
16015
- entity
15371
+ data: entity
16016
15372
  }));
16017
15373
  }, [allEntities]);
16018
15374
  const columnLoadingState = useMemo(() => {
@@ -16031,7 +15387,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16031
15387
  const assignColumn = useCallback((item) => {
16032
15388
  const column = entityColumnMap[item.id];
16033
15389
  if (column) return column;
16034
- const value = item.entity.values?.[columnProperty];
15390
+ const value = item.data.values?.[columnProperty];
16035
15391
  if (value && columns.includes(String(value))) return String(value);
16036
15392
  return columns[0] || "";
16037
15393
  }, [
@@ -16125,7 +15481,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16125
15481
  ...props,
16126
15482
  collection: collectionRef.current,
16127
15483
  onClick: stableOnClick,
16128
- selected: isEntitySelectedRef.current(props.item.entity),
15484
+ selected: isEntitySelectedRef.current(props.item.data),
16129
15485
  onSelectionChange: stableOnSelectionChange,
16130
15486
  selectionEnabled: selectionEnabledRef.current
16131
15487
  });
@@ -16227,7 +15583,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
16227
15583
  }),
16228
15584
  /* @__PURE__ */ jsx("div", {
16229
15585
  className: "flex-1 overflow-auto no-scrollbar",
16230
- children: /* @__PURE__ */ jsx(Board, {
15586
+ children: /* @__PURE__ */ jsx(KanbanView, {
16231
15587
  data: boardItems,
16232
15588
  columns,
16233
15589
  columnLabels,
@@ -18733,7 +18089,7 @@ function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, d
18733
18089
  })
18734
18090
  ]
18735
18091
  }), /* @__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"),
18092
+ 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
18093
  children: entries.map((entry) => /* @__PURE__ */ jsx(ResolvedDrawerNavigationItem, {
18738
18094
  icon: /* @__PURE__ */ jsx(IconForView, {
18739
18095
  collectionOrView: entry.collection ?? entry.view,
@@ -18823,7 +18179,13 @@ function CMSNavigationContent() {
18823
18179
  const registry = useRebaseRegistry();
18824
18180
  const ResolvedDrawerNavigationGroup = useComponentOverride("Shell.DrawerNavigationGroup", DrawerNavigationGroup);
18825
18181
  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");
18182
+ const studioViewSlugs = useMemo(() => {
18183
+ const slugs = /* @__PURE__ */ new Set();
18184
+ (registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
18185
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
18186
+ return slugs;
18187
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
18188
+ const filteredEntries = adminModeController.mode === "studio" ? allNavigationEntries.filter((e) => e.type === "view" || e.type === "admin") : allNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
18827
18189
  const entryGroups = new Set(filteredEntries.map((e) => e.group).filter(Boolean));
18828
18190
  const orderedGroups = navigationState.topLevelNavigation?.groups ?? [];
18829
18191
  const groupsToRender = [...orderedGroups.filter((g) => entryGroups.has(g)), ...[...entryGroups].filter((g) => !orderedGroups.includes(g))];
@@ -19261,11 +18623,12 @@ function DrawerWrapper(props) {
19261
18623
  * is auto-wired as a native feature (slots, provider, Studio view) without
19262
18624
  * needing any external plugin.
19263
18625
  */
19264
- function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
18626
+ function RebaseCMS({ collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
19265
18627
  const dispatch = useRebaseRegistryDispatch();
19266
18628
  useLayoutEffect(() => {
19267
18629
  dispatch.registerCMS({
19268
18630
  collections,
18631
+ views,
19269
18632
  homePage,
19270
18633
  entityViews,
19271
18634
  entityActions,
@@ -19277,6 +18640,7 @@ function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins,
19277
18640
  }, [
19278
18641
  dispatch,
19279
18642
  collections,
18643
+ views,
19280
18644
  homePage,
19281
18645
  entityViews,
19282
18646
  entityActions,
@@ -19416,7 +18780,7 @@ function useLocalCollectionsConfigController(clientOrUrl, baseCollections = [],
19416
18780
  //#region src/components/RebaseNavigation.tsx
19417
18781
  var EMPTY_PLUGINS = [];
19418
18782
  var EMPTY_COLLECTIONS = [];
19419
- var CollectionsStudioView = lazy(() => import("./CollectionsStudioView-B1mN33GB.js").then((n) => n.n).then((m) => ({ default: m.CollectionsStudioView })));
18783
+ var CollectionsStudioView = lazy(() => import("./RouterCollectionsStudioView-Dqx27GD8.js").then((n) => n.n).then((m) => ({ default: m.RouterCollectionsStudioView })));
19420
18784
  /**
19421
18785
  * Navigation layer — builds and provides all CMS navigation controllers:
19422
18786
  * collection registry, URL controller, navigation state, side entity,
@@ -19449,7 +18813,19 @@ function RebaseNavigation({ children }) {
19449
18813
  }, [collectionEditorConfig]);
19450
18814
  const cmsCollections = registry.cmsConfig?.collections ?? EMPTY_COLLECTIONS;
19451
18815
  const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
19452
- const collectionRegistryController = useBuildCollectionRegistryController({ userConfigPersistence });
18816
+ const dataSources = useDataSources();
18817
+ const collectionRegistryController = useBuildCollectionRegistryController({
18818
+ userConfigPersistence,
18819
+ dataSources: dataSources.registry
18820
+ });
18821
+ const defaultData = useData();
18822
+ const getCollectionRef = useRef(collectionRegistryController.getCollection);
18823
+ getCollectionRef.current = collectionRegistryController.getCollection;
18824
+ const routedData = useMemo(() => buildRoutedRebaseData({
18825
+ defaultData,
18826
+ sources: dataSources.sources,
18827
+ resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
18828
+ }), [defaultData, dataSources]);
19453
18829
  const urlController = useBuildUrlController({
19454
18830
  basePath: "/",
19455
18831
  baseCollectionPath: "/c",
@@ -19457,7 +18833,14 @@ function RebaseNavigation({ children }) {
19457
18833
  });
19458
18834
  const rebaseClient = useRebaseClient();
19459
18835
  const authController = useAuthController();
19460
- const internalConfigController = useLocalCollectionsConfigController(rebaseClient, useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]), collectionEditorEnabled ? {
18836
+ const resolvedCollections = useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]);
18837
+ useEffect(() => {
18838
+ if (process.env.NODE_ENV === "production") return;
18839
+ const { registry, sources } = dataSources;
18840
+ const missing = Object.values(registry).filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key]).map((d) => d.key);
18841
+ 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>.`);
18842
+ }, [dataSources]);
18843
+ const internalConfigController = useLocalCollectionsConfigController(rebaseClient, resolvedCollections, collectionEditorEnabled ? {
19461
18844
  readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
19462
18845
  getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
19463
18846
  } : { readOnly: true });
@@ -19474,6 +18857,7 @@ function RebaseNavigation({ children }) {
19474
18857
  return {
19475
18858
  slug: "schema",
19476
18859
  name: "Edit collections",
18860
+ group: "Database",
19477
18861
  icon: "LayoutList",
19478
18862
  nestedRoutes: true,
19479
18863
  view: /* @__PURE__ */ jsx(Suspense, {
@@ -19491,13 +18875,22 @@ function RebaseNavigation({ children }) {
19491
18875
  if (schemaView) return [...base, schemaView];
19492
18876
  return base;
19493
18877
  }, [registry.studioConfig?.devViews, schemaView]);
18878
+ const cmsViews = registry.cmsConfig?.views;
18879
+ const mergedViews = useMemo(() => {
18880
+ if (!cmsViews) return devViews;
18881
+ if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
18882
+ if (typeof cmsViews === "function") return async (params) => {
18883
+ return [...await cmsViews(params) ?? [], ...devViews];
18884
+ };
18885
+ return [...cmsViews, ...devViews];
18886
+ }, [cmsViews, devViews]);
19494
18887
  const navigationStateController = useBuildNavigationStateController({
19495
18888
  plugins: registry.cmsConfig?.plugins ?? EMPTY_PLUGINS,
19496
18889
  collections: collectionsBuilder,
19497
- views: devViews,
18890
+ views: mergedViews,
19498
18891
  navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
19499
18892
  authController: context.authController,
19500
- data: context.data,
18893
+ data: routedData,
19501
18894
  collectionRegistryController,
19502
18895
  urlController,
19503
18896
  adminMode: adminModeController?.mode
@@ -19517,22 +18910,25 @@ function RebaseNavigation({ children }) {
19517
18910
  registry.cmsConfig?.entityViews,
19518
18911
  registry.cmsConfig?.entityActions
19519
18912
  ]);
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] })
18913
+ const navigationContent = /* @__PURE__ */ jsx(RebaseDataContext.Provider, {
18914
+ value: routedData,
18915
+ children: /* @__PURE__ */ jsx(CustomizationControllerContext.Provider, {
18916
+ value: enrichedCustomizationController,
18917
+ children: /* @__PURE__ */ jsx(StudioBridgeRegistryProvider, { children: /* @__PURE__ */ jsx(CollectionRegistryContext.Provider, {
18918
+ value: collectionRegistryController,
18919
+ children: /* @__PURE__ */ jsx(UrlContext.Provider, {
18920
+ value: urlController,
18921
+ children: /* @__PURE__ */ jsx(NavigationStateContext.Provider, {
18922
+ value: navigationStateController,
18923
+ children: /* @__PURE__ */ jsxs(SideEntityProvider, { children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar, {
18924
+ collectionRegistryController,
18925
+ urlController,
18926
+ navigationStateController
18927
+ }), children] })
18928
+ })
19533
18929
  })
19534
- })
19535
- }) })
18930
+ }) })
18931
+ })
19536
18932
  });
19537
18933
  if (collectionEditorEnabled) return /* @__PURE__ */ jsx(ConfigControllerProvider, {
19538
18934
  collectionConfigController: internalConfigController,
@@ -20317,10 +19713,20 @@ function ContentHomePage({ additionalActions, additionalChildrenStart, additiona
20317
19713
  breadcrumbs.set({ breadcrumbs: [] });
20318
19714
  }, [breadcrumbs.set]);
20319
19715
  const { allowDragAndDrop = false, navigationEntries: unFilteredNavigationEntries = [], groups: unFilteredGroupOrder = [], onNavigationEntriesUpdate = () => {} } = navigationStateController.topLevelNavigation || {};
19716
+ const studioViewSlugs = useMemo(() => {
19717
+ const slugs = /* @__PURE__ */ new Set();
19718
+ (registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
19719
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
19720
+ return slugs;
19721
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
20320
19722
  const rawNavigationEntries = useMemo(() => {
20321
19723
  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]);
19724
+ return unFilteredNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
19725
+ }, [
19726
+ unFilteredNavigationEntries,
19727
+ adminModeController.mode,
19728
+ studioViewSlugs
19729
+ ]);
20324
19730
  const groupOrderFromNavController = useMemo(() => {
20325
19731
  const entryGroups = new Set(rawNavigationEntries.map((e) => e.group).filter(Boolean));
20326
19732
  return unFilteredGroupOrder.filter((g) => entryGroups.has(g));
@@ -20850,18 +20256,24 @@ function EntityFullScreenRoute({ pathname, navigationEntries, isNew, isCopy }) {
20850
20256
  function CustomViewRoute({ view }) {
20851
20257
  const breadcrumbs = useBreadcrumbsController();
20852
20258
  const urlController = useUrlController();
20259
+ const breadcrumbsRef = useRef(breadcrumbs);
20260
+ breadcrumbsRef.current = breadcrumbs;
20853
20261
  useEffect(() => {
20854
- breadcrumbs.set({ breadcrumbs: [{
20262
+ breadcrumbsRef.current.set({ breadcrumbs: [{
20855
20263
  title: view.name,
20856
20264
  url: urlController.buildAppUrlPath(view.slug)
20857
20265
  }] });
20858
20266
  }, [view.slug, urlController]);
20859
- return view.view;
20267
+ if (typeof view.view === "function") {
20268
+ const ViewComponent = view.view;
20269
+ return /* @__PURE__ */ jsx(ViewComponent, {});
20270
+ }
20271
+ return /* @__PURE__ */ jsx(Fragment, { children: view.view });
20860
20272
  }
20861
20273
  //#endregion
20862
20274
  //#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 })));
20275
+ var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-iGgS4rLX.js").then((n) => n.r).then((m) => ({ default: m.CollectionEditorDialog })));
20276
+ var PropertyFormDialog = lazy(() => import("./PropertyEditView-C9yhLyja.js").then((n) => n.t).then((m) => ({ default: m.PropertyFormDialog })));
20865
20277
  /**
20866
20278
  * Renders the CollectionEditorDialog and PropertyFormDialog inside
20867
20279
  * the RebaseShell tree where CMS-internal contexts
@@ -22185,7 +21597,7 @@ function LocalChangesMenu({ cachedData, formex, onClearLocalChanges, cacheKey, p
22185
21597
  })] });
22186
21598
  }
22187
21599
  //#endregion
22188
- //#region src/form/EntityForm.tsx
21600
+ //#region src/form/form_utils.ts
22189
21601
  function extractTouchedValues(values, touched) {
22190
21602
  let acc = {};
22191
21603
  if (!touched || typeof touched !== "object") return acc;
@@ -22201,7 +21613,7 @@ function extractTouchedValues(values, touched) {
22201
21613
  * from falsely triggering the unsaved local changes indicator.
22202
21614
  */
22203
21615
  /**
22204
- * CheckIcon if a value is semantically empty (null, undefined, or empty string).
21616
+ * Check if a value is semantically empty (null, undefined, or empty string).
22205
21617
  */
22206
21618
  function isSemanticEmpty(v) {
22207
21619
  return v === null || v === void 0 || v === "";
@@ -22245,23 +21657,51 @@ function getChanges(source, comparison) {
22245
21657
  }
22246
21658
  return changes;
22247
21659
  }
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;
21660
+ function getInitialEntityValues(authController, collection, path, status, entity, propertyConfigs) {
21661
+ const properties = collection.properties;
21662
+ if ((status === "existing" || status === "copy") && entity) {
21663
+ let values;
21664
+ if (!collection.alwaysApplyDefaultValues) values = entity.values ?? getDefaultValuesFor(properties);
21665
+ else values = mergeDeep(getDefaultValuesFor(properties), entity.values ?? {});
21666
+ if (status === "copy") {
21667
+ const result = { ...values };
21668
+ for (const [key, property] of Object.entries(properties)) if (property && "isId" in property && property.isId) delete result[key];
21669
+ return result;
22256
21670
  }
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();
21671
+ return values;
21672
+ } else if (status === "new") return getDefaultValuesFor(properties);
21673
+ else {
21674
+ console.error({
21675
+ status,
21676
+ entity
21677
+ });
21678
+ throw new Error("Form has not been initialised with the correct parameters");
21679
+ }
21680
+ }
21681
+ function zodToFormErrors(zodError) {
21682
+ let errors = {};
21683
+ for (const issue of zodError.issues) {
21684
+ const path = issue.path.join(".");
21685
+ if (path && !getIn(errors, path)) errors = setIn(errors, path, issue.message);
21686
+ }
21687
+ return errors;
21688
+ }
21689
+ //#endregion
21690
+ //#region src/form/EntityForm.tsx
21691
+ /**
21692
+ * Headless entity form component.
21693
+ *
21694
+ * Renders a form for an entity collection without any CMS or backend dependencies.
21695
+ * All backend concerns (save, caching, analytics, plugin slots) are provided via
21696
+ * callback props. For CMS-connected usage, use {@link EntityFormBinding} instead.
21697
+ *
21698
+ * @group Components
21699
+ */
21700
+ 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 }) {
21701
+ const customizationController = useCustomizationController();
21702
+ const navigateBack = useCallback(() => {
21703
+ if (navigateBackProp) navigateBackProp();
21704
+ }, [navigateBackProp]);
22265
21705
  const [status, setStatus] = useState(initialStatus);
22266
21706
  const updateStatus = (status) => {
22267
21707
  setStatus(status);
@@ -22269,18 +21709,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22269
21709
  };
22270
21710
  const [valuesToBeSaved, setValuesToBeSaved] = useState(void 0);
22271
21711
  useDebouncedCallback(valuesToBeSaved, () => {
22272
- if (valuesToBeSaved) saveEntity({
22273
- entityId: entityIdProp,
22274
- collection,
22275
- path,
22276
- values: valuesToBeSaved
22277
- });
21712
+ if (valuesToBeSaved && onSubmitProp) {
21713
+ setIsSavingAutoSave(true);
21714
+ Promise.resolve(onSubmitProp(valuesToBeSaved, formex)).finally(() => setIsSavingAutoSave(false));
21715
+ }
22278
21716
  }, false, 2e3);
22279
- const dataClient = useData();
22280
- const snackbarController = useSnackbarController();
22281
- const customizationController = useCustomizationController();
22282
- const context = useCMSContext();
22283
- const analyticsController = useAnalyticsController();
22284
21717
  const [underlyingChanges] = useState({});
22285
21718
  const initialEntityId = useMemo(() => {
22286
21719
  if (status === "new" || status === "copy") return;
@@ -22289,58 +21722,45 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22289
21722
  const [entityId, setEntityId] = useState(initialEntityId);
22290
21723
  const [entityIdError, setEntityIdError] = useState(false);
22291
21724
  const [savingError, setSavingError] = useState();
21725
+ const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
21726
+ const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
22292
21727
  const autoSave = collection.formAutoSave;
22293
- const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [
22294
- authController,
22295
- collection,
22296
- path,
21728
+ const baseInitialValues = useMemo(() => {
21729
+ if (computedInitialValues !== void 0) return computedInitialValues;
21730
+ if ((status === "existing" || status === "copy") && entity) return entity.values ?? getDefaultValuesFor(collection.properties);
21731
+ return getDefaultValuesFor(collection.properties);
21732
+ }, [
21733
+ computedInitialValues,
21734
+ collection.properties,
22297
21735
  status,
22298
- entity,
22299
- customizationController.propertyConfigs
21736
+ entity
22300
21737
  ]);
22301
- const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
22302
21738
  const [localChangesCleared, setLocalChangesCleared] = useState(false);
22303
- const localChangesBackup = getLocalChangesBackup(collection);
22304
- const autoApplyLocalChanges = localChangesBackup === "auto_apply";
22305
- const manualApplyLocalChanges = localChangesBackup === "manual_apply";
21739
+ const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
22306
21740
  const onSubmit = (values, formexController) => {
22307
21741
  setSavingError(void 0);
22308
21742
  setEntityIdError(false);
22309
21743
  if (status === "existing") {
22310
21744
  if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
22311
21745
  } 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
- }
21746
+ if (!onSubmitProp) {
21747
+ console.warn("EntityForm: no onSubmit callback provided. Form submission has no effect.");
21748
+ formexController.setSubmitting(false);
21749
+ return;
21750
+ }
21751
+ return Promise.resolve(save(values))?.then((savedEntity) => {
21752
+ if (savedEntity) formexController.resetForm({
21753
+ values: savedEntity.values || values,
21754
+ submitCount: 0,
21755
+ touched: {}
21756
+ });
22324
21757
  }).finally(() => {
22325
21758
  formexController.setSubmitting(false);
22326
21759
  });
22327
21760
  };
22328
21761
  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;
21762
+ return [initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues, Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0];
21763
+ }, [baseInitialValues, initialDirtyValues]);
22344
21764
  const internalFormex = useCreateFormex({
22345
21765
  initialValues,
22346
21766
  initialDirty,
@@ -22351,17 +21771,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22351
21771
  }), {}) : {},
22352
21772
  onSubmit,
22353
21773
  onReset: () => {
22354
- clearDirtyCache();
21774
+ onResetProp?.();
22355
21775
  onValuesModified?.(false, initialValues);
22356
21776
  },
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
- },
21777
+ onValuesChangeDeferred: onValuesChangeDeferredProp,
22365
21778
  validation: async (values) => {
22366
21779
  if (!validationSchema) return {};
22367
21780
  const result = await validationSchema.safeParseAsync(values);
@@ -22374,7 +21787,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22374
21787
  const handleKeyDown = (e) => {
22375
21788
  const isUndo = (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z";
22376
21789
  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) {
21790
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "s" && !disabled && formex.dirty) {
21791
+ e.preventDefault();
21792
+ formex.handleSubmit();
21793
+ } else if (isUndo && formex.canUndo) {
22378
21794
  e.preventDefault();
22379
21795
  formex.undo();
22380
21796
  } else if (isRedo && formex.canRedo) {
@@ -22385,31 +21801,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22385
21801
  window.addEventListener("keydown", handleKeyDown);
22386
21802
  return () => window.removeEventListener("keydown", handleKeyDown);
22387
21803
  }, [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
21804
  const afterSave = (updatedEntity) => {
22412
- clearDirtyCache();
21805
+ onResetProp?.();
22413
21806
  onValuesModified?.(false, updatedEntity.values);
22414
21807
  onEntityChange?.(updatedEntity);
22415
21808
  updateStatus("existing");
@@ -22422,62 +21815,19 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22422
21815
  collection
22423
21816
  });
22424
21817
  };
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
21818
  const lastSavedValues = useRef(entity?.values);
22466
21819
  const save = async (values) => {
22467
21820
  const valuesToSave = status === "existing" ? getChanges(values, entity?.values || {}) : values;
22468
21821
  if (status === "existing" && Object.keys(valuesToSave).length === 0 && entity) return Promise.resolve(entity);
22469
21822
  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;
21823
+ if (!onSubmitProp) return;
21824
+ if (autoSave ?? false) {
21825
+ setValuesToBeSaved(values);
21826
+ return Promise.resolve();
21827
+ }
21828
+ return Promise.resolve(onSubmitProp(valuesToSave, formex)).then((result) => {
21829
+ if (result) afterSave(result);
21830
+ return result;
22481
21831
  }).catch((e) => {
22482
21832
  console.error(e);
22483
21833
  setSavingError(e);
@@ -22508,44 +21858,18 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22508
21858
  path
22509
21859
  ]);
22510
21860
  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
21861
  const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
22527
21862
  const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
22528
21863
  const title = rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
21864
+ const modified = formex.dirty;
22529
21865
  useEffect(() => {
22530
21866
  if (!autoSave) onValuesModified?.(modified, formex.values);
22531
21867
  }, [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
- ]);
21868
+ const defaultUniqueFieldValidator = useCallback(async () => true, []);
21869
+ const uniqueFieldValidator = useMemo(() => {
21870
+ if (uniqueFieldValidatorProp) return uniqueFieldValidatorProp;
21871
+ return defaultUniqueFieldValidator;
21872
+ }, [uniqueFieldValidatorProp, defaultUniqueFieldValidator]);
22549
21873
  const validationSchema = useMemo(() => getEntitySchema(entityId, collection.properties, uniqueFieldValidator), [
22550
21874
  entityId,
22551
21875
  collection.properties,
@@ -22577,6 +21901,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22577
21901
  modifiedValues: formex.values,
22578
21902
  formContext
22579
21903
  });
21904
+ const isNewEntity = status === "new" || status === "copy";
21905
+ let firstFocusableIndex = -1;
22580
21906
  return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key) => {
22581
21907
  const property = collection.properties?.[key];
22582
21908
  if (property) {
@@ -22585,9 +21911,12 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22585
21911
  const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
22586
21912
  const disabled = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.ui?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto;
22587
21913
  if (isHidden(property)) return null;
21914
+ const widthPercentage = property.ui?.widthPercentage ?? 100;
21915
+ const shouldAutoFocus = isNewEntity && !disabled && firstFocusableIndex === -1;
21916
+ if (shouldAutoFocus) firstFocusableIndex = 0;
22588
21917
  return /* @__PURE__ */ jsx(FormEntry, {
22589
21918
  propertyKey: key,
22590
- widthPercentage: property.ui?.widthPercentage ?? 100,
21919
+ widthPercentage,
22591
21920
  children: /* @__PURE__ */ jsx(PropertyFieldBinding, {
22592
21921
  propertyKey: key,
22593
21922
  disabled,
@@ -22597,22 +21926,23 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22597
21926
  context: formContext,
22598
21927
  partOfArray: false,
22599
21928
  minimalistView: false,
22600
- autoFocus: false
21929
+ autoFocus: shouldAutoFocus
22601
21930
  })
22602
21931
  }, `field_${key}`);
22603
21932
  }
22604
21933
  const additionalField = collection.additionalFields?.find((f) => f.key === key);
22605
21934
  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, {
21935
+ const AdditionalFieldBuilder = additionalField.Builder;
21936
+ if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
21937
+ const additionalFieldContext = formContext;
21938
+ const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
22609
21939
  entity,
22610
- context
21940
+ context: additionalFieldContext
22611
21941
  }) : /* @__PURE__ */ jsx("div", {
22612
21942
  className: "w-full",
22613
21943
  children: additionalField.value?.({
22614
21944
  entity,
22615
- context
21945
+ context: additionalFieldContext
22616
21946
  })?.toString()
22617
21947
  });
22618
21948
  return /* @__PURE__ */ jsxs("div", {
@@ -22633,8 +21963,9 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22633
21963
  }).filter(Boolean) });
22634
21964
  };
22635
21965
  const formRef = useRef(null);
21966
+ const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22636
21967
  const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
22637
- pluginFormBefore,
21968
+ beforeFields,
22638
21969
  !Builder && /* @__PURE__ */ jsxs("div", {
22639
21970
  className: "w-full flex flex-col items-start my-4 lg:my-6",
22640
21971
  children: [
@@ -22647,7 +21978,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22647
21978
  color: "warning",
22648
21979
  size: "small",
22649
21980
  outerClassName: "w-full mb-4 text-xs",
22650
- children: t("entity_does_not_exist")
21981
+ children: "This entity does not exist yet"
22651
21982
  }),
22652
21983
  showEntityPath && /* @__PURE__ */ jsx(Alert, {
22653
21984
  color: "base",
@@ -22668,15 +21999,43 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22668
21999
  initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
22669
22000
  color: "info",
22670
22001
  size: "small",
22671
- children: t("entity_does_not_exist")
22002
+ children: "This entity does not exist yet"
22003
+ }),
22004
+ hasFormErrors && /* @__PURE__ */ jsx(Alert, {
22005
+ color: "error",
22006
+ size: "small",
22007
+ outerClassName: "w-full mt-2",
22008
+ children: "Please fix the highlighted errors before saving."
22672
22009
  }),
22673
22010
  formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
22674
22011
  className: "mt-12 flex flex-col gap-8",
22675
22012
  ref: formRef,
22676
22013
  children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
22677
22014
  }) }),
22678
- pluginFormAfter,
22679
- forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" })
22015
+ afterFields,
22016
+ forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" }),
22017
+ /* @__PURE__ */ jsxs(Dialog, {
22018
+ open: discardDialogOpen,
22019
+ onOpenChange: setDiscardDialogOpen,
22020
+ maxWidth: "sm",
22021
+ children: [
22022
+ /* @__PURE__ */ jsx(DialogTitle, { children: status === "existing" ? "Discard changes?" : "Clear form?" }),
22023
+ /* @__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." }) }),
22024
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
22025
+ variant: "text",
22026
+ onClick: () => setDiscardDialogOpen(false),
22027
+ children: "Cancel"
22028
+ }), /* @__PURE__ */ jsx(Button, {
22029
+ variant: "filled",
22030
+ color: "error",
22031
+ onClick: () => {
22032
+ setDiscardDialogOpen(false);
22033
+ formex.resetForm({ values: baseInitialValues });
22034
+ },
22035
+ children: status === "existing" ? "Discard" : "Clear"
22036
+ })] })
22037
+ ]
22038
+ })
22680
22039
  ] }) });
22681
22040
  useEffect(() => {
22682
22041
  if (entityId && onIdChange) onIdChange(entityId);
@@ -22691,7 +22050,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22691
22050
  formex,
22692
22051
  disabled: actionsDisabled,
22693
22052
  status,
22694
- pluginActions: pluginFormActions ?? [],
22053
+ pluginActions: pluginActionsProp ?? [],
22695
22054
  openEntityMode,
22696
22055
  showDefaultActions,
22697
22056
  navigateBack,
@@ -22701,7 +22060,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22701
22060
  value: formex,
22702
22061
  children: /* @__PURE__ */ jsxs("form", {
22703
22062
  onSubmit: formex.handleSubmit,
22704
- onReset: () => formex.resetForm({ values: baseInitialValues }),
22063
+ onReset: (e) => {
22064
+ e.preventDefault();
22065
+ if (formex.dirty) setDiscardDialogOpen(true);
22066
+ else formex.resetForm({ values: baseInitialValues });
22067
+ },
22705
22068
  noValidate: true,
22706
22069
  className: cls("@container flex-1 flex flex-row w-full overflow-y-auto justify-center", className),
22707
22070
  children: [/* @__PURE__ */ jsx("div", {
@@ -22710,15 +22073,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22710
22073
  children: /* @__PURE__ */ jsxs("div", {
22711
22074
  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
22075
  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,
22076
+ className: "flex flex-row gap-4 justify-end h-0 overflow-visible sticky top-4 z-10",
22077
+ children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx(LocalChangesMenu, {
22078
+ cacheKey: localChangesCacheKey,
22716
22079
  properties: collection.properties,
22717
22080
  cachedData: localChangesData,
22718
22081
  formex,
22719
- onClearLocalChanges: () => setLocalChangesCleared(true)
22720
- }), formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
22721
- title: t("form_modified"),
22082
+ onClearLocalChanges: () => {
22083
+ setLocalChangesCleared(true);
22084
+ onClearLocalChanges?.();
22085
+ }
22086
+ }), isSavingAutoSave ? /* @__PURE__ */ jsx(Tooltip, {
22087
+ title: "Saving…",
22088
+ children: /* @__PURE__ */ jsx(Chip, {
22089
+ size: "small",
22090
+ className: "py-1",
22091
+ colorScheme: "blueDarker",
22092
+ children: /* @__PURE__ */ jsx(LoaderIcon, {
22093
+ size: iconSize.smallest,
22094
+ className: "animate-spin"
22095
+ })
22096
+ })
22097
+ }) : formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
22098
+ title: "Form has been modified",
22722
22099
  children: /* @__PURE__ */ jsx(Chip, {
22723
22100
  size: "small",
22724
22101
  className: "py-1",
@@ -22726,7 +22103,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22726
22103
  children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
22727
22104
  })
22728
22105
  }) : /* @__PURE__ */ jsx(Tooltip, {
22729
- title: t("form_in_sync"),
22106
+ title: "Form is in sync",
22730
22107
  children: /* @__PURE__ */ jsx(Chip, {
22731
22108
  size: "small",
22732
22109
  className: "py-1",
@@ -22739,40 +22116,401 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
22739
22116
  })
22740
22117
  });
22741
22118
  }
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");
22119
+ function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
22120
+ useEffect(() => {
22121
+ if (!autoSave) return;
22122
+ if (formex.values && !deepEqual(formex.values, lastSavedValues.current)) save(formex.values);
22123
+ }, [autoSave, formex.values]);
22124
+ }
22125
+ //#endregion
22126
+ //#region src/form/EntityFormBinding.tsx
22127
+ /**
22128
+ * CMS-connected wrapper around EntityForm.
22129
+ *
22130
+ * Provides all backend concerns that the headless EntityForm delegates via callbacks:
22131
+ * - Save via the data layer (`saveEntityWithCallbacks`)
22132
+ * - Unique field validation via the data layer
22133
+ * - Entity caching (local changes backup/restore)
22134
+ * - Plugin slots (form.before, form.after, form.actions)
22135
+ * - Snackbar notifications on save success/error
22136
+ * - Analytics events
22137
+ * - Navigation via side entity controller
22138
+ *
22139
+ * Use this component inside the CMS (`<RebaseShell>`).
22140
+ * For headless usage outside the CMS, use `EntityForm` directly.
22141
+ *
22142
+ * @group Components
22143
+ */
22144
+ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, initialStatus, onSaved, onValuesModified, onEntityChange, onStatusChange, openEntityMode = "full_screen", navigateBack: navigateBackProp, initialDirtyValues, ...restProps }) {
22145
+ const { t } = useTranslation();
22146
+ const sideEntityController = useSideEntityController();
22147
+ const collectionRegistryController = useCollectionRegistryController();
22148
+ const authController = useAuthController();
22149
+ const dataClient = useData();
22150
+ const snackbarController = useSnackbarController();
22151
+ const customizationController = useCustomizationController();
22152
+ const context = useCMSContext();
22153
+ const analyticsController = useAnalyticsController();
22154
+ const [status, setStatus] = useState(initialStatus);
22155
+ const [entityId, setEntityId] = useState(() => {
22156
+ if (status === "new" || status === "copy") return void 0;
22157
+ return entityIdProp;
22158
+ });
22159
+ const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [
22160
+ authController,
22161
+ collection,
22162
+ path,
22163
+ status,
22164
+ entity,
22165
+ customizationController.propertyConfigs
22166
+ ]);
22167
+ const localChangesBackup = getLocalChangesBackup(collection);
22168
+ const autoApplyLocalChanges = localChangesBackup === "auto_apply";
22169
+ const manualApplyLocalChanges = localChangesBackup === "manual_apply";
22170
+ const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
22171
+ const [localChangesCleared, setLocalChangesCleared] = useState(false);
22172
+ const computedInitialValues = useMemo(() => {
22173
+ const withLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
22174
+ return initialDirtyValues ? mergeDeep(withLocalChanges, initialDirtyValues) : withLocalChanges;
22175
+ }, [
22176
+ autoApplyLocalChanges,
22177
+ localChangesDataRaw,
22178
+ baseInitialValues,
22179
+ initialDirtyValues
22180
+ ]);
22181
+ const localChangesData = useMemo(() => {
22182
+ if (!localChangesDataRaw) return void 0;
22183
+ const cleaned = removeEmptyContainers(getChanges(localChangesDataRaw, computedInitialValues));
22184
+ if (cleaned && typeof cleaned === "object" && Object.keys(cleaned).length === 0) return;
22185
+ return cleaned;
22186
+ }, [localChangesDataRaw, computedInitialValues]);
22187
+ const hasLocalChanges = !localChangesCleared && !!localChangesData && Object.keys(localChangesData).length > 0;
22188
+ const localChangesCacheKey = useMemo(() => {
22189
+ return status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
22190
+ }, [
22191
+ status,
22192
+ path,
22193
+ entityId
22194
+ ]);
22195
+ const navigateBack = useCallback(() => {
22196
+ if (navigateBackProp) {
22197
+ navigateBackProp();
22198
+ return;
22199
+ }
22200
+ if (openEntityMode === "side_panel" || openEntityMode === "dialog") sideEntityController.close();
22201
+ else window.history.back();
22202
+ }, [
22203
+ navigateBackProp,
22204
+ openEntityMode,
22205
+ sideEntityController
22206
+ ]);
22207
+ const clearDirtyCache = useCallback(() => {
22208
+ if (status === "new" || status === "copy") {
22209
+ removeEntityFromMemoryCache(path + "#new");
22210
+ removeEntityFromCache(path + "#new");
22211
+ } else {
22212
+ removeEntityFromMemoryCache(path + "/" + entityId);
22213
+ removeEntityFromCache(path + "/" + entityId);
22214
+ }
22215
+ }, [
22216
+ status,
22217
+ path,
22218
+ entityId
22219
+ ]);
22220
+ const onReset = useCallback(() => {
22221
+ clearDirtyCache();
22222
+ }, [clearDirtyCache]);
22223
+ const onValuesChangeDeferred = useCallback((values, controller) => {
22224
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
22225
+ if (controller.dirty) {
22226
+ const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
22227
+ if (touchedValues && Object.keys(touchedValues).length > 0) saveEntityToCache(key, touchedValues);
22228
+ else removeEntityFromCache(key);
22229
+ }
22230
+ }, [
22231
+ status,
22232
+ path,
22233
+ entityId
22234
+ ]);
22235
+ const onSubmit = useCallback(async (values, formex) => {
22236
+ try {
22237
+ const savedEntity = await saveEntityWithCallbacks({
22238
+ path,
22239
+ entityId,
22240
+ values,
22241
+ previousValues: entity?.values,
22242
+ collection,
22243
+ status,
22244
+ data: dataClient,
22245
+ context,
22246
+ afterSave: (updatedEntity) => {
22247
+ clearDirtyCache();
22248
+ onValuesModified?.(false, updatedEntity.values);
22249
+ onEntityChange?.(updatedEntity);
22250
+ setStatus("existing");
22251
+ setEntityId(updatedEntity.id);
22252
+ onStatusChange?.("existing");
22253
+ if (onSaved) onSaved({
22254
+ entity: updatedEntity,
22255
+ status,
22256
+ path,
22257
+ entityId: updatedEntity.id,
22258
+ collection
22259
+ });
22260
+ },
22261
+ afterSaveError: (e) => {
22262
+ snackbarController.open({
22263
+ type: "error",
22264
+ title: t("error_saving_entity"),
22265
+ message: e?.message
22266
+ });
22267
+ console.error("Error saving entity", path, entityId, e);
22268
+ }
22269
+ });
22270
+ if (!collection.formAutoSave) snackbarController.open({
22271
+ type: "success",
22272
+ message: `${collection.singularName ?? collection.name}: ${t("saved_correctly")}`
22273
+ });
22274
+ const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
22275
+ analyticsController.onAnalyticsEvent?.(eventName, { path });
22276
+ return savedEntity;
22277
+ } catch (e) {
22278
+ console.error(e);
22279
+ throw e;
22280
+ }
22281
+ }, [
22282
+ path,
22283
+ entityId,
22284
+ entity,
22285
+ collection,
22286
+ status,
22287
+ dataClient,
22288
+ context,
22289
+ clearDirtyCache,
22290
+ onValuesModified,
22291
+ onEntityChange,
22292
+ onSaved,
22293
+ onStatusChange,
22294
+ snackbarController,
22295
+ analyticsController,
22296
+ t
22297
+ ]);
22298
+ const uniqueFieldValidator = useCallback(async ({ name, value }) => {
22299
+ try {
22300
+ const { data } = await dataClient.collection(path).find({
22301
+ where: { [name]: `eq.${value}` },
22302
+ limit: 2
22303
+ });
22304
+ return (entityId ? data.filter((e) => e.id !== entityId) : data).length === 0;
22305
+ } catch (e) {
22306
+ console.error("Error checking unique field", e);
22307
+ return true;
22308
+ }
22309
+ }, [
22310
+ dataClient,
22311
+ path,
22312
+ entityId
22313
+ ]);
22314
+ const parentCollectionSlugs = collectionRegistryController.getParentCollectionSlugs(path);
22315
+ const parentEntityIds = collectionRegistryController.getParentEntityIds(path);
22316
+ const formActionProps = useMemo(() => ({
22317
+ entityId,
22318
+ parentCollectionSlugs,
22319
+ parentEntityIds,
22320
+ path,
22321
+ status,
22322
+ collection,
22323
+ context,
22324
+ formContext: void 0,
22325
+ openEntityMode,
22326
+ disabled: false
22327
+ }), [
22328
+ entityId,
22329
+ parentCollectionSlugs,
22330
+ parentEntityIds,
22331
+ path,
22332
+ status,
22333
+ collection,
22334
+ context,
22335
+ openEntityMode
22336
+ ]);
22337
+ const pluginFormActions = useSlot("form.actions", formActionProps);
22338
+ const pluginFormBefore = useSlot("form.before", formActionProps);
22339
+ const pluginFormAfter = useSlot("form.after", formActionProps);
22340
+ return /* @__PURE__ */ jsx(EntityForm, {
22341
+ ...restProps,
22342
+ path,
22343
+ entityId: entityIdProp,
22344
+ collection,
22345
+ entity,
22346
+ initialStatus,
22347
+ initialDirtyValues,
22348
+ onSaved,
22349
+ onValuesModified,
22350
+ onEntityChange,
22351
+ onStatusChange,
22352
+ openEntityMode,
22353
+ navigateBack,
22354
+ onSubmit,
22355
+ onValuesChangeDeferred,
22356
+ onReset,
22357
+ uniqueFieldValidator,
22358
+ beforeFields: pluginFormBefore,
22359
+ afterFields: pluginFormAfter,
22360
+ pluginActions: pluginFormActions ?? [],
22361
+ computedInitialValues,
22362
+ hasLocalChanges,
22363
+ localChangesData,
22364
+ manualApplyLocalChanges,
22365
+ localChangesCacheKey,
22366
+ onClearLocalChanges: () => setLocalChangesCleared(true)
22367
+ });
22368
+ }
22369
+ //#endregion
22370
+ //#region src/util/propertyConfigMapper.ts
22371
+ /**
22372
+ * Map Rebase property types to headless-supported types.
22373
+ * `vector` and `binary` are not directly renderable, so we fall back to `string`.
22374
+ */
22375
+ var TYPE_MAP = {
22376
+ string: "string",
22377
+ number: "number",
22378
+ boolean: "boolean",
22379
+ date: "date",
22380
+ geopoint: "geopoint",
22381
+ reference: "reference",
22382
+ relation: "relation",
22383
+ array: "array",
22384
+ map: "map",
22385
+ vector: "string",
22386
+ binary: "string"
22387
+ };
22388
+ /**
22389
+ * Convert a single Property to a CollectionPropertyConfig.
22390
+ */
22391
+ function mapPropertyToConfig(property) {
22392
+ const base = {
22393
+ type: TYPE_MAP[property.type] ?? "string",
22394
+ name: property.name,
22395
+ description: property.description,
22396
+ columnWidth: property.ui?.columnWidth,
22397
+ hideFromCollection: property.ui?.hideFromCollection
22398
+ };
22399
+ if (property.type === "string") {
22400
+ if (property.enum) base.enum = mapEnumValues(property.enum);
22401
+ if (property.multiline) base.multiline = true;
22402
+ if (property.ui?.previewAsTag) base.previewAsTag = true;
22403
+ if (property.url || property.ui?.url) base.url = property.url ?? property.ui?.url;
22404
+ if (property.ui?.markdown || property.markdown) base.markdown = true;
22405
+ if (property.storage) base.storage = true;
22406
+ if (property.email) base.email = true;
22407
+ }
22408
+ if (property.type === "number" && property.enum) base.enum = mapEnumValues(property.enum);
22409
+ if (property.type === "array" && property.of && !Array.isArray(property.of)) base.of = mapPropertyToConfig(property.of);
22410
+ if (property.type === "map" && property.properties) {
22411
+ base.properties = mapPropertiesToConfigs(property.properties);
22412
+ if (property.propertiesOrder) base.propertiesOrder = property.propertiesOrder;
22761
22413
  }
22414
+ if (property.type === "date" && property.mode) base.mode = property.mode;
22415
+ if (property.ui?.Preview) {}
22416
+ return base;
22762
22417
  }
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);
22418
+ /**
22419
+ * Convert a Properties record to a CollectionPropertyConfig record.
22420
+ */
22421
+ function mapPropertiesToConfigs(properties) {
22422
+ const result = {};
22423
+ for (const [key, property] of Object.entries(properties)) result[key] = mapPropertyToConfig(property);
22424
+ return result;
22425
+ }
22426
+ /**
22427
+ * Convert Rebase EnumValues to CollectionEnumValueConfig format.
22428
+ * Handles both array form (EnumValueConfig[]) and record form.
22429
+ */
22430
+ function mapEnumValues(enumValues) {
22431
+ if (!enumValues) return void 0;
22432
+ if (Array.isArray(enumValues)) {
22433
+ const result = {};
22434
+ for (const item of enumValues) result[String(item.id)] = normalizeEnumValueConfig(item);
22435
+ return result;
22768
22436
  }
22769
- return errors;
22437
+ const result = {};
22438
+ for (const [key, value] of Object.entries(enumValues)) result[key] = typeof value === "string" ? value : normalizeEnumValueConfig(value);
22439
+ return result;
22770
22440
  }
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]);
22441
+ function normalizeEnumValueConfig(config) {
22442
+ return {
22443
+ label: config.label,
22444
+ color: typeof config.color === "string" ? config.color : void 0,
22445
+ disabled: config.disabled
22446
+ };
22447
+ }
22448
+ //#endregion
22449
+ //#region src/util/dataControllerAdapter.ts
22450
+ /**
22451
+ * Adapts an `EntityTableController<M>` (from @rebasepro/types) to a
22452
+ * `CollectionDataController<T>` (from @rebasepro/ui), bridging the
22453
+ * entity-aware data layer to the headless collection view.
22454
+ */
22455
+ /**
22456
+ * Flatten Entity<M> objects to plain row objects.
22457
+ * The headless view works with flat `Record<string, unknown>` rows,
22458
+ * while the entity system uses `{ id, path, values: M }`.
22459
+ *
22460
+ * The flattened row includes `id` and `path` at the top level,
22461
+ * plus all values spread.
22462
+ */
22463
+ function flattenEntities(entities) {
22464
+ return entities.map((entity) => ({
22465
+ id: entity.id,
22466
+ path: entity.path,
22467
+ ...entity.values
22468
+ }));
22469
+ }
22470
+ /**
22471
+ * React hook that adapts an EntityTableController to a CollectionDataController.
22472
+ *
22473
+ * Usage:
22474
+ * ```tsx
22475
+ * const tableController = useDataTableController({ path, collection });
22476
+ * const dataController = useCollectionDataController(tableController);
22477
+ *
22478
+ * <CollectionView dataController={dataController} ... />
22479
+ * ```
22480
+ */
22481
+ function useCollectionDataController(tableController) {
22482
+ const flatData = useMemo(() => flattenEntities(tableController.data), [tableController.data]);
22483
+ return useMemo(() => ({
22484
+ data: flatData,
22485
+ loading: tableController.dataLoading,
22486
+ noMoreToLoad: tableController.noMoreToLoad,
22487
+ error: tableController.dataLoadingError,
22488
+ filterValues: tableController.filterValues,
22489
+ setFilterValues: tableController.setFilterValues,
22490
+ sortBy: tableController.sortBy,
22491
+ setSortBy: tableController.setSortBy,
22492
+ searchString: tableController.searchString,
22493
+ setSearchString: tableController.setSearchString,
22494
+ clearFilter: tableController.clearFilter,
22495
+ initialScroll: tableController.initialScroll,
22496
+ onScroll: tableController.onScroll,
22497
+ paginationEnabled: tableController.paginationEnabled,
22498
+ pageSize: tableController.pageSize,
22499
+ itemCount: tableController.itemCount,
22500
+ setItemCount: tableController.setItemCount
22501
+ }), [flatData, tableController]);
22502
+ }
22503
+ /**
22504
+ * Non-hook version for cases where you already have the data
22505
+ * and just need to build a static controller.
22506
+ */
22507
+ function createStaticDataController(data, options) {
22508
+ return {
22509
+ data,
22510
+ loading: options?.loading ?? false,
22511
+ noMoreToLoad: true,
22512
+ error: options?.error
22513
+ };
22776
22514
  }
22777
22515
  //#endregion
22778
22516
  //#region src/data_import/import/ImportCollectionAction.tsx
@@ -23606,6 +23344,571 @@ var export_exports = /* @__PURE__ */ __exportAll({
23606
23344
  */
23607
23345
  var useCollectionsConfigController = () => useContext(ConfigControllerContext);
23608
23346
  //#endregion
23347
+ //#region src/collection_editor/serializable_utils.ts
23348
+ /**
23349
+ * Strip non-serializable fields from a `BaseUIConfig`.
23350
+ * Removes `Field` and `Preview` (ComponentRef).
23351
+ */
23352
+ function toSerializableUIConfig(ui) {
23353
+ if (!ui) return void 0;
23354
+ const { Field, Preview, ...rest } = ui;
23355
+ const result = {};
23356
+ for (const [key, value] of Object.entries(rest)) if (key !== "Field" && key !== "Preview" && value !== void 0) result[key] = value;
23357
+ return Object.keys(result).length > 0 ? result : void 0;
23358
+ }
23359
+ /**
23360
+ * Convert a `StorageConfig` to its serializable form.
23361
+ * Drops function variants of `storagePath`, `fileName`, and all function fields.
23362
+ */
23363
+ function toSerializableStorageConfig(storage) {
23364
+ if (!storage) return void 0;
23365
+ const storagePath = typeof storage.storagePath === "string" ? storage.storagePath : void 0;
23366
+ if (!storagePath) return void 0;
23367
+ const result = { storagePath };
23368
+ if (storage.acceptedFiles) result.acceptedFiles = storage.acceptedFiles;
23369
+ if (storage.imageResize) result.imageResize = storage.imageResize;
23370
+ if (storage.metadata) result.metadata = storage.metadata;
23371
+ if (typeof storage.fileName === "string") result.fileName = storage.fileName;
23372
+ if (storage.includeBucketUrl !== void 0) result.includeBucketUrl = storage.includeBucketUrl;
23373
+ if (storage.storeUrl !== void 0) result.storeUrl = storage.storeUrl;
23374
+ if (storage.maxSize !== void 0) result.maxSize = storage.maxSize;
23375
+ return result;
23376
+ }
23377
+ /**
23378
+ * Convert a RegExp or string `matches` validation to a plain string.
23379
+ */
23380
+ function serializeMatches(matches) {
23381
+ if (matches === void 0) return void 0;
23382
+ if (typeof matches === "string") return matches;
23383
+ return matches.source;
23384
+ }
23385
+ /**
23386
+ * Resolve a relation target to a string slug.
23387
+ * Functions are called to extract the target; EntityCollection objects
23388
+ * use their slug.
23389
+ */
23390
+ function resolveRelationTarget(target) {
23391
+ if (!target) return void 0;
23392
+ if (typeof target === "string") return target;
23393
+ if (typeof target === "function") try {
23394
+ const resolved = target();
23395
+ if (typeof resolved === "string") return resolved;
23396
+ if (resolved && typeof resolved === "object" && "slug" in resolved) return resolved.slug;
23397
+ } catch {}
23398
+ }
23399
+ /**
23400
+ * Strip non-serializable base property fields.
23401
+ * Returns a clean object with only the common serializable fields from BaseProperty.
23402
+ * Validation is intentionally excluded — each property type handles its own.
23403
+ */
23404
+ function toSerializableBaseFields(property) {
23405
+ const result = { name: property.name };
23406
+ if (property.description) result.description = property.description;
23407
+ if (property.propertyConfig) result.propertyConfig = property.propertyConfig;
23408
+ if (property.columnName) result.columnName = property.columnName;
23409
+ if (property.defaultValue !== void 0) result.defaultValue = property.defaultValue;
23410
+ if (property.conditions) result.conditions = property.conditions;
23411
+ if (property.metadata) result.metadata = property.metadata;
23412
+ const ui = toSerializableUIConfig(property.ui);
23413
+ if (ui) result.ui = ui;
23414
+ return result;
23415
+ }
23416
+ /**
23417
+ * Convert a single `Property` to its JSON-serializable form.
23418
+ * Strips functions, converts RegExp to string, Date to ISO string.
23419
+ */
23420
+ function toSerializableProperty(property) {
23421
+ const base = toSerializableBaseFields(property);
23422
+ switch (property.type) {
23423
+ case "string": {
23424
+ const sp = property;
23425
+ const result = {
23426
+ ...base,
23427
+ type: "string"
23428
+ };
23429
+ if (sp.columnType) result.columnType = sp.columnType;
23430
+ if (sp.isId !== void 0) result.isId = sp.isId;
23431
+ if (sp.enum) result.enum = sp.enum;
23432
+ if (sp.multiline) result.multiline = sp.multiline;
23433
+ if (sp.markdown) result.markdown = sp.markdown;
23434
+ if (sp.userSelect) result.userSelect = sp.userSelect;
23435
+ if (sp.url) result.url = sp.url;
23436
+ if (sp.email) result.email = sp.email;
23437
+ if (sp.previewAsTag) result.previewAsTag = sp.previewAsTag;
23438
+ if (sp.validation) {
23439
+ const { matches, ...restValidation } = sp.validation;
23440
+ const serializedValidation = { ...restValidation };
23441
+ const matchStr = serializeMatches(matches);
23442
+ if (matchStr !== void 0) serializedValidation.matches = matchStr;
23443
+ result.validation = serializedValidation;
23444
+ }
23445
+ const storage = toSerializableStorageConfig(sp.storage);
23446
+ if (storage) result.storage = storage;
23447
+ if (sp.reference) result.reference = toSerializableProperty(sp.reference);
23448
+ return result;
23449
+ }
23450
+ case "number": {
23451
+ const np = property;
23452
+ const result = {
23453
+ ...base,
23454
+ type: "number"
23455
+ };
23456
+ if (np.columnType) result.columnType = np.columnType;
23457
+ if (np.validation) result.validation = np.validation;
23458
+ if (np.isId !== void 0) result.isId = np.isId;
23459
+ if (np.enum) result.enum = np.enum;
23460
+ return result;
23461
+ }
23462
+ case "boolean": {
23463
+ const bp = property;
23464
+ const result = {
23465
+ ...base,
23466
+ type: "boolean"
23467
+ };
23468
+ if (bp.validation) result.validation = bp.validation;
23469
+ return result;
23470
+ }
23471
+ case "date": {
23472
+ const dp = property;
23473
+ const result = {
23474
+ ...base,
23475
+ type: "date"
23476
+ };
23477
+ if (dp.columnType) result.columnType = dp.columnType;
23478
+ if (dp.mode) result.mode = dp.mode;
23479
+ if (dp.timezone) result.timezone = dp.timezone;
23480
+ if (dp.autoValue) result.autoValue = dp.autoValue;
23481
+ if (dp.clearable) result.clearable = dp.clearable;
23482
+ if (dp.validation) {
23483
+ const serializedValidation = {};
23484
+ if (dp.validation.required !== void 0) serializedValidation.required = dp.validation.required;
23485
+ if (dp.validation.requiredMessage) serializedValidation.requiredMessage = dp.validation.requiredMessage;
23486
+ if (dp.validation.unique) serializedValidation.unique = dp.validation.unique;
23487
+ if (dp.validation.uniqueInArray) serializedValidation.uniqueInArray = dp.validation.uniqueInArray;
23488
+ if (dp.validation.min instanceof Date) serializedValidation.min = dp.validation.min.toISOString();
23489
+ else if (typeof dp.validation.min === "string") serializedValidation.min = dp.validation.min;
23490
+ if (dp.validation.max instanceof Date) serializedValidation.max = dp.validation.max.toISOString();
23491
+ else if (typeof dp.validation.max === "string") serializedValidation.max = dp.validation.max;
23492
+ result.validation = serializedValidation;
23493
+ }
23494
+ return result;
23495
+ }
23496
+ case "geopoint": {
23497
+ const gp = property;
23498
+ const result = {
23499
+ ...base,
23500
+ type: "geopoint"
23501
+ };
23502
+ if (gp.validation) result.validation = gp.validation;
23503
+ return result;
23504
+ }
23505
+ case "reference": {
23506
+ const rp = property;
23507
+ const result = {
23508
+ ...base,
23509
+ type: "reference"
23510
+ };
23511
+ if (rp.isId !== void 0) result.isId = rp.isId;
23512
+ if (rp.path) result.path = rp.path;
23513
+ if (rp.fixedFilter) result.fixedFilter = rp.fixedFilter;
23514
+ if (rp.includeId !== void 0) result.includeId = rp.includeId;
23515
+ if (rp.includeEntityLink !== void 0) result.includeEntityLink = rp.includeEntityLink;
23516
+ return result;
23517
+ }
23518
+ case "relation": {
23519
+ const rl = property;
23520
+ const result = {
23521
+ ...base,
23522
+ type: "relation"
23523
+ };
23524
+ if (rl.isId !== void 0) result.isId = rl.isId;
23525
+ const target = resolveRelationTarget(rl.target);
23526
+ if (target) result.target = target;
23527
+ if (rl.cardinality) result.cardinality = rl.cardinality;
23528
+ if (rl.direction) result.direction = rl.direction;
23529
+ if (rl.inverseRelationName) result.inverseRelationName = rl.inverseRelationName;
23530
+ if (rl.localKey) result.localKey = rl.localKey;
23531
+ if (rl.foreignKeyOnTarget) result.foreignKeyOnTarget = rl.foreignKeyOnTarget;
23532
+ if (rl.through) result.through = rl.through;
23533
+ if (rl.joinPath) result.joinPath = rl.joinPath;
23534
+ if (rl.onUpdate) result.onUpdate = rl.onUpdate;
23535
+ if (rl.onDelete) result.onDelete = rl.onDelete;
23536
+ if (rl.relationName) result.relationName = rl.relationName;
23537
+ if (rl.fixedFilter) result.fixedFilter = rl.fixedFilter;
23538
+ if (rl.includeId !== void 0) result.includeId = rl.includeId;
23539
+ if (rl.includeEntityLink !== void 0) result.includeEntityLink = rl.includeEntityLink;
23540
+ if (rl.widget) result.widget = rl.widget;
23541
+ return result;
23542
+ }
23543
+ case "array": {
23544
+ const ap = property;
23545
+ const result = {
23546
+ ...base,
23547
+ type: "array"
23548
+ };
23549
+ if (ap.columnType) result.columnType = ap.columnType;
23550
+ if (ap.validation) result.validation = ap.validation;
23551
+ if (ap.sortable !== void 0) result.sortable = ap.sortable;
23552
+ if (ap.canAddElements !== void 0) result.canAddElements = ap.canAddElements;
23553
+ if (ap.of) if (Array.isArray(ap.of)) result.of = ap.of.map(toSerializableProperty);
23554
+ else result.of = toSerializableProperty(ap.of);
23555
+ if (ap.oneOf) {
23556
+ result.oneOf = { properties: toSerializableProperties(ap.oneOf.properties) };
23557
+ if (ap.oneOf.propertiesOrder) result.oneOf.propertiesOrder = ap.oneOf.propertiesOrder;
23558
+ if (ap.oneOf.typeField) result.oneOf.typeField = ap.oneOf.typeField;
23559
+ if (ap.oneOf.valueField) result.oneOf.valueField = ap.oneOf.valueField;
23560
+ }
23561
+ return result;
23562
+ }
23563
+ case "map": {
23564
+ const mp = property;
23565
+ const result = {
23566
+ ...base,
23567
+ type: "map"
23568
+ };
23569
+ if (mp.columnType) result.columnType = mp.columnType;
23570
+ if (mp.validation) result.validation = mp.validation;
23571
+ if (mp.propertiesOrder) result.propertiesOrder = mp.propertiesOrder;
23572
+ if (mp.previewProperties) result.previewProperties = mp.previewProperties;
23573
+ if (mp.keyValue) result.keyValue = mp.keyValue;
23574
+ if (mp.properties) result.properties = toSerializableProperties(mp.properties);
23575
+ return result;
23576
+ }
23577
+ case "vector": {
23578
+ const vp = property;
23579
+ const result = {
23580
+ ...base,
23581
+ type: "vector",
23582
+ dimensions: vp.dimensions
23583
+ };
23584
+ if (vp.validation) result.validation = vp.validation;
23585
+ return result;
23586
+ }
23587
+ case "binary": {
23588
+ const bp = property;
23589
+ const result = {
23590
+ ...base,
23591
+ type: "binary"
23592
+ };
23593
+ if (bp.validation) result.validation = bp.validation;
23594
+ return result;
23595
+ }
23596
+ default: return base;
23597
+ }
23598
+ }
23599
+ /**
23600
+ * Convert a `Properties` record to its serializable form.
23601
+ */
23602
+ function toSerializableProperties(properties) {
23603
+ const result = {};
23604
+ for (const [key, property] of Object.entries(properties)) result[key] = toSerializableProperty(property);
23605
+ return result;
23606
+ }
23607
+ /**
23608
+ * Convert an `EntityCollection` to its JSON-serializable form.
23609
+ *
23610
+ * Strips all non-serializable fields (functions, React nodes, class instances)
23611
+ * while preserving the structural schema that the collection editor works with.
23612
+ *
23613
+ * The result is safe for `JSON.stringify()` and database storage.
23614
+ */
23615
+ function toSerializableCollection(collection) {
23616
+ const result = {
23617
+ slug: collection.slug,
23618
+ name: collection.name,
23619
+ properties: toSerializableProperties(collection.properties)
23620
+ };
23621
+ if (collection.singularName) result.singularName = collection.singularName;
23622
+ if (collection.description) result.description = collection.description;
23623
+ if (collection.group) result.group = collection.group;
23624
+ if (collection.driver) result.driver = collection.driver;
23625
+ if (collection.databaseId) result.databaseId = collection.databaseId;
23626
+ if (collection.titleProperty) result.titleProperty = collection.titleProperty;
23627
+ if (collection.ownerId) result.ownerId = collection.ownerId;
23628
+ if (collection.metadata) result.metadata = collection.metadata;
23629
+ if (collection.table) result.table = collection.table;
23630
+ if ("schema" in collection && collection.schema) result.schema = collection.schema;
23631
+ if (collection.orderProperty) result.orderProperty = collection.orderProperty;
23632
+ if (typeof collection.icon === "string") result.icon = collection.icon;
23633
+ if (collection.propertiesOrder) result.propertiesOrder = collection.propertiesOrder;
23634
+ if (collection.previewProperties) result.previewProperties = collection.previewProperties;
23635
+ if (collection.listProperties) result.listProperties = collection.listProperties;
23636
+ if (collection.enabledViews) result.enabledViews = collection.enabledViews;
23637
+ if (collection.disableDefaultActions) result.disableDefaultActions = collection.disableDefaultActions;
23638
+ if (collection.securityRules) result.securityRules = collection.securityRules;
23639
+ if (collection.openEntityMode) result.openEntityMode = collection.openEntityMode;
23640
+ if (collection.defaultEntityAction) result.defaultEntityAction = collection.defaultEntityAction;
23641
+ if (collection.defaultViewMode) result.defaultViewMode = collection.defaultViewMode;
23642
+ if (collection.defaultSize) result.defaultSize = collection.defaultSize;
23643
+ if (collection.localChangesBackup !== void 0) result.localChangesBackup = collection.localChangesBackup;
23644
+ if (collection.kanban) result.kanban = collection.kanban;
23645
+ if (collection.fixedFilter) result.fixedFilter = collection.fixedFilter;
23646
+ if (collection.defaultFilter) result.defaultFilter = collection.defaultFilter;
23647
+ if (collection.filterPresets) result.filterPresets = collection.filterPresets;
23648
+ if (collection.sort) result.sort = collection.sort;
23649
+ if (collection.pagination !== void 0) result.pagination = collection.pagination;
23650
+ if (collection.selectionEnabled !== void 0) result.selectionEnabled = collection.selectionEnabled;
23651
+ if (collection.inlineEditing !== void 0) result.inlineEditing = collection.inlineEditing;
23652
+ if (collection.hideFromNavigation !== void 0) result.hideFromNavigation = collection.hideFromNavigation;
23653
+ if (collection.hideIdFromForm !== void 0) result.hideIdFromForm = collection.hideIdFromForm;
23654
+ if (collection.hideIdFromCollection !== void 0) result.hideIdFromCollection = collection.hideIdFromCollection;
23655
+ if (collection.formAutoSave !== void 0) result.formAutoSave = collection.formAutoSave;
23656
+ if (collection.alwaysApplyDefaultValues !== void 0) result.alwaysApplyDefaultValues = collection.alwaysApplyDefaultValues;
23657
+ if (collection.includeJsonView !== void 0) result.includeJsonView = collection.includeJsonView;
23658
+ if (collection.history !== void 0) result.history = collection.history;
23659
+ if (collection.sideDialogWidth !== void 0) result.sideDialogWidth = collection.sideDialogWidth;
23660
+ if (typeof collection.exportable === "boolean") result.exportable = collection.exportable;
23661
+ if (typeof collection.auth === "boolean") result.auth = collection.auth;
23662
+ if (typeof collection.defaultSelectedView === "string") result.defaultSelectedView = collection.defaultSelectedView;
23663
+ return result;
23664
+ }
23665
+ /**
23666
+ * Convert a serializable property back to the original `Property` type.
23667
+ *
23668
+ * This is a mostly pass-through operation since serializable properties
23669
+ * are a subset of the original types. The main conversions are:
23670
+ * - `validation.matches` (string) stays as string (compatible with Property)
23671
+ * - `validation.min/max` on dates: ISO strings → Date objects
23672
+ */
23673
+ function fromSerializableProperty(serialized) {
23674
+ switch (serialized.type) {
23675
+ case "date": {
23676
+ const { validation: dateValidation, ...dateRest } = serialized;
23677
+ const result = {
23678
+ ...dateRest,
23679
+ type: "date"
23680
+ };
23681
+ if (dateValidation) {
23682
+ const convertedValidation = { ...dateValidation };
23683
+ if (typeof dateValidation.min === "string") convertedValidation.min = new Date(dateValidation.min);
23684
+ if (typeof dateValidation.max === "string") convertedValidation.max = new Date(dateValidation.max);
23685
+ result.validation = convertedValidation;
23686
+ }
23687
+ return result;
23688
+ }
23689
+ case "array": {
23690
+ const sp = serialized;
23691
+ const result = { ...sp };
23692
+ if (sp.of) if (Array.isArray(sp.of)) result.of = sp.of.map(fromSerializableProperty);
23693
+ else result.of = fromSerializableProperty(sp.of);
23694
+ if (sp.oneOf) result.oneOf = {
23695
+ ...sp.oneOf,
23696
+ properties: fromSerializableProperties(sp.oneOf.properties)
23697
+ };
23698
+ return result;
23699
+ }
23700
+ case "map": {
23701
+ const sp = serialized;
23702
+ const result = { ...sp };
23703
+ if (sp.properties) result.properties = fromSerializableProperties(sp.properties);
23704
+ return result;
23705
+ }
23706
+ case "string": {
23707
+ const sp = serialized;
23708
+ const result = { ...sp };
23709
+ if (sp.reference) result.reference = fromSerializableProperty(sp.reference);
23710
+ return result;
23711
+ }
23712
+ default: return serialized;
23713
+ }
23714
+ }
23715
+ /**
23716
+ * Convert a `SerializableProperties` record back to `Properties`.
23717
+ */
23718
+ function fromSerializableProperties(serialized) {
23719
+ const result = {};
23720
+ for (const [key, property] of Object.entries(serialized)) result[key] = fromSerializableProperty(property);
23721
+ return result;
23722
+ }
23723
+ /**
23724
+ * Convert a `SerializableCollection` back to an `EntityCollection`.
23725
+ *
23726
+ * The result will NOT contain any of the non-serializable fields
23727
+ * (callbacks, entityActions, etc.) — those must be re-attached by the
23728
+ * consumer if needed.
23729
+ */
23730
+ function fromSerializableCollection(serialized) {
23731
+ const { properties, ...rest } = serialized;
23732
+ return {
23733
+ ...rest,
23734
+ properties: fromSerializableProperties(properties)
23735
+ };
23736
+ }
23737
+ //#endregion
23738
+ //#region src/collection_editor/useJsonCollectionsConfigController.tsx
23739
+ /**
23740
+ * A `CollectionsConfigController` implementation that persists collection
23741
+ * configurations as JSON via a pluggable `JsonCollectionStore` adapter.
23742
+ *
23743
+ * Use this when you want to store collection schemas in a database, API,
23744
+ * localStorage, or any other JSON-compatible backend — as opposed to the
23745
+ * default code-based persistence.
23746
+ *
23747
+ * @example
23748
+ * ```tsx
23749
+ * const store: JsonCollectionStore = {
23750
+ * load: async () => { const res = await fetch('/api/schemas'); return res.json(); },
23751
+ * save: async (slug, data) => { await fetch(`/api/schemas/${slug}`, { method: 'PUT', body: JSON.stringify(data) }); },
23752
+ * delete: async (slug) => { await fetch(`/api/schemas/${slug}`, { method: 'DELETE' }); },
23753
+ * };
23754
+ *
23755
+ * const configController = useJsonCollectionsConfigController({ store });
23756
+ * ```
23757
+ */
23758
+ function useJsonCollectionsConfigController({ store, readOnly = false, autoLoad = true }) {
23759
+ const [collections, setCollections] = useState([]);
23760
+ const [loading, setLoading] = useState(autoLoad);
23761
+ const [navigationEntries, setNavigationEntries] = useState([]);
23762
+ const storeRef = useRef(store);
23763
+ storeRef.current = store;
23764
+ useEffect(() => {
23765
+ if (!autoLoad) return;
23766
+ let cancelled = false;
23767
+ (async () => {
23768
+ try {
23769
+ const [serialized, navEntries] = await Promise.all([storeRef.current.load(), storeRef.current.loadNavigationEntries?.() ?? Promise.resolve([])]);
23770
+ if (cancelled) return;
23771
+ setCollections(serialized.map(fromSerializableCollection));
23772
+ setNavigationEntries(navEntries);
23773
+ } catch (e) {
23774
+ console.error("useJsonCollectionsConfigController: failed to load collections", e);
23775
+ } finally {
23776
+ if (!cancelled) setLoading(false);
23777
+ }
23778
+ })();
23779
+ return () => {
23780
+ cancelled = true;
23781
+ };
23782
+ }, [autoLoad]);
23783
+ /**
23784
+ * Update local state and persist a collection to the store.
23785
+ */
23786
+ const persistCollection = useCallback(async (collection) => {
23787
+ const serializable = toSerializableCollection(collection);
23788
+ await storeRef.current.save(collection.slug, serializable);
23789
+ }, []);
23790
+ /**
23791
+ * Find collection by id (slug).
23792
+ */
23793
+ const getCollection = useCallback((id) => {
23794
+ const found = collections.find((c) => c.slug === id);
23795
+ if (found) return found;
23796
+ throw new Error(`Collection "${id}" not found`);
23797
+ }, [collections]);
23798
+ const saveCollection = useCallback(async ({ id, collectionData }) => {
23799
+ const collection = collectionData;
23800
+ await persistCollection(collection);
23801
+ setCollections((prev) => {
23802
+ const idx = prev.findIndex((c) => c.slug === id);
23803
+ if (idx >= 0) {
23804
+ const next = [...prev];
23805
+ next[idx] = collection;
23806
+ return next;
23807
+ }
23808
+ return [...prev, collection];
23809
+ });
23810
+ }, [persistCollection]);
23811
+ const updateCollection = useCallback(async ({ id, collectionData, previousId }) => {
23812
+ setCollections((prev) => {
23813
+ const lookupId = previousId ?? id;
23814
+ const idx = prev.findIndex((c) => c.slug === lookupId);
23815
+ if (idx < 0) return prev;
23816
+ const merged = {
23817
+ ...prev[idx],
23818
+ ...collectionData
23819
+ };
23820
+ const next = [...prev];
23821
+ next[idx] = merged;
23822
+ persistCollection(merged).catch((e) => console.error("useJsonCollectionsConfigController: failed to update collection", e));
23823
+ if (previousId && previousId !== id) storeRef.current.delete(previousId).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete old slug", e));
23824
+ return next;
23825
+ });
23826
+ }, [persistCollection]);
23827
+ const deleteCollection = useCallback(async ({ id }) => {
23828
+ await storeRef.current.delete(id);
23829
+ setCollections((prev) => prev.filter((c) => c.slug !== id));
23830
+ }, []);
23831
+ const saveProperty = useCallback(async ({ path, propertyKey, property, newPropertiesOrder }) => {
23832
+ setCollections((prev) => {
23833
+ const idx = prev.findIndex((c) => c.slug === path);
23834
+ if (idx < 0) return prev;
23835
+ const collection = { ...prev[idx] };
23836
+ collection.properties = {
23837
+ ...collection.properties,
23838
+ [propertyKey]: property
23839
+ };
23840
+ if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
23841
+ const next = [...prev];
23842
+ next[idx] = collection;
23843
+ persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to save property", e));
23844
+ return next;
23845
+ });
23846
+ }, [persistCollection]);
23847
+ const deleteProperty = useCallback(async ({ path, propertyKey, newPropertiesOrder }) => {
23848
+ setCollections((prev) => {
23849
+ const idx = prev.findIndex((c) => c.slug === path);
23850
+ if (idx < 0) return prev;
23851
+ const collection = { ...prev[idx] };
23852
+ const { [propertyKey]: _removed, ...remaining } = collection.properties;
23853
+ collection.properties = remaining;
23854
+ if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
23855
+ const next = [...prev];
23856
+ next[idx] = collection;
23857
+ persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete property", e));
23858
+ return next;
23859
+ });
23860
+ }, [persistCollection]);
23861
+ const updatePropertiesOrder = useCallback(async ({ collection, newPropertiesOrder }) => {
23862
+ const updated = {
23863
+ ...collection,
23864
+ propertiesOrder: newPropertiesOrder
23865
+ };
23866
+ setCollections((prev) => {
23867
+ const idx = prev.findIndex((c) => c.slug === collection.slug);
23868
+ if (idx < 0) return prev;
23869
+ const next = [...prev];
23870
+ next[idx] = updated;
23871
+ return next;
23872
+ });
23873
+ await persistCollection(updated);
23874
+ }, [persistCollection]);
23875
+ const updateKanbanColumnsOrder = useCallback(async ({ collection, kanbanColumnProperty, newColumnsOrder }) => {}, []);
23876
+ const saveNavigationEntriesHandler = useCallback(async (entries) => {
23877
+ setNavigationEntries(entries);
23878
+ if (storeRef.current.saveNavigationEntries) await storeRef.current.saveNavigationEntries(entries);
23879
+ }, []);
23880
+ return useMemo(() => ({
23881
+ loading,
23882
+ readOnly,
23883
+ readOnlyReason: readOnly ? "Configuration is read-only." : void 0,
23884
+ collections,
23885
+ getCollection,
23886
+ saveCollection,
23887
+ updateCollection,
23888
+ deleteCollection,
23889
+ saveProperty,
23890
+ deleteProperty,
23891
+ updatePropertiesOrder,
23892
+ updateKanbanColumnsOrder,
23893
+ navigationEntries,
23894
+ saveNavigationEntries: saveNavigationEntriesHandler
23895
+ }), [
23896
+ loading,
23897
+ readOnly,
23898
+ collections,
23899
+ getCollection,
23900
+ saveCollection,
23901
+ updateCollection,
23902
+ deleteCollection,
23903
+ saveProperty,
23904
+ deleteProperty,
23905
+ updatePropertiesOrder,
23906
+ updateKanbanColumnsOrder,
23907
+ navigationEntries,
23908
+ saveNavigationEntriesHandler
23909
+ ]);
23910
+ }
23911
+ //#endregion
23609
23912
  //#region src/collection_editor/validateCollectionJson.ts
23610
23913
  /**
23611
23914
  * Valid dataType values for properties
@@ -24006,6 +24309,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
24006
24309
  return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
24007
24310
  }
24008
24311
  //#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 };
24312
+ export { RelationPreview as $, SelectableTable as $n, DateTimeFieldBinding as $t, useCollectionDataController as A, getLastSegment$1 as An, renderSkeletonCaptionText as Ar, useImportConfig as At, ArrayOfMapsPreview as B, UrlContext as Bn, getFieldId as Bt, downloadEntitiesExport as C, deleteEntityAction as Cn, isRelationProperty as Cr, useCollectionEditorController as Ct, ImportDataPreview as D, addInitialSlash as Dn, StorageThumbnail as Dr, flattenEntry as Dt, ImportCollectionAction as E, CreationResultDialog as En, EnumValuesChip as Er, convertDataToEntity as Et, extractTouchedValues as F, useBuildCollectionRegistryController as Fn, sanitizeUrl as Fr, PropertyConfigBadge as Ft, DatePreview as G, useCollectionRegistryController as Gn, SelectFieldBinding as Gt, UserPreview as H, NavigationStateContext as Hn, TextFieldBinding as Ht, getChanges as I, useEntityHistory as In, ImagePreview as Ir, DEFAULT_FIELD_CONFIGS as It, ArrayOneOfPreview as J, getEntityViewWidth as Jn, ReferenceAsStringFieldBinding as Jt, KeyValuePreview as K, useSideDialogsController as Kn, RepeatFieldBinding as Kt, getInitialEntityValues as L, resolveNavigationFrom as Ln, EmptyValue as Lr, getDefaultFieldConfig as Lt, mapPropertyToConfig as M, removeInitialSlash as Mn, renderSkeletonImageThumbnail as Mr, ImportFileUpload as Mt, EntityFormBinding as N, removeTrailingSlash as Nn, renderSkeletonText as Nr, convertFileToJson as Nt, PropertySelectEntry as O, getCollectionBySlugWithin as On, StorageThumbnailInternal as Or, processValueMapping as Ot, EntityForm as P, resolveCollectionPathIds$1 as Pn, UrlComponentPreview as Pr, unflattenObject as Pt, ArrayOfStorageComponentsPreview as Q, EntityCollectionTable as Qn, KeyValueFieldBinding as Qt, removeEmptyContainers as R, useResolvedNavigationFrom as Rn, getDefaultFieldId as Rt, downloadDataAsCsv as S, copyEntityAction as Sn, isReferenceProperty as Sr, EntityCollectionViewActions as St, getEntityJsonExportableData as T, resetPasswordAction as Tn, StringPropertyPreview as Tr, DataNewPropertiesMapping as Tt, NumberPropertyPreview as U, useNavigationStateController as Un, SwitchFieldBinding as Ut, PropertyPreview as V, useUrlController as Vn, VectorFieldBinding as Vt, BooleanPreview as W, CollectionRegistryContext as Wn, StorageUploadFieldBinding as Wt, ArrayPropertyEnumPreview as X, mergeEntityActions as Xn, MarkdownEditorFieldBinding as Xt, ArrayOfStringsPreview as Y, useBuildSideEntityController as Yn, MultiSelectFieldBinding as Yt, ArrayEnumPreview as Z, useSelectionController as Zn, MapFieldBinding as Zt, toSerializableProperty as _, useBuildUrlController as _n, getIconForProperty as _r, SideEntityProvider as _t, namespaceToPropertiesPath as a, ReadOnlyFieldBinding as an, useEntitySelectionDialog as ar, RebaseLayout as at, BasicExportAction as b, useResolvedViews as bn, getPropertyInPath as br, EntityCollectionCardView as bt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as c, LabelWithIconAndTooltip as cn, resolveEntityView as cr, RebaseAuthGate as ct, useJsonCollectionsConfigController as d, FieldHelperText as dn, ReferencePreview as dr, Drawer as dt, BlockFieldBinding as en, EntityCollectionRowActions as er, CollectionPanel as et, fromSerializableCollection as f, SearchIconsView as fn, EntityPreview as fr, DefaultDrawer as ft, toSerializableProperties as g, useBreadcrumbsController as gn, getDefaultPropertiesOrder as gr, AdminModeSyncer as gt, toSerializableCollection as h, SideDialogs as hn, getBracketNotation as hr, useApp as ht, namespaceToPropertiesOrderPath as i, PropertyFieldBinding as in, useSideEntityController as ir, RebaseRoute as it, mapPropertiesToConfigs as j, removeInitialAndTrailingSlashes$1 as jn, renderSkeletonIcon as jr, ImportSaveInProgress as jt, createStaticDataController as k, getCollectionPathsCombinations as kn, SkeletonPropertyComponent as kr, getInferenceType as kt, buildCollectionGenerationCallback as l, PropertyIdCopyTooltip as ln, EntityView as lr, RebaseCMS as lt, fromSerializableProperty as m, EntitySelectionTable as mn, getEntityTitlePropertyKey as mr, AppBar as mt, getFullIdPath as n, ArrayContainer as nn, SelectableTableContext as nr, RebaseRouteDefs as nt, MissingReferenceWidget as o, FormLayout as on, VirtualTableInput as or, RebaseNavigation as ot, fromSerializableProperties as p, FieldCaption as pn, getEntityPreviewKeys as pr, DrawerFooterActions as pt, MapPropertyPreview as q, buildSidePanelsFromUrl as qn, ReferenceFieldBinding as qt, idToPropertiesPath as r, ArrayCustomShapedFieldBinding as rn, SideEntityControllerContext as rr, CustomViewRoute as rt, CollectionGenerationApiError as s, FormEntry as sn, resolveEntityAction as sr, useLocalCollectionsConfigController as st, getFullId as t, ArrayOfReferencesFieldBinding as tn, useClearRestoreValue as tr, RebaseShell as tt, validateCollectionJson as u, LabelWithIcon as un, ArrayOfReferencesPreview as ur, Scaffold as ut, useCollectionsConfigController as v, useBuildNavigationStateController as vn, getIconForWidget as vr, ReferenceWidget as vt, getEntityCSVExportableData as w, editEntityAction as wn, ArrayPropertyPreview as wr, ImportNewPropertyFieldPreview as wt, downloadBlob as x, useResolvedCollections as xn, getResolvedPropertyInPath as xr, EntityCard as xt, ExportCollectionAction as y, useTopLevelNavigation as yn, getPropertiesWithPropertiesOrder as yr, EntityCollectionView as yt, zodToFormErrors as z, useCMSContext as zn, getFieldConfig as zt };
24010
24313
 
24011
- //# sourceMappingURL=src-BYniefVu.js.map
24314
+ //# sourceMappingURL=src-DOX2nOyh.js.map