@rebasepro/admin 0.12.0 → 0.12.1-canary.g009ed95

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 (172) hide show
  1. package/README.md +1 -1
  2. package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog-B-iVE9y3.js} +75 -62
  3. package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog-B-iVE9y3.js.map} +1 -1
  4. package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-DoLEH_bQ.js} +36 -16
  5. package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-DoLEH_bQ.js.map} +1 -1
  6. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-C3669Ezm.js} +5 -5
  7. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-C3669Ezm.js.map} +1 -1
  8. package/dist/collection_editor/serializable_types.d.ts +4 -1
  9. package/dist/collection_editor/ui/collection_editor/CollectionRelationsTab.d.ts +1 -0
  10. package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +8 -0
  11. package/dist/collection_editor_ui.js +5 -5
  12. package/dist/collection_editor_ui.js.map +1 -1
  13. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +10 -0
  14. package/dist/components/CollectionViewBinding/SplitViewContext.d.ts +23 -0
  15. package/dist/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.d.ts +26 -0
  16. package/dist/components/DetailViewBinding.d.ts +5 -0
  17. package/dist/components/EditFormActions.d.ts +12 -1
  18. package/dist/components/EditViewBinding.d.ts +6 -1
  19. package/dist/components/EntityIdentityBar.d.ts +53 -0
  20. package/dist/components/EntityInspector.d.ts +29 -0
  21. package/dist/components/EntityPreviewNesting.d.ts +25 -0
  22. package/dist/components/EntityViewBinding.d.ts +9 -0
  23. package/dist/components/InlineEntityPreview.d.ts +47 -0
  24. package/dist/components/index.d.ts +2 -0
  25. package/dist/editor.js +18 -10
  26. package/dist/editor.js.map +1 -1
  27. package/dist/{export-BzIpUdSK.js → export-B8DIn-u8.js} +2 -2
  28. package/dist/{export-BzIpUdSK.js.map → export-B8DIn-u8.js.map} +1 -1
  29. package/dist/form/EntityForm.d.ts +1 -1
  30. package/dist/form/EntityFormActions.d.ts +11 -1
  31. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  32. package/dist/form/components/FieldBlock.d.ts +30 -0
  33. package/dist/form/components/FormRail.d.ts +18 -0
  34. package/dist/form/components/FormSections.d.ts +16 -0
  35. package/dist/form/components/index.d.ts +3 -2
  36. package/dist/form/components/useRailVisible.d.ts +24 -0
  37. package/dist/form/field_bindings/BinaryFieldBinding.d.ts +1 -1
  38. package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +1 -1
  39. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +1 -1
  40. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  41. package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +1 -1
  42. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  43. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  44. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
  45. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -1
  46. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  47. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +1 -1
  48. package/dist/form/field_bindings/VectorFieldBinding.d.ts +1 -1
  49. package/dist/{history-g9688IaK.js → history-ClwDEtqN.js} +32 -31
  50. package/dist/history-ClwDEtqN.js.map +1 -0
  51. package/dist/hooks/navigation/utils.d.ts +16 -0
  52. package/dist/hooks/useBuildSidePanel.d.ts +14 -0
  53. package/dist/hooks/useEntityDisplayTitle.d.ts +18 -0
  54. package/dist/hooks/useRecordActions.d.ts +17 -0
  55. package/dist/{import-BjUG-cIY.js → import-Cwzf4RW5.js} +2 -2
  56. package/dist/{import-BjUG-cIY.js.map → import-Cwzf4RW5.js.map} +1 -1
  57. package/dist/index.js +106 -35
  58. package/dist/index.js.map +1 -1
  59. package/dist/markdown-CklalUUk.js.map +1 -1
  60. package/dist/preview/components/InlineEntityListPreview.d.ts +15 -0
  61. package/dist/preview/index.d.ts +1 -0
  62. package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
  63. package/dist/preview/property_previews/ArrayOfRelationsPreview.d.ts +1 -1
  64. package/dist/types/components/EntityFormActionsProps.d.ts +0 -1
  65. package/dist/types/components/EntityFormProps.d.ts +0 -5
  66. package/dist/types/fields.d.ts +24 -0
  67. package/dist/{util-B5fJm1FA.js → util-DSh3HRzK.js} +1974 -1130
  68. package/dist/util-DSh3HRzK.js.map +1 -0
  69. package/package.json +32 -34
  70. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  71. package/src/collection_editor/serializable_types.ts +4 -3
  72. package/src/collection_editor/serializable_utils.ts +51 -17
  73. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +65 -22
  74. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +38 -30
  75. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -1
  76. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
  77. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +1 -1
  78. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +16 -0
  79. package/src/collection_editor/useLocalCollectionsConfigController.tsx +127 -15
  80. package/src/collection_editor/validateCollectionJson.ts +3 -3
  81. package/src/components/AdminModeSyncer.tsx +1 -1
  82. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +10 -2
  83. package/src/components/CollectionTableBinding/column_utils.tsx +26 -2
  84. package/src/components/CollectionTableBinding/table_bindings.tsx +2 -1
  85. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +23 -16
  86. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +7 -1
  87. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +8 -2
  88. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +22 -11
  89. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +33 -0
  90. package/src/components/CollectionViewBinding/SplitListView.tsx +18 -3
  91. package/src/components/CollectionViewBinding/SplitViewContext.tsx +34 -0
  92. package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +48 -9
  93. package/src/components/CollectionViewBinding/useBoardDataController.tsx +80 -25
  94. package/src/components/CollectionViewBinding/usePreviewSlots.ts +18 -6
  95. package/src/components/DefaultAppBar.tsx +1 -1
  96. package/src/components/DefaultDrawer.tsx +1 -1
  97. package/src/components/DetailViewBinding.tsx +100 -143
  98. package/src/components/DrawerNavigationItem.tsx +1 -1
  99. package/src/components/EditFormActions.tsx +54 -129
  100. package/src/components/EditViewBinding.tsx +182 -93
  101. package/src/components/EntityIdentityBar.tsx +288 -0
  102. package/src/components/EntityInspector.tsx +132 -0
  103. package/src/components/EntityPreviewBinding.tsx +6 -2
  104. package/src/components/EntityPreviewNesting.tsx +50 -0
  105. package/src/components/EntityViewBinding.tsx +35 -32
  106. package/src/components/HomePage/FavouritesView.tsx +1 -1
  107. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  108. package/src/components/HomePage/SmallNavigationCard.tsx +1 -1
  109. package/src/components/InlineEntityPreview.tsx +149 -0
  110. package/src/components/RebaseLayout.tsx +1 -1
  111. package/src/components/RebaseNavigation.tsx +45 -5
  112. package/src/components/RebaseRouteDefs.tsx +1 -1
  113. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  114. package/src/components/SidePanelBinding.tsx +19 -13
  115. package/src/components/UserSelector.tsx +12 -6
  116. package/src/components/common/default_entity_actions.tsx +4 -3
  117. package/src/components/history/EntityHistoryEntry.tsx +28 -7
  118. package/src/components/history/EntityHistoryView.tsx +20 -20
  119. package/src/components/history/LastEditedByIndicator.tsx +4 -3
  120. package/src/components/index.ts +2 -0
  121. package/src/data_import/utils/get_import_inference_type.ts +6 -0
  122. package/src/form/EntityForm.tsx +267 -179
  123. package/src/form/EntityFormActions.tsx +50 -99
  124. package/src/form/EntityFormBinding.tsx +4 -2
  125. package/src/form/PropertyFieldBinding.tsx +4 -0
  126. package/src/form/components/FieldBlock.tsx +119 -0
  127. package/src/form/components/FormRail.tsx +125 -0
  128. package/src/form/components/FormSections.tsx +118 -0
  129. package/src/form/components/index.tsx +3 -2
  130. package/src/form/components/useRailVisible.tsx +46 -0
  131. package/src/form/field_bindings/BinaryFieldBinding.tsx +3 -2
  132. package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
  133. package/src/form/field_bindings/GeopointFieldBinding.tsx +3 -2
  134. package/src/form/field_bindings/MapFieldBinding.tsx +1 -2
  135. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -2
  136. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
  137. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
  138. package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -1
  139. package/src/form/field_bindings/RelationFieldBinding.tsx +6 -4
  140. package/src/form/field_bindings/RepeatFieldBinding.tsx +4 -1
  141. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
  142. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +31 -8
  143. package/src/form/field_bindings/SwitchFieldBinding.tsx +7 -3
  144. package/src/form/field_bindings/TextFieldBinding.tsx +18 -16
  145. package/src/form/field_bindings/UserSelectFieldBinding.tsx +3 -2
  146. package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
  147. package/src/hooks/navigation/useBuildUrlController.tsx +1 -1
  148. package/src/hooks/navigation/useTopLevelNavigation.ts +18 -7
  149. package/src/hooks/navigation/utils.ts +20 -0
  150. package/src/hooks/useBuildSideDialogsController.tsx +2 -2
  151. package/src/hooks/useBuildSidePanel.tsx +22 -61
  152. package/src/hooks/useEntityDisplayTitle.tsx +60 -0
  153. package/src/hooks/useHistory.ts +6 -5
  154. package/src/hooks/useRecordActions.tsx +69 -0
  155. package/src/hooks/useResolvedUsers.ts +10 -5
  156. package/src/preview/PropertyPreview.tsx +48 -22
  157. package/src/preview/components/InlineEntityListPreview.tsx +42 -0
  158. package/src/preview/components/ReferencePreview.tsx +34 -14
  159. package/src/preview/components/RelationPreview.tsx +34 -14
  160. package/src/preview/index.ts +1 -0
  161. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +24 -1
  162. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +25 -1
  163. package/src/routes/RebaseRoute.tsx +2 -2
  164. package/src/types/components/EntityFormActionsProps.tsx +0 -1
  165. package/src/types/components/EntityFormProps.tsx +0 -6
  166. package/src/types/fields.tsx +27 -0
  167. package/dist/form/components/FormEntry.d.ts +0 -6
  168. package/dist/form/components/FormLayout.d.ts +0 -5
  169. package/dist/history-g9688IaK.js.map +0 -1
  170. package/dist/util-B5fJm1FA.js.map +0 -1
  171. package/src/form/components/FormEntry.tsx +0 -22
  172. package/src/form/components/FormLayout.tsx +0 -16
@@ -1,9 +1,9 @@
1
1
  import * as React$1 from "react";
2
2
  import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { deepEqual } from "fast-equals";
4
- import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoaderIcon, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, TableView, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
4
+ import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
5
5
  import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityChildViews, getLabelOrConfigFrom, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isPropertyBuilder, normalizeToEntityRelation, resolveArrayProperties, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
6
- import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, IconForView, PluginProviderStack, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
6
+ import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
7
7
  import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { defaultDateFormat, getHashValue, getValueInPath, hydrateRegExp, isObject, mergeDeep, randomString, toArray } from "@rebasepro/utils";
@@ -14,7 +14,7 @@ import { useDropzone } from "react-dropzone";
14
14
  import { EntityReference, EntityRelation, GeoPoint, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig } from "@rebasepro/types";
15
15
  import { z } from "zod";
16
16
  import useMeasure from "react-use-measure";
17
- import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
17
+ import { useLocation, useNavigate, useSearchParams } from "react-router";
18
18
  import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
19
19
  import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
20
20
  import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
@@ -24,40 +24,139 @@ import { inferTypeFromValue } from "@rebasepro/inference";
24
24
  import { Highlight, themes } from "prism-react-renderer";
25
25
  import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
26
26
  import { resolveAdminCollection } from "@rebasepro/admin-types";
27
- //#region src/form/components/FormEntry.tsx
28
- function FormEntry({ propertyKey, widthPercentage = 100, children }) {
29
- return /* @__PURE__ */ jsx("div", {
30
- id: `form_field_${propertyKey}`,
31
- className: "relative",
32
- style: { width: widthPercentage === 100 ? "100%" : `calc(${widthPercentage}% - 8px)` },
33
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children })
34
- });
27
+ //#region src/util/property_utils.tsx
28
+ /**
29
+ * Resolve a size value (string token or number) to a numeric pixel value
30
+ * suitable for passing to Lucide icons.
31
+ */
32
+ function resolveSize(size) {
33
+ if (typeof size === "number") return size;
34
+ return iconSize[size];
35
35
  }
36
- //#endregion
37
- //#region src/form/components/FormLayout.tsx
38
- function FormLayout({ children, className }) {
39
- return /* @__PURE__ */ jsx("div", {
40
- className: cls("flex flex-wrap gap-x-4 w-full space-y-6", className),
41
- children
42
- });
36
+ function isReferenceProperty(property) {
37
+ if (!property) return null;
38
+ if (property.type === "reference") return true;
39
+ if (property.type === "array") if (Array.isArray(property.of)) return false;
40
+ else return property.of?.type === "reference";
41
+ return false;
42
+ }
43
+ function isRelationProperty(property) {
44
+ if (!property) return null;
45
+ if (property.type === "relation") return true;
46
+ if (property.type === "array") if (Array.isArray(property.of)) return false;
47
+ else return property.of?.type === "relation";
48
+ return false;
49
+ }
50
+ function getIconForWidget(widget, size) {
51
+ return /* @__PURE__ */ jsx(widget?.Icon ?? CircleIcon, { size: resolveSize(size) });
43
52
  }
44
- //#endregion
45
- //#region src/form/components/LabelWithIcon.tsx
46
53
  /**
47
- * Render the label of with an icon and the title of a property
48
- * @group Form custom fields
54
+ * Returns a default Lucide icon component based on property type.
55
+ * This provides a sensible fallback when no PropertyConfig is available.
49
56
  */
50
- var LabelWithIcon = forwardRef(({ icon, title, small, className, required }, ref) => {
51
- return /* @__PURE__ */ jsxs("div", {
52
- ref,
53
- className: cls("align-middle inline-flex items-center my-0.5", small ? "gap-1" : "gap-2", className),
54
- children: [icon, /* @__PURE__ */ jsx("span", {
55
- className: `text-start font-medium text-${small ? "base" : "sm"} origin-top-left transform ${small ? "translate-x-2 scale-75" : ""}`,
56
- children: (title ?? "") + (required ? " *" : "")
57
- })]
57
+ function getDefaultIconForProperty(property) {
58
+ switch (property.type) {
59
+ case "string":
60
+ if (property.storage) return UploadIcon;
61
+ if (property.admin?.urlPreview) return GlobeIcon;
62
+ if (property.email) return MailIcon;
63
+ if (property.admin?.multiline || property.admin?.markdown) return AlignLeftIcon;
64
+ return TextIcon;
65
+ case "number": return HashIcon;
66
+ case "boolean": return FlagIcon;
67
+ case "date": return CalendarIcon;
68
+ case "map": return VoteIcon;
69
+ case "array": {
70
+ const of = property.of;
71
+ if (property.oneOf) return Rows3Icon;
72
+ if (of && !Array.isArray(of)) {
73
+ if (of.type === "reference") return LinkIcon;
74
+ if (of.type === "string" && of.storage) return UploadIcon;
75
+ }
76
+ return RepeatIcon;
77
+ }
78
+ case "reference": return LinkIcon;
79
+ case "relation": return LinkIcon;
80
+ case "vector": return HashIcon;
81
+ default: return CircleIcon;
82
+ }
83
+ }
84
+ function getIconForProperty(property, size = "small", fields = {}) {
85
+ const px = resolveSize(size);
86
+ if (isPropertyBuilder(property)) return /* @__PURE__ */ jsx(FunctionSquareIcon, { size: px });
87
+ const configId = property.propertyConfig || void 0;
88
+ const widget = configId ? fields[configId] : void 0;
89
+ if (widget?.Icon) return getIconForWidget(widget, size);
90
+ return /* @__PURE__ */ jsx(getDefaultIconForProperty(property), { size: px });
91
+ }
92
+ /**
93
+ * Get a property in a property tree from a path like
94
+ * `address.street`
95
+ * @param properties
96
+ * @param path
97
+ */
98
+ function getPropertyInPath(properties, path) {
99
+ if (typeof properties === "object") {
100
+ if (path in properties) return properties[path];
101
+ if (path.includes(".")) {
102
+ const pathSegments = path.split(".");
103
+ const childProperty = properties[pathSegments[0]];
104
+ if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
105
+ }
106
+ }
107
+ }
108
+ function getResolvedPropertyInPath(properties, path) {
109
+ if (typeof properties === "object") {
110
+ if (path in properties) return properties[path];
111
+ if (path.includes(".")) {
112
+ const pathSegments = path.split(".");
113
+ const childProperty = properties[pathSegments[0]];
114
+ if (childProperty.type === "map" && childProperty.properties) return getResolvedPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
115
+ }
116
+ }
117
+ }
118
+ function getBracketNotation(path) {
119
+ return path.replace(/\.([^.]*)/g, "[$1]");
120
+ }
121
+ /**
122
+ * Get properties sorted by their order, but include ALL properties.
123
+ * Nested keys (like "data.mode") in propertiesOrder are ignored - they're for column ordering.
124
+ * @param properties
125
+ * @param propertiesOrder
126
+ */
127
+ function getPropertiesWithPropertiesOrder(properties, propertiesOrder) {
128
+ if (!propertiesOrder) return properties;
129
+ const propertyKeys = Object.keys(properties);
130
+ const validOrderKeys = propertiesOrder.filter((key) => !key.includes(".") && properties[key]);
131
+ const result = {};
132
+ validOrderKeys.forEach((key) => {
133
+ const property = properties[key];
134
+ if (typeof property === "object" && property.type === "map" && property.properties) {
135
+ const mapProp = property;
136
+ result[key] = {
137
+ ...mapProp,
138
+ properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
139
+ };
140
+ } else if (property) result[key] = property;
58
141
  });
59
- });
60
- LabelWithIcon.displayName = "LabelWithIcon";
142
+ propertyKeys.forEach((key) => {
143
+ if (!result[key]) {
144
+ const property = properties[key];
145
+ if (typeof property === "object" && property.type === "map" && property.properties) {
146
+ const mapProp = property;
147
+ result[key] = {
148
+ ...mapProp,
149
+ properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
150
+ };
151
+ } else if (property) result[key] = property;
152
+ }
153
+ });
154
+ return result;
155
+ }
156
+ function getDefaultPropertiesOrder(collection) {
157
+ if (collection.propertiesOrder) return collection.propertiesOrder;
158
+ return [...Object.keys(collection.properties), ...(collection.additionalFields ?? []).map((field) => field.key)];
159
+ }
61
160
  //#endregion
62
161
  //#region src/components/PropertyIdCopyTooltip.tsx
63
162
  function PropertyIdCopyTooltip({ propertyKey, className, children }) {
@@ -98,6 +197,289 @@ function PropertyIdCopyTooltipContent({ propertyKey }) {
98
197
  });
99
198
  }
100
199
  //#endregion
200
+ //#region src/form/components/FieldBlock.tsx
201
+ /**
202
+ * Does this property's editor supply its own heading?
203
+ *
204
+ * Arrays, maps, `oneOf` blocks and key-value editors render as a collapsible
205
+ * panel whose header *is* the label — hiding it would leave a panel you cannot
206
+ * identify or, worse, cannot expand. Those keep their own; everything else gets
207
+ * its label from {@link FieldBlock}, which is what makes a text field and a
208
+ * select finally agree on where a label goes.
209
+ *
210
+ * Exported because the layout resolver's span rules and this rule have to stay
211
+ * in step: the self-labelling types are exactly the ones that take a full row.
212
+ */
213
+ function isSelfLabellingProperty(property) {
214
+ if (!property) return false;
215
+ switch (property.type) {
216
+ case "map": return !property.admin?.spreadChildren;
217
+ case "array": {
218
+ if (property.oneOf) return true;
219
+ if (Array.isArray(property.of) || !property.of) return true;
220
+ const of = property.of;
221
+ if (of.type === "string" && (of.enum || of.storage)) return false;
222
+ if (of.type === "number" && of.enum) return false;
223
+ return true;
224
+ }
225
+ default: return false;
226
+ }
227
+ }
228
+ /**
229
+ * One field's worth of the form grid: label, control, description.
230
+ *
231
+ * The label carries a small type icon so a number reads differently from a
232
+ * relation at a glance, and the description sits under the control where it
233
+ * annotates what you just filled in.
234
+ */
235
+ function FieldBlock({ propertyKey, property, showLabel, children }) {
236
+ const description = property?.description?.trim();
237
+ const required = Boolean(property?.validation?.required);
238
+ return /* @__PURE__ */ jsxs("div", {
239
+ id: `form_field_${propertyKey}`,
240
+ className: "relative flex flex-col min-w-0",
241
+ children: [
242
+ showLabel && /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
243
+ propertyKey,
244
+ children: /* @__PURE__ */ jsxs("div", {
245
+ className: cls("flex items-center gap-1.5 text-sm font-medium leading-tight mb-1.5", "text-text-secondary dark:text-text-secondary-dark"),
246
+ children: [
247
+ property && /* @__PURE__ */ jsx("span", {
248
+ className: "shrink-0 text-text-disabled dark:text-text-disabled-dark",
249
+ children: getIconForProperty(property, "smallest")
250
+ }),
251
+ /* @__PURE__ */ jsx("span", {
252
+ className: "truncate",
253
+ children: property?.name ?? propertyKey
254
+ }),
255
+ required && /* @__PURE__ */ jsx("span", {
256
+ className: "text-red-500 dark:text-red-500 -ml-1",
257
+ children: "*"
258
+ })
259
+ ]
260
+ })
261
+ }),
262
+ /* @__PURE__ */ jsx("div", {
263
+ className: "min-w-0",
264
+ children: /* @__PURE__ */ jsx(ErrorBoundary, { children })
265
+ }),
266
+ showLabel && description && /* @__PURE__ */ jsx(Typography, {
267
+ variant: "caption",
268
+ color: "disabled",
269
+ className: "mt-1.5 ml-0.5 leading-snug",
270
+ children: description
271
+ })
272
+ ]
273
+ });
274
+ }
275
+ //#endregion
276
+ //#region src/form/components/FormRail.tsx
277
+ /**
278
+ * The metadata column beside the form.
279
+ *
280
+ * This replaces a `w-80 2xl:w-96` rail that held a Save button and several
281
+ * hundred pixels of nothing. It renders only when it has something to say, and
282
+ * the actions it used to hold now live in the identity bar, where they stay
283
+ * visible while you scroll.
284
+ */
285
+ function FormRail({ fields, showRecordMeta, entity, renderField }) {
286
+ if (!fields.length && !showRecordMeta) return null;
287
+ return /* @__PURE__ */ jsxs("aside", {
288
+ className: cls("flex flex-col gap-6 shrink-0 w-72 border-l overflow-y-auto", "px-5 py-6 bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
289
+ children: [fields.length > 0 && /* @__PURE__ */ jsx("div", {
290
+ className: "flex flex-col gap-5",
291
+ children: fields.map(renderField)
292
+ }), showRecordMeta && entity && /* @__PURE__ */ jsx(RecordMeta, { entity })]
293
+ });
294
+ }
295
+ function RecordMeta({ entity }) {
296
+ const createdAt = pickDate(entity.values, [
297
+ "created_at",
298
+ "createdAt",
299
+ "created_on"
300
+ ]);
301
+ const updatedAt = pickDate(entity.values, [
302
+ "updated_at",
303
+ "updatedAt",
304
+ "modified_at"
305
+ ]);
306
+ return /* @__PURE__ */ jsxs("div", {
307
+ className: "flex flex-col gap-2.5",
308
+ children: [
309
+ /* @__PURE__ */ jsx(Typography, {
310
+ variant: "caption",
311
+ className: "text-xs font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
312
+ children: "Record"
313
+ }),
314
+ /* @__PURE__ */ jsx(MetaRow, {
315
+ label: "ID",
316
+ value: String(entity.id),
317
+ copyable: true
318
+ }),
319
+ createdAt && /* @__PURE__ */ jsx(MetaRow, {
320
+ label: "Created",
321
+ value: createdAt
322
+ }),
323
+ updatedAt && /* @__PURE__ */ jsx(MetaRow, {
324
+ label: "Updated",
325
+ value: updatedAt
326
+ })
327
+ ]
328
+ });
329
+ }
330
+ function MetaRow({ label, value, copyable }) {
331
+ const [copied, setCopied] = React.useState(false);
332
+ const copy = () => {
333
+ navigator.clipboard?.writeText(value).then(() => {
334
+ setCopied(true);
335
+ setTimeout(() => setCopied(false), 1600);
336
+ }, () => void 0);
337
+ };
338
+ return /* @__PURE__ */ jsxs("div", {
339
+ className: "flex items-baseline justify-between gap-3 min-w-0",
340
+ children: [/* @__PURE__ */ jsx("span", {
341
+ className: "text-xs text-text-disabled dark:text-text-disabled-dark shrink-0",
342
+ children: label
343
+ }), /* @__PURE__ */ jsxs("span", {
344
+ className: "flex items-center gap-1 min-w-0",
345
+ children: [/* @__PURE__ */ jsx("span", {
346
+ className: "font-mono text-xs text-text-secondary dark:text-text-secondary-dark truncate",
347
+ title: value,
348
+ children: value
349
+ }), copyable && /* @__PURE__ */ jsx(Tooltip, {
350
+ title: copied ? "Copied" : "Copy",
351
+ children: /* @__PURE__ */ jsx(IconButton, {
352
+ size: "smallest",
353
+ onClick: copy,
354
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(CopyIcon, { size: iconSize.smallest })
355
+ })
356
+ })]
357
+ })]
358
+ });
359
+ }
360
+ /**
361
+ * Audit timestamps are a convention, not a contract — a collection may name them
362
+ * anything or not have them at all — so this reads the values it recognises and
363
+ * stays quiet otherwise rather than rendering an empty row.
364
+ */
365
+ function pickDate(values, keys) {
366
+ if (!values) return void 0;
367
+ for (const key of keys) {
368
+ const value = values[key];
369
+ if (value instanceof Date) return value.toLocaleDateString();
370
+ if (typeof value === "string" && value) {
371
+ const parsed = new Date(value);
372
+ if (!Number.isNaN(parsed.getTime())) return parsed.toLocaleDateString();
373
+ }
374
+ }
375
+ }
376
+ //#endregion
377
+ //#region src/form/components/FormSections.tsx
378
+ function FormSections({ sections, collapsed, onToggle, errorKeys, renderField }) {
379
+ return /* @__PURE__ */ jsx("div", {
380
+ className: "flex flex-col gap-8",
381
+ children: sections.map((section) => {
382
+ const hasError = section.fields.some((f) => errorKeys.has(f.key));
383
+ const isCollapsed = section.collapsible && collapsed.has(section.key) && !hasError;
384
+ return /* @__PURE__ */ jsxs("section", {
385
+ className: "min-w-0",
386
+ children: [section.title && /* @__PURE__ */ jsx(Header, {
387
+ title: section.title,
388
+ collapsible: section.collapsible,
389
+ collapsed: isCollapsed,
390
+ controls: `form_section_${section.key}`,
391
+ onToggle: () => onToggle(section.key)
392
+ }), !isCollapsed && /* @__PURE__ */ jsx("div", {
393
+ id: `form_section_${section.key}`,
394
+ className: cls("grid gap-x-4 gap-y-5 min-w-0", "grid-cols-1 @2xl:grid-cols-4"),
395
+ children: section.fields.map(renderField)
396
+ })]
397
+ }, section.key);
398
+ })
399
+ });
400
+ }
401
+ function Header({ title, collapsible, collapsed, controls, onToggle }) {
402
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
403
+ collapsible && /* @__PURE__ */ jsx(ChevronDownIcon, {
404
+ size: iconSize.smallest,
405
+ className: cls("text-text-disabled dark:text-text-disabled-dark transition-transform duration-150", collapsed && "-rotate-90")
406
+ }),
407
+ /* @__PURE__ */ jsx("span", {
408
+ className: "text-xs font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark whitespace-nowrap",
409
+ children: title
410
+ }),
411
+ /* @__PURE__ */ jsx("span", { className: cls("flex-1 border-t", defaultBorderMixin) })
412
+ ] });
413
+ const className = "flex items-center gap-2.5 w-full mb-3.5";
414
+ if (!collapsible) return /* @__PURE__ */ jsx("div", {
415
+ className,
416
+ children: content
417
+ });
418
+ return /* @__PURE__ */ jsx("button", {
419
+ type: "button",
420
+ onClick: onToggle,
421
+ "aria-expanded": !collapsed,
422
+ "aria-controls": collapsed ? void 0 : controls,
423
+ className: cls(className, "text-left hover:opacity-80 transition-opacity"),
424
+ children: content
425
+ });
426
+ }
427
+ //#endregion
428
+ //#region src/form/components/useRailVisible.tsx
429
+ /**
430
+ * Below this the form is a single column and the rail is not shown.
431
+ *
432
+ * Roughly the 288px rail plus a content column still wide enough to hold a
433
+ * four-up row. Above it the rail is worth having even in the split view; below
434
+ * it — a narrow split pane, the side panel, the dialog, a phone — its fields
435
+ * fold back into the column.
436
+ */
437
+ var RAIL_MIN_WIDTH = 1e3;
438
+ /**
439
+ * Is there room beside the form for the metadata rail?
440
+ *
441
+ * Measured on the form element rather than the viewport, because the same form
442
+ * renders in a side panel and a dialog inside a wide window — a media query
443
+ * would say "desktop" and hand a 600px panel a rail it cannot fit.
444
+ *
445
+ * This has to be a real measurement rather than a CSS `hidden` class: the
446
+ * layout resolver *moves* fields into the rail, so hiding it with CSS did not
447
+ * fold them back into the column, it made them unreachable. Status, category
448
+ * and any other rail field simply could not be edited in the side panel, the
449
+ * dialog, or on a phone.
450
+ */
451
+ function useRailVisible(ref) {
452
+ const [visible, setVisible] = useState(false);
453
+ useLayoutEffect(() => {
454
+ const element = ref.current;
455
+ if (!element) return;
456
+ const measure = () => setVisible(element.getBoundingClientRect().width >= RAIL_MIN_WIDTH);
457
+ measure();
458
+ if (typeof ResizeObserver === "undefined") return;
459
+ const observer = new ResizeObserver(measure);
460
+ observer.observe(element);
461
+ return () => observer.disconnect();
462
+ }, [ref]);
463
+ return visible;
464
+ }
465
+ //#endregion
466
+ //#region src/form/components/LabelWithIcon.tsx
467
+ /**
468
+ * Render the label of with an icon and the title of a property
469
+ * @group Form custom fields
470
+ */
471
+ var LabelWithIcon = forwardRef(({ icon, title, small, className, required }, ref) => {
472
+ return /* @__PURE__ */ jsxs("div", {
473
+ ref,
474
+ className: cls("align-middle inline-flex items-center my-0.5", small ? "gap-1" : "gap-2", className),
475
+ children: [icon, /* @__PURE__ */ jsx("span", {
476
+ className: `text-start font-medium text-${small ? "base" : "sm"} origin-top-left transform ${small ? "translate-x-2 scale-75" : ""}`,
477
+ children: (title ?? "") + (required ? " *" : "")
478
+ })]
479
+ });
480
+ });
481
+ LabelWithIcon.displayName = "LabelWithIcon";
482
+ //#endregion
101
483
  //#region src/form/components/LabelWithIconAndTooltip.tsx
102
484
  /**
103
485
  * Render the label of with an icon and the title of a property
@@ -576,174 +958,40 @@ function StringPropertyPreview({ propertyKey, value, property, size, textOnly })
576
958
  }) : /* @__PURE__ */ jsx(Fragment, { children: strValue });
577
959
  }
578
960
  }
579
- //#endregion
580
- //#region src/preview/property_previews/ArrayPropertyPreview.tsx
581
- /**
582
- * @group Preview components
583
- */
584
- function ArrayPropertyPreview({ propertyKey, value, property, size }) {
585
- if (property.type !== "array") throw Error("Picked wrong preview component ArrayPreview");
586
- if (!property.of) throw Error(`You need to specify an 'of' prop (or specify a custom field) in your array property ${propertyKey}`);
587
- const authController = useAuthController();
588
- const resolvedProperties = resolveArrayProperties({
589
- propertyKey,
590
- property,
591
- propertyConfigs: useCustomizationController().propertyConfigs,
592
- authController
593
- });
594
- const values = value;
595
- if (!values) return null;
596
- const childSize = size === "medium" ? "medium" : "small";
597
- return /* @__PURE__ */ jsx("div", {
598
- className: "w-full flex flex-col gap-2",
599
- children: values && values.map((val, index) => {
600
- if (!resolvedProperties) throw Error("Property resolvedProperties is undefined");
601
- const of = resolvedProperties[index] ?? resolvedProperties[index] ?? (Array.isArray(property.of) ? property.of[index] : property.of);
602
- return of ? /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx("div", {
603
- className: cls(defaultBorderMixin, "m-1 border-b last:border-b-0"),
604
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
605
- propertyKey,
606
- value: val,
607
- property: of,
608
- size: childSize
609
- }) })
610
- }) }, "preview_array_" + index) : null;
611
- })
612
- });
613
- }
614
- //#endregion
615
- //#region src/util/property_utils.tsx
616
- /**
617
- * Resolve a size value (string token or number) to a numeric pixel value
618
- * suitable for passing to Lucide icons.
619
- */
620
- function resolveSize(size) {
621
- if (typeof size === "number") return size;
622
- return iconSize[size];
623
- }
624
- function isReferenceProperty(property) {
625
- if (!property) return null;
626
- if (property.type === "reference") return true;
627
- if (property.type === "array") if (Array.isArray(property.of)) return false;
628
- else return property.of?.type === "reference";
629
- return false;
630
- }
631
- function isRelationProperty(property) {
632
- if (!property) return null;
633
- if (property.type === "relation") return true;
634
- if (property.type === "array") if (Array.isArray(property.of)) return false;
635
- else return property.of?.type === "relation";
636
- return false;
637
- }
638
- function getIconForWidget(widget, size) {
639
- return /* @__PURE__ */ jsx(widget?.Icon ?? CircleIcon, { size: resolveSize(size) });
640
- }
641
- /**
642
- * Returns a default Lucide icon component based on property type.
643
- * This provides a sensible fallback when no PropertyConfig is available.
644
- */
645
- function getDefaultIconForProperty(property) {
646
- switch (property.type) {
647
- case "string":
648
- if (property.storage) return UploadIcon;
649
- if (property.admin?.urlPreview) return GlobeIcon;
650
- if (property.email) return MailIcon;
651
- if (property.admin?.multiline || property.admin?.markdown) return AlignLeftIcon;
652
- return TextIcon;
653
- case "number": return HashIcon;
654
- case "boolean": return FlagIcon;
655
- case "date": return CalendarIcon;
656
- case "map": return VoteIcon;
657
- case "array": {
658
- const of = property.of;
659
- if (property.oneOf) return Rows3Icon;
660
- if (of && !Array.isArray(of)) {
661
- if (of.type === "reference") return LinkIcon;
662
- if (of.type === "string" && of.storage) return UploadIcon;
663
- }
664
- return RepeatIcon;
665
- }
666
- case "reference": return LinkIcon;
667
- case "relation": return LinkIcon;
668
- case "vector": return HashIcon;
669
- default: return CircleIcon;
670
- }
671
- }
672
- function getIconForProperty(property, size = "small", fields = {}) {
673
- const px = resolveSize(size);
674
- if (isPropertyBuilder(property)) return /* @__PURE__ */ jsx(FunctionSquareIcon, { size: px });
675
- const configId = property.propertyConfig || void 0;
676
- const widget = configId ? fields[configId] : void 0;
677
- if (widget?.Icon) return getIconForWidget(widget, size);
678
- return /* @__PURE__ */ jsx(getDefaultIconForProperty(property), { size: px });
679
- }
680
- /**
681
- * Get a property in a property tree from a path like
682
- * `address.street`
683
- * @param properties
684
- * @param path
685
- */
686
- function getPropertyInPath(properties, path) {
687
- if (typeof properties === "object") {
688
- if (path in properties) return properties[path];
689
- if (path.includes(".")) {
690
- const pathSegments = path.split(".");
691
- const childProperty = properties[pathSegments[0]];
692
- if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
693
- }
694
- }
695
- }
696
- function getResolvedPropertyInPath(properties, path) {
697
- if (typeof properties === "object") {
698
- if (path in properties) return properties[path];
699
- if (path.includes(".")) {
700
- const pathSegments = path.split(".");
701
- const childProperty = properties[pathSegments[0]];
702
- if (childProperty.type === "map" && childProperty.properties) return getResolvedPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
703
- }
704
- }
705
- }
706
- function getBracketNotation(path) {
707
- return path.replace(/\.([^.]*)/g, "[$1]");
708
- }
961
+ //#endregion
962
+ //#region src/preview/property_previews/ArrayPropertyPreview.tsx
709
963
  /**
710
- * Get properties sorted by their order, but include ALL properties.
711
- * Nested keys (like "data.mode") in propertiesOrder are ignored - they're for column ordering.
712
- * @param properties
713
- * @param propertiesOrder
964
+ * @group Preview components
714
965
  */
715
- function getPropertiesWithPropertiesOrder(properties, propertiesOrder) {
716
- if (!propertiesOrder) return properties;
717
- const propertyKeys = Object.keys(properties);
718
- const validOrderKeys = propertiesOrder.filter((key) => !key.includes(".") && properties[key]);
719
- const result = {};
720
- validOrderKeys.forEach((key) => {
721
- const property = properties[key];
722
- if (typeof property === "object" && property.type === "map" && property.properties) {
723
- const mapProp = property;
724
- result[key] = {
725
- ...mapProp,
726
- properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
727
- };
728
- } else if (property) result[key] = property;
966
+ function ArrayPropertyPreview({ propertyKey, value, property, size }) {
967
+ if (property.type !== "array") throw Error("Picked wrong preview component ArrayPreview");
968
+ if (!property.of) throw Error(`You need to specify an 'of' prop (or specify a custom field) in your array property ${propertyKey}`);
969
+ const authController = useAuthController();
970
+ const resolvedProperties = resolveArrayProperties({
971
+ propertyKey,
972
+ property,
973
+ propertyConfigs: useCustomizationController().propertyConfigs,
974
+ authController
729
975
  });
730
- propertyKeys.forEach((key) => {
731
- if (!result[key]) {
732
- const property = properties[key];
733
- if (typeof property === "object" && property.type === "map" && property.properties) {
734
- const mapProp = property;
735
- result[key] = {
736
- ...mapProp,
737
- properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
738
- };
739
- } else if (property) result[key] = property;
740
- }
976
+ const values = value;
977
+ if (!values) return null;
978
+ const childSize = size === "medium" ? "medium" : "small";
979
+ return /* @__PURE__ */ jsx("div", {
980
+ className: "w-full flex flex-col gap-2",
981
+ children: values && values.map((val, index) => {
982
+ if (!resolvedProperties) throw Error("Property resolvedProperties is undefined");
983
+ const of = resolvedProperties[index] ?? resolvedProperties[index] ?? (Array.isArray(property.of) ? property.of[index] : property.of);
984
+ return of ? /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx("div", {
985
+ className: cls(defaultBorderMixin, "m-1 border-b last:border-b-0"),
986
+ children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
987
+ propertyKey,
988
+ value: val,
989
+ property: of,
990
+ size: childSize
991
+ }) })
992
+ }) }, "preview_array_" + index) : null;
993
+ })
741
994
  });
742
- return result;
743
- }
744
- function getDefaultPropertiesOrder(collection) {
745
- if (collection.propertiesOrder) return collection.propertiesOrder;
746
- return [...Object.keys(collection.properties), ...(collection.additionalFields ?? []).map((field) => field.key)];
747
995
  }
748
996
  //#endregion
749
997
  //#region src/util/previews.ts
@@ -875,6 +1123,43 @@ var SidePanelControllerContext = React.createContext({});
875
1123
  */
876
1124
  var useSidePanel = () => useContext(SidePanelControllerContext);
877
1125
  //#endregion
1126
+ //#region src/components/EntityPreviewNesting.tsx
1127
+ /**
1128
+ * How deep the current entity preview is nested inside another one.
1129
+ *
1130
+ * A reference or relation rendered on its own (a form field, a table cell) is
1131
+ * at depth 0 and gets the full card treatment: image slot, id, title, preview
1132
+ * properties and a link to the side panel. A reference rendered *inside*
1133
+ * another entity preview is at depth 1+, where repeating that card produces a
1134
+ * box-inside-a-box with its own borders, id line and action button. Nested
1135
+ * previews collapse to a compact chip instead.
1136
+ */
1137
+ var EntityPreviewDepthContext = React$1.createContext(0);
1138
+ /**
1139
+ * Current entity preview nesting depth. 0 means "not inside another preview".
1140
+ */
1141
+ function useEntityPreviewDepth() {
1142
+ return React$1.useContext(EntityPreviewDepthContext);
1143
+ }
1144
+ /**
1145
+ * True when the preview being rendered lives inside another entity preview,
1146
+ * and should therefore render its compact variant.
1147
+ */
1148
+ function useIsNestedEntityPreview() {
1149
+ return useEntityPreviewDepth() > 0;
1150
+ }
1151
+ /**
1152
+ * Marks its children as being rendered inside an entity preview, so any
1153
+ * reference or relation below collapses to a chip.
1154
+ */
1155
+ function NestedEntityPreviewBoundary({ children }) {
1156
+ const depth = useEntityPreviewDepth();
1157
+ return /* @__PURE__ */ jsx(EntityPreviewDepthContext.Provider, {
1158
+ value: depth + 1,
1159
+ children
1160
+ });
1161
+ }
1162
+ //#endregion
878
1163
  //#region src/components/EntityPreviewBinding.tsx
879
1164
  /**
880
1165
  * This component contains the main logic and content for displaying a entity preview,
@@ -906,7 +1191,7 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
906
1191
  const restProperties = listProperties.filter((p) => p !== titleProperty && p !== imagePropertyKey);
907
1192
  const imageValue = imagePropertyKey ? getValueInPath(entity.values, imagePropertyKey) : void 0;
908
1193
  const usedImageValue = imageProperty !== void 0 ? "of" in imageProperty ? (imageValue ?? []).length > 0 ? imageValue[0] : void 0 : imageValue : void 0;
909
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1194
+ return /* @__PURE__ */ jsxs(NestedEntityPreviewBoundary, { children: [
910
1195
  /* @__PURE__ */ jsxs("div", {
911
1196
  className: cls("flex shrink-0", {
912
1197
  "w-6 h-6 mx-1 my-0.5": size === "small" || size === "smallest",
@@ -1050,15 +1335,118 @@ var EntityPreviewContainer = React$1.forwardRef(({ children, hover, onClick, siz
1050
1335
  });
1051
1336
  EntityPreviewContainer.displayName = "EntityPreviewContainer";
1052
1337
  //#endregion
1338
+ //#region src/components/InlineEntityPreview.tsx
1339
+ var inlineMixin = "inline-flex items-center gap-1 min-w-0 max-w-full align-middle";
1340
+ /**
1341
+ * An entity rendered as one line of text: a small link glyph that marks it as
1342
+ * pointing elsewhere, then the target's title in whatever typography surrounds
1343
+ * it.
1344
+ *
1345
+ * This is what a reference or relation renders as when it is *not* the subject
1346
+ * of its slot — nested inside another entity preview, or filling a title slot.
1347
+ * The card treatment (image, id, preview properties, side-panel button) only
1348
+ * makes sense at the top level; repeating it one level down produces a bordered
1349
+ * box inside a bordered box, each with its own action buttons.
1350
+ *
1351
+ * @group Preview components
1352
+ */
1353
+ function InlineEntityPreview({ entity, collection: collectionProp, label, disabled, onClick, includeEntityLink = true, iconSize = 12, className }) {
1354
+ const collectionRegistryController = useCollectionRegistryController();
1355
+ const analyticsController = useAnalyticsController();
1356
+ const sidePanelController = useSidePanel();
1357
+ const collection = collectionProp ?? collectionRegistryController.getCollection(entity.path);
1358
+ const titlePropertyKey = collection ? getEntityTitlePropertyKeyForEntity(collection, entity.values, entity.id) : void 0;
1359
+ const titleValue = titlePropertyKey ? getValueInPath(entity.values, titlePropertyKey) : void 0;
1360
+ const resolvedLabel = label ?? (titleValue !== void 0 && titleValue !== null && String(titleValue).trim().length > 0 ? String(titleValue) : String(entity.id));
1361
+ const interactive = !disabled && (onClick !== void 0 || includeEntityLink && collection !== void 0);
1362
+ const handleClick = (event) => {
1363
+ if (!interactive) return;
1364
+ event.preventDefault();
1365
+ event.stopPropagation();
1366
+ if (onClick) {
1367
+ onClick(event);
1368
+ return;
1369
+ }
1370
+ analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
1371
+ path: entity.path,
1372
+ entityId: entity.id
1373
+ });
1374
+ sidePanelController.open({
1375
+ entityId: entity.id,
1376
+ path: entity.path,
1377
+ collection,
1378
+ updateUrl: true
1379
+ });
1380
+ };
1381
+ const content = /* @__PURE__ */ jsxs("span", {
1382
+ tabIndex: interactive ? 0 : void 0,
1383
+ role: interactive ? "button" : void 0,
1384
+ onClick: interactive ? handleClick : void 0,
1385
+ onKeyDown: interactive ? (event) => {
1386
+ if (event.key === "Enter" || event.key === " ") handleClick(event);
1387
+ } : void 0,
1388
+ className: cls(inlineMixin, interactive ? "cursor-pointer hover:underline underline-offset-2" : "", className),
1389
+ children: [/* @__PURE__ */ jsx(LinkIcon, {
1390
+ size: iconSize,
1391
+ className: "shrink-0 opacity-40"
1392
+ }), /* @__PURE__ */ jsx("span", {
1393
+ className: "truncate",
1394
+ children: resolvedLabel
1395
+ })]
1396
+ });
1397
+ if (!interactive) return content;
1398
+ return /* @__PURE__ */ jsx(Tooltip, {
1399
+ title: `See details for ${resolvedLabel}`,
1400
+ asChild: true,
1401
+ children: content
1402
+ });
1403
+ }
1404
+ /**
1405
+ * Placeholder shown while the target of an inline reference is being fetched.
1406
+ */
1407
+ function InlineEntityPreviewSkeleton({ className }) {
1408
+ return /* @__PURE__ */ jsxs("span", {
1409
+ className: cls(inlineMixin, className),
1410
+ children: [/* @__PURE__ */ jsx(LinkIcon, {
1411
+ size: 12,
1412
+ className: "shrink-0 opacity-40"
1413
+ }), /* @__PURE__ */ jsx(Skeleton, { className: "w-20 h-3" })]
1414
+ });
1415
+ }
1416
+ /**
1417
+ * Shown when the target of an inline reference cannot be resolved — a deleted
1418
+ * row, or a collection that is not registered. Keeps the line readable instead
1419
+ * of dropping an error box into the middle of a card.
1420
+ */
1421
+ function InlineEntityPreviewMissing({ label, tooltip, className }) {
1422
+ const content = /* @__PURE__ */ jsxs("span", {
1423
+ className: cls(inlineMixin, "text-text-secondary dark:text-text-secondary-dark", className),
1424
+ children: [/* @__PURE__ */ jsx(LinkIcon, {
1425
+ size: 12,
1426
+ className: "shrink-0 opacity-40"
1427
+ }), /* @__PURE__ */ jsx("span", {
1428
+ className: "truncate italic",
1429
+ children: label
1430
+ })]
1431
+ });
1432
+ if (!tooltip) return content;
1433
+ return /* @__PURE__ */ jsx(Tooltip, {
1434
+ title: tooltip,
1435
+ asChild: true,
1436
+ children: content
1437
+ });
1438
+ }
1439
+ //#endregion
1053
1440
  //#region src/preview/components/ReferencePreview.tsx
1054
1441
  /**
1055
1442
  * @group Preview components
1056
1443
  */
1057
1444
  var ReferencePreview = function ReferencePreview(props) {
1058
1445
  const reference = props.reference;
1446
+ const nested = useIsNestedEntityPreview();
1059
1447
  if (!(typeof reference === "object" && "isEntityReference" in reference && reference.isEntityReference())) {
1060
1448
  console.warn("Reference preview received value of type", typeof reference);
1061
- if (props.textOnly) return /* @__PURE__ */ jsx("span", { children: String(reference) });
1449
+ if (props.textOnly || nested) return /* @__PURE__ */ jsx("span", { children: String(reference) });
1062
1450
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1063
1451
  onClick: props.onClick,
1064
1452
  size: props.size ?? "medium",
@@ -1073,9 +1461,14 @@ var ReferencePreview = function ReferencePreview(props) {
1073
1461
  var DefaultMissingReference$2 = () => null;
1074
1462
  function ReferencePreviewInternalInner({ disabled, reference, previewProperties, size, hover, onClick, includeEntityLink = true, includeId = true, textOnly, collection }) {
1075
1463
  const ResolvedMissingReference = useComponentOverride("Entity.MissingReference", DefaultMissingReference$2);
1464
+ const nested = useIsNestedEntityPreview();
1076
1465
  if (!collection) if (ResolvedMissingReference !== DefaultMissingReference$2) return /* @__PURE__ */ jsx(ResolvedMissingReference, { path: reference.path });
1077
1466
  else {
1078
1467
  if (textOnly) return /* @__PURE__ */ jsx("span", { children: reference.path });
1468
+ if (nested) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
1469
+ label: String(reference.id),
1470
+ tooltip: `Collection not found: ${reference.path}`
1471
+ });
1079
1472
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1080
1473
  onClick,
1081
1474
  size: size ?? "medium",
@@ -1113,6 +1506,7 @@ function ReferencePreviewInternal(props) {
1113
1506
  function ReferencePreviewExisting({ reference, collection, previewProperties, size, disabled, includeEntityLink, includeId, onClick, hover, textOnly }) {
1114
1507
  const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
1115
1508
  useCustomizationController();
1509
+ const inline = useIsNestedEntityPreview() || Boolean(textOnly);
1116
1510
  const { entity, dataLoading, dataLoadingError } = useFetch({
1117
1511
  path: reference.path,
1118
1512
  entityId: reference.id,
@@ -1128,7 +1522,10 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
1128
1522
  tooltip: reference.path
1129
1523
  });
1130
1524
  if (body) {
1131
- if (textOnly) return /* @__PURE__ */ jsx("span", { children: reference.id });
1525
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
1526
+ label: String(reference.id),
1527
+ tooltip: reference.pathWithId
1528
+ });
1132
1529
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1133
1530
  onClick: disabled ? void 0 : onClick,
1134
1531
  hover: disabled ? void 0 : hover,
@@ -1137,7 +1534,7 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
1137
1534
  });
1138
1535
  }
1139
1536
  if (dataLoading && !usedEntity) {
1140
- if (textOnly) return /* @__PURE__ */ jsx(Skeleton, { className: "inline-block w-20 h-4" });
1537
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewSkeleton, {});
1141
1538
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1142
1539
  onClick: disabled ? void 0 : onClick,
1143
1540
  hover: disabled ? void 0 : hover,
@@ -1146,7 +1543,10 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
1146
1543
  });
1147
1544
  }
1148
1545
  if (!usedEntity) {
1149
- if (textOnly) return /* @__PURE__ */ jsx("span", { children: reference.id });
1546
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
1547
+ label: String(reference.id),
1548
+ tooltip: "Entity not found"
1549
+ });
1150
1550
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1151
1551
  onClick: disabled ? void 0 : onClick,
1152
1552
  hover: disabled ? void 0 : hover,
@@ -1154,14 +1554,13 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
1154
1554
  children: /* @__PURE__ */ jsx(ErrorView, { error: "Entity not found" })
1155
1555
  });
1156
1556
  }
1157
- if (textOnly) {
1158
- const titleProperty = getEntityTitlePropertyKeyForEntity(collection, usedEntity.values, usedEntity.id);
1159
- const titleValue = titleProperty ? getValueInPath(usedEntity.values, titleProperty) : void 0;
1160
- return /* @__PURE__ */ jsx("span", {
1161
- className: "truncate",
1162
- children: titleValue !== void 0 && titleValue !== null ? String(titleValue) : String(reference.id)
1163
- });
1164
- }
1557
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreview, {
1558
+ entity: usedEntity,
1559
+ collection,
1560
+ disabled,
1561
+ onClick,
1562
+ includeEntityLink: !textOnly && includeEntityLink !== false
1563
+ });
1165
1564
  return /* @__PURE__ */ jsx(ResolvedEntityPreview, {
1166
1565
  size,
1167
1566
  previewKeys: previewProperties,
@@ -1176,14 +1575,53 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
1176
1575
  }
1177
1576
  var referencesCache = /* @__PURE__ */ new Map();
1178
1577
  //#endregion
1578
+ //#region src/preview/components/InlineEntityListPreview.tsx
1579
+ /**
1580
+ * How many links a nested list shows before collapsing the rest into a count.
1581
+ */
1582
+ var MAX_INLINE_ITEMS = 3;
1583
+ /**
1584
+ * One wrapping line of inline entity links, with the overflow collapsed into a
1585
+ * `+N`. Used for arrays of references or relations that are nested inside
1586
+ * another preview, where stacking full-width cards turns a card into a wall.
1587
+ *
1588
+ * @group Preview components
1589
+ */
1590
+ function InlineEntityListPreview({ items, renderItem, max = MAX_INLINE_ITEMS, className }) {
1591
+ if (!items || items.length === 0) return null;
1592
+ const shown = items.slice(0, max);
1593
+ const overflow = items.length - shown.length;
1594
+ return /* @__PURE__ */ jsxs("span", {
1595
+ className: cls("inline-flex flex-wrap items-center gap-x-2 gap-y-0.5 min-w-0 max-w-full", className),
1596
+ children: [shown.map((item, index) => renderItem(item, index)), overflow > 0 && /* @__PURE__ */ jsxs("span", {
1597
+ className: "text-text-secondary dark:text-text-secondary-dark shrink-0",
1598
+ children: ["+", overflow]
1599
+ })]
1600
+ });
1601
+ }
1602
+ //#endregion
1179
1603
  //#region src/preview/property_previews/ArrayOfReferencesPreview.tsx
1180
1604
  /**
1181
1605
  * @group Preview components
1182
1606
  */
1183
- function ArrayOfReferencesPreview({ propertyKey, value, property, size }) {
1607
+ function ArrayOfReferencesPreview({ propertyKey, value, property, size, textOnly }) {
1608
+ const nested = useIsNestedEntityPreview();
1184
1609
  if (Array.isArray(property?.of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
1185
1610
  if (property?.type !== "array" || !property.of || property.of.type !== "reference") throw Error("Picked wrong preview component ArrayOfReferencesPreview");
1186
1611
  const childSize = size === "medium" ? "medium" : "small";
1612
+ const ofProperty = property.of;
1613
+ if (nested || textOnly) return /* @__PURE__ */ jsx(InlineEntityListPreview, {
1614
+ items: value ?? [],
1615
+ renderItem: (reference, index) => /* @__PURE__ */ jsx(ReferencePreview, {
1616
+ disabled: !ofProperty.path,
1617
+ previewProperties: ofProperty.admin?.previewProperties,
1618
+ size: childSize,
1619
+ reference,
1620
+ textOnly,
1621
+ includeId: ofProperty.admin?.includeId,
1622
+ includeEntityLink: ofProperty.admin?.includeEntityLink
1623
+ }, `preview_array_ref_${propertyKey}_${index}`)
1624
+ });
1187
1625
  return /* @__PURE__ */ jsx("div", {
1188
1626
  className: "flex flex-col w-full",
1189
1627
  children: value ? value.map((reference, index) => {
@@ -1248,9 +1686,10 @@ function extractDisplayFromPlainObject(obj) {
1248
1686
  */
1249
1687
  var RelationPreview = function RelationPreview(props) {
1250
1688
  const relation = props.relation;
1689
+ const nested = useIsNestedEntityPreview();
1251
1690
  if (!(typeof relation === "object" && "isEntityRelation" in relation && relation.isEntityRelation())) {
1252
1691
  console.warn("Relation preview received value of type", typeof relation);
1253
- if (props.textOnly) return /* @__PURE__ */ jsx("span", {
1692
+ if (props.textOnly || nested) return /* @__PURE__ */ jsx("span", {
1254
1693
  className: "truncate",
1255
1694
  children: extractDisplayFromPlainObject(relation)
1256
1695
  });
@@ -1268,9 +1707,14 @@ var RelationPreview = function RelationPreview(props) {
1268
1707
  var DefaultMissingReference$1 = () => null;
1269
1708
  function RelationPreviewInternalInner({ disabled, relation, previewProperties, size, hover, onClick, includeEntityLink = true, includeId = true, textOnly, collection }) {
1270
1709
  const ResolvedMissingReference = useComponentOverride("Entity.MissingReference", DefaultMissingReference$1);
1710
+ const nested = useIsNestedEntityPreview();
1271
1711
  if (!collection) if (ResolvedMissingReference !== DefaultMissingReference$1) return /* @__PURE__ */ jsx(ResolvedMissingReference, { path: relation.path });
1272
1712
  else {
1273
1713
  if (textOnly) return /* @__PURE__ */ jsx("span", { children: relation.path });
1714
+ if (nested) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
1715
+ label: String(relation.id),
1716
+ tooltip: `Collection not found: ${relation.path}`
1717
+ });
1274
1718
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1275
1719
  size,
1276
1720
  children: /* @__PURE__ */ jsx(ErrorView, { error: `Collection not found: ${relation.path}` })
@@ -1305,6 +1749,7 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
1305
1749
  const passedEntity = relation.data;
1306
1750
  const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
1307
1751
  useCustomizationController();
1752
+ const inline = useIsNestedEntityPreview() || Boolean(textOnly);
1308
1753
  const { entity, dataLoading, dataLoadingError } = useFetch({
1309
1754
  path: relation.path,
1310
1755
  entityId: passedEntity ? void 0 : relation.id,
@@ -1320,7 +1765,10 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
1320
1765
  tooltip: relation.path
1321
1766
  });
1322
1767
  if (body) {
1323
- if (textOnly) return /* @__PURE__ */ jsx("span", { children: relation.id });
1768
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
1769
+ label: String(relation.id),
1770
+ tooltip: relation.pathWithId
1771
+ });
1324
1772
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1325
1773
  onClick: disabled ? void 0 : onClick,
1326
1774
  hover: disabled ? void 0 : hover,
@@ -1329,7 +1777,7 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
1329
1777
  });
1330
1778
  }
1331
1779
  if (dataLoading && !usedEntity) {
1332
- if (textOnly) return /* @__PURE__ */ jsx(Skeleton, { className: "inline-block w-20 h-4" });
1780
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewSkeleton, {});
1333
1781
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1334
1782
  onClick: disabled ? void 0 : onClick,
1335
1783
  hover: disabled ? void 0 : hover,
@@ -1338,7 +1786,10 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
1338
1786
  });
1339
1787
  }
1340
1788
  if (!usedEntity) {
1341
- if (textOnly) return /* @__PURE__ */ jsx("span", { children: relation.id });
1789
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
1790
+ label: String(relation.id),
1791
+ tooltip: "Entity not found"
1792
+ });
1342
1793
  return /* @__PURE__ */ jsx(EntityPreviewContainer, {
1343
1794
  onClick: disabled ? void 0 : onClick,
1344
1795
  hover: disabled ? void 0 : hover,
@@ -1346,14 +1797,13 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
1346
1797
  children: /* @__PURE__ */ jsx(ErrorView, { error: "Entity not found" })
1347
1798
  });
1348
1799
  }
1349
- if (textOnly) {
1350
- const titleProperty = getEntityTitlePropertyKeyForEntity(collection, usedEntity.values, usedEntity.id);
1351
- const titleValue = titleProperty ? getValueInPath(usedEntity.values, titleProperty) : void 0;
1352
- return /* @__PURE__ */ jsx("span", {
1353
- className: "truncate",
1354
- children: titleValue !== void 0 && titleValue !== null ? String(titleValue) : String(relation.id)
1355
- });
1356
- }
1800
+ if (inline) return /* @__PURE__ */ jsx(InlineEntityPreview, {
1801
+ entity: usedEntity,
1802
+ collection,
1803
+ disabled,
1804
+ onClick,
1805
+ includeEntityLink: !textOnly && includeEntityLink !== false
1806
+ });
1357
1807
  return /* @__PURE__ */ jsx(ResolvedEntityPreview, {
1358
1808
  size,
1359
1809
  previewKeys: previewProperties,
@@ -1372,10 +1822,23 @@ var relationsCache = /* @__PURE__ */ new Map();
1372
1822
  /**
1373
1823
  * @group Preview components
1374
1824
  */
1375
- function ArrayOfRelationsPreview({ propertyKey, value, property, size }) {
1825
+ function ArrayOfRelationsPreview({ propertyKey, value, property, size, textOnly }) {
1826
+ const nested = useIsNestedEntityPreview();
1376
1827
  if (Array.isArray(property?.of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
1377
1828
  if (property?.type !== "array" || !property.of || property.of.type !== "relation") throw Error("Picked wrong preview component ArrayOfRelationsPreview");
1378
1829
  const ofProperty = property.of;
1830
+ if (nested || textOnly) return /* @__PURE__ */ jsx(InlineEntityListPreview, {
1831
+ items: (value ?? []).map((relation) => normalizeToEntityRelation(relation)).filter((relation) => Boolean(relation)),
1832
+ renderItem: (relation, index) => /* @__PURE__ */ jsx(RelationPreview, {
1833
+ disabled: !ofProperty.relation,
1834
+ previewProperties: ofProperty.admin?.previewProperties,
1835
+ size: "small",
1836
+ relation,
1837
+ textOnly,
1838
+ includeId: ofProperty.admin?.includeId,
1839
+ includeEntityLink: ofProperty.admin?.includeEntityLink
1840
+ }, `preview_array_rel_${propertyKey}_${index}`)
1841
+ });
1379
1842
  return /* @__PURE__ */ jsx("div", {
1380
1843
  className: "flex flex-col w-full gap-0.5",
1381
1844
  children: value ? value.map((relation, index) => {
@@ -1696,11 +2159,11 @@ function getCache(baseUrl) {
1696
2159
  }
1697
2160
  return cache;
1698
2161
  }
1699
- async function fetchUsers(baseUrl, ids, token) {
2162
+ async function fetchUsers(apiBase, ids, token) {
1700
2163
  const resolved = /* @__PURE__ */ new Map();
1701
2164
  for (const id of ids) resolved.set(id, null);
1702
2165
  const params = new URLSearchParams({ ids: ids.join(",") });
1703
- const response = await fetch(`${baseUrl}/api/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
2166
+ const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
1704
2167
  if (!response.ok) return resolved;
1705
2168
  const data = await response.json();
1706
2169
  const rows = data.users ?? data.data ?? [];
@@ -1753,7 +2216,7 @@ function requestUser(baseUrl, uid, getAuthToken) {
1753
2216
  function useResolvedUser(uid) {
1754
2217
  const client = useRebaseClient();
1755
2218
  const { getAuthToken } = useAuthController();
1756
- const baseUrl = client?.baseUrl;
2219
+ const baseUrl = apiBaseOf(client);
1757
2220
  const [user, setUser] = useState(() => baseUrl && uid ? getCache(baseUrl).users.get(uid) ?? null : null);
1758
2221
  useEffect(() => {
1759
2222
  if (!baseUrl || !uid) {
@@ -1815,6 +2278,7 @@ function UserPreview({ value }) {
1815
2278
  var PropertyPreview = React.memo(function PropertyPreview(props) {
1816
2279
  const authController = useAuthController();
1817
2280
  const customizationController = useCustomizationController();
2281
+ const nested = useIsNestedEntityPreview();
1818
2282
  let content;
1819
2283
  const { property: inputProperty, propertyKey, value, size, height, width, interactive, fill } = props;
1820
2284
  const property = resolveProperty({
@@ -1935,12 +2399,14 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
1935
2399
  property
1936
2400
  });
1937
2401
  else content = buildWrongValueType(propertyKey, property.type, value);
1938
- else if (property.type === "date") if (value instanceof Date) content = /* @__PURE__ */ jsx(DatePreview, {
1939
- date: value,
1940
- mode: property.mode
1941
- });
1942
- else content = buildWrongValueType(propertyKey, property.type, value);
1943
- else if (property.type === "reference") if (typeof property.path === "string") if (typeof value === "object" && value !== null && "isEntityReference" in value && value.isEntityReference()) content = /* @__PURE__ */ jsx(ReferencePreview, {
2402
+ else if (property.type === "date") {
2403
+ const date = asDate(value);
2404
+ if (date) content = /* @__PURE__ */ jsx(DatePreview, {
2405
+ date,
2406
+ mode: property.mode
2407
+ });
2408
+ else content = buildWrongValueType(propertyKey, property.type, value);
2409
+ } else if (property.type === "reference") if (typeof property.path === "string") if (typeof value === "object" && value !== null && "isEntityReference" in value && value.isEntityReference()) content = /* @__PURE__ */ jsx(ReferencePreview, {
1944
2410
  disabled: !property.path,
1945
2411
  previewProperties: property.admin?.previewProperties,
1946
2412
  includeId: property.admin?.includeId,
@@ -1952,26 +2418,28 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
1952
2418
  else content = buildWrongValueType(propertyKey, property.type, value);
1953
2419
  else content = /* @__PURE__ */ jsx(EmptyValue, {});
1954
2420
  else if (property.type === "relation") if (!value) content = /* @__PURE__ */ jsx(EmptyValue, {});
1955
- else if (Array.isArray(value)) content = /* @__PURE__ */ jsx("div", {
1956
- className: "flex flex-col w-full gap-0.5",
1957
- children: value.map((item, index) => {
1958
- const entityRelation = normalizeToEntityRelation(item, "relation");
1959
- if (!entityRelation) return null;
1960
- return /* @__PURE__ */ jsx("div", {
2421
+ else if (Array.isArray(value)) {
2422
+ const relations = value.map((item) => normalizeToEntityRelation(item, "relation")).filter(Boolean);
2423
+ const renderRelation = (entityRelation, index) => /* @__PURE__ */ jsx(RelationPreview, {
2424
+ disabled: !property.relation,
2425
+ previewProperties: property.admin?.previewProperties,
2426
+ includeId: property.admin?.includeId,
2427
+ includeEntityLink: property.admin?.includeEntityLink,
2428
+ size: "small",
2429
+ relation: entityRelation,
2430
+ textOnly: props.textOnly
2431
+ }, `preview_rel_${propertyKey}_${index}`);
2432
+ content = nested || props.textOnly ? /* @__PURE__ */ jsx(InlineEntityListPreview, {
2433
+ items: relations,
2434
+ renderItem: renderRelation
2435
+ }) : /* @__PURE__ */ jsx("div", {
2436
+ className: "flex flex-col w-full gap-0.5",
2437
+ children: relations.map((entityRelation, index) => /* @__PURE__ */ jsx("div", {
1961
2438
  className: "w-full",
1962
- children: /* @__PURE__ */ jsx(RelationPreview, {
1963
- disabled: !property.relation,
1964
- previewProperties: property.admin?.previewProperties,
1965
- includeId: property.admin?.includeId,
1966
- includeEntityLink: property.admin?.includeEntityLink,
1967
- size: "small",
1968
- relation: entityRelation,
1969
- textOnly: props.textOnly
1970
- })
1971
- }, `preview_rel_${propertyKey}_${index}`);
1972
- })
1973
- });
1974
- else {
2439
+ children: renderRelation(entityRelation, index)
2440
+ }, `preview_rel_wrapper_${propertyKey}_${index}`))
2441
+ });
2442
+ } else {
1975
2443
  const relationValue = normalizeToEntityRelation(value, "relation");
1976
2444
  if (relationValue) content = /* @__PURE__ */ jsx(RelationPreview, {
1977
2445
  disabled: !property.relation,
@@ -1999,6 +2467,16 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
1999
2467
  else content = JSON.stringify(value);
2000
2468
  return content === void 0 || content === null || Array.isArray(content) && content.length === 0 ? /* @__PURE__ */ jsx(EmptyValue, {}) : content;
2001
2469
  }, deepEqual);
2470
+ /**
2471
+ * A `Date`, or a string/number that unambiguously names one. Returns null for
2472
+ * anything else, so an actual type mismatch still reports as one.
2473
+ */
2474
+ function asDate(value) {
2475
+ if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
2476
+ if (typeof value !== "string" && typeof value !== "number") return null;
2477
+ const parsed = new Date(value);
2478
+ return isNaN(parsed.getTime()) ? null : parsed;
2479
+ }
2002
2480
  function buildWrongValueType(name, type, value) {
2003
2481
  console.warn(`Unexpected value for property ${name}, of type ${type}`, value);
2004
2482
  return /* @__PURE__ */ jsx(ErrorView, {
@@ -2104,10 +2582,10 @@ function FieldHelperText({ error, showError, property, includeDescription = true
2104
2582
  * and tables to the specified properties.
2105
2583
  * @group Form fields
2106
2584
  */
2107
- function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalistView, property, includeDescription, context }) {
2585
+ function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalistView, property, includeDescription, hideLabel, context }) {
2108
2586
  const skipCardWrapper = property.type === "relation" || property.type === "reference";
2109
2587
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2110
- !minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
2588
+ !minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
2111
2589
  propertyKey,
2112
2590
  icon: getIconForProperty(property, "small"),
2113
2591
  required: property.validation?.required,
@@ -2265,8 +2743,10 @@ function VirtualTableSelect(props) {
2265
2743
  if (ref.current && focused) ref.current?.focus({ preventScroll: true });
2266
2744
  }, [focused, ref]);
2267
2745
  const onChange = useCallback((updatedValue) => {
2268
- if (valueType === "number") if (multiple) updateValue(updatedValue.map((v) => parseFloat(v)));
2269
- else updateValue(parseFloat(updatedValue));
2746
+ if (valueType === "number") if (multiple) {
2747
+ const newValue = updatedValue.map((v) => parseFloat(v));
2748
+ updateValue(newValue);
2749
+ } else updateValue(parseFloat(updatedValue));
2270
2750
  else if (valueType === "string") if (!updatedValue) updateValue(null);
2271
2751
  else updateValue(updatedValue);
2272
2752
  else throw Error("Missing mapping in TableSelect");
@@ -2344,7 +2824,8 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
2344
2824
  const offsetRef = useRef(0);
2345
2825
  const userCacheRef = useRef(/* @__PURE__ */ new Map());
2346
2826
  const fetchUsers = useCallback(async (searchStr, offset, append) => {
2347
- if (!client?.baseUrl) return;
2827
+ const apiBase = apiBaseOf(client);
2828
+ if (!apiBase) return;
2348
2829
  setIsLoading(true);
2349
2830
  try {
2350
2831
  const token = await getAuthToken?.();
@@ -2353,12 +2834,12 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
2353
2834
  offset: String(offset)
2354
2835
  });
2355
2836
  if (searchStr) params.set("search", searchStr);
2356
- const response = await fetch(`${client.baseUrl}/api/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
2837
+ const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
2357
2838
  if (!response.ok) throw new Error("Failed to fetch users");
2358
2839
  const data = await response.json();
2359
2840
  const newItems = (data.data ?? data.users ?? []).map((r) => {
2360
2841
  const user = {
2361
- uid: r.id ?? "",
2842
+ uid: r.uid ?? r.id ?? "",
2362
2843
  email: r.email ?? "",
2363
2844
  displayName: r.displayName ?? r.display_name ?? null,
2364
2845
  photoURL: r.photoURL ?? r.photo_url ?? null,
@@ -2383,6 +2864,7 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
2383
2864
  }
2384
2865
  }, [
2385
2866
  client?.baseUrl,
2867
+ client?.apiPath,
2386
2868
  getAuthToken,
2387
2869
  pageSize
2388
2870
  ]);
@@ -3811,12 +4293,13 @@ function getTableBindingForProperty(property, selected) {
3811
4293
  updateValue
3812
4294
  }) };
3813
4295
  else if (property.type === "date") return {
3814
- Component: ({ propertyKey, error, validationError, disabled, selected, property, internalValue, updateValue }) => {
4296
+ Component: ({ propertyKey, error, validationError, disabled, selected, size, property, internalValue, updateValue }) => {
3815
4297
  const { locale } = useCustomizationController();
3816
4298
  return /* @__PURE__ */ jsx(VirtualTableDateField, {
3817
4299
  name: propertyKey,
3818
4300
  error: validationError ?? error,
3819
4301
  disabled,
4302
+ small: getPreviewSizeFrom(size) !== "medium",
3820
4303
  mode: property.mode,
3821
4304
  focused: selected,
3822
4305
  internalValue,
@@ -4427,6 +4910,7 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
4427
4910
  propertyKey
4428
4911
  });
4429
4912
  };
4913
+ const tableBinding = useMemo(() => getTableBindingForProperty(property, selected), [property, selected]);
4430
4914
  let innerComponent;
4431
4915
  let allowScroll = false;
4432
4916
  let showExpandIcon = false;
@@ -4460,7 +4944,6 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
4460
4944
  })
4461
4945
  }, `${propertyKey}_${entity.path}_${entity.id}`);
4462
4946
  if (!customField && (!customPreview || selected)) {
4463
- const tableBinding = getTableBindingForProperty(property, selected);
4464
4947
  if (tableBinding) {
4465
4948
  const Component = tableBinding.Component;
4466
4949
  innerComponent = /* @__PURE__ */ jsx(Component, {
@@ -4828,7 +5311,7 @@ function propertiesToColumns({ properties, sortable, fixedFilter, engine, Additi
4828
5311
  align: getTableCellAlignment(property),
4829
5312
  icon: getIconForProperty(property, "small"),
4830
5313
  title: property.name ?? key,
4831
- sortable,
5314
+ sortable: sortable && sortableProperty(property),
4832
5315
  filter: !disabledFilter && filterable,
4833
5316
  width: getTablePropertyColumnWidth(property),
4834
5317
  resizable: true,
@@ -4844,6 +5327,27 @@ function propertiesToColumns({ properties, sortable, fixedFilter, engine, Additi
4844
5327
  };
4845
5328
  });
4846
5329
  }
5330
+ /**
5331
+ * Whether this column can be ordered by.
5332
+ *
5333
+ * `sortable` arrived as one flag for the whole table while `filter` was
5334
+ * computed per property, so every column advertised a sort — including the ones
5335
+ * no `ORDER BY` can be written for. A to-many relation is a *set* per row
5336
+ * (`posts.tags`), and there is no single value to order on; the driver used to
5337
+ * answer by silently dropping the sort and returning rows in whatever order
5338
+ * Postgres pleased, which is how a header that did nothing went unnoticed.
5339
+ *
5340
+ * Now that the driver refuses instead, offering the control would turn a
5341
+ * useless header into a failed request. Same authority as the filter side: ask
5342
+ * the property, not the table.
5343
+ */
5344
+ function sortableProperty(property) {
5345
+ if (property.type !== "relation") return true;
5346
+ const relationProperty = property;
5347
+ const kind = relationProperty.relation?.kind ?? relationProperty.resolvedRelation?.kind;
5348
+ if (!kind) return true;
5349
+ return kind === "belongsTo";
5350
+ }
4847
5351
  function filterableProperty(property, partOfArray = false, engine) {
4848
5352
  if (!isFilterableRelation(property, engine)) return false;
4849
5353
  if (partOfArray) return [
@@ -4974,7 +5478,8 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
4974
5478
  disabled: isNullOperation,
4975
5479
  placeholder: isNullOperation ? "null" : void 0,
4976
5480
  onChange: (evt) => {
4977
- updateFilter(operation, type === "number" ? parseFloat(evt.target.value) : evt.target.value);
5481
+ const val = type === "number" ? parseFloat(evt.target.value) : evt.target.value;
5482
+ updateFilter(operation, val);
4978
5483
  },
4979
5484
  endAdornment: internalValue !== void 0 && internalValue != null && /* @__PURE__ */ jsx(IconButton, {
4980
5485
  onClick: (e) => updateFilter(operation, void 0),
@@ -5710,7 +6215,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
5710
6215
  children: /* @__PURE__ */ jsx("div", {
5711
6216
  className: "h-full w-full flex flex-col bg-white dark:bg-surface-900",
5712
6217
  ref,
5713
- children: /* @__PURE__ */ jsx(TableView, {
6218
+ children: /* @__PURE__ */ jsx(VirtualTable, {
5714
6219
  data,
5715
6220
  columns,
5716
6221
  cellRenderer: (props) => cellRenderer(props),
@@ -5842,6 +6347,13 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
5842
6347
  }), {}) : entity;
5843
6348
  const Builder = additionalField.Builder;
5844
6349
  if (!Builder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
6350
+ const child = Builder ? /* @__PURE__ */ jsx(Builder, {
6351
+ entity,
6352
+ context
6353
+ }) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
6354
+ entity,
6355
+ context
6356
+ })?.toString() });
5845
6357
  return /* @__PURE__ */ jsx(EntityTableCell, {
5846
6358
  width,
5847
6359
  size,
@@ -5858,13 +6370,7 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
5858
6370
  isDragging,
5859
6371
  isDraggable,
5860
6372
  frozen,
5861
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: Builder ? /* @__PURE__ */ jsx(Builder, {
5862
- entity,
5863
- context
5864
- }) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
5865
- entity,
5866
- context
5867
- })?.toString() }) })
6373
+ children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
5868
6374
  }, `additional_table_cell_${entity.id}_${column.key}`);
5869
6375
  }, [size]);
5870
6376
  const engine = useCollectionScope()?.engine;
@@ -6017,51 +6523,22 @@ function useSelectionController(onSelectionChange) {
6017
6523
  return prev.filter((item) => !(item.id === entity.id && item.path === entity.path));
6018
6524
  }
6019
6525
  return prev;
6020
- });
6021
- }, []);
6022
- const isEntitySelected = useCallback((entity) => {
6023
- return Boolean(selectedEntities.find((e) => e.id === entity.id && e.path === entity.path));
6024
- }, [selectedEntities]);
6025
- return useMemo(() => ({
6026
- selectedEntities,
6027
- setSelectedEntities,
6028
- isEntitySelected,
6029
- toggleEntitySelection
6030
- }), [
6031
- selectedEntities,
6032
- setSelectedEntities,
6033
- isEntitySelected,
6034
- toggleEntitySelection
6035
- ]);
6036
- }
6037
- //#endregion
6038
- //#region src/util/resolutions.ts
6039
- /**
6040
- * Built-in entity views that are resolved by token name.
6041
- * These are always available without needing to be registered
6042
- * in the customization controller's entityViews array.
6043
- */
6044
- var BUILTIN_ENTITY_VIEWS = { "__rebase_history": {
6045
- key: "__rebase_history",
6046
- name: "History",
6047
- tabComponent: React.createElement(HistoryIcon, { size: 20 }),
6048
- position: "end"
6049
- } };
6050
- function resolveEntityView(entityView, contextEntityViews) {
6051
- if (typeof entityView === "string") return BUILTIN_ENTITY_VIEWS[entityView] ?? contextEntityViews?.find((entry) => entry.key === entityView);
6052
- else return entityView;
6053
- }
6054
- function resolveEntityAction(entityAction, contextEntityActions) {
6055
- if (typeof entityAction === "string") return contextEntityActions?.find((entry) => entry.key === entityAction);
6056
- else return entityAction;
6057
- }
6058
- function resolvedSelectedEntityView(customViews, customizationController, selectedTab, _canEdit) {
6059
- const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView(e, customizationController.entityViews)).filter(Boolean).filter((e) => e.key !== "__rebase_history") : [];
6060
- return {
6061
- resolvedEntityViews,
6062
- selectedEntityView: resolvedEntityViews.find((e) => e.key === selectedTab),
6063
- selectedSecondaryForm: customViews && resolvedEntityViews.filter((e) => e.includeActions).find((e) => e.key === selectedTab)
6064
- };
6526
+ });
6527
+ }, []);
6528
+ const isEntitySelected = useCallback((entity) => {
6529
+ return Boolean(selectedEntities.find((e) => e.id === entity.id && e.path === entity.path));
6530
+ }, [selectedEntities]);
6531
+ return useMemo(() => ({
6532
+ selectedEntities,
6533
+ setSelectedEntities,
6534
+ isEntitySelected,
6535
+ toggleEntitySelection
6536
+ }), [
6537
+ selectedEntities,
6538
+ setSelectedEntities,
6539
+ isEntitySelected,
6540
+ toggleEntitySelection
6541
+ ]);
6065
6542
  }
6066
6543
  //#endregion
6067
6544
  //#region src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts
@@ -6233,6 +6710,35 @@ function mergeEntityActions(currentActions, newActions) {
6233
6710
  });
6234
6711
  return updatedActions;
6235
6712
  }
6713
+ //#endregion
6714
+ //#region src/util/resolutions.ts
6715
+ /**
6716
+ * Built-in entity views that are resolved by token name.
6717
+ * These are always available without needing to be registered
6718
+ * in the customization controller's entityViews array.
6719
+ */
6720
+ var BUILTIN_ENTITY_VIEWS = { "__rebase_history": {
6721
+ key: "__rebase_history",
6722
+ name: "History",
6723
+ tabComponent: React.createElement(HistoryIcon, { size: 20 }),
6724
+ position: "end"
6725
+ } };
6726
+ function resolveEntityView(entityView, contextEntityViews) {
6727
+ if (typeof entityView === "string") return BUILTIN_ENTITY_VIEWS[entityView] ?? contextEntityViews?.find((entry) => entry.key === entityView);
6728
+ else return entityView;
6729
+ }
6730
+ function resolveEntityAction(entityAction, contextEntityActions) {
6731
+ if (typeof entityAction === "string") return contextEntityActions?.find((entry) => entry.key === entityAction);
6732
+ else return entityAction;
6733
+ }
6734
+ function resolvedSelectedEntityView(customViews, customizationController, selectedTab, _canEdit) {
6735
+ const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView(e, customizationController.entityViews)).filter(Boolean).filter((e) => e.key !== "__rebase_history") : [];
6736
+ return {
6737
+ resolvedEntityViews,
6738
+ selectedEntityView: resolvedEntityViews.find((e) => e.key === selectedTab),
6739
+ selectedSecondaryForm: customViews && resolvedEntityViews.filter((e) => e.includeActions).find((e) => e.key === selectedTab)
6740
+ };
6741
+ }
6236
6742
  var BreadcrumbContext = React.createContext({
6237
6743
  breadcrumbs: [],
6238
6744
  set: () => {},
@@ -6315,7 +6821,10 @@ function SearchIconsView({ selectedIcon = "", onIconSelected }) {
6315
6821
  const [query, setQuery] = React.useState("");
6316
6822
  const updateSearchResults = React.useMemo(() => debounce((value) => {
6317
6823
  if (!value || value === "") setKeys(null);
6318
- else setKeys(iconsSearch.search(value).slice(0, 50).map((e) => e.item.key));
6824
+ else {
6825
+ const limited = iconsSearch.search(value).slice(0, 50);
6826
+ setKeys(limited.map((e) => e.item.key));
6827
+ }
6319
6828
  }, UPDATE_SEARCH_INDEX_WAIT_MS), []);
6320
6829
  React.useEffect(() => {
6321
6830
  updateSearchResults(query);
@@ -6516,15 +7025,18 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
6516
7025
  }, []);
6517
7026
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, {}));
6518
7027
  useEffect(() => {
6519
- if (hasValue && value && value.length !== internalIds.length) setInternalIds(value.map((v, index) => {
6520
- const hashValue = (getHashValue(v) ?? String(index)) + index;
6521
- if (hashValue in internalIdsRef.current) return internalIdsRef.current[hashValue];
6522
- else {
6523
- const newInternalId = getRandomId$1();
6524
- internalIdsRef.current[hashValue] = newInternalId;
6525
- return newInternalId;
6526
- }
6527
- }));
7028
+ if (hasValue && value && value.length !== internalIds.length) {
7029
+ const newInternalIds = value.map((v, index) => {
7030
+ const hashValue = (getHashValue(v) ?? String(index)) + index;
7031
+ if (hashValue in internalIdsRef.current) return internalIdsRef.current[hashValue];
7032
+ else {
7033
+ const newInternalId = getRandomId$1();
7034
+ internalIdsRef.current[hashValue] = newInternalId;
7035
+ return newInternalId;
7036
+ }
7037
+ });
7038
+ setInternalIds(newInternalIds);
7039
+ }
6528
7040
  }, [
6529
7041
  hasValue,
6530
7042
  internalIds.length,
@@ -6585,7 +7097,8 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
6585
7097
  const oldIndex = internalIds.indexOf(active.id);
6586
7098
  const newIndex = internalIds.indexOf(over.id);
6587
7099
  if (oldIndex === -1 || newIndex === -1) return;
6588
- setInternalIds(arrayMove$1(internalIds, oldIndex, newIndex));
7100
+ const newIds = arrayMove$1(internalIds, oldIndex, newIndex);
7101
+ setInternalIds(newIds);
6589
7102
  onValueChange(arrayMove$1(value ?? [], oldIndex, newIndex));
6590
7103
  };
6591
7104
  return sortable ? /* @__PURE__ */ jsx(DndContext, {
@@ -6921,6 +7434,7 @@ var useImportConfig = () => {
6921
7434
  //#endregion
6922
7435
  //#region src/data_import/utils/get_import_inference_type.ts
6923
7436
  function getInferenceType(value) {
7437
+ if (value === null || value === void 0) return "string";
6924
7438
  if (typeof value === "number") return "number";
6925
7439
  else if (typeof value === "string") return "string";
6926
7440
  else if (typeof value === "boolean") return "boolean";
@@ -7308,8 +7822,9 @@ var ConfigControllerProvider = React.memo(function ConfigControllerProvider({ ch
7308
7822
  editedCollectionId
7309
7823
  });
7310
7824
  const namespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(0, propertyKey.lastIndexOf(".")) : void 0;
7825
+ const propertyKeyWithoutNamespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(propertyKey.lastIndexOf(".") + 1) : propertyKey;
7311
7826
  setCurrentPropertyDialog({
7312
- propertyKey: propertyKey && propertyKey.includes(".") ? propertyKey.substring(propertyKey.lastIndexOf(".") + 1) : propertyKey,
7827
+ propertyKey: propertyKeyWithoutNamespace,
7313
7828
  property,
7314
7829
  namespace,
7315
7830
  currentPropertiesOrder,
@@ -7365,7 +7880,10 @@ var ConfigControllerProvider = React.memo(function ConfigControllerProvider({ ch
7365
7880
  onFetchTableMetadata,
7366
7881
  handleClose: (collection) => {
7367
7882
  if (currentDialog?.redirect) {
7368
- if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionSlugs.length) navigate(urlController.buildUrlCollectionPath(collection.slug));
7883
+ if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionSlugs.length) {
7884
+ const url = urlController.buildUrlCollectionPath(collection.slug);
7885
+ navigate(url);
7886
+ }
7369
7887
  }
7370
7888
  setCurrentDialog(void 0);
7371
7889
  }
@@ -7532,8 +8050,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
7532
8050
  }
7533
8051
  //#endregion
7534
8052
  //#region src/components/CollectionViewBinding/CollectionViewActions.tsx
7535
- var ImportCollectionAction = lazy(() => import("./import-BjUG-cIY.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
7536
- var ExportCollectionAction = lazy(() => import("./export-BzIpUdSK.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
8053
+ var ImportCollectionAction = lazy(() => import("./import-Cwzf4RW5.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
8054
+ var ExportCollectionAction = lazy(() => import("./export-B8DIn-u8.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
7537
8055
  function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
7538
8056
  const context = useAdminContext();
7539
8057
  const { canCreate, canDelete } = usePermissions();
@@ -7578,8 +8096,9 @@ function CollectionViewActions({ collection, relativePath, parentCollectionSlugs
7578
8096
  })
7579
8097
  }));
7580
8098
  const multipleDeleteEnabled = canDelete(collection, path, null);
8099
+ const hasSelection = Boolean(selectedEntities?.length);
7581
8100
  let multipleDeleteButton;
7582
- {
8101
+ if (hasSelection) {
7583
8102
  const button = largeLayout && !compact ? /* @__PURE__ */ jsxs(Button, {
7584
8103
  variant: "text",
7585
8104
  disabled: !selectedEntities?.length || !multipleDeleteEnabled,
@@ -7673,7 +8192,9 @@ function relationCardinality(relation) {
7673
8192
  * Pure function — call inside `useMemo`.
7674
8193
  */
7675
8194
  function resolveCollectionSlotKeys(collection, authController, propertyConfigs) {
7676
- const titleKeyCandidates = getTitlePropertyCandidates(collection);
8195
+ const rankedCandidates = getTitlePropertyCandidates(collection);
8196
+ const leadingRelationKey = getLeadingRelationTitleKey(collection);
8197
+ const titleKeyCandidates = leadingRelationKey ? [leadingRelationKey, ...rankedCandidates.filter((key) => key !== leadingRelationKey)] : rankedCandidates;
7677
8198
  const titleKey = titleKeyCandidates[0];
7678
8199
  const imageKey = getEntityImagePreviewPropertyKey(collection);
7679
8200
  let statusKey;
@@ -7721,9 +8242,9 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
7721
8242
  const hasExplicitOrder = !!collection.propertiesOrder;
7722
8243
  const relationKeys = [];
7723
8244
  if (!hasExplicitOrder) {
7724
- for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "relation") relationKeys.push(key);
8245
+ for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "relation" && key !== titleKey) relationKeys.push(key);
7725
8246
  }
7726
- const excludeKeys = new Set([
8247
+ const excludeKeys = /* @__PURE__ */ new Set([
7727
8248
  titleKey,
7728
8249
  imageKey,
7729
8250
  statusKey,
@@ -7882,9 +8403,7 @@ function resolveEntitySlots(entity, collection, slotKeys) {
7882
8403
  }
7883
8404
  }
7884
8405
  if (items.length > 0) {
7885
- const relIndex = relationKeys.indexOf(relKey);
7886
- const chipColors = Object.values(CHIP_COLORS);
7887
- const colorScheme = chipColors[relIndex % chipColors.length];
8406
+ const colorScheme = CHIP_COLORS[CHIP_SEED_KEYS[relationKeys.indexOf(relKey) % CHIP_SEED_KEYS.length]];
7888
8407
  relations.push({
7889
8408
  property: prop,
7890
8409
  propertyKey: relKey,
@@ -8356,7 +8875,7 @@ function CollectionListViewBinding({ collection, tableController, onEntityClick,
8356
8875
  const showStatus = statusPropertyKey && size !== "xs" && size !== "s";
8357
8876
  const subtitleKey = previewKeys.length > 0 ? previewKeys[0] : void 0;
8358
8877
  const allKeys = resolvedCollection.propertiesOrder || Object.keys(resolvedCollection.properties);
8359
- const usedKeys = new Set([
8878
+ const usedKeys = /* @__PURE__ */ new Set([
8360
8879
  titlePropertyKey,
8361
8880
  imagePropertyKey,
8362
8881
  statusPropertyKey,
@@ -8948,44 +9467,228 @@ var PropertyCollectionView = ({ data, properties, baseKey = "", suppressHeader =
8948
9467
  };
8949
9468
  //#endregion
8950
9469
  //#region src/components/EntityViewBinding.tsx
9470
+ /**
9471
+ * The read-only rendering of a record's values.
9472
+ *
9473
+ * This used to prepend a synthetic `Id` row of its own. Between that row, the
9474
+ * collection's own id property, and the `path/id` chip above it, the same UUID
9475
+ * appeared three times — and in the ~340px split pane each copy wrapped over
9476
+ * four lines, so most of the first screen was one id repeated. The id is now a
9477
+ * copyable chip in the identity bar, once.
9478
+ */
8951
9479
  function EntityViewBinding({ entity, collection, path, className }) {
8952
9480
  const customizationController = useCustomizationController();
8953
- const properties = collection.properties;
8954
- return /* @__PURE__ */ jsx("div", {
8955
- className: "w-full " + className,
8956
- children: /* @__PURE__ */ jsxs("div", {
8957
- className: "w-full mb-4 p-4",
8958
- children: [/* @__PURE__ */ jsxs("div", {
8959
- className: `grid grid-cols-12 gap-x-4 py-4 items-start border-b ${defaultBorderMixin}`,
8960
- children: [/* @__PURE__ */ jsx("div", {
8961
- className: "col-span-4 pr-2",
8962
- children: /* @__PURE__ */ jsx(Typography, {
8963
- variant: "caption",
8964
- color: "secondary",
8965
- component: "span",
8966
- className: "break-words",
8967
- children: "Id"
9481
+ const properties = React.useMemo(() => {
9482
+ const entries = Object.entries(collection.properties ?? {});
9483
+ const idKeys = entries.filter(([, p]) => p && "isId" in p && p.isId);
9484
+ if (idKeys.length !== 1) return collection.properties;
9485
+ return Object.fromEntries(entries.filter(([key]) => key !== idKeys[0][0]));
9486
+ }, [collection.properties]);
9487
+ const externalLink = customizationController?.entityLinkBuilder?.({ entity });
9488
+ return /* @__PURE__ */ jsxs("div", {
9489
+ className: "w-full " + (className ?? ""),
9490
+ children: [externalLink && /* @__PURE__ */ jsx("div", {
9491
+ className: "flex justify-end mb-2",
9492
+ children: /* @__PURE__ */ jsx(Tooltip, {
9493
+ title: "Open in the live site",
9494
+ children: /* @__PURE__ */ jsx("a", {
9495
+ href: externalLink,
9496
+ rel: "noopener noreferrer",
9497
+ target: "_blank",
9498
+ children: /* @__PURE__ */ jsx(IconButton, {
9499
+ size: "small",
9500
+ children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
8968
9501
  })
8969
- }), /* @__PURE__ */ jsx("div", {
8970
- className: "col-span-8",
8971
- children: /* @__PURE__ */ jsxs("div", {
8972
- className: "flex-grow text-surface-900 dark:text-white flex items-center",
8973
- children: [/* @__PURE__ */ jsx("span", {
8974
- className: "flex-grow mr-2",
8975
- children: entity.id
8976
- }), customizationController?.entityLinkBuilder && /* @__PURE__ */ jsx("a", {
8977
- href: customizationController.entityLinkBuilder({ entity }),
8978
- rel: "noopener noreferrer",
8979
- target: "_blank",
8980
- children: /* @__PURE__ */ jsx(IconButton, { children: /* @__PURE__ */ jsx(ExternalLinkIcon, {}) })
8981
- })]
9502
+ })
9503
+ })
9504
+ }), /* @__PURE__ */ jsx(PropertyCollectionView, {
9505
+ data: entity.values,
9506
+ properties,
9507
+ size: "medium"
9508
+ })]
9509
+ });
9510
+ }
9511
+ //#endregion
9512
+ //#region src/components/EntityIdentityBar.tsx
9513
+ /**
9514
+ * The persistent header of an entity view: who this record is, and what you can
9515
+ * do to it.
9516
+ *
9517
+ * The title and the id used to live inside the scrolling form, above 72px of
9518
+ * empty space — 219px of chrome that carried nothing and scrolled away the
9519
+ * moment you touched the wheel, leaving no indication of which record you were
9520
+ * editing. They live here instead, and the left half of a bar that was
9521
+ * previously empty now does the work.
9522
+ */
9523
+ function EntityIdentityBar({ collection, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, recordActions, pluginActions, trailing, leading }) {
9524
+ const { t } = useTranslation();
9525
+ const saveLabel = status === "existing" ? t("save") : status === "copy" ? t("create_copy") : t("create");
9526
+ const closeLabel = status === "existing" ? t("save_and_close") : status === "copy" ? t("create_copy_and_close") : t("create_and_close");
9527
+ const hasMenu = Boolean(recordActions) || Boolean(onInspect) || Boolean(onViewHistory);
9528
+ return /* @__PURE__ */ jsxs("div", {
9529
+ className: cls("h-[52px] shrink-0 flex items-center gap-2 pl-1.5 pr-2 border-b", "bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
9530
+ children: [
9531
+ leading,
9532
+ onBack && /* @__PURE__ */ jsx(Tooltip, {
9533
+ title: t("back"),
9534
+ children: /* @__PURE__ */ jsx(IconButton, {
9535
+ size: "small",
9536
+ onClick: onBack,
9537
+ "aria-label": t("back"),
9538
+ children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
9539
+ })
9540
+ }),
9541
+ /* @__PURE__ */ jsxs(Typography, {
9542
+ variant: "caption",
9543
+ className: "text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden sm:block",
9544
+ children: [collection.name, "\xA0/"]
9545
+ }),
9546
+ /* @__PURE__ */ jsx("span", {
9547
+ className: "font-headers font-semibold text-[15px] tracking-tight truncate min-w-0",
9548
+ title,
9549
+ children: title
9550
+ }),
9551
+ entityId !== void 0 && /* @__PURE__ */ jsx(IdChip, { value: String(entityId) }),
9552
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
9553
+ onSave && /* @__PURE__ */ jsx(SaveState, {
9554
+ dirty,
9555
+ saving,
9556
+ status,
9557
+ t
9558
+ }),
9559
+ pluginActions,
9560
+ onDiscard && dirty && !saving && /* @__PURE__ */ jsx(Button, {
9561
+ variant: "text",
9562
+ size: "small",
9563
+ onClick: onDiscard,
9564
+ children: status === "existing" ? t("discard") : t("clear")
9565
+ }),
9566
+ onSave && /* @__PURE__ */ jsx(Tooltip, {
9567
+ title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
9568
+ children: /* @__PURE__ */ jsx(LoadingButton, {
9569
+ variant: onSaveAndClose ? "text" : "filled",
9570
+ color: "primary",
9571
+ size: "small",
9572
+ loading: saving && !onSaveAndClose,
9573
+ disabled: saveDisabled,
9574
+ onClick: onSave,
9575
+ children: saveLabel
9576
+ })
9577
+ }),
9578
+ onSaveAndClose && /* @__PURE__ */ jsx(LoadingButton, {
9579
+ variant: "filled",
9580
+ color: "primary",
9581
+ size: "small",
9582
+ loading: saving,
9583
+ disabled: saveDisabled,
9584
+ onClick: onSaveAndClose,
9585
+ children: closeLabel
9586
+ }),
9587
+ hasMenu && /* @__PURE__ */ jsxs(Menu, {
9588
+ align: "end",
9589
+ trigger: /* @__PURE__ */ jsx(IconButton, {
9590
+ size: "small",
9591
+ "aria-label": t("more_actions") ?? "More actions",
9592
+ children: /* @__PURE__ */ jsx(MoreVerticalIcon, { size: iconSize.smallest })
9593
+ }),
9594
+ children: [
9595
+ recordActions,
9596
+ recordActions && (onInspect || onViewHistory) && /* @__PURE__ */ jsx(Separator, { orientation: "horizontal" }),
9597
+ onViewHistory && /* @__PURE__ */ jsxs(MenuItem, {
9598
+ onClick: onViewHistory,
9599
+ children: [/* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest }), t("record_history") ?? "History"]
9600
+ }),
9601
+ onInspect && /* @__PURE__ */ jsxs(MenuItem, {
9602
+ onClick: onInspect,
9603
+ children: [/* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest }), t("inspect_record") ?? "Inspect record"]
8982
9604
  })
8983
- })]
8984
- }), /* @__PURE__ */ jsx(PropertyCollectionView, {
8985
- data: entity.values,
8986
- properties,
8987
- size: "medium"
8988
- })]
9605
+ ]
9606
+ }),
9607
+ trailing
9608
+ ]
9609
+ });
9610
+ }
9611
+ /**
9612
+ * The words the unlabelled floating circle never said.
9613
+ *
9614
+ * That indicator was a ✓ / pencil / spinner chip stuck to the top-right of the
9615
+ * scroll area in an `h-0 overflow-visible` container, so it drifted across
9616
+ * field values as you scrolled — and it duplicated what the Save button was
9617
+ * already able to tell you.
9618
+ */
9619
+ function SaveState({ dirty, saving, status, t }) {
9620
+ if (saving) return /* @__PURE__ */ jsx(StateText, { children: t("saving") });
9621
+ if (dirty) return /* @__PURE__ */ jsx(StateText, { children: t("unsaved_changes_title") });
9622
+ if (status === "existing") return /* @__PURE__ */ jsx(StateText, { children: t("entity_saved") ?? "Saved" });
9623
+ return null;
9624
+ }
9625
+ function StateText({ children }) {
9626
+ return /* @__PURE__ */ jsx("span", {
9627
+ className: "text-xs text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden md:inline",
9628
+ children
9629
+ });
9630
+ }
9631
+ /**
9632
+ * The id, as a chip you can copy rather than a full-width alert holding a UUID.
9633
+ */
9634
+ function IdChip({ value }) {
9635
+ const { t } = useTranslation();
9636
+ const [copied, setCopied] = useState(false);
9637
+ const truncated = value.length > 14 ? `${value.slice(0, 8)}…${value.slice(-4)}` : value;
9638
+ const copy = () => {
9639
+ navigator.clipboard?.writeText(value).then(() => {
9640
+ setCopied(true);
9641
+ setTimeout(() => setCopied(false), 1600);
9642
+ }, () => void 0);
9643
+ };
9644
+ return /* @__PURE__ */ jsx(Tooltip, {
9645
+ title: copied ? t("copied") : value,
9646
+ children: /* @__PURE__ */ jsxs("button", {
9647
+ type: "button",
9648
+ onClick: copy,
9649
+ "aria-label": `${t("copy_id")} ${value}`,
9650
+ className: cls("hidden md:inline-flex items-center gap-1.5 shrink-0 px-2 py-0.5 rounded-md", "font-mono text-[11px] text-text-secondary dark:text-text-secondary-dark", "bg-surface-accent-200/50 dark:bg-white/[0.055]", "hover:bg-surface-accent-200/75 dark:hover:bg-white/[0.09] transition-colors"),
9651
+ children: [truncated, copied ? /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(CopyIcon, { size: iconSize.smallest })]
9652
+ })
9653
+ });
9654
+ }
9655
+ //#endregion
9656
+ //#region src/components/CollectionViewBinding/SplitViewContext.tsx
9657
+ var SplitViewContext = React.createContext(void 0);
9658
+ /**
9659
+ * The enclosing split view, or `undefined` when the component is not rendered
9660
+ * inside one (a side panel, a dialog, a full-screen route).
9661
+ */
9662
+ function useSplitView() {
9663
+ return useContext(SplitViewContext);
9664
+ }
9665
+ function SplitViewProvider({ value, children }) {
9666
+ return /* @__PURE__ */ jsx(SplitViewContext.Provider, {
9667
+ value,
9668
+ children
9669
+ });
9670
+ }
9671
+ //#endregion
9672
+ //#region src/components/CollectionViewBinding/SplitListCloseButton.tsx
9673
+ /**
9674
+ * Closes the list of a split view, leaving the open record on its own.
9675
+ *
9676
+ * It leads the record's own app bar, where the breadcrumb starts — this is the
9677
+ * entity pane's old "Open full screen" button, moved to that edge and wearing an
9678
+ * X: closing the list and opening the record full screen are the same thing, so
9679
+ * there is one control and one route (`#full`) rather than two.
9680
+ */
9681
+ function SplitListCloseButton() {
9682
+ const { t } = useTranslation();
9683
+ const splitView = useSplitView();
9684
+ if (!splitView?.detailOpen) return null;
9685
+ return /* @__PURE__ */ jsx(Tooltip, {
9686
+ title: t("hide_list"),
9687
+ children: /* @__PURE__ */ jsx(IconButton, {
9688
+ size: "small",
9689
+ onClick: splitView.openFullScreen,
9690
+ "aria-label": t("hide_list"),
9691
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
8989
9692
  })
8990
9693
  });
8991
9694
  }
@@ -9018,15 +9721,96 @@ function JsonPreviewBinding({ values }) {
9018
9721
  });
9019
9722
  }
9020
9723
  //#endregion
9724
+ //#region src/components/EntityInspector.tsx
9725
+ var EntityHistoryView = lazy(() => import("./history-ClwDEtqN.js").then((m) => ({ default: m.EntityHistoryView })));
9726
+ /**
9727
+ * Raw values and revision history, as an inspector rather than as tabs.
9728
+ *
9729
+ * These were the first two entries in the entity tab strip — two icon-only tabs
9730
+ * with no label and no tooltip, sitting *before* the record you opened the page
9731
+ * to edit. They are developer tools, not places the record lives, so they belong
9732
+ * behind one affordance in the bar; a content editor should never meet `<>`.
9733
+ *
9734
+ * The panel starts below the identity bar deliberately: you keep seeing which
9735
+ * record you are inspecting while you inspect it.
9736
+ */
9737
+ function EntityInspector({ tab, onTabChange, onClose, collection, entity, formContext, values, includeHistory }) {
9738
+ const { t } = useTranslation();
9739
+ const open = tab !== null;
9740
+ useEffect(() => {
9741
+ if (!open) return;
9742
+ const onKeyDown = (e) => {
9743
+ if (e.key === "Escape") {
9744
+ e.stopPropagation();
9745
+ onClose();
9746
+ }
9747
+ };
9748
+ window.addEventListener("keydown", onKeyDown);
9749
+ return () => window.removeEventListener("keydown", onKeyDown);
9750
+ }, [open, onClose]);
9751
+ if (!open) return null;
9752
+ return /* @__PURE__ */ jsxs("div", {
9753
+ className: "absolute inset-x-0 bottom-0 top-[52px] z-30 flex justify-end",
9754
+ role: "dialog",
9755
+ "aria-label": t("inspect_record") ?? "Inspect record",
9756
+ children: [/* @__PURE__ */ jsx("div", {
9757
+ className: "absolute inset-0 bg-black/25",
9758
+ onClick: onClose,
9759
+ "aria-hidden": true
9760
+ }), /* @__PURE__ */ jsxs("div", {
9761
+ className: cls("relative flex flex-col w-full max-w-md border-l shadow-xl", "bg-white dark:bg-surface-800", defaultBorderMixin),
9762
+ children: [/* @__PURE__ */ jsxs("div", {
9763
+ className: cls("h-11 shrink-0 flex items-center gap-2 pl-4 pr-1.5 border-b", defaultBorderMixin),
9764
+ children: [
9765
+ /* @__PURE__ */ jsx("span", {
9766
+ className: "text-sm font-semibold",
9767
+ children: t("inspect") ?? "Inspect"
9768
+ }),
9769
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
9770
+ includeHistory ? /* @__PURE__ */ jsxs(Tabs, {
9771
+ value: tab ?? "json",
9772
+ className: "!w-fit",
9773
+ onValueChange: (v) => onTabChange(v),
9774
+ children: [/* @__PURE__ */ jsx(Tab, {
9775
+ value: "json",
9776
+ className: "text-xs",
9777
+ children: t("json")
9778
+ }), /* @__PURE__ */ jsx(Tab, {
9779
+ value: "history",
9780
+ className: "text-xs",
9781
+ children: t("record_history") ?? "History"
9782
+ })]
9783
+ }) : null,
9784
+ /* @__PURE__ */ jsx(IconButton, {
9785
+ size: "small",
9786
+ onClick: onClose,
9787
+ "aria-label": t("close_inspector") ?? "Close inspector",
9788
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
9789
+ })
9790
+ ]
9791
+ }), /* @__PURE__ */ jsx("div", {
9792
+ className: "flex-1 min-h-0 overflow-auto",
9793
+ children: /* @__PURE__ */ jsxs(ErrorBoundary, { children: [tab === "json" && /* @__PURE__ */ jsx(JsonPreviewBinding, { values: values ?? entity?.values ?? {} }), tab === "history" && includeHistory && formContext && /* @__PURE__ */ jsx(Suspense, {
9794
+ fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
9795
+ children: /* @__PURE__ */ jsx(EntityHistoryView, {
9796
+ collection,
9797
+ entity,
9798
+ formContext,
9799
+ modifiedValues: values ?? entity?.values
9800
+ })
9801
+ })] })
9802
+ })]
9803
+ })]
9804
+ });
9805
+ }
9806
+ //#endregion
9021
9807
  //#region src/util/view_constants.ts
9022
9808
  /**
9023
9809
  * Shared tab value constants used by EditViewBinding and DetailViewBinding.
9024
9810
  */
9025
9811
  var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
9026
- var JSON_TAB_VALUE = "__json";
9027
9812
  //#endregion
9028
9813
  //#region src/components/DetailViewBinding.tsx
9029
- var EntityHistoryView$1 = lazy(() => import("./history-g9688IaK.js").then((m) => ({ default: m.EntityHistoryView })));
9030
9814
  function DetailViewBinding({ entityId, ...props }) {
9031
9815
  const { entity, dataLoading, dataLoadingError } = useFetch({
9032
9816
  path: props.path,
@@ -9050,7 +9834,7 @@ function DetailViewBinding({ entityId, ...props }) {
9050
9834
  children: content
9051
9835
  });
9052
9836
  }
9053
- function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions }) {
9837
+ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions, barActionsStart }) {
9054
9838
  const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
9055
9839
  const { t } = useTranslation();
9056
9840
  const context = useRebaseContext();
@@ -9093,22 +9877,20 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
9093
9877
  const customViewsCount = customViews?.length ?? 0;
9094
9878
  const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
9095
9879
  const includeHistoryView = Boolean(collection.history);
9096
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView || includeHistoryView;
9880
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
9881
+ const [inspectorTab, setInspectorTab] = useState(null);
9882
+ const legacyInspectorTab = selectedTab === "__json" ? "json" : selectedTab === "__rebase_history" ? "history" : void 0;
9883
+ useEffect(() => {
9884
+ if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
9885
+ }, [legacyInspectorTab]);
9097
9886
  const { resolvedEntityViews } = resolvedSelectedEntityView(customViews, customizationController, void 0, canEdit);
9098
9887
  const activeTab = useMemo(() => {
9099
- return new Set([
9888
+ return /* @__PURE__ */ new Set([
9100
9889
  "__main_##Q$SC^#S6",
9101
- ...includeJsonView ? ["__json"] : [],
9102
- ...includeHistoryView ? ["__rebase_history"] : [],
9103
9890
  ...resolvedEntityViews.map((v) => v.key),
9104
9891
  ...subcollections.map((s) => s.slug)
9105
9892
  ]);
9106
- }, [
9107
- includeJsonView,
9108
- includeHistoryView,
9109
- resolvedEntityViews,
9110
- subcollections
9111
- ]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
9893
+ }, [resolvedEntityViews, subcollections]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
9112
9894
  const { selectedEntityView } = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
9113
9895
  const mainViewVisible = activeTab === MAIN_TAB_VALUE;
9114
9896
  const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
@@ -9198,25 +9980,6 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
9198
9980
  }, `custom_view_${customView.key}`);
9199
9981
  }).filter(Boolean);
9200
9982
  const globalLoading = dataLoading && !usedEntity;
9201
- const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
9202
- const jsonView = activeTab === "__json" || jsonTabMounted ? /* @__PURE__ */ jsx("div", {
9203
- className: cls("relative flex-1 h-full overflow-auto w-full", { "hidden": activeTab !== JSON_TAB_VALUE }),
9204
- role: "tabpanel",
9205
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(JsonPreviewBinding, { values: usedEntity?.values ?? {} }) })
9206
- }, "json_view") : null;
9207
- const historyView = includeHistoryView && activeTab === "__rebase_history" ? /* @__PURE__ */ jsx("div", {
9208
- className: "relative flex-1 h-full overflow-auto w-full",
9209
- role: "tabpanel",
9210
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, {
9211
- fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
9212
- children: /* @__PURE__ */ jsx(EntityHistoryView$1, {
9213
- collection,
9214
- entity: usedEntity,
9215
- formContext: readOnlyFormContext,
9216
- modifiedValues: usedEntity?.values
9217
- })
9218
- }) })
9219
- }, "history_view") : null;
9220
9983
  const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
9221
9984
  const subcollectionId = subcollection.slug;
9222
9985
  const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
@@ -9315,13 +10078,13 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
9315
10078
  children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
9316
10079
  })
9317
10080
  }) : null;
9318
- const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog" || Boolean(backToCollectionButton);
9319
- const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
10081
+ const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
9320
10082
  title: "Open full screen",
9321
10083
  children: /* @__PURE__ */ jsx(IconButton, {
9322
10084
  size: "small",
9323
10085
  onClick: () => {
9324
- navigate(`${urlController.buildUrlCollectionPath(`${path}/${entityId}`)}#full`);
10086
+ const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
10087
+ navigate(`${entityUrl}#full`);
9325
10088
  },
9326
10089
  children: /* @__PURE__ */ jsx(Maximize2Icon, { size: iconSize.smallest })
9327
10090
  })
@@ -9336,59 +10099,27 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
9336
10099
  }) : null;
9337
10100
  const mainView = /* @__PURE__ */ jsx("div", {
9338
10101
  className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", !mainViewVisible ? "hidden" : ""),
9339
- children: /* @__PURE__ */ jsxs("div", {
9340
- className: cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit"),
9341
- children: [/* @__PURE__ */ jsxs("div", {
9342
- className: cls("flex flex-col w-full", layout === "dialog" ? "pt-4 pb-12 px-6 sm:px-8" : "pt-12 pb-16 px-4 sm:px-8 md:px-10"),
9343
- children: [
9344
- /* @__PURE__ */ jsxs("div", {
9345
- className: "w-full flex flex-col items-start my-4 lg:my-6",
9346
- children: [/* @__PURE__ */ jsxs("div", {
9347
- className: "flex items-center justify-between w-full",
9348
- children: [/* @__PURE__ */ jsx(Typography, {
9349
- className: cls("my-4 grow line-clamp-1", collection.hideIdFromForm ? "mb-6" : ""),
9350
- variant: "h4",
9351
- children: title
9352
- }), editButton]
9353
- }), /* @__PURE__ */ jsx(Alert, {
9354
- color: "base",
9355
- outerClassName: "w-full",
9356
- size: "small",
9357
- children: /* @__PURE__ */ jsxs("code", {
9358
- className: "text-xs select-all text-text-secondary dark:text-text-secondary-dark",
9359
- children: [
9360
- usedEntity?.path ?? path,
9361
- "/",
9362
- entityId
9363
- ]
9364
- })
9365
- })]
9366
- }),
9367
- /* @__PURE__ */ jsx("div", {
9368
- className: "mt-12 flex flex-col gap-8",
9369
- children: propertyDetailView()
9370
- }),
9371
- /* @__PURE__ */ jsx("div", { className: "h-16" })
9372
- ]
9373
- }), canEdit && onEditClick && layout === "full_screen" && /* @__PURE__ */ jsx("div", {
9374
- className: cls("overflow-auto h-full hidden @6xl:flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin),
9375
- children: /* @__PURE__ */ jsx(Button, {
9376
- fullWidth: true,
9377
- variant: "filled",
9378
- color: "primary",
9379
- startIcon: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.small }),
9380
- onClick: onEditClick,
9381
- children: t("edit_entity")
9382
- })
9383
- })]
10102
+ children: /* @__PURE__ */ jsx("div", {
10103
+ className: cls("w-full max-w-3xl 2xl:max-w-4xl flex flex-col", layout === "dialog" ? "pt-5 pb-12 px-6 sm:px-8" : "pt-6 pb-16 px-5 sm:px-8"),
10104
+ children: propertyDetailView()
9384
10105
  })
9385
10106
  });
9386
10107
  let result = /* @__PURE__ */ jsxs("div", {
9387
10108
  className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
9388
10109
  children: [
9389
- shouldShowTopBar && /* @__PURE__ */ jsxs("div", {
9390
- className: cls("h-[52px] items-center flex overflow-hidden w-full border-b pl-2 pr-2 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
9391
- children: [
10110
+ /* @__PURE__ */ jsx(EntityIdentityBar, {
10111
+ collection,
10112
+ title,
10113
+ entityId,
10114
+ status: "existing",
10115
+ dirty: false,
10116
+ saving: false,
10117
+ onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
10118
+ onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
10119
+ leading: /* @__PURE__ */ jsxs(Fragment, { children: [barActionsStart, layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {})] }),
10120
+ trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
10121
+ editButton,
10122
+ pluginActionsTop,
9392
10123
  backToCollectionButton,
9393
10124
  fullScreenButton,
9394
10125
  barActions?.({
@@ -9396,51 +10127,46 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
9396
10127
  entityId,
9397
10128
  values: usedEntity?.values ?? {},
9398
10129
  status: "existing"
9399
- }),
9400
- pluginActionsTop,
9401
- hasAdditionalViews && /* @__PURE__ */ jsx("div", {
9402
- className: "flex-1 flex justify-end min-w-0 shrink-0",
9403
- children: /* @__PURE__ */ jsxs(Tabs, {
9404
- className: "!w-fit max-w-full",
9405
- value: activeTab,
9406
- onValueChange: (value) => {
9407
- onSideTabClick(value);
9408
- },
9409
- children: [
9410
- includeJsonView && /* @__PURE__ */ jsx(Tab, {
9411
- disabled: !hasAdditionalViews,
9412
- value: "__json",
9413
- className: "text-sm",
9414
- children: /* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest })
9415
- }),
9416
- includeHistoryView && /* @__PURE__ */ jsx(Tab, {
9417
- disabled: !hasAdditionalViews,
9418
- value: "__rebase_history",
9419
- className: "text-sm",
9420
- children: /* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest })
9421
- }),
9422
- /* @__PURE__ */ jsx(Tab, {
9423
- disabled: !hasAdditionalViews,
9424
- value: "__main_##Q$SC^#S6",
9425
- className: "text-sm min-w-[90px]",
9426
- children: /* @__PURE__ */ jsxs("span", {
9427
- className: "flex items-center gap-1.5",
9428
- children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
9429
- })
9430
- }),
9431
- customViewTabsStart,
9432
- customViewTabsEnd,
9433
- subcollectionTabs
9434
- ]
9435
- })
9436
10130
  })
9437
- ]
10131
+ ] })
10132
+ }),
10133
+ hasAdditionalViews && /* @__PURE__ */ jsx("div", {
10134
+ className: cls("h-10 shrink-0 flex items-stretch border-b px-2 min-w-0", defaultBorderMixin),
10135
+ children: /* @__PURE__ */ jsxs(Tabs, {
10136
+ className: "!w-full",
10137
+ innerClassName: "h-full",
10138
+ variant: "boxy",
10139
+ value: activeTab,
10140
+ onValueChange: (value) => {
10141
+ onSideTabClick(value);
10142
+ },
10143
+ children: [
10144
+ /* @__PURE__ */ jsx(Tab, {
10145
+ value: "__main_##Q$SC^#S6",
10146
+ children: /* @__PURE__ */ jsxs("span", {
10147
+ className: "flex items-center gap-1.5",
10148
+ children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
10149
+ })
10150
+ }),
10151
+ customViewTabsStart,
10152
+ customViewTabsEnd,
10153
+ subcollectionTabs
10154
+ ]
10155
+ })
9438
10156
  }),
9439
10157
  globalLoading ? /* @__PURE__ */ jsx(DetailViewSkeleton, { collection }) : mainView,
9440
- jsonView,
9441
- historyView,
9442
10158
  customViewsView,
9443
- subCollectionsViews
10159
+ subCollectionsViews,
10160
+ /* @__PURE__ */ jsx(EntityInspector, {
10161
+ tab: inspectorTab,
10162
+ onTabChange: setInspectorTab,
10163
+ onClose: () => setInspectorTab(null),
10164
+ collection,
10165
+ entity: usedEntity,
10166
+ formContext: readOnlyFormContext,
10167
+ values: usedEntity?.values,
10168
+ includeHistory: includeHistoryView
10169
+ })
9444
10170
  ]
9445
10171
  });
9446
10172
  if (plugins && plugins.length > 0) result = /* @__PURE__ */ jsx(PluginProviderStack, {
@@ -9753,27 +10479,43 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
9753
10479
  }
9754
10480
  }, String(renderedEntityId)) })
9755
10481
  }) : /* @__PURE__ */ jsx(Fragment, {});
9756
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ResizablePanels, {
9757
- stacked: !largeLayout,
9758
- firstPanel: listPanel,
9759
- secondPanel: detailPanel,
9760
- showSecondPanel: isDetailVisible,
9761
- panelSizePercent: animationPhase === "entering" ? 100 : panelSize,
9762
- onPanelSizeChange: setPanelSize,
9763
- minPanelSizePx: 240
9764
- }), /* @__PURE__ */ jsx(UnsavedChangesDialog, {
9765
- open: blocker?.state === "blocked",
9766
- handleOk: () => {
9767
- dirty.current = false;
9768
- blocker?.proceed?.();
9769
- },
9770
- handleCancel: () => blocker?.reset?.(),
9771
- body: /* @__PURE__ */ jsxs(Fragment, { children: [
9772
- "You have unsaved changes in this ",
9773
- /* @__PURE__ */ jsx("b", { children: collection.singularName ?? collection.name }),
9774
- "."
9775
- ] })
9776
- })] });
10482
+ const openFullScreen = useCallback(() => {
10483
+ if (selectedEntityId === void 0) return;
10484
+ navigate(`${buildUrl(`${path}/${selectedEntityId}${isEditMode ? "/edit" : ""}`)}#full`);
10485
+ }, [
10486
+ navigate,
10487
+ buildUrl,
10488
+ path,
10489
+ selectedEntityId,
10490
+ isEditMode
10491
+ ]);
10492
+ return /* @__PURE__ */ jsxs(SplitViewProvider, {
10493
+ value: useMemo(() => ({
10494
+ detailOpen: selectedEntityId !== void 0,
10495
+ openFullScreen
10496
+ }), [selectedEntityId, openFullScreen]),
10497
+ children: [/* @__PURE__ */ jsx(ResizablePanels, {
10498
+ stacked: !largeLayout,
10499
+ firstPanel: listPanel,
10500
+ secondPanel: detailPanel,
10501
+ showSecondPanel: isDetailVisible,
10502
+ panelSizePercent: animationPhase === "entering" ? 100 : panelSize,
10503
+ onPanelSizeChange: setPanelSize,
10504
+ minPanelSizePx: 240
10505
+ }), /* @__PURE__ */ jsx(UnsavedChangesDialog, {
10506
+ open: blocker?.state === "blocked",
10507
+ handleOk: () => {
10508
+ dirty.current = false;
10509
+ blocker?.proceed?.();
10510
+ },
10511
+ handleCancel: () => blocker?.reset?.(),
10512
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [
10513
+ "You have unsaved changes in this ",
10514
+ /* @__PURE__ */ jsx("b", { children: collection.singularName ?? collection.name }),
10515
+ "."
10516
+ ] })
10517
+ })]
10518
+ });
9777
10519
  }
9778
10520
  //#endregion
9779
10521
  //#region src/components/CollectionViewBinding/BoardCardBinding.tsx
@@ -9927,6 +10669,12 @@ var BoardCardBinding = memo(BoardCardBindingInner);
9927
10669
  //#region src/components/CollectionViewBinding/useBoardDataController.tsx
9928
10670
  var DEFAULT_PAGE_SIZE$1 = 20;
9929
10671
  /**
10672
+ * How long a column waits for its subscription's first payload before reading
10673
+ * once over HTTP instead. Long enough that a healthy socket always wins the
10674
+ * race, short enough that a dead one does not hold a blank board.
10675
+ */
10676
+ var LIVE_FIRST_PAINT_TIMEOUT_MS = 2500;
10677
+ /**
9930
10678
  * Shallow equality for entity value records.
9931
10679
  * Handles primitives and reference equality for nested values.
9932
10680
  * Avoids JSON.stringify allocation on every comparison.
@@ -10152,36 +10900,66 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
10152
10900
  };
10153
10901
  });
10154
10902
  };
10903
+ const accessor = currentDataClient.collection(currentResolvedPath);
10904
+ const includeParams = getRelationIncludeParams(currentCollection);
10905
+ const fetchOnce = () => accessor.find({
10906
+ where: whereFilter,
10907
+ limit: itemCount,
10908
+ orderBy: orderByParam,
10909
+ include: includeParams
10910
+ });
10155
10911
  const onError = (error) => {
10156
10912
  if (isCleaningUpRef.current) return;
10157
10913
  console.error(`Error loading column ${column}:`, error);
10914
+ fetchOnce().then((res) => onUpdate(res.data)).catch(() => {
10915
+ if (isCleaningUpRef.current) return;
10916
+ setColumnData((prev) => ({
10917
+ ...prev,
10918
+ [column]: {
10919
+ ...prev[column],
10920
+ entities: prev[column]?.entities ?? [],
10921
+ loading: false,
10922
+ hasMore: false,
10923
+ error
10924
+ }
10925
+ }));
10926
+ });
10927
+ };
10928
+ if (accessor.listen) {
10929
+ let liveDataReceived = false;
10930
+ const unsubscribe = accessor.listen({
10931
+ where: whereFilter,
10932
+ limit: itemCount,
10933
+ orderBy: orderByParam,
10934
+ include: includeParams
10935
+ }, (res) => {
10936
+ liveDataReceived = true;
10937
+ onUpdate(res.data);
10938
+ }, onError);
10939
+ const firstPaintFallback = setTimeout(() => {
10940
+ if (liveDataReceived || isCleaningUpRef.current) return;
10941
+ fetchOnce().then((res) => {
10942
+ if (liveDataReceived || isCleaningUpRef.current) return;
10943
+ onUpdate(res.data);
10944
+ }).catch(() => void 0);
10945
+ }, LIVE_FIRST_PAINT_TIMEOUT_MS);
10946
+ unsubscribersRef.current[column] = () => {
10947
+ clearTimeout(firstPaintFallback);
10948
+ unsubscribe?.();
10949
+ };
10950
+ } else fetchOnce().then((res) => onUpdate(res.data)).catch((error) => {
10951
+ if (isCleaningUpRef.current) return;
10158
10952
  setColumnData((prev) => ({
10159
10953
  ...prev,
10160
10954
  [column]: {
10161
10955
  ...prev[column],
10162
- entities: [],
10956
+ entities: prev[column]?.entities ?? [],
10163
10957
  loading: false,
10164
10958
  hasMore: false,
10165
10959
  error
10166
10960
  }
10167
10961
  }));
10168
- };
10169
- const accessor = currentDataClient.collection(currentResolvedPath);
10170
- const includeParams = getRelationIncludeParams(currentCollection);
10171
- if (accessor.listen) {
10172
- const unsubscribe = accessor.listen({
10173
- where: whereFilter,
10174
- limit: itemCount,
10175
- orderBy: orderByParam,
10176
- include: includeParams
10177
- }, (res) => onUpdate(res.data), onError);
10178
- unsubscribersRef.current[column] = unsubscribe;
10179
- } else accessor.find({
10180
- where: whereFilter,
10181
- limit: itemCount,
10182
- orderBy: orderByParam,
10183
- include: includeParams
10184
- }).then((res) => onUpdate(res.data)).catch(onError);
10962
+ });
10185
10963
  }, []);
10186
10964
  useEffect(() => {
10187
10965
  isCleaningUpRef.current = false;
@@ -10203,7 +10981,8 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
10203
10981
  const timeoutId = setTimeout(() => {
10204
10982
  if (isCleaningUpRef.current) return;
10205
10983
  currentColumns.forEach((column) => {
10206
- subscribeToColumn(column, currentColumnItemCounts[column] ?? pageSize);
10984
+ const itemCount = currentColumnItemCounts[column] ?? pageSize;
10985
+ subscribeToColumn(column, itemCount);
10207
10986
  if (isColumnExcludedByFilter(column, currentFilterValues, currentColumnProperty)) return;
10208
10987
  const accessor = currentDataClient.collection(currentResolvedPath);
10209
10988
  if (accessor.count) {
@@ -10395,6 +11174,40 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
10395
11174
  }
10396
11175
  //#endregion
10397
11176
  //#region src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts
11177
+ /**
11178
+ * The digits a board order key is built from: base36, lower case only.
11179
+ *
11180
+ * The library's default alphabet is base62, and its keys only sort correctly
11181
+ * under byte ordering. The column is sorted by the *database*, and Postgres'
11182
+ * default collation is not byte ordering — under `en_US.UTF-8`, `"aa"` sorts
11183
+ * before `"aC"`, so as soon as a board had been dragged around enough to reach
11184
+ * the upper-case digits its order stopped matching the keys. One case has no
11185
+ * such ambiguity: digits before letters is true of every collation in use.
11186
+ *
11187
+ * A key written with the old alphabet no longer validates, which is what makes
11188
+ * the board offer to initialise the column — one click, and the order is
11189
+ * rewritten in a form the database can sort.
11190
+ */
11191
+ var ORDER_KEY_DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz";
11192
+ /**
11193
+ * Whether a stored order value is a key `fractional-indexing` can interpolate
11194
+ * against.
11195
+ *
11196
+ * Anything else — a plain `"12"` from a seed or a migration, an empty string —
11197
+ * makes `generateKeyBetween` throw, and the fallback below then hands out the
11198
+ * same first key to every card, so each drag lands at the bottom of the column
11199
+ * on top of the last one that did. Asking the library itself is the only
11200
+ * definition of "valid" worth trusting.
11201
+ */
11202
+ function isValidOrderKey(value) {
11203
+ if (typeof value !== "string" || value.length === 0) return false;
11204
+ try {
11205
+ generateKeyBetween(value, null, ORDER_KEY_DIGITS);
11206
+ return true;
11207
+ } catch {
11208
+ return false;
11209
+ }
11210
+ }
10398
11211
  function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
10399
11212
  const handleItemsReorder = useCallback(async (items, moveInfo) => {
10400
11213
  const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
@@ -10411,17 +11224,21 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
10411
11224
  if (orderProperty) {
10412
11225
  const targetColumnItems = items;
10413
11226
  const movedIndex = targetColumnItems.findIndex((item) => item.id === moveInfo.itemId);
10414
- const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].data.values?.[orderProperty] ?? null : null;
10415
- const nextKey = movedIndex < targetColumnItems.length - 1 ? targetColumnItems[movedIndex + 1].data.values?.[orderProperty] ?? null : null;
11227
+ const keyAt = (index) => {
11228
+ const value = targetColumnItems[index]?.data.values?.[orderProperty];
11229
+ return isValidOrderKey(value) ? value : null;
11230
+ };
11231
+ const prevKey = movedIndex > 0 ? keyAt(movedIndex - 1) : null;
11232
+ const nextKey = movedIndex < targetColumnItems.length - 1 ? keyAt(movedIndex + 1) : null;
10416
11233
  try {
10417
11234
  const a = prevKey;
10418
11235
  let b = nextKey;
10419
11236
  if (a !== null && b !== null && a >= b) b = null;
10420
- const newKey = generateKeyBetween(a, b);
11237
+ const newKey = generateKeyBetween(a, b, ORDER_KEY_DIGITS);
10421
11238
  updatedValues = setIn(updatedValues, orderProperty, newKey);
10422
11239
  } catch (e) {
10423
11240
  console.warn("fractional-indexing error, falling back:", e);
10424
- const newKey = generateKeyBetween(null, null);
11241
+ const newKey = generateKeyBetween(null, null, ORDER_KEY_DIGITS);
10425
11242
  updatedValues = setIn(updatedValues, orderProperty, newKey);
10426
11243
  }
10427
11244
  }
@@ -10628,10 +11445,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
10628
11445
  const itemsNeedBackfill = useMemo(() => {
10629
11446
  if (!orderProperty || dataLoading) return false;
10630
11447
  if (missingOrderCount > 0) return true;
10631
- return allEntities.some((entity) => {
10632
- const orderValue = entity.values?.[orderProperty];
10633
- return orderValue === void 0 || orderValue === null;
10634
- });
11448
+ return allEntities.some((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
10635
11449
  }, [
10636
11450
  allEntities,
10637
11451
  orderProperty,
@@ -10662,7 +11476,8 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
10662
11476
  loading: colData?.loading ?? true,
10663
11477
  hasMore: colData?.hasMore ?? false,
10664
11478
  itemCount: colData?.entities?.length ?? 0,
10665
- totalCount: colData?.totalCount
11479
+ totalCount: colData?.totalCount,
11480
+ error: colData?.error
10666
11481
  };
10667
11482
  });
10668
11483
  return state;
@@ -10693,11 +11508,8 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
10693
11508
  analyticsController.onAnalyticsEvent?.("kanban_backfill_order", { path: fullPath });
10694
11509
  setBackfillLoading(true);
10695
11510
  try {
10696
- const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: 1e4 })).data.filter((entity) => {
10697
- const orderValue = entity.values?.[orderProperty];
10698
- return orderValue === void 0 || orderValue === null;
10699
- });
10700
- const keys = generateNKeysBetween(null, null, entitiesToUpdate.length);
11511
+ const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: 1e4 })).data.filter((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
11512
+ const keys = generateNKeysBetween(null, null, entitiesToUpdate.length, ORDER_KEY_DIGITS);
10701
11513
  const updates = [];
10702
11514
  entitiesToUpdate.forEach((entity, index) => {
10703
11515
  const updatedValues = setIn({ ...entity.values }, orderProperty, keys[index]);
@@ -10865,7 +11677,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
10865
11677
  })]
10866
11678
  }),
10867
11679
  /* @__PURE__ */ jsx("div", {
10868
- className: "flex-1 overflow-auto no-scrollbar",
11680
+ className: "flex-1 min-h-0 overflow-hidden",
10869
11681
  children: /* @__PURE__ */ jsx(KanbanView, {
10870
11682
  data: boardItems,
10871
11683
  columns,
@@ -11472,9 +12284,9 @@ function ResetPasswordActionDialog({ user, open, onClose }) {
11472
12284
  setLoading(true);
11473
12285
  setError(null);
11474
12286
  try {
11475
- const baseUrl = client?.baseUrl || "";
12287
+ const apiBase = apiBaseOf(client) ?? "";
11476
12288
  const token = await getAuthToken?.();
11477
- const response = await fetch(`${baseUrl}/api/admin/users/${user.uid}/reset-password`, {
12289
+ const response = await fetch(`${apiBase}/admin/users/${user.uid}/reset-password`, {
11478
12290
  method: "POST",
11479
12291
  headers: {
11480
12292
  "Content-Type": "application/json",
@@ -11786,7 +12598,8 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
11786
12598
  const updatePopupLocation = useCallback((newPositionCandidate) => {
11787
12599
  const draggableBoundingRect = draggableRef.current?.getBoundingClientRect();
11788
12600
  if (!cellRect || !draggableBoundingRect || draggableBoundingRect.width === 0 || draggableBoundingRect.height === 0) return;
11789
- const newNormalizedPosition = normalizePosition(newPositionCandidate ?? getInitialLocation(), draggableBoundingRect, windowSize);
12601
+ const basePosition = newPositionCandidate ?? getInitialLocation();
12602
+ const newNormalizedPosition = normalizePosition(basePosition, draggableBoundingRect, windowSize);
11790
12603
  if (!popupLocation || newNormalizedPosition.x !== popupLocation.x || newNormalizedPosition.y !== popupLocation.y) setPopupLocation(newNormalizedPosition);
11791
12604
  }, [
11792
12605
  cellRect,
@@ -12348,6 +13161,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
12348
13161
  const { t } = useTranslation();
12349
13162
  const navigate = useNavigate();
12350
13163
  const urlController = useUrlController();
13164
+ const splitView = useSplitView();
12351
13165
  const [filtersDialogOpen, setFiltersDialogOpen] = useState(false);
12352
13166
  const filterValues = tableController.filterValues;
12353
13167
  const fixedFilter = collection.fixedFilter;
@@ -12371,15 +13185,16 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
12371
13185
  urlController,
12372
13186
  path
12373
13187
  ]);
12374
- const backButton = compact && /* @__PURE__ */ jsx(Tooltip, {
12375
- title: t("back"),
13188
+ const backButton = compact && !splitView?.detailOpen && /* @__PURE__ */ jsx(Tooltip, {
13189
+ title: t("close"),
12376
13190
  children: /* @__PURE__ */ jsx(IconButton, {
12377
13191
  size: "small",
12378
13192
  onClick: handleBackClick,
13193
+ "aria-label": t("close"),
12379
13194
  className: "mr-1",
12380
- children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.small })
13195
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
12381
13196
  })
12382
- }, "back_tooltip");
13197
+ }, "close_tooltip");
12383
13198
  const filtersButton = resolvedProperties && tableController.setFilterValues && /* @__PURE__ */ jsx(Tooltip, {
12384
13199
  title: t("filters"),
12385
13200
  children: /* @__PURE__ */ jsx(Badge, {
@@ -12392,16 +13207,15 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
12392
13207
  startIcon: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.small }),
12393
13208
  className: cls(activeFilterCount > 0 && "text-primary"),
12394
13209
  children: activeFilterCount > 0 ? `(${activeFilterCount})` : t("filters")
12395
- }) : /* @__PURE__ */ jsx(Button, {
12396
- variant: "text",
13210
+ }) : /* @__PURE__ */ jsx(IconButton, {
12397
13211
  size: "small",
12398
13212
  onClick: () => setFiltersDialogOpen(true),
12399
13213
  className: cls(activeFilterCount > 0 && "text-primary"),
12400
- children: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.small })
13214
+ children: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.smallest })
12401
13215
  })
12402
13216
  })
12403
13217
  }, "filters_tooltip");
12404
- const filterPresetsButton = collection.filterPresets?.length ? /* @__PURE__ */ jsx(FilterPresetsButton, {
13218
+ const filterPresetsButton = !compact && collection.filterPresets?.length ? /* @__PURE__ */ jsx(FilterPresetsButton, {
12405
13219
  filterPresets: collection.filterPresets,
12406
13220
  tableController,
12407
13221
  compact
@@ -12587,7 +13401,10 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
12587
13401
  }, [defaultViewMode, setSearchParams]);
12588
13402
  useEffect(() => {
12589
13403
  if (urlView) setViewModeState(urlView);
12590
- else setViewModeState(getSavedView() ?? defaultViewMode);
13404
+ else {
13405
+ const savedView = getSavedView();
13406
+ setViewModeState(savedView ?? defaultViewMode);
13407
+ }
12591
13408
  }, [
12592
13409
  urlView,
12593
13410
  getSavedView,
@@ -12619,9 +13436,10 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
12619
13436
  entityId: clickedEntity.id
12620
13437
  });
12621
13438
  if (collection) addRecentId(collection.slug, clickedEntity.id);
13439
+ const entityPath = path ?? clickedEntity.path;
12622
13440
  navigateToEntity({
12623
13441
  navigation: urlController,
12624
- path: path ?? clickedEntity.path,
13442
+ path: entityPath,
12625
13443
  sidePanelController,
12626
13444
  openEntityMode,
12627
13445
  collection,
@@ -12765,7 +13583,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
12765
13583
  const onColumnResize = useCallback(({ width, key }) => {
12766
13584
  const collection = collectionRef.current;
12767
13585
  if (!getPropertyInPath(collection.properties, key)) return;
12768
- onCollectionModifiedForUser(path, buildPropertyWidthOverwrite(key, width));
13586
+ const localCollection = buildPropertyWidthOverwrite(key, width);
13587
+ onCollectionModifiedForUser(path, localCollection);
12769
13588
  }, [onCollectionModifiedForUser, path]);
12770
13589
  const onListSizeChanged = useCallback((size) => {
12771
13590
  setListSize(size);
@@ -12947,21 +13766,24 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
12947
13766
  return (largeLayout ? 80 + actionsWidth : 70 + actionsWidth) + (collapsedActions.length > 0 ? largeLayout ? 40 : 30 : 0);
12948
13767
  }, [getActionsForEntity, largeLayout]);
12949
13768
  const tableRowActionsBuilder = useCallback(({ entity, size, width, frozen }) => {
13769
+ const isSelected = Boolean(usedSelectionController.selectedEntities.find((e) => e.id == entity.id && e.path == entity.path));
13770
+ const customEntityActions = (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
13771
+ const actions = getActionsForEntity({
13772
+ entity,
13773
+ customEntityActions
13774
+ });
12950
13775
  return /* @__PURE__ */ jsx(CollectionRowActions, {
12951
13776
  entity,
12952
13777
  width,
12953
13778
  frozen,
12954
- isSelected: Boolean(usedSelectionController.selectedEntities.find((e) => e.id == entity.id && e.path == entity.path)),
13779
+ isSelected,
12955
13780
  selectionEnabled: activeSelectionEnabled,
12956
13781
  size,
12957
13782
  highlightEntity: setHighlightedEntity,
12958
13783
  unhighlightEntity: unselectNavigatedEntity,
12959
13784
  collection,
12960
13785
  path,
12961
- actions: getActionsForEntity({
12962
- entity,
12963
- customEntityActions: (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean)
12964
- }),
13786
+ actions,
12965
13787
  hideId: collection?.hideIdFromCollection,
12966
13788
  onCollectionChange: updateLastDeleteTimestamp,
12967
13789
  selectionController: usedSelectionController,
@@ -12983,12 +13805,13 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
12983
13805
  const { resolvedSlots } = customizationController;
12984
13806
  const headerActionContributions = useMemo(() => resolvedSlots.filter((s) => s.slot === "collection.header.action").sort((a, b) => (a.order ?? 50) - (b.order ?? 50)), [resolvedSlots]);
12985
13807
  const buildAdditionalHeaderWidget = useCallback(({ property, propertyKey, onHover }) => {
13808
+ const collection = collectionRef.current;
12986
13809
  const headerSlotProps = {
12987
13810
  property,
12988
13811
  propertyKey,
12989
13812
  onHover,
12990
13813
  path,
12991
- collection: collectionRef.current,
13814
+ collection,
12992
13815
  tableController,
12993
13816
  parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY,
12994
13817
  parentEntityIds: parentEntityIds ?? EMPTY_ARRAY
@@ -13242,7 +14065,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
13242
14065
  }, `split-list-view-${path}`) : /* @__PURE__ */ jsxs("div", {
13243
14066
  className: "flex flex-col w-full h-full",
13244
14067
  children: [toolbarNode, /* @__PURE__ */ jsx("div", {
13245
- className: cls("flex-1 flex flex-col", (viewMode === "list" || viewMode === "cards") && "overflow-y-auto"),
14068
+ className: cls("flex-1 min-h-0 flex flex-col", (viewMode === "list" || viewMode === "cards") && "overflow-y-auto"),
13246
14069
  children: collectionSurface
13247
14070
  })]
13248
14071
  }),
@@ -13455,7 +14278,18 @@ function EntityIdHeaderWidget({ collection, path, idPath, openEntityMode }) {
13455
14278
  }
13456
14279
  //#endregion
13457
14280
  //#region src/components/EditFormActions.tsx
13458
- function EditFormActions({ collection, path, entity, layout, savingError, formex, disabled, status, pluginActions, openEntityMode, showDefaultActions = true, navigateBack, formContext }) {
14281
+ /**
14282
+ * The entity form's footer.
14283
+ *
14284
+ * It used to render in two shapes: this footer, and a `w-80 2xl:w-96` sticky
14285
+ * side rail carrying the same Save and Discard above several hundred pixels of
14286
+ * nothing. The rail is gone, and Save/Discard have moved to the identity bar
14287
+ * where they stay visible while the form scrolls — so with
14288
+ * `showDefaultActions={false}` this renders only what the bar cannot host:
14289
+ * record actions (copy, delete, custom), plugin actions, the saving error, and
14290
+ * the side-panel-only "save and close".
14291
+ */
14292
+ function EditFormActions({ collection, path, entity, savingError, formex, disabled, status, pluginActions, openEntityMode, showDefaultActions = true, navigateBack, formContext }) {
13459
14293
  const { canCreate, canDelete } = usePermissions();
13460
14294
  const context = useAdminContext();
13461
14295
  const sidePanelController = context.sidePanelController;
@@ -13464,31 +14298,29 @@ function EditFormActions({ collection, path, entity, layout, savingError, formex
13464
14298
  const { t } = useTranslation();
13465
14299
  const childViewSource = useChildViewSource(path);
13466
14300
  const isLinkedChildView = childViewSource?.kind === "relation" && childViewSource.mode === "linked";
13467
- const entityActions = useMemo(() => {
13468
- const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
13469
- const createEnabled = canCreate(collection, path);
13470
- const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
13471
- const disableActions = collection.disableDefaultActions ?? [];
13472
- const actions = [];
13473
- if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
13474
- if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
13475
- if (customEntityActions) return mergeEntityActions(actions, customEntityActions);
13476
- return actions;
13477
- }, [
13478
- canCreate,
13479
- canDelete,
13480
- collection,
13481
- path,
13482
- customizationController.entityActions?.length,
13483
- entity,
13484
- collection.disableDefaultActions,
13485
- isLinkedChildView
13486
- ]);
13487
- const formActions = showDefaultActions ? entityActions.filter((a) => a.includeInForm === void 0 || a.includeInForm) : [];
13488
- const bottomActions = buildBottomActions$1({
14301
+ return buildBottomActions$1({
13489
14302
  savingError,
13490
14303
  entity,
13491
- formActions,
14304
+ formActions: useMemo(() => {
14305
+ const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
14306
+ const createEnabled = canCreate(collection, path);
14307
+ const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
14308
+ const disableActions = collection.disableDefaultActions ?? [];
14309
+ const actions = [];
14310
+ if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
14311
+ if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
14312
+ if (customEntityActions) return mergeEntityActions(actions, customEntityActions);
14313
+ return actions;
14314
+ }, [
14315
+ canCreate,
14316
+ canDelete,
14317
+ collection,
14318
+ path,
14319
+ customizationController.entityActions?.length,
14320
+ entity,
14321
+ collection.disableDefaultActions,
14322
+ isLinkedChildView
14323
+ ]).filter((a) => a.includeInForm === void 0 || a.includeInForm),
13492
14324
  collection,
13493
14325
  context,
13494
14326
  sidePanelController,
@@ -13501,35 +14333,17 @@ function EditFormActions({ collection, path, entity, layout, savingError, formex
13501
14333
  formContext,
13502
14334
  formex,
13503
14335
  t,
13504
- className: layout === "responsive" ? "@6xl:hidden" : void 0
14336
+ showDefaultActions
13505
14337
  });
13506
- const sideActions = buildSideActions$1({
13507
- savingError,
13508
- entity,
13509
- formActions,
13510
- collection,
13511
- context,
13512
- sidePanelController,
13513
- sideDialogContext,
13514
- disabled,
13515
- status,
13516
- pluginActions,
13517
- openEntityMode,
13518
- navigateBack,
13519
- formContext,
13520
- formex,
13521
- t,
13522
- className: layout === "responsive" ? "hidden @6xl:flex" : void 0
13523
- });
13524
- if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
13525
- return layout === "bottom" ? bottomActions : sideActions;
13526
14338
  }
13527
- function buildBottomActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, className }) {
14339
+ function buildBottomActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, showDefaultActions, className }) {
13528
14340
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
13529
14341
  const canClose = openEntityMode === "side_panel" || openEntityMode === "dialog";
14342
+ const showBackToDetail = collection.defaultEntityAction === "view" && status === "existing";
14343
+ if (!(showDefaultActions || Boolean(savingError))) return null;
13530
14344
  return /* @__PURE__ */ jsxs(DialogActions, {
13531
14345
  className,
13532
- position: "absolute",
14346
+ position: "sticky",
13533
14347
  children: [
13534
14348
  savingError && /* @__PURE__ */ jsx("div", {
13535
14349
  className: "text-right",
@@ -13567,21 +14381,20 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
13567
14381
  size: iconSize.smallest
13568
14382
  })
13569
14383
  }) : null,
13570
- /* @__PURE__ */ jsx(Button, {
13571
- variant: "text",
13572
- color: "primary",
13573
- disabled: disabled || formex.isSubmitting,
13574
- type: "reset",
13575
- children: status === "existing" ? t("discard") : t("clear")
13576
- }),
13577
- collection.defaultEntityAction === "view" && status === "existing" && /* @__PURE__ */ jsx(Button, {
14384
+ showBackToDetail && /* @__PURE__ */ jsx(Button, {
13578
14385
  variant: "text",
13579
14386
  color: "primary",
13580
14387
  disabled,
13581
14388
  onClick: navigateBack,
13582
14389
  children: t("back_to_detail") ?? "Back to details"
13583
14390
  }),
13584
- /* @__PURE__ */ jsx(Tooltip, {
14391
+ showDefaultActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
14392
+ variant: "text",
14393
+ color: "primary",
14394
+ disabled: disabled || formex.isSubmitting,
14395
+ type: "reset",
14396
+ children: status === "existing" ? t("discard") : t("clear")
14397
+ }), /* @__PURE__ */ jsx(Tooltip, {
13585
14398
  title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
13586
14399
  children: /* @__PURE__ */ jsxs(Button, {
13587
14400
  variant: canClose ? "text" : "filled",
@@ -13597,7 +14410,7 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
13597
14410
  status === "new" && t("create")
13598
14411
  ]
13599
14412
  })
13600
- }),
14413
+ })] }),
13601
14414
  canClose && /* @__PURE__ */ jsxs(LoadingButton, {
13602
14415
  variant: "filled",
13603
14416
  color: "primary",
@@ -13616,77 +14429,6 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
13616
14429
  ]
13617
14430
  });
13618
14431
  }
13619
- function buildSideActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, className }) {
13620
- const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
13621
- return /* @__PURE__ */ jsxs("div", {
13622
- 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),
13623
- children: [
13624
- /* @__PURE__ */ jsx(Tooltip, {
13625
- title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
13626
- children: /* @__PURE__ */ jsxs(LoadingButton, {
13627
- fullWidth: true,
13628
- variant: "filled",
13629
- color: "primary",
13630
- type: "submit",
13631
- loading: formex.isSubmitting,
13632
- startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
13633
- disabled: disabled || formex.isSubmitting,
13634
- onClick: () => {
13635
- sideDialogContext.setPendingClose?.(false);
13636
- },
13637
- children: [
13638
- status === "existing" && t("save"),
13639
- status === "copy" && t("create_copy"),
13640
- status === "new" && t("create")
13641
- ]
13642
- })
13643
- }),
13644
- /* @__PURE__ */ jsx(Button, {
13645
- fullWidth: true,
13646
- variant: "text",
13647
- disabled: disabled || formex.isSubmitting,
13648
- type: "reset",
13649
- children: status === "existing" ? t("discard") : t("clear")
13650
- }),
13651
- collection.defaultEntityAction === "view" && status === "existing" && /* @__PURE__ */ jsx(Button, {
13652
- fullWidth: true,
13653
- variant: "text",
13654
- disabled,
13655
- onClick: navigateBack,
13656
- children: t("back_to_detail") ?? "Back to details"
13657
- }),
13658
- pluginActions,
13659
- formActions.length > 0 && /* @__PURE__ */ jsx("div", {
13660
- className: "flex flex-row flex-wrap mt-2",
13661
- children: formActions.map((action, index) => {
13662
- const props = {
13663
- view: "form",
13664
- entity,
13665
- path: getCollectionDataPath(collection),
13666
- collection,
13667
- context,
13668
- sidePanelController,
13669
- openEntityMode,
13670
- navigateBack,
13671
- formContext
13672
- };
13673
- return /* @__PURE__ */ jsx(EntityActionButton, {
13674
- action,
13675
- enabled: !action.isEnabled || action.isEnabled(props),
13676
- props
13677
- }, action.key ?? action.name ?? index);
13678
- })
13679
- }),
13680
- savingError && /* @__PURE__ */ jsx("div", {
13681
- className: "text-right",
13682
- children: /* @__PURE__ */ jsx(Typography, {
13683
- color: "error",
13684
- children: savingError.message
13685
- })
13686
- })
13687
- ]
13688
- });
13689
- }
13690
14432
  function EntityActionButton({ action, enabled, props }) {
13691
14433
  const snackbarController = useSnackbarController();
13692
14434
  const [loading, setLoading] = React.useState(false);
@@ -13728,8 +14470,68 @@ function EntityActionButton({ action, enabled, props }) {
13728
14470
  });
13729
14471
  }
13730
14472
  //#endregion
14473
+ //#region src/hooks/useRecordActions.tsx
14474
+ /**
14475
+ * The actions you can perform *on* a record — copy, delete, and whatever the
14476
+ * collection adds — resolved against the current user's permissions.
14477
+ *
14478
+ * Extracted from the form's footer so the identity bar can own them. They are
14479
+ * not ways to leave the form, so they never belonged next to Save; putting them
14480
+ * in the bar's overflow menu is what let the footer disappear instead of
14481
+ * becoming a full-width strip holding two icon buttons.
14482
+ */
14483
+ function useRecordActions({ collection, path, entity }) {
14484
+ const { canCreate, canDelete } = usePermissions();
14485
+ const customizationController = useCustomizationController();
14486
+ const childViewSource = useChildViewSource(path);
14487
+ const isLinkedChildView = childViewSource?.kind === "relation" && childViewSource.mode === "linked";
14488
+ return useMemo(() => {
14489
+ const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
14490
+ const createEnabled = canCreate(collection, path);
14491
+ const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
14492
+ const disableActions = collection.disableDefaultActions ?? [];
14493
+ const actions = [];
14494
+ if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
14495
+ if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
14496
+ return (customEntityActions.length ? mergeEntityActions(actions, customEntityActions) : actions).filter((a) => a.includeInForm === void 0 || a.includeInForm);
14497
+ }, [
14498
+ canCreate,
14499
+ canDelete,
14500
+ collection,
14501
+ path,
14502
+ customizationController.entityActions?.length,
14503
+ entity,
14504
+ collection.disableDefaultActions,
14505
+ isLinkedChildView
14506
+ ]);
14507
+ }
14508
+ //#endregion
14509
+ //#region src/hooks/useEntityDisplayTitle.tsx
14510
+ /**
14511
+ * The human name for a record, for the identity bar and the breadcrumb.
14512
+ *
14513
+ * The guard on `status` is the point. The title resolver walks candidate
14514
+ * properties and returns the first non-empty one, and on a brand new entity the
14515
+ * only populated values are the property defaults — so creating a blog post
14516
+ * opened a page headed **draft**, the default of its `status` enum. A record
14517
+ * that does not exist yet has no name; it has an intent.
14518
+ */
14519
+ function useEntityDisplayTitle({ collection, values, entityId, status }) {
14520
+ const isNew = status === "new";
14521
+ const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, values, entityId);
14522
+ const rawTitle = !isNew && values && titlePropertyKey ? getValueInPath(values, titlePropertyKey) : void 0;
14523
+ const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
14524
+ const singular = collection.singularName ?? collection.name;
14525
+ if (isNew) return `New ${singular.toLowerCase()}`;
14526
+ if (titleUser) return getUserLabel(titleUser);
14527
+ if (rawTitle !== void 0 && rawTitle !== null) {
14528
+ const resolved = resolveTitleToString(rawTitle);
14529
+ if (resolved) return resolved;
14530
+ }
14531
+ return singular;
14532
+ }
14533
+ //#endregion
13731
14534
  //#region src/components/EditViewBinding.tsx
13732
- var EntityHistoryView = lazy(() => import("./history-g9688IaK.js").then((m) => ({ default: m.EntityHistoryView })));
13733
14535
  /**
13734
14536
  * This is the default view that is used as the content of a side panel when
13735
14537
  * a record is opened.
@@ -13778,11 +14580,12 @@ function EditViewBinding({ entityId, ...props }) {
13778
14580
  children: content
13779
14581
  });
13780
14582
  }
13781
- function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, status, setStatus, formProps, canEdit }) {
14583
+ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, barActionsStart, status, setStatus, formProps, canEdit }) {
13782
14584
  const ResolvedFormActions = useComponentOverride("EditView.FormActions", EditFormActions);
13783
14585
  const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
13784
14586
  const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
13785
14587
  const context = useRebaseContext();
14588
+ const adminContext = useAdminContext();
13786
14589
  const urlController = useUrlController();
13787
14590
  const navigate = useNavigate();
13788
14591
  const [usedEntity, setUsedEntity] = useState(entity);
@@ -13793,7 +14596,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
13793
14596
  useLargeLayout();
13794
14597
  const customizationController = useCustomizationController();
13795
14598
  const plugins = customizationController.plugins;
13796
- const pluginActionsTop = useSlot("form.actions.top", useMemo(() => ({
14599
+ const formActionTopProps = useMemo(() => ({
13797
14600
  entityId,
13798
14601
  parentCollectionSlugs,
13799
14602
  parentEntityIds,
@@ -13814,7 +14617,8 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
13814
14617
  context,
13815
14618
  formContext,
13816
14619
  layout
13817
- ]));
14620
+ ]);
14621
+ const pluginActionsTop = useSlot("form.actions.top", formActionTopProps);
13818
14622
  const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(collection ? collection.defaultSelectedView : void 0, {
13819
14623
  status,
13820
14624
  entityId
@@ -13840,24 +14644,17 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
13840
14644
  const customViewsCount = customViews?.length ?? 0;
13841
14645
  const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
13842
14646
  const includeHistoryView = Boolean(collection.history);
13843
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView || includeHistoryView;
14647
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
13844
14648
  const { resolvedEntityViews } = resolvedSelectedEntityView(customViews, customizationController, void 0, canEdit);
14649
+ const legacyInspectorTab = selectedTab === "__json" ? "json" : selectedTab === "__rebase_history" ? "history" : void 0;
13845
14650
  const activeTab = useMemo(() => {
13846
- return new Set([
14651
+ return /* @__PURE__ */ new Set([
13847
14652
  "__main_##Q$SC^#S6",
13848
- ...includeJsonView ? ["__json"] : [],
13849
- ...includeHistoryView ? ["__rebase_history"] : [],
13850
14653
  ...resolvedEntityViews.map((v) => v.key),
13851
14654
  ...subcollections.map((s) => s.slug)
13852
14655
  ]);
13853
- }, [
13854
- includeJsonView,
13855
- includeHistoryView,
13856
- resolvedEntityViews,
13857
- subcollections
13858
- ]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
14656
+ }, [resolvedEntityViews, subcollections]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
13859
14657
  const { selectedEntityView, selectedSecondaryForm } = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
13860
- const actionsAtTheBottom = layout === "side_panel" || layout === "dialog" || selectedEntityView?.includeActions === "bottom";
13861
14658
  const mainViewVisible = activeTab === "__main_##Q$SC^#S6" || Boolean(selectedSecondaryForm);
13862
14659
  const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
13863
14660
  if (activeTab) mountedTabsRef.current.add(activeTab);
@@ -13922,29 +14719,49 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
13922
14719
  modifiedValues: usedFormContext?.formex?.values ?? usedEntity?.values,
13923
14720
  formContext: usedFormContext
13924
14721
  })
13925
- }) })
13926
- }, `custom_view_${customView.key}`);
13927
- }).filter(Boolean);
13928
- const globalLoading = dataLoading && !usedEntity || canEdit === void 0 && (status === "existing" || status === "copy");
13929
- const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
13930
- const jsonView = activeTab === "__json" || jsonTabMounted ? /* @__PURE__ */ jsx("div", {
13931
- className: cls("relative flex-1 h-full overflow-auto w-full", { "hidden": activeTab !== JSON_TAB_VALUE }),
13932
- role: "tabpanel",
13933
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(JsonPreviewBinding, { values: formContext?.values ?? entity?.values ?? {} }) })
13934
- }, "json_view") : null;
13935
- const historyView = includeHistoryView && activeTab === "__rebase_history" ? /* @__PURE__ */ jsx("div", {
13936
- className: "relative flex-1 h-full overflow-auto w-full",
13937
- role: "tabpanel",
13938
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, {
13939
- fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
13940
- children: /* @__PURE__ */ jsx(EntityHistoryView, {
13941
- collection,
13942
- entity: usedEntity,
13943
- formContext,
13944
- modifiedValues: formContext?.values ?? usedEntity?.values
13945
- })
13946
- }) })
13947
- }, "history_view") : null;
14722
+ }) })
14723
+ }, `custom_view_${customView.key}`);
14724
+ }).filter(Boolean);
14725
+ const globalLoading = dataLoading && !usedEntity || canEdit === void 0 && (status === "existing" || status === "copy");
14726
+ const displayTitle = useEntityDisplayTitle({
14727
+ collection,
14728
+ values: formContext?.values ?? usedEntity?.values,
14729
+ entityId,
14730
+ status
14731
+ });
14732
+ const formex = formContext?.formex;
14733
+ const hasFormErrors = Boolean(formex && Object.keys(formex.errors).length > 0 && formex.submitCount > 0);
14734
+ const saveDisabled = Boolean(!formContext || formContext.disabled || formex?.isSubmitting || status === "existing" && !formex?.dirty);
14735
+ const [inspectorTab, setInspectorTab] = useState(null);
14736
+ const sideDialogContext = useSideDialogContext();
14737
+ const canCloseAfterSave = layout === "side_panel" || layout === "dialog";
14738
+ const recordActions = useRecordActions({
14739
+ collection,
14740
+ path,
14741
+ entity: usedEntity
14742
+ });
14743
+ const recordActionItems = usedEntity && recordActions.length ? recordActions.map((action, index) => {
14744
+ const clickProps = {
14745
+ view: "form",
14746
+ entity: usedEntity,
14747
+ path,
14748
+ collection,
14749
+ context,
14750
+ sidePanelController: adminContext.sidePanelController,
14751
+ openEntityMode: layout,
14752
+ navigateBack: navigateBack ?? (() => void 0),
14753
+ formContext
14754
+ };
14755
+ return /* @__PURE__ */ jsxs(MenuItem, {
14756
+ disabled: !(!action.isEnabled || action.isEnabled(clickProps)),
14757
+ onClick: () => action.onClick(clickProps),
14758
+ children: [getIcon(action.icon, void 0, void 0, "smallest"), action.name]
14759
+ }, action.key ?? action.name ?? index);
14760
+ }) : null;
14761
+ const formPluginActions = useSlot("form.actions", formActionTopProps);
14762
+ useEffect(() => {
14763
+ if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
14764
+ }, [legacyInspectorTab]);
13948
14765
  const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
13949
14766
  const subcollectionId = subcollection.slug;
13950
14767
  const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
@@ -14038,7 +14855,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
14038
14855
  entity,
14039
14856
  initialDirtyValues,
14040
14857
  openEntityMode: layout,
14041
- forceActionsAtTheBottom: actionsAtTheBottom,
14858
+ showDefaultActions: false,
14042
14859
  initialStatus: status,
14043
14860
  className: cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className),
14044
14861
  EntityFormActionsComponent: ResolvedFormActions,
@@ -14100,14 +14917,14 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
14100
14917
  })
14101
14918
  }, `entity_detail_collection_tab_${view.name}`);
14102
14919
  });
14103
- const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog";
14104
- const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
14920
+ const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
14105
14921
  title: "Open full screen",
14106
14922
  children: /* @__PURE__ */ jsx(IconButton, {
14107
14923
  size: "small",
14108
14924
  onClick: () => {
14109
14925
  const editSuffix = collection.defaultEntityAction === "view" ? "/edit" : "";
14110
- navigate(`${urlController.buildUrlCollectionPath(`${path}/${entityId}${editSuffix}`)}#full`);
14926
+ const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}${editSuffix}`);
14927
+ navigate(`${entityUrl}#full`);
14111
14928
  },
14112
14929
  children: /* @__PURE__ */ jsx(Maximize2Icon, { size: iconSize.smallest })
14113
14930
  })
@@ -14115,60 +14932,78 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
14115
14932
  let result = /* @__PURE__ */ jsxs("div", {
14116
14933
  className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
14117
14934
  children: [
14118
- shouldShowTopBar && /* @__PURE__ */ jsxs("div", {
14119
- className: cls("h-[52px] items-center flex overflow-hidden w-full border-b pl-2 pr-2 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
14120
- children: [
14935
+ /* @__PURE__ */ jsx(EntityIdentityBar, {
14936
+ collection,
14937
+ title: displayTitle,
14938
+ entityId: status === "existing" ? entityId : void 0,
14939
+ status,
14940
+ dirty: Boolean(formContext?.formex?.dirty),
14941
+ saving: Boolean(formContext?.isSaving),
14942
+ hasErrors: hasFormErrors,
14943
+ saveDisabled: !canEdit || saveDisabled,
14944
+ onBack: layout === "full_screen" ? () => navigate(withViewMode(urlController.buildUrlCollectionPath(path))) : void 0,
14945
+ onSave: canEdit && formContext ? () => {
14946
+ sideDialogContext.setPendingClose?.(false);
14947
+ formContext.submit();
14948
+ } : void 0,
14949
+ onSaveAndClose: canEdit && formContext && canCloseAfterSave ? () => {
14950
+ sideDialogContext.setPendingClose?.(true);
14951
+ formContext.submit();
14952
+ } : void 0,
14953
+ onDiscard: canEdit && formContext ? () => formContext.formex.resetForm() : void 0,
14954
+ onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
14955
+ onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
14956
+ recordActions: recordActionItems,
14957
+ pluginActions: formPluginActions,
14958
+ leading: /* @__PURE__ */ jsxs(Fragment, { children: [barActionsStart, layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {})] }),
14959
+ trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
14960
+ pluginActionsTop,
14121
14961
  fullScreenButton,
14122
14962
  barActions?.({
14123
14963
  path,
14124
14964
  entityId,
14125
14965
  values: formContext?.values ?? usedEntity?.values ?? {},
14126
14966
  status
14127
- }),
14128
- pluginActionsTop,
14129
- hasAdditionalViews && /* @__PURE__ */ jsx("div", {
14130
- className: "flex-1 flex justify-end min-w-0 shrink-0",
14131
- children: /* @__PURE__ */ jsxs(Tabs, {
14132
- className: "!w-fit max-w-full",
14133
- value: activeTab,
14134
- onValueChange: (value) => {
14135
- onSideTabClick(value);
14136
- },
14137
- children: [
14138
- includeJsonView && /* @__PURE__ */ jsx(Tab, {
14139
- disabled: !hasAdditionalViews,
14140
- value: "__json",
14141
- className: "text-sm",
14142
- children: /* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest })
14143
- }),
14144
- includeHistoryView && /* @__PURE__ */ jsx(Tab, {
14145
- disabled: !hasAdditionalViews,
14146
- value: "__rebase_history",
14147
- className: "text-sm",
14148
- children: /* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest })
14149
- }),
14150
- /* @__PURE__ */ jsx(Tab, {
14151
- disabled: !hasAdditionalViews,
14152
- value: "__main_##Q$SC^#S6",
14153
- className: "text-sm min-w-[90px]",
14154
- children: /* @__PURE__ */ jsxs("span", {
14155
- className: "flex items-center gap-1.5",
14156
- children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
14157
- })
14158
- }),
14159
- customViewTabsStart,
14160
- customViewTabsEnd,
14161
- subcollectionTabs
14162
- ]
14163
- })
14164
14967
  })
14165
- ]
14968
+ ] })
14969
+ }),
14970
+ hasAdditionalViews && /* @__PURE__ */ jsx("div", {
14971
+ className: cls("h-10 shrink-0 flex items-stretch border-b px-2 min-w-0", defaultBorderMixin),
14972
+ children: /* @__PURE__ */ jsxs(Tabs, {
14973
+ className: "!w-full",
14974
+ innerClassName: "h-full",
14975
+ variant: "boxy",
14976
+ value: activeTab,
14977
+ onValueChange: (value) => {
14978
+ onSideTabClick(value);
14979
+ },
14980
+ children: [
14981
+ /* @__PURE__ */ jsx(Tab, {
14982
+ value: "__main_##Q$SC^#S6",
14983
+ children: /* @__PURE__ */ jsxs("span", {
14984
+ className: "flex items-center gap-1.5",
14985
+ children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
14986
+ })
14987
+ }),
14988
+ customViewTabsStart,
14989
+ customViewTabsEnd,
14990
+ subcollectionTabs
14991
+ ]
14992
+ })
14166
14993
  }),
14167
14994
  globalLoading ? /* @__PURE__ */ jsx(EntityFormSkeleton, { collection }) : /* @__PURE__ */ jsxs(Fragment, { children: [entityReadOnlyView, entityView] }),
14168
- jsonView,
14169
- historyView,
14170
14995
  customViewsView,
14171
- subCollectionsViews
14996
+ subCollectionsViews,
14997
+ /* @__PURE__ */ jsx(EntityInspector, {
14998
+ tab: inspectorTab,
14999
+ onTabChange: setInspectorTab,
15000
+ onClose: () => setInspectorTab(null),
15001
+ collection,
15002
+ entity: usedEntity,
15003
+ formContext,
15004
+ values: formContext?.values ?? usedEntity?.values,
15005
+ includeHistory: includeHistoryView
15006
+ })
14172
15007
  ]
14173
15008
  });
14174
15009
  if (plugins && plugins.length > 0) result = /* @__PURE__ */ jsx(PluginProviderStack, {
@@ -14397,21 +15232,25 @@ function SidePanelBinding(props) {
14397
15232
  parentCollectionSlugs,
14398
15233
  parentEntityIds,
14399
15234
  onEditClick: openEditView,
14400
- barActions: ({ status, values }) => /* @__PURE__ */ jsxs("div", {
15235
+ barActionsStart: /* @__PURE__ */ jsx(IconButton, {
15236
+ className: "self-center",
15237
+ size: "small",
15238
+ onClick: onClose,
15239
+ children: /* @__PURE__ */ jsx(XIcon, {})
15240
+ }),
15241
+ barActions: ({ status, values }) => /* @__PURE__ */ jsx("div", {
14401
15242
  className: "flex gap-1",
14402
- children: [/* @__PURE__ */ jsx(IconButton, {
14403
- className: "self-center",
14404
- size: "small",
14405
- onClick: onClose,
14406
- children: /* @__PURE__ */ jsx(XIcon, {})
14407
- }), allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
15243
+ children: allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
14408
15244
  className: "self-center",
14409
15245
  size: "small",
14410
15246
  onClick: () => {
14411
- if (entityId) navigate(urlController.buildUrlCollectionPath(`${path}/${entityId}`), { state: null });
15247
+ if (entityId) {
15248
+ const fullScreenUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
15249
+ navigate(fullScreenUrl, { state: null });
15250
+ }
14412
15251
  },
14413
15252
  children: /* @__PURE__ */ jsx(Maximize2Icon, {})
14414
- })]
15253
+ })
14415
15254
  }),
14416
15255
  onTabChange: ({ entityId: tabEntityId, selectedTab, collection: paramCollection }) => {
14417
15256
  if (collection?.openEntityMode === "dialog" || paramCollection?.openEntityMode === "dialog") return;
@@ -14432,25 +15271,31 @@ function SidePanelBinding(props) {
14432
15271
  onValuesModified,
14433
15272
  onSaved: onUpdate,
14434
15273
  navigateBack: closeEditView,
14435
- barActions: ({ status, values }) => /* @__PURE__ */ jsxs("div", {
15274
+ barActionsStart: /* @__PURE__ */ jsx(IconButton, {
15275
+ className: "self-center",
15276
+ size: "small",
15277
+ onClick: onClose,
15278
+ children: /* @__PURE__ */ jsx(XIcon, {})
15279
+ }),
15280
+ barActions: ({ status, values }) => /* @__PURE__ */ jsx("div", {
14436
15281
  className: "flex gap-1",
14437
- children: [/* @__PURE__ */ jsx(IconButton, {
14438
- className: "self-center",
14439
- size: "small",
14440
- onClick: onClose,
14441
- children: /* @__PURE__ */ jsx(XIcon, {})
14442
- }), allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
15282
+ children: allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
14443
15283
  className: "self-center",
14444
15284
  size: "small",
14445
15285
  onClick: () => {
14446
15286
  saveEntityToMemoryCache(status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId, values);
14447
15287
  setBlocked(false);
14448
15288
  setBlockedNavigationMessage(void 0);
14449
- if (entityId) navigate(urlController.buildUrlCollectionPath(`${path}/${entityId}`), { state: null });
14450
- else navigate(urlController.buildUrlCollectionPath(path) + "#new", { state: null });
15289
+ if (entityId) {
15290
+ const fullScreenUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
15291
+ navigate(fullScreenUrl, { state: null });
15292
+ } else {
15293
+ const fullScreenUrl = urlController.buildUrlCollectionPath(path);
15294
+ navigate(fullScreenUrl + "#new", { state: null });
15295
+ }
14451
15296
  },
14452
15297
  children: /* @__PURE__ */ jsx(Maximize2Icon, {})
14453
- })]
15298
+ })
14454
15299
  }),
14455
15300
  onTabChange: ({ entityId: tabEntityId, selectedTab, collection: paramCollection }) => {
14456
15301
  if (collection?.openEntityMode === "dialog" || paramCollection?.openEntityMode === "dialog") return;
@@ -14485,39 +15330,25 @@ function ResolvedSidePanelBinding(props) {
14485
15330
  }
14486
15331
  var NEW_URL_HASH = "new_side";
14487
15332
  var SIDE_URL_HASH = "side";
15333
+ /**
15334
+ * How wide the side panel opens.
15335
+ *
15336
+ * One width for the whole panel, whatever tab is showing. It used to depend on
15337
+ * the selected tab — the form got one width and a subcollection or custom view
15338
+ * got `calc(55vw + 768px)` — so the panel physically resized under the cursor
15339
+ * every time you switched tabs. And the "form" width was derived from how
15340
+ * deeply nested the collection's *properties* were, which is not a thing anyone
15341
+ * can predict from looking at the panel.
15342
+ *
15343
+ * Now: `width` prop, then the collection's `sideDialogWidth`, then a default
15344
+ * sized for the content the panel actually has to hold — the form column plus
15345
+ * its metadata rail — capped so it never eats the whole window.
15346
+ */
14488
15347
  function getEntityViewWidth(props, small, customizationController) {
14489
15348
  if (small) return CONTAINER_FULL_WIDTH;
14490
- const { selectedSecondaryForm } = resolvedSelectedEntityView(props.collection?.entityViews, customizationController, props.selectedTab);
14491
- const shouldUseSmallLayout = !props.selectedTab || props.selectedTab === "edit" || props.selectedTab === "__json" || props.selectedTab === "__rebase_history" || Boolean(selectedSecondaryForm);
14492
- let resolvedWidth;
14493
- if (props.width) resolvedWidth = typeof props.width === "number" ? `${props.width}px` : props.width;
14494
- else if (props.collection?.sideDialogWidth) resolvedWidth = typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
14495
- if (!shouldUseSmallLayout) return `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedWidth ?? FORM_CONTAINER_WIDTH})`;
14496
- else if (resolvedWidth) return resolvedWidth;
14497
- else if (!props.collection) return FORM_CONTAINER_WIDTH;
14498
- else return calculateCollectionDesiredWidth(props.collection);
14499
- }
14500
- var collectionViewWidthCache = {};
14501
- function calculateCollectionDesiredWidth(collection) {
14502
- if (collectionViewWidthCache[collection.slug]) return collectionViewWidthCache[collection.slug];
14503
- let result = FORM_CONTAINER_WIDTH;
14504
- if (collection?.properties) {
14505
- const values = Object.values(collection.properties).map((p) => getNestedPropertiesDepth(p));
14506
- const maxDepth = Math.max(...values);
14507
- if (maxDepth < 3) result = FORM_CONTAINER_WIDTH;
14508
- else result = 768 + 32 * (maxDepth - 2) + "px";
14509
- }
14510
- collectionViewWidthCache[collection.slug] = result;
14511
- return result;
14512
- }
14513
- function getNestedPropertiesDepth(property, accumulator = 0) {
14514
- if (property.type === "map" && property.properties) {
14515
- const values = Object.values(property.properties).flatMap((childProperty) => getNestedPropertiesDepth(childProperty, accumulator + 1));
14516
- return Math.max(...values);
14517
- } else if (property.type === "array" && property.oneOf) return accumulator + 3;
14518
- else if (property.type === "array" && property.of) if (Array.isArray(property.of)) return Math.max(...property.of.map((p) => getNestedPropertiesDepth(p, accumulator + 1)));
14519
- else return getNestedPropertiesDepth(property.of, accumulator + 1);
14520
- else return accumulator + 1;
15349
+ if (props.width) return typeof props.width === "number" ? `${props.width}px` : props.width;
15350
+ if (props.collection?.sideDialogWidth) return typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
15351
+ return SIDE_PANEL_DEFAULT_WIDTH;
14521
15352
  }
14522
15353
  var useBuildSidePanel = (collectionRegistryController, urlController, navigationStateController, sideDialogsController, authController) => {
14523
15354
  const location = useLocation();
@@ -14761,6 +15592,7 @@ function useResolvedNavigationFrom({ path }) {
14761
15592
  function useHistory(params) {
14762
15593
  const { slug, entityId, enabled = true, pageSize = 10 } = params;
14763
15594
  const apiConfig = useApiConfig();
15595
+ const apiBase = useApiBase();
14764
15596
  const [entries, setEntries] = useState([]);
14765
15597
  const [total, setTotal] = useState(0);
14766
15598
  const [isLoading, setIsLoading] = useState(false);
@@ -14777,7 +15609,7 @@ function useHistory(params) {
14777
15609
  const token = await apiConfig.getAuthToken?.();
14778
15610
  const headers = { "Content-Type": "application/json" };
14779
15611
  if (token) headers["Authorization"] = `Bearer ${token}`;
14780
- const url = `${apiConfig.apiUrl}/api/data/${slug}/${entityId}/history?limit=${pageSize}&offset=${currentOffset}`;
15612
+ const url = `${apiBase}/data/${slug}/${entityId}/history?limit=${pageSize}&offset=${currentOffset}`;
14781
15613
  const response = await fetch(url, {
14782
15614
  headers,
14783
15615
  signal
@@ -14802,6 +15634,7 @@ function useHistory(params) {
14802
15634
  }
14803
15635
  }, [
14804
15636
  apiConfig,
15637
+ apiBase,
14805
15638
  slug,
14806
15639
  entityId,
14807
15640
  enabled,
@@ -14846,7 +15679,7 @@ function useHistory(params) {
14846
15679
  const token = await apiConfig.getAuthToken?.();
14847
15680
  const headers = { "Content-Type": "application/json" };
14848
15681
  if (token) headers["Authorization"] = `Bearer ${token}`;
14849
- const url = `${apiConfig.apiUrl}/api/data/${slug}/${entityId}/history/${historyId}/revert`;
15682
+ const url = `${apiBase}/data/${slug}/${entityId}/history/${historyId}/revert`;
14850
15683
  const response = await fetch(url, {
14851
15684
  method: "POST",
14852
15685
  headers
@@ -14861,6 +15694,7 @@ function useHistory(params) {
14861
15694
  return result.data;
14862
15695
  }, [
14863
15696
  apiConfig,
15697
+ apiBase,
14864
15698
  slug,
14865
15699
  entityId
14866
15700
  ]);
@@ -15161,6 +15995,24 @@ function filterViewsByRole(views, authController) {
15161
15995
  //#endregion
15162
15996
  //#region src/hooks/navigation/utils.ts
15163
15997
  var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
15998
+ /**
15999
+ * Group names that sink to the bottom of the drawer on their name alone.
16000
+ *
16001
+ * A magic string, and it stays one for compatibility: apps have been relying on
16002
+ * `group: "Settings"` landing last since before there was a flag, and taking it
16003
+ * away would silently reshuffle their navigation.
16004
+ *
16005
+ * It is a bad way to ask for the behaviour, though — it is invisible from the
16006
+ * `group?: string` type, and it is defeated by the most ordinary edit there is:
16007
+ * translating the label. `group: "Ajustes"` is the same group to a reader and a
16008
+ * different one to this comparison, so the ordering just quietly stops
16009
+ * happening. Set {@link AppView.pinToBottom} instead, which says so.
16010
+ */
16011
+ var NAVIGATION_BOTTOM_GROUP_NAMES = ["Admin", "Settings"];
16012
+ /** Whether a group sinks to the bottom by virtue of its name. @see NAVIGATION_BOTTOM_GROUP_NAMES */
16013
+ function isBottomPinnedGroupName(groupName) {
16014
+ return groupName !== void 0 && NAVIGATION_BOTTOM_GROUP_NAMES.includes(groupName);
16015
+ }
15164
16016
  function getGroup(collectionOrView) {
15165
16017
  const trimmed = collectionOrView.group?.trim();
15166
16018
  if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
@@ -15562,10 +16414,9 @@ function useTopLevelNavigation(props) {
15562
16414
  return acc;
15563
16415
  }, [])
15564
16416
  ];
15565
- const groupOrderValue = (groupName) => {
15566
- if (groupName === "Admin" || groupName === "Settings" || groupName === "Admin") return 1;
15567
- return 0;
15568
- };
16417
+ const pinnedGroups = /* @__PURE__ */ new Set();
16418
+ for (const view of [...views ?? [], ...adminViews ?? []]) if (view.pinToBottom) pinnedGroups.add(getGroup(view));
16419
+ const groupOrderValue = (groupName) => isBottomPinnedGroupName(groupName) || groupName !== void 0 && pinnedGroups.has(groupName) ? 1 : 0;
15569
16420
  const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
15570
16421
  navigationEntries = navigationEntries.sort((a, b) => {
15571
16422
  const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
@@ -15591,8 +16442,8 @@ function useTopLevelNavigation(props) {
15591
16442
  ...additionalGroups
15592
16443
  ];
15593
16444
  const uniqueGroupsArray = [...new Set(allDefinedGroups)];
15594
- const lastGroups = uniqueGroupsArray.filter((g) => g === "Admin" || g === "Settings" || g === "Admin");
15595
- const uniqueGroups = [...uniqueGroupsArray.filter((g) => g !== "Admin" && g !== "Settings" && g !== "Admin"), ...lastGroups];
16445
+ const lastGroups = uniqueGroupsArray.filter((g) => groupOrderValue(g) === 1);
16446
+ const uniqueGroups = [...uniqueGroupsArray.filter((g) => groupOrderValue(g) === 0), ...lastGroups];
15596
16447
  const computedTopLevelNav = {
15597
16448
  allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
15598
16449
  navigationEntries,
@@ -16401,7 +17252,7 @@ function BlockEntry({ name, index, value, typeField, valueField, properties, aut
16401
17252
  * and tables to the specified properties.
16402
17253
  * @group Form fields
16403
17254
  */
16404
- function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error, showError, disabled, touched, property, includeDescription }) {
17255
+ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error, showError, disabled, touched, property, includeDescription, hideLabel }) {
16405
17256
  const { locale } = useCustomizationController();
16406
17257
  const internalValue = value || null;
16407
17258
  useClearRestoreValue({
@@ -16419,7 +17270,7 @@ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error,
16419
17270
  locale,
16420
17271
  error: showError,
16421
17272
  disabled,
16422
- label: /* @__PURE__ */ jsx(LabelWithIcon, {
17273
+ label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
16423
17274
  icon: getIconForProperty(property, "small"),
16424
17275
  required: property.validation?.required,
16425
17276
  className: showError ? "text-red-500 dark:text-red-500" : "text-text-secondary dark:text-text-secondary-dark",
@@ -16884,7 +17735,7 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
16884
17735
  if (!property.properties) throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property '${propertyKey}'${property.name ? ` ("${property.name}")` : ""}`);
16885
17736
  const mapProperties = property.properties;
16886
17737
  const mapFormView = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
16887
- className: cls("py-1 flex flex-col space-y-2", minimalistView && property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0),
17738
+ className: "py-1 flex flex-col space-y-2",
16888
17739
  children: Object.entries(mapProperties).filter(([_, property]) => !isHidden(property)).map(([entryKey, childProperty], index) => {
16889
17740
  const thisDisabled = isReadOnly(childProperty) || Boolean(childProperty.admin?.disabled);
16890
17741
  return /* @__PURE__ */ jsx("div", {
@@ -16914,7 +17765,6 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
16914
17765
  expanded
16915
17766
  } });
16916
17767
  },
16917
- className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0,
16918
17768
  innerClassName: "px-2 md:px-4 pb-2 md:pb-4 pt-1 md:pt-2 bg-white dark:bg-surface-900",
16919
17769
  title: /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
16920
17770
  propertyKey,
@@ -16971,7 +17821,7 @@ function canonicalizeMarkdown(md) {
16971
17821
  return normalizeMarkdown(md);
16972
17822
  }
16973
17823
  }
16974
- function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, includeDescription, showError, error, minimalistView, disabled: disabledProp, isSubmitting, context, customProps }) {
17824
+ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, includeDescription, hideLabel, showError, error, minimalistView, disabled: disabledProp, isSubmitting, context, customProps }) {
16975
17825
  useAuthController();
16976
17826
  const disabled = disabledProp || isSubmitting;
16977
17827
  const highlight = customProps?.highlight;
@@ -17086,7 +17936,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
17086
17936
  /* @__PURE__ */ jsxs("div", {
17087
17937
  className: "flex items-center w-full",
17088
17938
  children: [
17089
- /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
17939
+ !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
17090
17940
  propertyKey,
17091
17941
  icon: getIconForProperty(property, "small"),
17092
17942
  required: property.validation?.required,
@@ -17126,7 +17976,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
17126
17976
  * and tables to the specified properties.
17127
17977
  * @group Form fields
17128
17978
  */
17129
- function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, property, includeDescription, size = "large", autoFocus }) {
17979
+ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, property, includeDescription, hideLabel, size = "large", autoFocus }) {
17130
17980
  const of = property.of;
17131
17981
  if (!of) throw Error("Using wrong component ArrayEnumSelect");
17132
17982
  if (Array.isArray(of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
@@ -17143,11 +17993,12 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
17143
17993
  });
17144
17994
  const validValue = !!value && Array.isArray(value);
17145
17995
  const renderValue = useCallback((enumKey, list) => {
17996
+ const enumValue = enumKey !== void 0 ? getLabelOrConfigFrom(enumValues, enumKey) : void 0;
17146
17997
  return /* @__PURE__ */ jsxs(EnumValuesChip, {
17147
17998
  enumKey,
17148
17999
  enumValues,
17149
18000
  size: "medium",
17150
- children: [(enumKey !== void 0 ? getLabelOrConfigFrom(enumValues, enumKey) : void 0)?.label ?? enumKey, !list && /* @__PURE__ */ jsx("button", {
18001
+ children: [enumValue?.label ?? enumKey, !list && /* @__PURE__ */ jsx("button", {
17151
18002
  className: "ml-1 ring-offset-background rounded-full outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
17152
18003
  onMouseDown: (e) => {
17153
18004
  e.preventDefault();
@@ -17172,7 +18023,7 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
17172
18023
  value: validValue ? value.map((v) => v?.toString()) : [],
17173
18024
  disabled,
17174
18025
  modalPopover: true,
17175
- label: /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
18026
+ label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
17176
18027
  propertyKey,
17177
18028
  icon: getIconForProperty(property, "small"),
17178
18029
  required: property.validation?.required,
@@ -17210,7 +18061,7 @@ function ReferenceFieldBinding(props) {
17210
18061
  if (typeof props.property.path !== "string") return /* @__PURE__ */ jsx(ReadOnlyFieldBinding, { ...props });
17211
18062
  return /* @__PURE__ */ jsx(ReferenceFieldBindingInternal, { ...props });
17212
18063
  }
17213
- function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, size = "medium" }) {
18064
+ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, hideLabel, size = "medium" }) {
17214
18065
  if (!property.path) throw new Error("Property path is required for ReferenceFieldBinding");
17215
18066
  const refValue = value;
17216
18067
  useClearRestoreValue({
@@ -17240,7 +18091,7 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
17240
18091
  referenceDialogController.open();
17241
18092
  };
17242
18093
  return /* @__PURE__ */ jsxs(Fragment, { children: [
17243
- !minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
18094
+ !minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
17244
18095
  propertyKey,
17245
18096
  icon: getIconForProperty(property, "small"),
17246
18097
  required: property.validation?.required,
@@ -17321,6 +18172,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
17321
18172
  context,
17322
18173
  partOfArray: true,
17323
18174
  minimalistView: false,
18175
+ hideLabel: true,
17324
18176
  autoFocus: internalId === lastAddedId,
17325
18177
  index
17326
18178
  }) });
@@ -17337,8 +18189,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
17337
18189
  canAddElements,
17338
18190
  sortable,
17339
18191
  newDefaultEntry: getDefaultValueFor(property.of),
17340
- onValueChange: (value) => setFieldValue(propertyKey, value),
17341
- className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0
18192
+ onValueChange: (value) => setFieldValue(propertyKey, value)
17342
18193
  });
17343
18194
  const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
17344
18195
  propertyKey,
@@ -17382,7 +18233,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
17382
18233
  * and tables to the specified properties.
17383
18234
  * @group Form fields
17384
18235
  */
17385
- function SelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, size = "large" }) {
18236
+ function SelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, hideLabel, size = "large" }) {
17386
18237
  const enumValues = resolveEnumValues(property.enum ?? []);
17387
18238
  useClearRestoreValue({
17388
18239
  property,
@@ -17401,7 +18252,7 @@ function SelectFieldBinding({ propertyKey, value, setValue, error, showError, di
17401
18252
  fullWidth: true,
17402
18253
  position: "item-aligned",
17403
18254
  inputClassName: cls("w-full"),
17404
- label: /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
18255
+ label: hideLabel ? void 0 : /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
17405
18256
  propertyKey,
17406
18257
  children: /* @__PURE__ */ jsx(LabelWithIcon, {
17407
18258
  icon: getIconForProperty(property, "small"),
@@ -17482,13 +18333,13 @@ function StorageItemPreview({ name, property, value, onRemove, disabled, size, p
17482
18333
  }
17483
18334
  //#endregion
17484
18335
  //#region src/form/field_bindings/StorageUploadFieldBinding.tsx
17485
- var dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent min-h-[254px] outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
18336
+ var dropZoneClasses = "box-border relative pt-[2px] h-[172px] items-center border border-transparent outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
17486
18337
  var disabledClasses = fieldBackgroundDisabledMixin;
17487
18338
  var nonActiveDropClasses = fieldBackgroundHoverMixin;
17488
18339
  var activeDropClasses = "pt-0 border-2 border-solid";
17489
18340
  var acceptDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-green-500";
17490
18341
  var rejectDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-red-500";
17491
- function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, minimalistView, property, includeDescription, context, isSubmitting }) {
18342
+ function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, minimalistView, property, includeDescription, hideLabel, context, isSubmitting }) {
17492
18343
  useAuthController();
17493
18344
  const storageSource = useStorageSource();
17494
18345
  const disabled = isReadOnly(property) || !!property.admin?.disabled || isSubmitting || context.disabled;
@@ -17509,7 +18360,7 @@ function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showEr
17509
18360
  setValue
17510
18361
  });
17511
18362
  return /* @__PURE__ */ jsxs(Fragment, { children: [
17512
- !minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
18363
+ !minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
17513
18364
  propertyKey,
17514
18365
  icon: getIconForProperty(property, "small"),
17515
18366
  required: property.validation?.required,
@@ -17579,7 +18430,7 @@ function SortableStorageItem({ id, entry, property, metadata, storagePathBuilder
17579
18430
  style,
17580
18431
  ...attributes,
17581
18432
  ...listeners,
17582
- className: cls("rounded-md m-1"),
18433
+ className: cls("rounded-md shrink-0"),
17583
18434
  tabIndex: -1,
17584
18435
  children: child
17585
18436
  });
@@ -17619,7 +18470,7 @@ function FileDropComponent({ storage, disabled, onFilesAdded, multipleFilesSuppo
17619
18470
  [disabledClasses]: disabled || isDndItemDragging
17620
18471
  }),
17621
18472
  children: [/* @__PURE__ */ jsxs("div", {
17622
- className: cls("flex items-center p-1 px-4 no-scrollbar", multipleFilesSupported && internalValue.length ? "overflow-auto" : "", multipleFilesSupported && internalValue.length ? "min-h-[180px]" : "min-h-[250px]"),
18473
+ className: cls("flex items-center gap-2 p-1 px-3 no-scrollbar", multipleFilesSupported && internalValue.length ? "overflow-x-auto overflow-y-hidden" : "", "h-[126px]"),
17623
18474
  children: [/* @__PURE__ */ jsx("input", {
17624
18475
  autoFocus,
17625
18476
  ...getInputProps()
@@ -17636,10 +18487,10 @@ function FileDropComponent({ storage, disabled, onFilesAdded, multipleFilesSuppo
17636
18487
  isSortable: multipleFilesSupported
17637
18488
  }, entry.id))]
17638
18489
  }), /* @__PURE__ */ jsx("div", {
17639
- className: "flex-grow min-h-[38px] box-border m-2 text-center",
18490
+ className: "box-border mx-3 mb-2.5 h-[34px] flex items-center justify-center text-center",
17640
18491
  children: /* @__PURE__ */ jsx(Typography, {
17641
18492
  align: "center",
17642
- variant: "label",
18493
+ variant: "caption",
17643
18494
  className: disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
17644
18495
  children: helpText
17645
18496
  })
@@ -17742,7 +18593,7 @@ function StorageUpload({ property, name, value, setInternalValue, onChange, mult
17742
18593
  * and tables to the specified properties.
17743
18594
  * @group Form fields
17744
18595
  */
17745
- var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, disabled, size = "large", property, includeDescription }) {
18596
+ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, disabled, size = "large", property, includeDescription, hideLabel }) {
17746
18597
  useClearRestoreValue({
17747
18598
  property,
17748
18599
  value,
@@ -17754,8 +18605,8 @@ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setVa
17754
18605
  value,
17755
18606
  onValueChange: (v) => setValue(v),
17756
18607
  error: showError,
17757
- className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0,
17758
- label: /* @__PURE__ */ jsx(LabelWithIcon, {
18608
+ position: hideLabel ? "start" : void 0,
18609
+ label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
17759
18610
  icon: getIconForProperty(property, "small"),
17760
18611
  required: property.validation?.required,
17761
18612
  title: property.name ?? propertyKey
@@ -17780,7 +18631,7 @@ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setVa
17780
18631
  * and tables to the specified properties.
17781
18632
  * @group Form fields
17782
18633
  */
17783
- function TextFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
18634
+ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
17784
18635
  let multiline;
17785
18636
  let url;
17786
18637
  if (property.type === "string") {
@@ -17813,7 +18664,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
17813
18664
  if (property.email) inputType = "email";
17814
18665
  else if (property.admin?.urlPreview) inputType = "url";
17815
18666
  }
17816
- const label = /* @__PURE__ */ jsx(LabelWithIcon, {
18667
+ const label = hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
17817
18668
  icon: getIconForProperty(property, "small"),
17818
18669
  required: property.validation?.required || property.isId === true,
17819
18670
  title: property.name ?? propertyKey
@@ -17822,9 +18673,9 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
17822
18673
  /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
17823
18674
  propertyKey,
17824
18675
  children: isMultiline ? /* @__PURE__ */ jsxs("div", {
17825
- className: cls("rounded-md relative max-w-full min-h-[64px]", fieldBackgroundMixin, fieldBackgroundHoverMixin, showError && error ? "border border-red-500 dark:border-red-600" : "", property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0),
18676
+ className: cls("rounded-md relative max-w-full min-h-[64px]", fieldBackgroundMixin, fieldBackgroundHoverMixin, showError && error ? "border border-red-500 dark:border-red-600" : ""),
17826
18677
  children: [
17827
- /* @__PURE__ */ jsx("div", {
18678
+ label && /* @__PURE__ */ jsx("div", {
17828
18679
  className: "pointer-events-none absolute top-1 text-xs font-medium px-3 text-text-secondary dark:text-text-secondary-dark",
17829
18680
  children: label
17830
18681
  }),
@@ -17833,7 +18684,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
17833
18684
  onChange,
17834
18685
  autoFocus,
17835
18686
  disabled,
17836
- className: cls("rounded-md resize-none w-full outline-none p-[32px] text-base bg-transparent min-h-[64px] px-3 pt-8", disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500", showError && error ? "text-red-500 dark:text-red-600" : "")
18687
+ className: cls("rounded-md resize-none w-full outline-none text-base bg-transparent min-h-[64px] px-3", label ? "pt-8 pb-2" : "py-2", disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500", showError && error ? "text-red-500 dark:text-red-600" : "")
17837
18688
  }),
17838
18689
  property.admin?.clearable && /* @__PURE__ */ jsx("div", {
17839
18690
  className: "flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-4",
@@ -17848,12 +18699,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
17848
18699
  value: displayValue,
17849
18700
  onChange,
17850
18701
  autoFocus,
17851
- className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0,
17852
- label: /* @__PURE__ */ jsx(LabelWithIcon, {
17853
- icon: getIconForProperty(property, "small"),
17854
- required: property.validation?.required || property.isId === true,
17855
- title: property.name ?? propertyKey
17856
- }),
18702
+ label,
17857
18703
  type: inputType,
17858
18704
  disabled,
17859
18705
  endAdornment: property.admin?.clearable && /* @__PURE__ */ jsx(IconButton, {
@@ -17892,7 +18738,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
17892
18738
  * Vector object structure expected by Rebase driver.
17893
18739
  * @group Form fields
17894
18740
  */
17895
- function VectorFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
18741
+ function VectorFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
17896
18742
  const isVectorObject = (val) => {
17897
18743
  return typeof val === "object" && val !== null && "value" in val;
17898
18744
  };
@@ -17922,7 +18768,7 @@ function VectorFieldBinding({ propertyKey, value, setValue, error, showError, di
17922
18768
  value: raw.split(",").map((str) => str.trim()).filter((str) => str !== "").map(Number)
17923
18769
  });
17924
18770
  };
17925
- const label = /* @__PURE__ */ jsx(LabelWithIcon, {
18771
+ const label = hideLabel ? null : /* @__PURE__ */ jsx(LabelWithIcon, {
17926
18772
  icon: getIconForProperty(property, "small"),
17927
18773
  required: property.validation?.required,
17928
18774
  title: property.name ?? propertyKey
@@ -18065,7 +18911,7 @@ var inRange = (n, [min, max]) => n >= min && n <= max;
18065
18911
  *
18066
18912
  * @group Form fields
18067
18913
  */
18068
- function GeopointFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
18914
+ function GeopointFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
18069
18915
  const coordinates = readCoordinates(value);
18070
18916
  const [latText, setLatText] = useState(() => coordinates ? String(coordinates.latitude) : "");
18071
18917
  const [lngText, setLngText] = useState(() => coordinates ? String(coordinates.longitude) : "");
@@ -18111,7 +18957,7 @@ function GeopointFieldBinding({ propertyKey, value, setValue, error, showError,
18111
18957
  children: [
18112
18958
  /* @__PURE__ */ jsxs("div", {
18113
18959
  className: "flex items-center justify-between mb-1",
18114
- children: [/* @__PURE__ */ jsx(LabelWithIcon, {
18960
+ children: [!hideLabel && /* @__PURE__ */ jsx(LabelWithIcon, {
18115
18961
  icon: getIconForProperty(property, "small"),
18116
18962
  required: property.validation?.required,
18117
18963
  title: property.name ?? propertyKey
@@ -18199,7 +19045,7 @@ function humanSize(bytes) {
18199
19045
  *
18200
19046
  * @group Form fields
18201
19047
  */
18202
- function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
19048
+ function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
18203
19049
  const [text, setText] = useState(typeof value === "string" ? value : "");
18204
19050
  const [isEditing, setIsEditing] = useState(false);
18205
19051
  useEffect(() => {
@@ -18230,7 +19076,7 @@ function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, di
18230
19076
  children: [
18231
19077
  /* @__PURE__ */ jsx("div", {
18232
19078
  className: "flex items-center justify-between mb-1",
18233
- children: /* @__PURE__ */ jsx(LabelWithIcon, {
19079
+ children: !hideLabel && /* @__PURE__ */ jsx(LabelWithIcon, {
18234
19080
  icon: getIconForProperty(property, "small"),
18235
19081
  required: property.validation?.required,
18236
19082
  title: property.name ?? propertyKey
@@ -18426,14 +19272,14 @@ function RelationFieldBinding(props) {
18426
19272
  relation
18427
19273
  });
18428
19274
  }
18429
- function RelationSelectorBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, setValue, relation, manyRelation }) {
19275
+ function RelationSelectorBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation, manyRelation }) {
18430
19276
  const singleValue = (!manyRelation && value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null) ?? null;
18431
19277
  const multipleValue = manyRelation && Array.isArray(value) ? value : [];
18432
19278
  const selectorSize = size === "large" ? "medium" : size;
18433
19279
  return /* @__PURE__ */ jsxs("div", {
18434
19280
  className: "",
18435
19281
  children: [
18436
- /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
19282
+ !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
18437
19283
  propertyKey,
18438
19284
  icon: getIconForProperty(property, "small"),
18439
19285
  required: property.validation?.required,
@@ -18461,7 +19307,7 @@ function RelationSelectorBinding({ propertyKey, value, size, error, showError, d
18461
19307
  ]
18462
19308
  });
18463
19309
  }
18464
- function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, setValue, relation }) {
19310
+ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation }) {
18465
19311
  const normalizedValue = value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null;
18466
19312
  const validValue = !!normalizedValue;
18467
19313
  const collection = relation?.target();
@@ -18483,7 +19329,7 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
18483
19329
  };
18484
19330
  const usedRelation = Array.isArray(value) ? void 0 : normalizedValue ?? void 0;
18485
19331
  return /* @__PURE__ */ jsxs(Fragment, { children: [
18486
- /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
19332
+ !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
18487
19333
  propertyKey,
18488
19334
  icon: getIconForProperty(property, "small"),
18489
19335
  required: property.validation?.required,
@@ -18532,10 +19378,10 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
18532
19378
  * and tables to the specified properties.
18533
19379
  * @group Form fields
18534
19380
  */
18535
- function UserSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, size = "large" }) {
19381
+ function UserSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, hideLabel, size = "large" }) {
18536
19382
  const selectorSize = size === "large" ? "medium" : size;
18537
19383
  return /* @__PURE__ */ jsxs(Fragment, { children: [
18538
- /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
19384
+ !hideLabel && /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
18539
19385
  propertyKey,
18540
19386
  children: /* @__PURE__ */ jsx(LabelWithIcon, {
18541
19387
  icon: getIconForProperty(property, "small"),
@@ -19047,7 +19893,7 @@ var PropertyFieldBinding = React.memo(PropertyFieldBindingInternal, (a, b) => {
19047
19893
  if (shouldPropertyReRender(b.property)) return false;
19048
19894
  return false;
19049
19895
  });
19050
- function PropertyFieldBindingInternal({ propertyKey, property, context, includeDescription, underlyingValueHasChanged, disabled: disabledProp, partOfArray, partOfBlock, minimalistView, autoFocus, index, size, onPropertyChange }) {
19896
+ function PropertyFieldBindingInternal({ propertyKey, property, context, includeDescription, hideLabel, underlyingValueHasChanged, disabled: disabledProp, partOfArray, partOfBlock, minimalistView, autoFocus, index, size, onPropertyChange }) {
19051
19897
  const authController = useAuthController();
19052
19898
  const customizationController = useCustomizationController();
19053
19899
  return /* @__PURE__ */ jsx(Field, {
@@ -19099,6 +19945,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
19099
19945
  propertyKey,
19100
19946
  property: resolvedProperty,
19101
19947
  includeDescription,
19948
+ hideLabel,
19102
19949
  underlyingValueHasChanged,
19103
19950
  context,
19104
19951
  disabled,
@@ -19114,7 +19961,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
19114
19961
  }
19115
19962
  }, propertyKey);
19116
19963
  }
19117
- function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
19964
+ function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, hideLabel, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
19118
19965
  const { plugins } = useCustomizationController();
19119
19966
  const customFieldProps = property.admin?.customProps;
19120
19967
  const value = formexFieldProps.field.value;
@@ -19148,6 +19995,7 @@ function FieldInternal({ Component, componentProps: { propertyKey, property, inc
19148
19995
  showError,
19149
19996
  isSubmitting,
19150
19997
  includeDescription: includeDescription ?? true,
19998
+ hideLabel: hideLabel ?? false,
19151
19999
  property,
19152
20000
  disabled: disabled ?? false,
19153
20001
  underlyingValueHasChanged: underlyingValueHasChanged ?? false,
@@ -19227,22 +20075,18 @@ var ErrorFocus = ({ containerRef }) => {
19227
20075
  };
19228
20076
  //#endregion
19229
20077
  //#region src/form/EntityFormActions.tsx
19230
- function EntityFormActions({ path, collection, entity, layout, savingError, formex, disabled, status, pluginActions, openEntityMode, navigateBack, formContext }) {
19231
- const bottomActionsProps = {
19232
- path,
19233
- savingError,
19234
- entity,
19235
- collection,
19236
- disabled,
19237
- status,
19238
- pluginActions,
19239
- openEntityMode,
19240
- navigateBack,
19241
- formContext,
19242
- formex,
19243
- className: layout === "responsive" ? "@6xl:hidden" : void 0
19244
- };
19245
- const sideActionsProps = {
20078
+ /**
20079
+ * The form's own action bar.
20080
+ *
20081
+ * There used to be two variants: this one, and a `w-80 2xl:w-96` sticky rail
20082
+ * holding the same two buttons above several hundred pixels of nothing — a
20083
+ * fifth of a 1440px viewport reserved for a Save button. The rail is gone. When
20084
+ * the container supplies its own actions (the entity view's identity bar does)
20085
+ * it passes `showDefaultActions={false}`, and this renders only what the bar
20086
+ * cannot host: the saving error, plugin actions, and collection form actions.
20087
+ */
20088
+ function EntityFormActions({ path, collection, entity, savingError, formex, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, showDefaultActions = true }) {
20089
+ return buildBottomActions({
19246
20090
  path,
19247
20091
  savingError,
19248
20092
  entity,
@@ -19254,17 +20098,16 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
19254
20098
  navigateBack,
19255
20099
  formContext,
19256
20100
  formex,
19257
- className: layout === "responsive" ? "hidden @6xl:flex" : void 0
19258
- };
19259
- const bottomActions = buildBottomActions(bottomActionsProps);
19260
- const sideActions = buildSideActions(sideActionsProps);
19261
- if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
19262
- return layout === "bottom" ? bottomActions : sideActions;
20101
+ showDefaultActions
20102
+ });
19263
20103
  }
19264
- function buildBottomActions({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, className }) {
20104
+ function buildBottomActions({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, showDefaultActions, className }) {
19265
20105
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
20106
+ const entityActions = entity ? formActions ?? [] : [];
20107
+ const hasPluginActions = Boolean(pluginActions && pluginActions.length > 0);
20108
+ if (!showDefaultActions && !savingError && !entityActions.length && !hasPluginActions) return null;
19266
20109
  return /* @__PURE__ */ jsxs(DialogActions, {
19267
- position: "absolute",
20110
+ position: "sticky",
19268
20111
  className,
19269
20112
  children: [
19270
20113
  savingError && /* @__PURE__ */ jsx("div", {
@@ -19274,9 +20117,9 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
19274
20117
  children: savingError.message
19275
20118
  })
19276
20119
  }),
19277
- entity && (formActions ?? []).length > 0 && /* @__PURE__ */ jsx("div", {
20120
+ entityActions.length > 0 && /* @__PURE__ */ jsx("div", {
19278
20121
  className: "grow flex overflow-auto no-scrollbar",
19279
- children: (formActions ?? []).map((action) => /* @__PURE__ */ jsx(IconButton, {
20122
+ children: entityActions.map((action) => /* @__PURE__ */ jsx(IconButton, {
19280
20123
  color: "primary",
19281
20124
  onClick: (event) => {
19282
20125
  event.stopPropagation();
@@ -19296,14 +20139,13 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
19296
20139
  }, action.name))
19297
20140
  }),
19298
20141
  pluginActions,
19299
- /* @__PURE__ */ jsx(Button, {
20142
+ showDefaultActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
19300
20143
  variant: "text",
19301
20144
  disabled: disabled || formex.isSubmitting,
19302
20145
  color: "primary",
19303
20146
  type: "reset",
19304
20147
  children: status === "existing" ? "Discard" : "Clear"
19305
- }),
19306
- /* @__PURE__ */ jsx(Tooltip, {
20148
+ }), /* @__PURE__ */ jsx(Tooltip, {
19307
20149
  title: hasErrors ? "Fix highlighted errors before saving" : void 0,
19308
20150
  children: /* @__PURE__ */ jsxs(LoadingButton, {
19309
20151
  variant: "filled",
@@ -19318,47 +20160,7 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
19318
20160
  status === "new" && "Create"
19319
20161
  ]
19320
20162
  })
19321
- })
19322
- ]
19323
- });
19324
- }
19325
- function buildSideActions({ savingError, entity, formActions, path, openEntityMode, collection, disabled, status, pluginActions, formex, className }) {
19326
- const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
19327
- return /* @__PURE__ */ jsxs("div", {
19328
- 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),
19329
- children: [
19330
- /* @__PURE__ */ jsx(Tooltip, {
19331
- title: hasErrors ? "Fix highlighted errors before saving" : void 0,
19332
- children: /* @__PURE__ */ jsxs(LoadingButton, {
19333
- fullWidth: true,
19334
- variant: "filled",
19335
- color: "primary",
19336
- type: "submit",
19337
- loading: formex.isSubmitting,
19338
- startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
19339
- disabled: disabled || formex.isSubmitting,
19340
- children: [
19341
- status === "existing" && "Save",
19342
- status === "copy" && "Create copy",
19343
- status === "new" && "Create"
19344
- ]
19345
- })
19346
- }),
19347
- /* @__PURE__ */ jsx(Button, {
19348
- fullWidth: true,
19349
- variant: "text",
19350
- disabled: disabled || formex.isSubmitting,
19351
- type: "reset",
19352
- children: status === "existing" ? "Discard" : "Clear"
19353
- }),
19354
- pluginActions,
19355
- savingError && /* @__PURE__ */ jsx("div", {
19356
- className: "text-right",
19357
- children: /* @__PURE__ */ jsx(Typography, {
19358
- color: "error",
19359
- children: savingError.message
19360
- })
19361
- })
20163
+ })] })
19362
20164
  ]
19363
20165
  });
19364
20166
  }
@@ -19520,7 +20322,7 @@ function getChanges(source, comparison) {
19520
20322
  const changes = {};
19521
20323
  if (!source) return {};
19522
20324
  if (!comparison) return source;
19523
- const allKeys = Array.from(new Set([...Object.keys(source), ...Object.keys(comparison)]));
20325
+ const allKeys = Array.from(/* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(comparison)]));
19524
20326
  for (const key of allKeys) {
19525
20327
  const sourceValue = source[key];
19526
20328
  const comparisonValue = comparison[key];
@@ -19581,8 +20383,7 @@ function zodToFormErrors(zodError) {
19581
20383
  *
19582
20384
  * @group Components
19583
20385
  */
19584
- 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 }) {
19585
- useCustomizationController();
20386
+ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent = EntityFormActions, showDefaultActions = true, navigateBack: navigateBackProp, children, onSubmit: onSubmitProp, onValuesChangeDeferred: onValuesChangeDeferredProp, onReset: onResetProp, uniqueFieldValidator: uniqueFieldValidatorProp, beforeFields, afterFields, pluginActions: pluginActionsProp, computedInitialValues, hasLocalChanges: hasLocalChangesProp, localChangesData, manualApplyLocalChanges, localChangesCacheKey, onClearLocalChanges }) {
19586
20387
  const navigateBack = useCallback(() => {
19587
20388
  if (navigateBackProp) navigateBackProp();
19588
20389
  }, [navigateBackProp]);
@@ -19604,7 +20405,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19604
20405
  else return entityIdProp;
19605
20406
  }, [entityIdProp, status]);
19606
20407
  const [entityId, setEntityId] = useState(initialEntityId);
19607
- const [entityIdError, setEntityIdError] = useState(false);
19608
20408
  const [savingError, setSavingError] = useState();
19609
20409
  const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
19610
20410
  const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
@@ -19623,7 +20423,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19623
20423
  const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
19624
20424
  const onSubmit = (values, formexController) => {
19625
20425
  setSavingError(void 0);
19626
- setEntityIdError(false);
19627
20426
  if (status === "existing") {
19628
20427
  if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
19629
20428
  } else if (status !== "new" && status !== "copy") throw Error("New FormType added, check EntityForm");
@@ -19731,7 +20530,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19731
20530
  savingError,
19732
20531
  status,
19733
20532
  openEntityMode,
19734
- disabled
20533
+ disabled,
20534
+ isSaving: formex.isSubmitting || isSavingAutoSave
19735
20535
  };
19736
20536
  useEffect(() => {
19737
20537
  onFormContextReady?.(formContext);
@@ -19739,13 +20539,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19739
20539
  formex.version,
19740
20540
  collection,
19741
20541
  entityId,
19742
- path
20542
+ path,
20543
+ formex.dirty,
20544
+ formex.isSubmitting,
20545
+ formex.submitCount,
20546
+ isSavingAutoSave
19743
20547
  ]);
19744
20548
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
19745
- const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, formex.values, entityId);
19746
- const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
19747
- const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
19748
- const title = titleUser ? getUserLabel(titleUser) : rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
19749
20549
  const modified = formex.dirty;
19750
20550
  useEffect(() => {
19751
20551
  if (!autoSave) onValuesModified?.(modified, formex.values);
@@ -19779,6 +20579,106 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19779
20579
  formex.setFieldValue
19780
20580
  ]);
19781
20581
  const formFieldKeys = getFormFieldKeys(collection);
20582
+ const layout = useMemo(() => resolveFormLayout({
20583
+ collection,
20584
+ fieldKeys: formFieldKeys,
20585
+ status
20586
+ }), [
20587
+ collection,
20588
+ formFieldKeys.join(","),
20589
+ status
20590
+ ]);
20591
+ const formElementRef = useRef(null);
20592
+ const railVisible = useRailVisible(formElementRef);
20593
+ const showRail = !Builder && layout.hasRail && railVisible;
20594
+ const [collapsedSections, setCollapsedSections] = useState(() => new Set(layout.sections.filter((s) => s.collapsed).map((s) => s.key)));
20595
+ const toggleSection = useCallback((key) => {
20596
+ setCollapsedSections((prev) => {
20597
+ const next = new Set(prev);
20598
+ if (next.has(key)) next.delete(key);
20599
+ else next.add(key);
20600
+ return next;
20601
+ });
20602
+ }, []);
20603
+ const errorKeys = useMemo(() => new Set(formex.submitCount > 0 ? Object.keys(formex.errors) : []), [formex.errors, formex.submitCount]);
20604
+ const autoFocusKey = useMemo(() => {
20605
+ if (status !== "new" && status !== "copy") return void 0;
20606
+ return [...layout.sections.flatMap((s) => s.fields), ...layout.sidebar].find((field) => {
20607
+ const property = collection.properties?.[field.key];
20608
+ return property ? !isFieldDisabled(property) : false;
20609
+ })?.key;
20610
+ }, [
20611
+ layout,
20612
+ collection,
20613
+ status,
20614
+ disabledProp
20615
+ ]);
20616
+ function isFieldDisabled(property) {
20617
+ const isNew = status === "new" || status === "copy";
20618
+ const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
20619
+ return Boolean(disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.admin?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto);
20620
+ }
20621
+ const renderField = (field) => {
20622
+ if (field.additional) {
20623
+ const additionalField = collection.additionalFields?.find((f) => f.key === field.key);
20624
+ if (!additionalField || !entity) return null;
20625
+ const AdditionalFieldBuilder = additionalField.Builder;
20626
+ if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
20627
+ const additionalFieldContext = formContext;
20628
+ const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
20629
+ entity,
20630
+ context: additionalFieldContext
20631
+ }) : /* @__PURE__ */ jsx("div", {
20632
+ className: "w-full",
20633
+ children: additionalField.value?.({
20634
+ entity,
20635
+ context: additionalFieldContext
20636
+ })?.toString()
20637
+ });
20638
+ return /* @__PURE__ */ jsxs("div", {
20639
+ className: spanClass(field.span),
20640
+ children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
20641
+ propertyKey: field.key,
20642
+ icon: /* @__PURE__ */ jsx(AlignLeftIcon, { size: iconSize.small }),
20643
+ title: additionalField.name,
20644
+ className: "text-text-secondary dark:text-text-secondary-dark"
20645
+ }), /* @__PURE__ */ jsx("div", {
20646
+ className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-auto no-scrollbar"),
20647
+ children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
20648
+ })]
20649
+ }, `additional_${field.key}`);
20650
+ }
20651
+ const property = collection.properties?.[field.key];
20652
+ if (!property) {
20653
+ console.warn(`Property ${field.key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
20654
+ return null;
20655
+ }
20656
+ const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(field.key) && formex.touched[field.key];
20657
+ const disabled = isFieldDisabled(property);
20658
+ const selfLabelling = isSelfLabellingProperty(property);
20659
+ const cmsFormFieldProps = {
20660
+ propertyKey: field.key,
20661
+ disabled,
20662
+ property,
20663
+ includeDescription: false,
20664
+ hideLabel: !selfLabelling,
20665
+ underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
20666
+ context: formContext,
20667
+ partOfArray: false,
20668
+ minimalistView: false,
20669
+ size: "large",
20670
+ autoFocus: autoFocusKey === field.key
20671
+ };
20672
+ return /* @__PURE__ */ jsx("div", {
20673
+ className: spanClass(field.span),
20674
+ children: /* @__PURE__ */ jsx(FieldBlock, {
20675
+ propertyKey: field.key,
20676
+ property,
20677
+ showLabel: !selfLabelling,
20678
+ children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps })
20679
+ })
20680
+ }, `field_${field.key}`);
20681
+ };
19782
20682
  const formFields = () => {
19783
20683
  if (Builder) return /* @__PURE__ */ jsx(Builder, {
19784
20684
  collection,
@@ -19786,99 +20686,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19786
20686
  modifiedValues: formex.values,
19787
20687
  formContext
19788
20688
  });
19789
- const isNewEntity = status === "new" || status === "copy";
19790
- let firstFocusableIndex = -1;
19791
- return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key) => {
19792
- const property = collection.properties?.[key];
19793
- if (property) {
19794
- const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key) && formex.touched[key];
19795
- const isNew = status === "new" || status === "copy";
19796
- const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
19797
- const disabled = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.admin?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto;
19798
- if (isHidden(property)) return null;
19799
- const widthPercentage = property.admin?.widthPercentage ?? 100;
19800
- const shouldAutoFocus = isNewEntity && !disabled && firstFocusableIndex === -1;
19801
- if (shouldAutoFocus) firstFocusableIndex = 0;
19802
- return /* @__PURE__ */ jsx(FormEntry, {
19803
- propertyKey: key,
19804
- widthPercentage,
19805
- children: /* @__PURE__ */ jsx(PropertyFieldBinding, {
19806
- propertyKey: key,
19807
- disabled,
19808
- property,
19809
- includeDescription: Boolean(property.description),
19810
- underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
19811
- context: formContext,
19812
- partOfArray: false,
19813
- minimalistView: false,
19814
- autoFocus: shouldAutoFocus
19815
- })
19816
- }, `field_${key}`);
19817
- }
19818
- const additionalField = collection.additionalFields?.find((f) => f.key === key);
19819
- if (additionalField && entity) {
19820
- const AdditionalFieldBuilder = additionalField.Builder;
19821
- if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
19822
- const additionalFieldContext = formContext;
19823
- const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
19824
- entity,
19825
- context: additionalFieldContext
19826
- }) : /* @__PURE__ */ jsx("div", {
19827
- className: "w-full",
19828
- children: additionalField.value?.({
19829
- entity,
19830
- context: additionalFieldContext
19831
- })?.toString()
19832
- });
19833
- return /* @__PURE__ */ jsxs("div", {
19834
- className: "w-full",
19835
- children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
19836
- propertyKey: key,
19837
- icon: /* @__PURE__ */ jsx(AlignLeftIcon, { size: iconSize.small }),
19838
- title: additionalField.name,
19839
- className: "text-text-secondary dark:text-text-secondary-dark ml-3.5"
19840
- }), /* @__PURE__ */ jsx("div", {
19841
- className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar"),
19842
- children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
19843
- })]
19844
- }, `additional_${key}`);
19845
- }
19846
- console.warn(`Property ${key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
19847
- return null;
19848
- }).filter(Boolean) });
20689
+ return /* @__PURE__ */ jsx(FormSections, {
20690
+ sections: showRail || !layout.sidebar.length ? layout.sections : [...layout.sections, {
20691
+ key: "__rail",
20692
+ title: "Settings",
20693
+ collapsible: false,
20694
+ collapsed: false,
20695
+ fields: layout.sidebar
20696
+ }],
20697
+ collapsed: collapsedSections,
20698
+ onToggle: toggleSection,
20699
+ errorKeys,
20700
+ renderField
20701
+ });
19849
20702
  };
19850
20703
  const formRef = useRef(null);
19851
20704
  const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
19852
20705
  const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
19853
20706
  beforeFields,
19854
- !Builder && /* @__PURE__ */ jsxs("div", {
19855
- className: "w-full flex flex-col items-start my-4 lg:my-6",
19856
- children: [
19857
- /* @__PURE__ */ jsx(Typography, {
19858
- className: "my-4 grow line-clamp-1 " + (collection.hideIdFromForm ? "mb-6" : ""),
19859
- variant: "h4",
19860
- children: title ?? collection.singularName ?? collection.name
19861
- }),
19862
- !entity?.values && initialStatus === "existing" && /* @__PURE__ */ jsx(Alert, {
19863
- color: "warning",
19864
- size: "small",
19865
- outerClassName: "w-full mb-4 text-xs",
19866
- children: "This entity does not exist yet"
19867
- }),
19868
- showEntityPath && /* @__PURE__ */ jsx(Alert, {
19869
- color: "base",
19870
- outerClassName: "w-full",
19871
- size: "small",
19872
- children: /* @__PURE__ */ jsxs("code", {
19873
- className: "text-xs select-all text-text-secondary dark:text-text-secondary-dark",
19874
- children: [
19875
- entity?.path ?? path,
19876
- "/",
19877
- entityId
19878
- ]
19879
- })
19880
- })
19881
- ]
20707
+ !entity?.values && initialStatus === "existing" && /* @__PURE__ */ jsx(Alert, {
20708
+ color: "warning",
20709
+ size: "small",
20710
+ outerClassName: "w-full mb-4 text-xs",
20711
+ children: "This entity does not exist yet"
19882
20712
  }),
19883
20713
  children,
19884
20714
  initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
@@ -19893,12 +20723,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19893
20723
  children: "Please fix the highlighted errors before saving."
19894
20724
  }),
19895
20725
  formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
19896
- className: "mt-12 flex flex-col gap-8",
19897
20726
  ref: formRef,
19898
20727
  children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
19899
20728
  }) }),
19900
20729
  afterFields,
19901
- forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" }),
19902
20730
  /* @__PURE__ */ jsxs(Dialog, {
19903
20731
  open: discardDialogOpen,
19904
20732
  onOpenChange: setDiscardDialogOpen,
@@ -19930,7 +20758,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19930
20758
  collection,
19931
20759
  path,
19932
20760
  entity,
19933
- layout: forceActionsAtTheBottom ? "bottom" : "responsive",
19934
20761
  savingError,
19935
20762
  formex,
19936
20763
  disabled: actionsDisabled,
@@ -19944,6 +20771,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19944
20771
  return /* @__PURE__ */ jsx(Formex, {
19945
20772
  value: formex,
19946
20773
  children: /* @__PURE__ */ jsxs("form", {
20774
+ ref: formElementRef,
19947
20775
  onSubmit: formex.handleSubmit,
19948
20776
  onReset: (e) => {
19949
20777
  e.preventDefault();
@@ -19951,56 +20779,56 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19951
20779
  else formex.resetForm({ values: baseInitialValues });
19952
20780
  },
19953
20781
  noValidate: true,
19954
- className: cls("@container flex-1 flex flex-row w-full overflow-y-auto justify-center", className),
19955
- children: [/* @__PURE__ */ jsx("div", {
19956
- id: `form_${path}`,
19957
- className: cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit"),
19958
- children: /* @__PURE__ */ jsxs("div", {
19959
- 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"),
19960
- children: [/* @__PURE__ */ jsxs("div", {
19961
- className: "flex flex-row gap-4 justify-end h-0 overflow-visible sticky top-4 z-10",
19962
- children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx(LocalChangesMenu, {
19963
- cacheKey: localChangesCacheKey,
19964
- properties: collection.properties,
19965
- cachedData: localChangesData,
19966
- formex,
19967
- onClearLocalChanges: () => {
19968
- setLocalChangesCleared(true);
19969
- onClearLocalChanges?.();
19970
- }
19971
- }), isSavingAutoSave ? /* @__PURE__ */ jsx(Tooltip, {
19972
- title: "Saving…",
19973
- children: /* @__PURE__ */ jsx(Chip, {
19974
- size: "small",
19975
- className: "py-1",
19976
- colorScheme: "blueDarker",
19977
- children: /* @__PURE__ */ jsx(LoaderIcon, {
19978
- size: iconSize.smallest,
19979
- className: "animate-spin"
19980
- })
19981
- })
19982
- }) : formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
19983
- title: "Form has been modified",
19984
- children: /* @__PURE__ */ jsx(Chip, {
19985
- size: "small",
19986
- className: "py-1",
19987
- colorScheme: "orangeDarker",
19988
- children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
19989
- })
19990
- }) : /* @__PURE__ */ jsx(Tooltip, {
19991
- title: "Form is in sync",
19992
- children: /* @__PURE__ */ jsx(Chip, {
19993
- size: "small",
19994
- className: "py-1",
19995
- children: /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest })
20782
+ className: cls("@container/form flex-1 flex flex-col w-full min-h-0", className),
20783
+ children: [/* @__PURE__ */ jsxs("div", {
20784
+ className: "flex-1 min-h-0 flex flex-row w-full",
20785
+ children: [/* @__PURE__ */ jsx("div", {
20786
+ className: "flex-1 min-w-0 overflow-y-auto flex justify-center items-start",
20787
+ children: /* @__PURE__ */ jsxs("div", {
20788
+ id: `form_${path}`,
20789
+ className: cls("@container/col w-full max-w-3xl 2xl:max-w-4xl flex flex-col", openEntityMode === "dialog" ? "pt-5 pb-8 px-6 sm:px-8" : "pt-6 pb-12 px-5 sm:px-8"),
20790
+ children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx("div", {
20791
+ className: "flex justify-end mb-3",
20792
+ children: /* @__PURE__ */ jsx(LocalChangesMenu, {
20793
+ cacheKey: localChangesCacheKey,
20794
+ properties: collection.properties,
20795
+ cachedData: localChangesData,
20796
+ formex,
20797
+ onClearLocalChanges: () => {
20798
+ setLocalChangesCleared(true);
20799
+ onClearLocalChanges?.();
20800
+ }
19996
20801
  })
19997
- })]
19998
- }), formView]
19999
- })
20802
+ }), formView]
20803
+ })
20804
+ }), showRail && /* @__PURE__ */ jsx(FormRail, {
20805
+ fields: layout.sidebar,
20806
+ showRecordMeta: layout.showRecordMeta && status === "existing",
20807
+ entity,
20808
+ renderField
20809
+ })]
20000
20810
  }), dialogActions]
20001
20811
  })
20002
20812
  });
20003
20813
  }
20814
+ /**
20815
+ * Grid placement for a span.
20816
+ *
20817
+ * Written out as literal class strings rather than composed, because Tailwind
20818
+ * scans source text: a template literal would produce classes that exist at
20819
+ * runtime and were never generated into the stylesheet.
20820
+ *
20821
+ * Below the column breakpoint every field takes the single available column, so
20822
+ * the side panel, the dialog and mobile all get a plain stack.
20823
+ */
20824
+ function spanClass(span) {
20825
+ switch (span) {
20826
+ case 1: return "@2xl/col:col-span-1";
20827
+ case 2: return "@2xl/col:col-span-2";
20828
+ case 3: return "@2xl/col:col-span-3";
20829
+ case 4: return "@2xl/col:col-span-4";
20830
+ }
20831
+ }
20004
20832
  function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
20005
20833
  useEffect(() => {
20006
20834
  if (!autoSave) return;
@@ -20219,7 +21047,6 @@ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, i
20219
21047
  context,
20220
21048
  openEntityMode
20221
21049
  ]);
20222
- const pluginFormActions = useSlot("form.actions", formActionProps);
20223
21050
  const pluginFormBefore = useSlot("form.before", formActionProps);
20224
21051
  const pluginFormAfter = useSlot("form.after", formActionProps);
20225
21052
  return /* @__PURE__ */ jsx(EntityForm, {
@@ -20242,7 +21069,7 @@ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, i
20242
21069
  uniqueFieldValidator,
20243
21070
  beforeFields: pluginFormBefore,
20244
21071
  afterFields: pluginFormAfter,
20245
- pluginActions: pluginFormActions ?? [],
21072
+ pluginActions: [],
20246
21073
  computedInitialValues,
20247
21074
  hasLocalChanges,
20248
21075
  localChangesData,
@@ -20270,6 +21097,30 @@ var useCollectionsConfigController = () => useContext(ConfigControllerContext);
20270
21097
  * @module
20271
21098
  */
20272
21099
  /**
21100
+ * Rebuild a serialized relation's `target` slug into the thunk consumers call.
21101
+ *
21102
+ * Shared by the relation *property* and the collection-level `relations` array.
21103
+ * They are the same shape on the wire and they need the same treatment coming
21104
+ * back, but only the property path had it — so a collection imported from an
21105
+ * existing table came off disk with `relations[n].target` still a string, which
21106
+ * typechecks (the serializable shape erases the difference) and throws "target
21107
+ * is not a function" at the first consumer.
21108
+ *
21109
+ * The lookup is consulted lazily, inside the thunk, so collections may
21110
+ * reference each other in any order, including circularly.
21111
+ */
21112
+ function fromSerializableRelation(relation, label, lookup) {
21113
+ const slug = relation.target;
21114
+ return {
21115
+ ...relation,
21116
+ target: () => {
21117
+ const found = slug ? lookup?.(slug) : void 0;
21118
+ if (!found) throw new Error(`Relation "${relation.relationName ?? label}" targets collection "${slug ?? "(none)"}", which is not among the collections it was deserialized with. Pass the whole set to \`fromSerializableCollectionConfigs\` so slugs can be resolved against each other.`);
21119
+ return found;
21120
+ }
21121
+ };
21122
+ }
21123
+ /**
20273
21124
  * Strip non-serializable fields from a `AdminPropertyOptions`.
20274
21125
  * Removes `Field` and `Preview` (ComponentRef).
20275
21126
  */
@@ -20365,7 +21216,8 @@ function toSerializableRelation(link) {
20365
21216
  case "hasMany": return {
20366
21217
  kind: link.kind,
20367
21218
  ...common,
20368
- ...link.foreignKeyOnTarget ? { foreignKeyOnTarget: link.foreignKeyOnTarget } : {}
21219
+ ...link.foreignKeyOnTarget ? { foreignKeyOnTarget: link.foreignKeyOnTarget } : {},
21220
+ ...link.sourceKey ? { sourceKey: link.sourceKey } : {}
20369
21221
  };
20370
21222
  case "manyToMany": return {
20371
21223
  kind: "manyToMany",
@@ -20676,17 +21528,7 @@ function fromSerializableProperty(serialized, lookup) {
20676
21528
  ...rest,
20677
21529
  type: "relation"
20678
21530
  };
20679
- if (relation) {
20680
- const slug = relation.target;
20681
- result.relation = {
20682
- ...relation,
20683
- target: () => {
20684
- const found = slug ? lookup?.(slug) : void 0;
20685
- if (!found) throw new Error(`Relation "${relation.relationName ?? sp.name}" targets collection "${slug ?? "(none)"}", which is not among the collections it was deserialized with. Pass the whole set to \`fromSerializableCollectionConfigs\` so slugs can be resolved against each other.`);
20686
- return found;
20687
- }
20688
- };
20689
- }
21531
+ if (relation) result.relation = fromSerializableRelation(relation, sp.name ?? "", lookup);
20690
21532
  return result;
20691
21533
  }
20692
21534
  default: return serialized;
@@ -20708,9 +21550,10 @@ function fromSerializableProperties(serialized, lookup) {
20708
21550
  * consumer if needed.
20709
21551
  */
20710
21552
  function fromSerializableCollectionConfig(serialized, lookup) {
20711
- const { properties, ...rest } = serialized;
21553
+ const { properties, relations, ...rest } = serialized;
20712
21554
  return {
20713
21555
  ...rest,
21556
+ ...relations ? { relations: relations.map((relation, index) => fromSerializableRelation(relation, `relations[${index}]`, lookup)) } : {},
20714
21557
  properties: fromSerializableProperties(properties, lookup)
20715
21558
  };
20716
21559
  }
@@ -20814,14 +21657,15 @@ function validateProperty(property, path, errors) {
20814
21657
  });
20815
21658
  const allowedByKind = {
20816
21659
  belongsTo: ["localKey"],
20817
- hasOne: ["foreignKeyOnTarget"],
20818
- hasMany: ["foreignKeyOnTarget"],
21660
+ hasOne: ["foreignKeyOnTarget", "sourceKey"],
21661
+ hasMany: ["foreignKeyOnTarget", "sourceKey"],
20819
21662
  manyToMany: ["through"],
20820
21663
  via: ["joinPath", "cardinality"]
20821
21664
  };
20822
21665
  const linkFields = [
20823
21666
  "localKey",
20824
21667
  "foreignKeyOnTarget",
21668
+ "sourceKey",
20825
21669
  "through",
20826
21670
  "joinPath",
20827
21671
  "cardinality"
@@ -21098,6 +21942,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
21098
21942
  return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
21099
21943
  }
21100
21944
  //#endregion
21101
- export { useBuildCollectionRegistryController as $, StorageThumbnailInternal as $n, CollectionTableBinding as $t, getFieldId as A, ArrayOfReferencesPreview as An, convertFileToJson as At, MapFieldBinding as B, getBracketNotation as Bn, getCollectionBySlugWithin as Bt, removeEmptyContainers as C, MapPropertyPreview as Cn, convertDataToEntity as Ct, getDefaultFieldConfig as D, ArrayEnumPreview as Dn, useImportConfig as Dt, DEFAULT_FIELD_CONFIGS as E, ArrayPropertyEnumPreview as En, getInferenceType as Et, SelectFieldBinding as F, CollectionRegistryContext as Fn, FieldCaption as Ft, useSelectionDialog as G, getPropertyInPath as Gn, removeTrailingSlash$1 as Gt, DateTimeFieldBinding as H, getIconForProperty as Hn, getLastSegment$1 as Ht, RepeatFieldBinding as I, useCollectionRegistryController as In, useBreadcrumbsController as It, useBuildUrlController as J, isRelationProperty as Jn, resolveViewMode as Jt, SelectionTableBinding as K, getResolvedPropertyInPath as Kn, resolveCollectionPathIds$1 as Kt, ReferenceFieldBinding as L, getEntityPreviewKeys as Ln, BreadcrumbsProvider as Lt, TextFieldBinding as M, EntityPreviewBinding as Mn, ArrayContainer as Mt, SwitchFieldBinding as N, SidePanelControllerContext as Nn, PropertyConfigBadge as Nt, getDefaultFieldId as O, ArrayOfStorageComponentsPreview as On, ImportSaveInProgress as Ot, StorageUploadFieldBinding as P, useSidePanel as Pn, SearchIconsView as Pt, useResolvedCollections as Q, StorageThumbnail as Qn, VirtualTableInput$1 as Qt, MultiSelectFieldBinding as R, getEntityTitlePropertyKey as Rn, mergeEntityActions as Rt, getInitialEntityValues as S, KeyValuePreview as Sn, DataNewPropertiesMapping as St, PropertyFieldBinding as T, ArrayOfStringsPreview as Tn, processValueMapping as Tt, BlockFieldBinding as U, getIconForWidget as Un, removeInitialAndTrailingSlashes$1 as Ut, KeyValueFieldBinding as V, getDefaultPropertiesOrder as Vn, getCollectionPathsCombinations as Vt, ArrayOfReferencesFieldBinding as W, getPropertiesWithPropertiesOrder as Wn, removeInitialSlash as Wt, useTopLevelNavigation as X, StringPropertyPreview as Xn, resolveEntityView as Xt, useBuildNavigationStateController as Y, ArrayPropertyPreview as Yn, resolveEntityAction as Yt, useResolvedViews as Z, EnumValuesChip as Zn, useSelectionController as Zt, useCollectionsConfigController as _, getUserLabel as _n, CollectionViewActions as _t, namespaceToPropertiesPath as a, NavigationStateContext as an, UrlComponentPreview as ar, useBuildSidePanel as at, extractTouchedValues as b, BooleanPreview as bn, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, SideDialogsControllerContext as cn, EmptyValue as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useClearRestoreValue as dn, LabelWithIcon as dr, resetPasswordAction as dt, SelectableTable as en, SkeletonPropertyComponent as er, useHistory as et, fromSerializableProperties as f, ReadOnlyFieldBinding as fn, FormLayout as fr, CreationResultDialog as ft, toSerializableProperty as g, UserPreview as gn, EntityCardBinding as gt, toSerializableProperties as h, PropertyPreview as hn, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, useUrlController as in, renderSkeletonText as ir, getEntityViewWidth as it, VectorFieldBinding as j, ReferencePreview as jn, unflattenObject as jt, getFieldConfig as k, RelationPreview as kn, ImportFileUpload as kt, validateCollectionJson as l, SelectableTableContext as ln, LabelWithIconAndTooltip as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, ArrayOfMapsPreview as mn, EntityViewBinding as mt, getFullIdPath as n, useAdminContext as nn, renderSkeletonIcon as nr, useResolvedNavigationFrom as nt, CollectionGenerationApiError as o, useNavigationStateController as on, ImagePreview as or, EditViewBinding as ot, fromSerializableProperty as p, FieldHelperText as pn, FormEntry as pr, DetailViewBinding as pt, SideDialogs as q, isReferenceProperty as qn, resolveOpenEntityMode as qt, idToPropertiesPath as r, UrlContext as rn, renderSkeletonImageThumbnail as rr, buildSidePanelsFromUrl as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useSideDialogsController as sn, sanitizeUrl as sr, CollectionViewBinding as st, getFullId as t, CollectionRowActions as tn, renderSkeletonCaptionText as tr, resolveNavigationFrom as tt, fromSerializableCollectionConfig as u, ArrayCustomShapedFieldBinding as un, PropertyIdCopyTooltip as ur, editEntityAction as ut, EntityFormBinding as v, useResolvedUser as vn, useCollectionEditorController as vt, zodToFormErrors as w, ArrayOneOfPreview as wn, flattenEntry as wt, getChanges as x, DatePreview as xn, ImportNewPropertyFieldPreview as xt, EntityForm as y, NumberPropertyPreview as yn, ConfigControllerProvider as yt, MarkdownEditorFieldBinding as z, getEntityTitlePropertyKeyForEntity as zn, addInitialSlash as zt };
21945
+ export { useBuildCollectionRegistryController as $, sanitizeUrl as $n, CollectionTableBinding as $t, getFieldId as A, ArrayOfReferencesPreview as An, convertFileToJson as At, MapFieldBinding as B, getEntityTitlePropertyKeyForEntity as Bn, mergeEntityActions as Bt, removeEmptyContainers as C, MapPropertyPreview as Cn, convertDataToEntity as Ct, getDefaultFieldConfig as D, ArrayEnumPreview as Dn, useImportConfig as Dt, DEFAULT_FIELD_CONFIGS as E, ArrayPropertyEnumPreview as En, getInferenceType as Et, SelectFieldBinding as F, useSidePanel as Fn, FieldCaption as Ft, useSelectionDialog as G, StorageThumbnailInternal as Gn, removeInitialAndTrailingSlashes$1 as Gt, DateTimeFieldBinding as H, StringPropertyPreview as Hn, getCollectionBySlugWithin as Ht, RepeatFieldBinding as I, CollectionRegistryContext as In, useBreadcrumbsController as It, useBuildUrlController as J, renderSkeletonIcon as Jn, resolveCollectionPathIds$1 as Jt, SelectionTableBinding as K, SkeletonPropertyComponent as Kn, removeInitialSlash as Kt, ReferenceFieldBinding as L, useCollectionRegistryController as Ln, BreadcrumbsProvider as Lt, TextFieldBinding as M, ReferencePreview as Mn, ArrayContainer as Mt, SwitchFieldBinding as N, EntityPreviewBinding as Nn, PropertyConfigBadge as Nt, getDefaultFieldId as O, ArrayOfStorageComponentsPreview as On, ImportSaveInProgress as Ot, StorageUploadFieldBinding as P, SidePanelControllerContext as Pn, SearchIconsView as Pt, useResolvedCollections as Q, ImagePreview as Qn, VirtualTableInput$1 as Qt, MultiSelectFieldBinding as R, getEntityPreviewKeys as Rn, resolveEntityAction as Rt, getInitialEntityValues as S, KeyValuePreview as Sn, DataNewPropertiesMapping as St, PropertyFieldBinding as T, ArrayOfStringsPreview as Tn, processValueMapping as Tt, BlockFieldBinding as U, EnumValuesChip as Un, getCollectionPathsCombinations as Ut, KeyValueFieldBinding as V, ArrayPropertyPreview as Vn, addInitialSlash as Vt, ArrayOfReferencesFieldBinding as W, StorageThumbnail as Wn, getLastSegment$1 as Wt, useTopLevelNavigation as X, renderSkeletonText as Xn, resolveViewMode as Xt, useBuildNavigationStateController as Y, renderSkeletonImageThumbnail as Yn, resolveOpenEntityMode as Yt, useResolvedViews as Z, UrlComponentPreview as Zn, useSelectionController as Zt, useCollectionsConfigController as _, getUserLabel as _n, CollectionViewActions as _t, namespaceToPropertiesPath as a, NavigationStateContext as an, FieldBlock as ar, useBuildSidePanel as at, extractTouchedValues as b, BooleanPreview as bn, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, SideDialogsControllerContext as cn, getBracketNotation as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useClearRestoreValue as dn, getIconForWidget as dr, resetPasswordAction as dt, SelectableTable as en, EmptyValue as er, useHistory as et, fromSerializableProperties as f, ReadOnlyFieldBinding as fn, getPropertiesWithPropertiesOrder as fr, CreationResultDialog as ft, toSerializableProperty as g, UserPreview as gn, isRelationProperty as gr, EntityCardBinding as gt, toSerializableProperties as h, PropertyPreview as hn, isReferenceProperty as hr, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, useUrlController as in, FormRail as ir, getEntityViewWidth as it, VectorFieldBinding as j, InlineEntityListPreview as jn, unflattenObject as jt, getFieldConfig as k, RelationPreview as kn, ImportFileUpload as kt, validateCollectionJson as l, SelectableTableContext as ln, getDefaultPropertiesOrder as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, ArrayOfMapsPreview as mn, getResolvedPropertyInPath as mr, EntityViewBinding as mt, getFullIdPath as n, useAdminContext as nn, LabelWithIcon as nr, useResolvedNavigationFrom as nt, CollectionGenerationApiError as o, useNavigationStateController as on, isSelfLabellingProperty as or, EditViewBinding as ot, fromSerializableProperty as p, FieldHelperText as pn, getPropertyInPath as pr, DetailViewBinding as pt, SideDialogs as q, renderSkeletonCaptionText as qn, removeTrailingSlash$1 as qt, idToPropertiesPath as r, UrlContext as rn, FormSections as rr, buildSidePanelsFromUrl as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useSideDialogsController as sn, PropertyIdCopyTooltip as sr, CollectionViewBinding as st, getFullId as t, CollectionRowActions as tn, LabelWithIconAndTooltip as tr, resolveNavigationFrom as tt, fromSerializableCollectionConfig as u, ArrayCustomShapedFieldBinding as un, getIconForProperty as ur, editEntityAction as ut, EntityFormBinding as v, useResolvedUser as vn, useCollectionEditorController as vt, zodToFormErrors as w, ArrayOneOfPreview as wn, flattenEntry as wt, getChanges as x, DatePreview as xn, ImportNewPropertyFieldPreview as xt, EntityForm as y, NumberPropertyPreview as yn, ConfigControllerProvider as yt, MarkdownEditorFieldBinding as z, getEntityTitlePropertyKey as zn, resolveEntityView as zt };
21102
21946
 
21103
- //# sourceMappingURL=util-B5fJm1FA.js.map
21947
+ //# sourceMappingURL=util-DSh3HRzK.js.map