@rebasepro/admin 0.13.0 → 0.13.1-canary.g18cfeb7

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 (143) hide show
  1. package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-CUFXevVg.js} +3 -3
  2. package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-CUFXevVg.js.map} +1 -1
  3. package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-DDUC7giL.js} +2 -2
  4. package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-DDUC7giL.js.map} +1 -1
  5. package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-vzS848Gf.js} +9 -13
  6. package/dist/RouterCollectionsStudioView-vzS848Gf.js.map +1 -0
  7. package/dist/collection_editor/serializable_types.d.ts +1 -0
  8. package/dist/collection_editor_ui.js +4 -4
  9. package/dist/components/AdditionalFieldValue.d.ts +25 -0
  10. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
  11. package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
  12. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
  13. package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
  14. package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
  15. package/dist/components/DetailViewBinding.d.ts +15 -0
  16. package/dist/components/DrawerNavigationGroup.d.ts +9 -6
  17. package/dist/components/DrawerNavigationItem.d.ts +16 -6
  18. package/dist/components/EditViewBinding.d.ts +30 -1
  19. package/dist/components/EntityDisplayHeader.d.ts +53 -0
  20. package/dist/components/EntityIdentityBar.d.ts +43 -1
  21. package/dist/components/EntityViewBinding.d.ts +60 -6
  22. package/dist/components/RebaseLayout.d.ts +1 -1
  23. package/dist/components/RebaseShell.d.ts +1 -1
  24. package/dist/components/RelationSelector.d.ts +8 -1
  25. package/dist/components/UserSelector.d.ts +8 -1
  26. package/dist/components/app/Scaffold.d.ts +9 -3
  27. package/dist/components/index.d.ts +2 -0
  28. package/dist/{export-J79rMoNj.js → export-5bl2EoSs.js} +2 -2
  29. package/dist/{export-J79rMoNj.js.map → export-5bl2EoSs.js.map} +1 -1
  30. package/dist/form/components/FieldBlock.d.ts +58 -4
  31. package/dist/form/components/FormRail.d.ts +13 -0
  32. package/dist/form/components/FormSections.d.ts +15 -1
  33. package/dist/form/components/useRailVisible.d.ts +5 -4
  34. package/dist/form/form_utils.d.ts +38 -1
  35. package/dist/{history-D7L_JlLO.js → history-BuKKAhoY.js} +4 -4
  36. package/dist/{history-D7L_JlLO.js.map → history-BuKKAhoY.js.map} +1 -1
  37. package/dist/hooks/useEntityDisplay.d.ts +102 -0
  38. package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
  39. package/dist/{import-BvYl--U4.js → import-BhdWs34H.js} +3 -3
  40. package/dist/{import-BvYl--U4.js.map → import-BhdWs34H.js.map} +1 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +152 -57
  43. package/dist/index.js.map +1 -1
  44. package/dist/preview/compact_text.d.ts +23 -0
  45. package/dist/preview/components/BooleanPreview.d.ts +3 -1
  46. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  47. package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
  48. package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
  49. package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
  50. package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
  51. package/dist/routes/RebaseRoute.d.ts +1 -1
  52. package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
  53. package/dist/util/number_format.d.ts +14 -0
  54. package/dist/util/previews.d.ts +9 -2
  55. package/dist/{util-GiwPgxnL.js → util-DNG7iK0w.js} +3756 -2300
  56. package/dist/util-DNG7iK0w.js.map +1 -0
  57. package/package.json +9 -9
  58. package/src/collection_editor/serializable_types.ts +1 -0
  59. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
  60. package/src/components/AdditionalFieldValue.tsx +68 -0
  61. package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
  62. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
  63. package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
  64. package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
  65. package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
  66. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +723 -348
  67. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
  68. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +38 -8
  69. package/src/components/CollectionViewBinding/EntityCardBinding.tsx +23 -36
  70. package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
  71. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
  72. package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
  73. package/src/components/CollectionViewBinding/SplitListView.tsx +33 -2
  74. package/src/components/CollectionViewBinding/useBoardDataController.tsx +12 -3
  75. package/src/components/CollectionViewBinding/usePreviewSlots.ts +183 -64
  76. package/src/components/CollectionViewBinding/utils.ts +7 -4
  77. package/src/components/DefaultDrawer.tsx +19 -23
  78. package/src/components/DetailViewBinding.tsx +93 -56
  79. package/src/components/DrawerNavigationGroup.tsx +16 -39
  80. package/src/components/DrawerNavigationItem.tsx +44 -16
  81. package/src/components/EditViewBinding.tsx +215 -42
  82. package/src/components/EntityDisplayHeader.tsx +170 -0
  83. package/src/components/EntityIdentityBar.tsx +163 -23
  84. package/src/components/EntityInspector.tsx +24 -6
  85. package/src/components/EntityPreviewBinding.tsx +58 -18
  86. package/src/components/EntityViewBinding.tsx +342 -41
  87. package/src/components/InlineEntityPreview.tsx +10 -10
  88. package/src/components/RebaseLayout.tsx +2 -2
  89. package/src/components/RebaseShell.tsx +2 -2
  90. package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
  91. package/src/components/RelationSelector.tsx +15 -4
  92. package/src/components/SideDialogs.tsx +15 -3
  93. package/src/components/SidePanelBinding.tsx +122 -66
  94. package/src/components/UserSelector.tsx +15 -4
  95. package/src/components/app/Scaffold.tsx +112 -10
  96. package/src/components/history/LastEditedByIndicator.tsx +7 -12
  97. package/src/components/index.ts +3 -0
  98. package/src/data_import/utils/data.ts +9 -1
  99. package/src/form/EntityForm.tsx +64 -52
  100. package/src/form/EntityFormBinding.tsx +24 -11
  101. package/src/form/PropertyFieldBinding.tsx +2 -2
  102. package/src/form/components/FieldBlock.tsx +80 -10
  103. package/src/form/components/FormRail.tsx +19 -2
  104. package/src/form/components/FormSections.tsx +32 -5
  105. package/src/form/components/StorageUploadProgress.tsx +9 -3
  106. package/src/form/components/useRailVisible.tsx +5 -4
  107. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
  108. package/src/form/field_bindings/BinaryFieldBinding.tsx +10 -5
  109. package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
  110. package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
  111. package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
  112. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
  113. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
  114. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -1
  115. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
  116. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
  117. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  118. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
  119. package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
  120. package/src/form/field_bindings/VectorFieldBinding.tsx +6 -4
  121. package/src/form/form_utils.ts +78 -0
  122. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
  123. package/src/hooks/useEntityDisplay.tsx +262 -0
  124. package/src/hooks/useEntityDisplayTitle.tsx +27 -38
  125. package/src/index.ts +9 -1
  126. package/src/preview/PropertyPreview.tsx +25 -5
  127. package/src/preview/compact_text.ts +68 -0
  128. package/src/preview/components/BooleanPreview.tsx +5 -2
  129. package/src/preview/components/StorageThumbnail.tsx +5 -2
  130. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
  131. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
  132. package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
  133. package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
  134. package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
  135. package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
  136. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
  137. package/src/routes/RebaseRoute.tsx +62 -4
  138. package/src/types/components/PropertyPreviewProps.tsx +21 -0
  139. package/src/util/number_format.ts +46 -0
  140. package/src/util/previews.ts +8 -55
  141. package/src/util/property_utils.tsx +6 -1
  142. package/dist/RouterCollectionsStudioView-BsLTNoqX.js.map +0 -1
  143. package/dist/util-GiwPgxnL.js.map +0 -1
@@ -1,11 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { in as useUrlController } from "./util-GiwPgxnL.js";
3
- import { s as AIModifiedPathsProvider } from "./PropertyEditView-W8I3YV89.js";
4
- import { t as CollectionEditor } from "./CollectionEditorDialog-CjXkWZ64.js";
2
+ import { in as useUrlController } from "./util-DNG7iK0w.js";
3
+ import { s as AIModifiedPathsProvider } from "./PropertyEditView-DDUC7giL.js";
4
+ import { t as CollectionEditor } from "./CollectionEditorDialog-CUFXevVg.js";
5
5
  import { useEffect, useState } from "react";
6
6
  import { Button, IconButton, PlusIcon, ResizablePanels, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
7
7
  import { IconForView, UnsavedChangesDialog, useUnsavedChangesDialog } from "@rebasepro/app";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { readStoredString, writeStoredString } from "@rebasepro/utils";
9
10
  import { useLocation, useNavigate } from "react-router";
10
11
  //#region src/collection_editor/ui/collection_editor/CollectionStudioView.tsx
11
12
  function CollectionStudioView({ collectionId, onSave, onCancel, ...props }) {
@@ -74,17 +75,12 @@ function CollectionsStudioView({ configController, collections: collectionsProp,
74
75
  };
75
76
  const collections = collectionsProp ?? configController.collections ?? [];
76
77
  const [sidebarSize, setSidebarSize] = useState(() => {
77
- try {
78
- const saved = localStorage.getItem("rebase_collections_editor_sidebar_size");
79
- return saved !== null ? parseFloat(saved) : 25;
80
- } catch (e) {
81
- return 25;
82
- }
78
+ const saved = readStoredString("rebase_collections_editor_sidebar_size");
79
+ const parsed = saved === null ? NaN : parseFloat(saved);
80
+ return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;
83
81
  });
84
82
  useEffect(() => {
85
- try {
86
- localStorage.setItem("rebase_collections_editor_sidebar_size", sidebarSize.toString());
87
- } catch (e) {}
83
+ writeStoredString("rebase_collections_editor_sidebar_size", sidebarSize.toString());
88
84
  }, [sidebarSize]);
89
85
  return /* @__PURE__ */ jsx("div", {
90
86
  className: "flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark",
@@ -189,4 +185,4 @@ function RouterCollectionsStudioView(props) {
189
185
  //#endregion
190
186
  export { CollectionStudioView as i, RouterCollectionsStudioView_exports as n, CollectionsStudioView as r, RouterCollectionsStudioView as t };
191
187
 
192
- //# sourceMappingURL=RouterCollectionsStudioView-BsLTNoqX.js.map
188
+ //# sourceMappingURL=RouterCollectionsStudioView-vzS848Gf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouterCollectionsStudioView-vzS848Gf.js","names":[],"sources":["../src/collection_editor/ui/collection_editor/CollectionStudioView.tsx","../src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx","../src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx"],"sourcesContent":["\nimport { useUnsavedChangesDialog, UnsavedChangesDialog } from \"@rebasepro/app\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\nimport { Button, PlusIcon, Typography } from \"@rebasepro/ui\";\nimport { CollectionEditorDialogProps } from \"./CollectionEditorDialog\";\nimport { AIModifiedPathsProvider } from \"./AIModifiedPathsContext\";\nimport { CollectionEditor } from \"./CollectionEditorDialog\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\n\nexport type CollectionStudioViewProps = Omit<CollectionEditorDialogProps, \"open\" | \"isNewCollection\" | \"editedCollectionId\" | \"handleClose\" | \"handleCancel\"> & {\n collectionId?: string | \"new\";\n\n /**\n * Called after a successful save.\n * Receives the saved collection (or undefined if dismissed without saving).\n * When not provided, save completes silently.\n */\n onSave?: (collection?: AdminCollection) => void;\n\n /**\n * Called when the user cancels editing.\n * When not provided, cancel is a no-op.\n */\n onCancel?: () => void;\n};\n\nexport function CollectionStudioView({ collectionId, onSave, onCancel, ...props }: CollectionStudioViewProps) {\n\n // Form state from the editor\n const [formDirty, setFormDirty] = useState<boolean>(false);\n const [cancelRequested, setCancelRequested] = useState<boolean>(false);\n\n const { dialogProps, triggerDialog } = useUnsavedChangesDialog(\n formDirty,\n () => setFormDirty(false)\n );\n\n const activeCollectionId = collectionId;\n\n const handleCancelClick = () => {\n if (!formDirty) {\n onCancel?.();\n } else {\n setCancelRequested(true);\n triggerDialog();\n }\n };\n\n return (\n <div className=\"flex-grow flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800\">\n <AIModifiedPathsProvider>\n {activeCollectionId ? (\n <CollectionEditor\n key={activeCollectionId}\n {...props}\n fullScreen={true}\n open={true}\n isNewCollection={activeCollectionId === \"new\"}\n editedCollectionId={activeCollectionId !== \"new\" ? activeCollectionId : undefined}\n handleCancel={handleCancelClick}\n handleClose={(savedCollection) => {\n setFormDirty(false);\n if (savedCollection) {\n setTimeout(() => {\n onSave?.(savedCollection);\n }, 0);\n }\n }}\n setFormDirty={setFormDirty}\n />\n ) : (\n <div className=\"flex-grow flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one\n </Typography>\n <Button\n disabled={props.configController?.readOnly}\n onClick={() => onSave?.()}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n\n <UnsavedChangesDialog\n {...dialogProps}\n handleOk={() => {\n dialogProps.handleOk();\n if (cancelRequested) {\n onCancel?.();\n setCancelRequested(false);\n }\n }}\n handleCancel={() => {\n dialogProps.handleCancel();\n setCancelRequested(false);\n }}\n />\n </AIModifiedPathsProvider>\n </div>\n );\n}\n","\nimport { IconForView } from \"@rebasepro/app\";\nimport React, { useState, useEffect } from \"react\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n IconButton,\n iconSize,\n PlusIcon,\n ResizablePanels,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { CollectionsConfigController } from \"../../types/config_controller\";\nimport { CollectionStudioView } from \"./CollectionStudioView\";\nimport type { CollectionEditorExtensionProps } from \"../../extensibility_types\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\nimport { readStoredString, writeStoredString } from \"@rebasepro/utils\";\n\nexport interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {\n configController: CollectionsConfigController;\n\n /**\n * Collections to show in the sidebar.\n * When provided, overrides the collections from `configController`.\n * Use this to control exactly which collections the editor displays.\n */\n collections?: AdminCollection[];\n\n /**\n * Controlled active collection ID.\n * When provided together with `onActiveCollectionChange`,\n * navigation is fully external — no internal state is used.\n */\n activeCollectionId?: string;\n\n /**\n * Called when the user clicks a collection, \"new\", or navigates.\n * When provided, the component is fully controlled (no internal state).\n * When not provided, the component manages selection via internal state.\n *\n * Pass `\"new\"` to create a new collection. Pass `undefined` to deselect.\n */\n onActiveCollectionChange?: (collectionId: string | undefined) => void;\n}\n\nexport function CollectionsStudioView({\n configController,\n collections: collectionsProp,\n activeCollectionId: controlledActiveId,\n onActiveCollectionChange,\n propertyTypePresets,\n hiddenPropertyTypes,\n renderExtraPropertyFields,\n renderExtraCollectionFields,\n visibleTabs,\n standalone,\n}: CollectionsStudioViewProps) {\n\n // ── Navigation state ────────────────────────────────────────────────\n // If onActiveCollectionChange is provided, the component is controlled.\n // Otherwise, use internal state.\n const [internalActiveId, setInternalActiveId] = useState<string | undefined>(undefined);\n const isControlled = onActiveCollectionChange !== undefined;\n const activeCollectionId = isControlled ? controlledActiveId : internalActiveId;\n\n const setActiveCollectionId = (id: string | undefined) => {\n if (isControlled) {\n onActiveCollectionChange?.(id);\n } else {\n setInternalActiveId(id);\n }\n };\n\n // ── Collections list ────────────────────────────────────────────────\n const collections = collectionsProp ?? configController.collections ?? [];\n\n // ── Sidebar sizing ──────────────────────────────────────────────────\n // Checked, not just parsed: `parseFloat` answers NaN for anything it cannot\n // read, and a NaN pane size lays the sidebar out to nothing — with the bad\n // value still in storage on reload. `SplitListView.getSavedPanelSize` is the\n // same read, done this way.\n const [sidebarSize, setSidebarSize] = useState(() => {\n const saved = readStoredString(\"rebase_collections_editor_sidebar_size\");\n const parsed = saved === null ? NaN : parseFloat(saved);\n return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;\n });\n\n useEffect(() => {\n writeStoredString(\"rebase_collections_editor_sidebar_size\", sidebarSize.toString());\n }, [sidebarSize]);\n\n return (\n <div className=\"flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark\">\n <ResizablePanels\n orientation=\"horizontal\"\n panelSizePercent={sidebarSize}\n onPanelSizeChange={setSidebarSize}\n minPanelSizePx={220}\n firstPanel={\n <div className={cls(\"flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800 border-r\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold text-[11px] uppercase tracking-wider text-surface-400 dark:text-surface-400\">\n Collections\n </Typography>\n <Tooltip title={configController.readOnly ? configController.readOnlyReason || \"Read only\" : \"Add collection\"}>\n <div>\n <IconButton\n size=\"small\"\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n className={activeCollectionId === \"new\" ? \"text-primary dark:text-primary-dark\" : \"text-text-secondary dark:text-text-secondary-dark\"}\n >\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </Tooltip>\n </div>\n\n <div className=\"flex-grow overflow-y-auto w-full no-scrollbar p-2 space-y-0.5\">\n {collections.length === 0 && (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark italic\">\n No collections yet.\n </Typography>\n </div>\n )}\n {collections.map((collection) => {\n const collectionKey = collection.slug;\n const isSelected = activeCollectionId === collectionKey;\n return (\n <div\n key={collectionKey}\n onClick={() => setActiveCollectionId(collectionKey)}\n className={cls(\n \"flex items-center gap-2.5 px-3 h-[30px] cursor-pointer rounded-lg text-[13px] font-medium transition-colors\",\n isSelected\n ? \"bg-primary/8 text-primary dark:bg-primary/10 dark:text-primary-light font-semibold\"\n : \"hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white\"\n )}\n >\n <IconForView collectionOrView={collection} size={\"smallest\"} className={cls(\n isSelected\n ? \"text-primary dark:text-primary-light\"\n : \"text-surface-500 dark:text-text-secondary-dark\"\n )}/>\n <span className=\"truncate flex-1\">\n {collection.name || collection.slug}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n }\n secondPanel={\n <div className=\"flex-grow flex flex-col min-w-0 h-full w-full\">\n {/* We use key to force unmount when switching collections, preventing stale state */}\n {activeCollectionId ? (\n <CollectionStudioView\n key={activeCollectionId}\n configController={configController}\n collectionId={activeCollectionId}\n onSave={(savedCollection) => {\n // After creating a new collection, switch to it\n if (activeCollectionId === \"new\" && savedCollection?.slug) {\n setActiveCollectionId(savedCollection.slug);\n }\n }}\n onCancel={() => setActiveCollectionId(undefined)}\n propertyTypePresets={propertyTypePresets}\n hiddenPropertyTypes={hiddenPropertyTypes}\n renderExtraPropertyFields={renderExtraPropertyFields}\n renderExtraCollectionFields={renderExtraCollectionFields}\n visibleTabs={visibleTabs}\n standalone={standalone}\n />\n ) : (\n <div className=\"flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one to start editing\n </Typography>\n <Button\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n </div>\n }\n />\n </div>\n );\n}\n","/**\n * Router-aware wrapper around CollectionsStudioView.\n *\n * This component provides the react-router-based navigation behavior\n * that Rebase Studio expects: the active collection is derived from the\n * URL, and clicking a collection navigates via react-router.\n *\n * External consumers should use `CollectionsStudioView` directly\n * (which has no react-router dependency).\n */\nimport React from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { useUrlController } from \"../../_cms_internals\";\nimport { CollectionsStudioView, CollectionsStudioViewProps } from \"./CollectionsStudioView\";\n\nexport type RouterCollectionsStudioViewProps = Omit<CollectionsStudioViewProps, \"activeCollectionId\" | \"onActiveCollectionChange\">;\n\nexport function RouterCollectionsStudioView(props: RouterCollectionsStudioViewProps) {\n const navigate = useNavigate();\n const urlController = useUrlController();\n const location = useLocation();\n\n // Determine the active collection from the URL segment after \"schema/\"\n const basePath = urlController.buildAppUrlPath(\"schema\");\n const relativePath = location.pathname.replace(basePath, \"\").replace(/^\\//, \"\");\n const activeCollectionId = relativePath.split(\"/\")[0] || undefined;\n\n return (\n <CollectionsStudioView\n {...props}\n activeCollectionId={activeCollectionId}\n onActiveCollectionChange={(id) => {\n if (id) {\n navigate(urlController.buildAppUrlPath(`schema/${id}`));\n } else {\n navigate(urlController.buildAppUrlPath(\"schema\"));\n }\n }}\n />\n );\n}\n"],"mappings":"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"}
@@ -116,6 +116,7 @@ export interface SerializableAdminRelationOptions extends SerializableAdminBaseO
116
116
  fixedFilter?: FilterValues<string>;
117
117
  includeId?: boolean;
118
118
  includeEntityLink?: boolean;
119
+ renderInForm?: boolean;
119
120
  }
120
121
  /** JSON-serializable version of `AdminArrayOptions`. */
121
122
  export interface SerializableAdminArrayOptions extends SerializableAdminBaseOptions {
@@ -1,7 +1,7 @@
1
- import { in as useUrlController } from "./util-GiwPgxnL.js";
2
- import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-W8I3YV89.js";
3
- import { n as CollectionEditorDialog } from "./CollectionEditorDialog-CjXkWZ64.js";
4
- import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-BsLTNoqX.js";
1
+ import { in as useUrlController } from "./util-DNG7iK0w.js";
2
+ import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-DDUC7giL.js";
3
+ import { n as CollectionEditorDialog } from "./CollectionEditorDialog-CUFXevVg.js";
4
+ import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-vzS848Gf.js";
5
5
  import "react";
6
6
  import { useSnackbarController } from "@rebasepro/app";
7
7
  import { jsx } from "react/jsx-runtime";
@@ -0,0 +1,25 @@
1
+ import type { AdditionalFieldDelegate, RebaseContext } from "@rebasepro/admin-types";
2
+ import type { Entity } from "@rebasepro/types";
3
+ import React from "react";
4
+ export interface AdditionalFieldValueProps<M extends Record<string, unknown>> {
5
+ field: AdditionalFieldDelegate<M>;
6
+ entity: Entity<M>;
7
+ context: RebaseContext;
8
+ }
9
+ /**
10
+ * Renders an `additionalFields` entry that supplies a `value` rather than a
11
+ * `Builder`.
12
+ *
13
+ * `value` is typed `string | number | Promise<string | number>`, and the promise
14
+ * arm has been part of that type from the start — the CSV export awaits it
15
+ * correctly. Every *renderer* called `.toString()` on the result instead, so an
16
+ * async value drew the literal text `[object Promise]` in the cell: a shape the
17
+ * type promised, that worked in one of its two consumers, and failed silently in
18
+ * the other. That is what made the obvious port of a FireCMS column — one that
19
+ * fetches a title from a subcollection — look supported and not be.
20
+ *
21
+ * Three call sites had the same three lines copied between them (the table cell,
22
+ * the form and the read-only record), which is why the bug reached all three.
23
+ * Now there is one.
24
+ */
25
+ export declare function AdditionalFieldValue<M extends Record<string, unknown>>({ field, entity, context }: AdditionalFieldValueProps<M>): React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import type { AdminCollection } from "@rebasepro/admin-types";
2
+ import { FilterValues } from "@rebasepro/types";
2
3
  import React from "react";
3
4
  /**
4
5
  * @group Components
@@ -39,3 +40,18 @@ export type CollectionViewBindingProps<M extends Record<string, unknown>> = {
39
40
  selectedTab?: string;
40
41
  } & AdminCollection<M>;
41
42
  export declare const CollectionViewBinding: React.FunctionComponent<CollectionViewBindingProps<any>>;
43
+ export declare function EntitiesCount({ path, collection, filter, sortBy, searchString, onCountChange }: {
44
+ path: string;
45
+ collection: AdminCollection;
46
+ filter?: FilterValues<any>;
47
+ sortBy?: [string, "asc" | "desc"];
48
+ /**
49
+ * Required, not optional. The term sits in the same scope as the element
50
+ * that mounts this and is passed to the toolbar and the empty state beside
51
+ * it — it was simply not passed here, so a searched list showed the count
52
+ * of the unsearched collection. A required prop is what stops the next
53
+ * caller doing the same; there is no useful "count without the search".
54
+ */
55
+ searchString: string | undefined;
56
+ onCountChange?: (count: number | null | undefined) => void;
57
+ }): null;
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+ import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
3
+ import type { PreviewSlot } from "./usePreviewSlots";
4
+ export interface SlotValueProps {
5
+ slot: PreviewSlot | undefined;
6
+ size: PreviewSize;
7
+ /**
8
+ * Relations and references render as one line of linked text rather than as
9
+ * a card. Defaults to true: a slot is a line in a row or a card, and a
10
+ * relation that renders its own bordered card there — with its own id line
11
+ * and its own side-panel button — is a card inside a card.
12
+ *
13
+ * Pass `false` only for a slot with room for one, which in practice is
14
+ * none of them.
15
+ */
16
+ textOnly?: boolean;
17
+ /** Images only: fill the container rather than fitting inside it. */
18
+ fill?: boolean;
19
+ }
20
+ /**
21
+ * One display slot, rendered.
22
+ *
23
+ * A slot's value arrives one of two ways and they cannot be rendered the same:
24
+ *
25
+ * - **From a property** — render it with that property's own preview, so an
26
+ * enum status keeps its colour, a date keeps its format and a storage path
27
+ * becomes a thumbnail. This is why a declared path beats a resolver whenever
28
+ * the value is actually on the record.
29
+ * - **From a resolver** — there is no property, because the value was computed
30
+ * or fetched. All that can be said about it is that it is text.
31
+ *
32
+ * The branch lives here, once, rather than at each of the fourteen places the
33
+ * list row, the card and the board card render a slot. That count is the reason:
34
+ * the last time a rule like this was inlined per call site, it drifted into
35
+ * seven versions.
36
+ */
37
+ export declare function SlotValue({ slot, size, textOnly, fill }: SlotValueProps): React.JSX.Element | null;
38
+ /**
39
+ * The `tags` slot, as chips beside the status.
40
+ *
41
+ * Honours both arms the same way the rest of the file does. A declared **path**
42
+ * goes through {@link SlotValue}, so an array of enum values keeps its own
43
+ * colours — that is the stated advantage of naming a property. A **resolver**
44
+ * returns bare strings with no property to render them as, so they become plain
45
+ * chips; a single string is accepted as one tag, which the role's type documents.
46
+ *
47
+ * Lives here rather than in the list, the card and the board card because that is
48
+ * three copies of the same decision, and this file exists to hold exactly one.
49
+ */
50
+ export declare function TagChips({ slot, max }: {
51
+ slot: PreviewSlot;
52
+ max?: number;
53
+ }): React.JSX.Element | null;
54
+ /**
55
+ * Whether a computed image string can be fetched as-is, or has to be resolved
56
+ * through storage first.
57
+ *
58
+ * Exported because it is the whole decision: rendering either branch needs a
59
+ * provider (a storage source, a DOM), and the thing worth pinning is which
60
+ * branch a given string takes. `//host/x` is protocol-relative and equally
61
+ * fetchable; `data:` and `blob:` are already the bytes.
62
+ */
63
+ export declare function isFetchableImageUrl(value: string): boolean;
@@ -3,8 +3,21 @@ import React from "react";
3
3
  * Closes the list of a split view, leaving the open record on its own.
4
4
  *
5
5
  * It leads the record's own app bar, where the breadcrumb starts — this is the
6
- * entity pane's old "Open full screen" button, moved to that edge and wearing an
7
- * X: closing the list and opening the record full screen are the same thing, so
8
- * there is one control and one route (`#full`) rather than two.
6
+ * entity pane's old "Open full screen" button, moved to that edge: closing the
7
+ * list and opening the record full screen are the same thing, so there is one
8
+ * control and one route (`#full`) rather than two. Full screen carries the
9
+ * mirrored icon to bring the list back.
10
+ *
11
+ * A pane glyph rather than the double chevron it wore first. Now that the bar
12
+ * ends in a ✕ that dismisses the record, an arrow pointing off the left edge of
13
+ * the record's own bar was the other thing on it that read as "close me" — and
14
+ * it is the one control here that does not act on the record at all.
9
15
  */
10
- export declare function SplitListCloseButton(): React.JSX.Element | null;
16
+ export declare function SplitListCloseButton({ onBeforeHide }?: {
17
+ /**
18
+ * Run before the layout changes, while this form is still mounted: the edit
19
+ * view uses it to hand its edit in progress to the full-screen form that is
20
+ * about to show the same record.
21
+ */
22
+ onBeforeHide?: () => void;
23
+ }): React.JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ /**
3
+ * Brings the list back beside a record that was opened full screen.
4
+ *
5
+ * The mirror of {@link SplitListCloseButton}: the same edge of the same bar,
6
+ * the same pane glyph opening rather than closing, and the same single route —
7
+ * showing the list is dropping the `#full` hash. It replaces the back arrow
8
+ * where it applies, because the list it unfolds already holds the record.
9
+ */
10
+ export declare function SplitListShowButton({ onClick }: {
11
+ onClick: () => void;
12
+ }): React.JSX.Element;
@@ -1,16 +1,32 @@
1
1
  import type { Property, RelationProperty } from "@rebasepro/types";
2
2
  import type { Entity } from "@rebasepro/types";
3
- import type { PropertyConfig, AdminCollection } from "@rebasepro/admin-types";
3
+ import type { PropertyConfig, AdminCollection, EntityDisplayRole } from "@rebasepro/admin-types";
4
4
  import type { AuthController } from "@rebasepro/admin-types";
5
5
  import { ChipColorScheme } from "@rebasepro/ui";
6
6
  /**
7
- * A resolved "slot" containing the property definition, key, and the
8
- * concrete value extracted from a entity.
7
+ * A resolved "slot": what fills one display role for one record.
8
+ *
9
+ * `property` and `propertyKey` are set when the value was read off the record,
10
+ * and absent when a `display` resolver computed it — a computed title has no
11
+ * column behind it, so there is nothing to render it *as*. Renderers must go
12
+ * through {@link SlotValue}, which makes that choice once: a property renders
13
+ * with its own preview (an enum keeps its coloured chip, a date its format), a
14
+ * computed value renders as text.
9
15
  */
10
16
  export interface PreviewSlot {
11
- property: Property;
12
- propertyKey: string;
17
+ property?: Property;
18
+ propertyKey?: string;
13
19
  value: unknown;
20
+ /**
21
+ * Which display role this slot fills.
22
+ *
23
+ * Load-bearing for the computed case. Without a property there is nothing to
24
+ * infer from, and "render it as text" is right for a title and wrong for an
25
+ * image: a resolver that returned a URL had that URL printed inside the card's
26
+ * image frame. The role is the one thing that distinguishes them, so it
27
+ * travels with the slot rather than being re-derived at each call site.
28
+ */
29
+ role?: EntityDisplayRole;
14
30
  }
15
31
  /**
16
32
  * Date slot extends the base with a pre-formatted string.
@@ -66,6 +82,19 @@ export interface EntityPreviewSlots {
66
82
  status: PreviewSlot | undefined;
67
83
  /** META — date / timestamp */
68
84
  date: DatePreviewSlot | undefined;
85
+ /**
86
+ * TAGS — free chips beside the status.
87
+ *
88
+ * Declared only. Every other role has an inference ladder behind it, and
89
+ * this one deliberately does not: there is no "first enum" or "looks like a
90
+ * timestamp" that means *labels*, so guessing would scatter arbitrary chips
91
+ * across every row of every panel that never asked for them.
92
+ *
93
+ * The role existed on `EntityDisplay` and in `useEntityDisplay` before this
94
+ * field did, which is why a declared `display.tags` resolved correctly and
95
+ * then had nowhere to go.
96
+ */
97
+ tags: PreviewSlot | undefined;
69
98
  /** Entity ID (always available) */
70
99
  entityId: string | number;
71
100
  }
@@ -82,6 +111,8 @@ export interface CollectionSlotKeys {
82
111
  relationKeys: string[];
83
112
  statusKey: string | undefined;
84
113
  dateKey: string | undefined;
114
+ /** Declared only — see {@link EntityPreviewSlots.tags}. */
115
+ tagsKey: string | undefined;
85
116
  }
86
117
  /**
87
118
  * Resolve which property key fills each slot for a given collection.
@@ -102,3 +133,22 @@ export declare function useCollectionSlotKeys(collection: AdminCollection<Record
102
133
  * `React.memo` render functions or loops without violating hook rules.
103
134
  */
104
135
  export declare function resolveEntitySlots(entity: Entity<Record<string, unknown>>, collection: AdminCollection<Record<string, unknown>>, slotKeys: CollectionSlotKeys): EntityPreviewSlots;
136
+ /**
137
+ * The slots for one record, with `admin.display` resolvers applied.
138
+ *
139
+ * {@link resolveEntitySlots} answers the question from values already in hand —
140
+ * which is everything a slot filled by a property needs, and nothing a slot
141
+ * filled by a *resolver* can use. A computed title may have to be fetched, so it
142
+ * arrives after the first render; this hook is where the two meet.
143
+ *
144
+ * Until a resolver lands, the derived slot shows. That is deliberate: a list of
145
+ * fifty rows must not blank its titles for a beat while fifty promises settle,
146
+ * and the derived answer — the best-ranked property carrying a readable value —
147
+ * is a reasonable thing to be looking at meanwhile. When the resolved value
148
+ * arrives it replaces the slot outright, property and all, because there is no
149
+ * property behind a computed value (see {@link PreviewSlot}).
150
+ *
151
+ * Every role is hooked unconditionally, so the hook order is fixed regardless of
152
+ * what a given collection declares.
153
+ */
154
+ export declare function useEntitySlots(entity: Entity<Record<string, unknown>>, collection: AdminCollection<Record<string, unknown>>, slotKeys: CollectionSlotKeys): EntityPreviewSlots;
@@ -6,6 +6,8 @@ export type BarActionsParams = {
6
6
  status: EntityStatus;
7
7
  path: string;
8
8
  entityId?: string | number;
9
+ /** Always false here — this view reads a record, it does not edit one. */
10
+ dirty: boolean;
9
11
  };
10
12
  export type OnTabChangeParams<M extends Record<string, unknown>> = {
11
13
  path: string;
@@ -22,6 +24,13 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
22
24
  parentEntityIds: string[];
23
25
  onTabChange?: (props: OnTabChangeParams<M>) => void;
24
26
  onEditClick?: () => void;
27
+ /**
28
+ * Dismiss this record — the ✕ at the identity bar's trailing edge.
29
+ *
30
+ * Set by the layouts that own the record's place on screen but are not side
31
+ * dialogs: the split, where dismissing is navigating back to the collection.
32
+ */
33
+ onCloseRequest?: () => void;
25
34
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
26
35
  barActions?: (params: BarActionsParams) => React.ReactNode;
27
36
  /**
@@ -29,5 +38,11 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
29
38
  * of the breadcrumb — the side panel's close button.
30
39
  */
31
40
  barActionsStart?: React.ReactNode;
41
+ /**
42
+ * Full screen only: bring the list back beside this record. Set when the
43
+ * record was opened out of a split collection, so folding the list away is
44
+ * reversible; it takes the place of the back arrow when present.
45
+ */
46
+ onShowList?: () => void;
32
47
  }
33
48
  export declare function DetailViewBinding<M extends Record<string, unknown>>({ entityId, ...props }: DetailViewBindingProps<M>): React.JSX.Element;
@@ -22,11 +22,12 @@ export interface DrawerNavigationGroupProps {
22
22
  */
23
23
  drawerOpen: boolean;
24
24
  /**
25
- * Whether tooltips should be shown (drawer closed + hovered)
25
+ * @deprecated No longer read. Entry tooltips are uncontrolled they follow the
26
+ * pointer and focus, and are suppressed wherever the label is already visible.
26
27
  */
27
- tooltipsOpen: boolean;
28
+ tooltipsOpen?: boolean;
28
29
  /**
29
- * Whether admin menu is open (used to control tooltip visibility)
30
+ * @deprecated No longer read. See {@link tooltipsOpen}.
30
31
  */
31
32
  adminMenuOpen?: boolean;
32
33
  /**
@@ -44,8 +45,10 @@ export interface DrawerNavigationGroupProps {
44
45
  /**
45
46
  * Lucide icon name for the group header, from `NavigationGroupMapping.icon`.
46
47
  *
47
- * Supplying one moves the visual anchor from the rows to the group: the header
48
- * gets the icon, and the entries below trade theirs for an indent.
48
+ * It decorates the header and nothing else: the entries below are untouched and
49
+ * keep their own icons. An app that wants the categorised look rows giving up
50
+ * their icons to indent under the group — builds it in its own drawer, by
51
+ * overriding `Shell.DrawerNavigationItem` and passing `indented`.
49
52
  */
50
53
  icon?: string;
51
54
  }
@@ -53,4 +56,4 @@ export interface DrawerNavigationGroupProps {
53
56
  * Shared drawer navigation group component used by both DefaultDrawer and RebaseCloudDrawer.
54
57
  * Renders a collapsible group with header and navigation items.
55
58
  */
56
- export declare function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, drawerOpen, tooltipsOpen, adminMenuOpen, headerActions, onItemClick, hideHeader, icon }: DrawerNavigationGroupProps): React.JSX.Element;
59
+ export declare function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, drawerOpen, headerActions, onItemClick, hideHeader, icon }: DrawerNavigationGroupProps): React.JSX.Element;
@@ -1,16 +1,26 @@
1
1
  import React from "react";
2
- export declare function DrawerNavigationItem({ name, icon, drawerOpen, adminMenuOpen, tooltipsOpen, url, onClick, indented }: {
2
+ /**
3
+ * Props of a drawer navigation row, named so an app overriding
4
+ * `Shell.DrawerNavigationItem` can type its wrapper against them.
5
+ */
6
+ export type DrawerNavigationItemProps = {
3
7
  icon: React.ReactElement;
4
8
  name: string;
5
- tooltipsOpen: boolean;
9
+ /** @deprecated No longer read: the tooltip is uncontrolled. */
10
+ tooltipsOpen?: boolean;
6
11
  drawerOpen: boolean;
12
+ /** @deprecated No longer read: the tooltip is uncontrolled. */
7
13
  adminMenuOpen?: boolean;
8
14
  url: string;
9
15
  onClick?: () => void;
10
16
  /**
11
- * Render as a child of an icon-bearing group: no icon of its own, indented to
12
- * sit under the group's label. Set by {@link DrawerNavigationGroup} only when
13
- * the group actually has an icon and the drawer is expanded.
17
+ * Drop the row's icon and indent the label into the space it occupied, so the
18
+ * row reads as a child of the group header above it.
19
+ *
20
+ * Nothing in the framework sets this: the stock drawer always passes `false`,
21
+ * and an app that wants the categorised look opts into it by overriding
22
+ * `Shell.DrawerNavigationItem` and deciding for itself which rows get it.
14
23
  */
15
24
  indented?: boolean;
16
- }): React.JSX.Element;
25
+ };
26
+ export declare function DrawerNavigationItem({ name, icon, drawerOpen, url, onClick, indented }: DrawerNavigationItemProps): React.JSX.Element;
@@ -10,6 +10,19 @@ export type BarActionsParams = {
10
10
  status: EntityStatus;
11
11
  path: string;
12
12
  entityId?: string | number;
13
+ /** Whether `values` differ from what is stored. */
14
+ dirty: boolean;
15
+ /**
16
+ * Hand the edit in progress to the layout this action is about to open, so
17
+ * the record arrives showing — and still holding — what the user typed here.
18
+ * Call it from any action that changes layout without leaving the record;
19
+ * it decides on its own whether there is anything worth carrying, so a
20
+ * record nobody edited still arrives clean.
21
+ *
22
+ * Only supplied where there is a form to carry: the detail view passes no
23
+ * such thing.
24
+ */
25
+ carryEdit?: () => void;
13
26
  };
14
27
  export type OnTabChangeParams<M extends Record<string, unknown>> = {
15
28
  path: string;
@@ -33,6 +46,16 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
33
46
  onSaved?: (params: OnUpdateParams) => void;
34
47
  onTabChange?: (props: OnTabChangeParams<M>) => void;
35
48
  navigateBack?: () => void;
49
+ /**
50
+ * Dismiss this record — the ✕ at the identity bar's trailing edge, and what
51
+ * "save and close" reaches once the save lands.
52
+ *
53
+ * Set by the layouts that own the record's place on screen but are not side
54
+ * dialogs: the split, where dismissing is navigating back to the collection.
55
+ * The side panel and the dialog close through {@link useSideDialogContext}
56
+ * instead, which is what runs their unsaved-changes prompt.
57
+ */
58
+ onCloseRequest?: () => void;
36
59
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
37
60
  barActions?: (params: BarActionsParams) => React.ReactNode;
38
61
  /**
@@ -40,6 +63,12 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
40
63
  * of the breadcrumb — the side panel's close button.
41
64
  */
42
65
  barActionsStart?: React.ReactNode;
66
+ /**
67
+ * Full screen only: bring the list back beside this record. Set when the
68
+ * record was opened out of a split collection, so folding the list away is
69
+ * reversible; it takes the place of the back arrow when present.
70
+ */
71
+ onShowList?: () => void;
43
72
  formProps?: Partial<EntityFormBindingProps<M>>;
44
73
  /**
45
74
  * Pre-populate the form with these values when creating a new entity.
@@ -53,7 +82,7 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
53
82
  * a record is opened.
54
83
  */
55
84
  export declare function EditViewBinding<M extends Record<string, unknown>>({ entityId, ...props }: EditViewBindingProps<M>): React.JSX.Element;
56
- export declare function EditViewBindingInner<M extends Record<string, unknown>>({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout, barActions, barActionsStart, status, setStatus, formProps, canEdit }: EditViewBindingProps<M> & {
85
+ export declare function EditViewBindingInner<M extends Record<string, unknown>>({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, onCloseRequest, entity, initialDirtyValues, dataLoading, layout, barActions, barActionsStart, onShowList, status, setStatus, formProps, canEdit }: EditViewBindingProps<M> & {
57
86
  entity?: Entity<M>;
58
87
  initialDirtyValues?: Partial<M>;
59
88
  dataLoading: boolean;
@@ -0,0 +1,53 @@
1
+ import type { AdminCollection } from "@rebasepro/admin-types";
2
+ import type { Entity } from "@rebasepro/types";
3
+ import React from "react";
4
+ /**
5
+ * The roles whose property the read-only view takes out of its grid.
6
+ *
7
+ * A role rendered here and left in the grid prints the value twice, so the
8
+ * header's own rendering and this list have to agree — but the list is *not*
9
+ * simply "every role the header draws". Two are deliberately absent:
10
+ *
11
+ * `title`, because the heading above a record and a labelled field are not the
12
+ * same thing. `Order #` is a column with a name, a description and a format that
13
+ * someone reads the record to find; `ORD-2026-0043` sitting in the identity bar
14
+ * beside the breadcrumb is a label for the page. Removing the field because the
15
+ * page is named after it takes a value out of the record on the grounds that its
16
+ * text appears elsewhere, which is not a reason — a value in the database is
17
+ * shown.
18
+ *
19
+ * `date`, because the rail's record block already carries the timestamps, and a
20
+ * collection whose `date` role points at a business date — an order date, a due
21
+ * date — wants that field where it declared it.
22
+ */
23
+ export declare const HEADER_DISPLAY_ROLES: readonly ["subtitle", "image", "status", "tags"];
24
+ export interface EntityDisplayHeaderProps<M extends Record<string, unknown>> {
25
+ entity: Entity<M>;
26
+ collection: AdminCollection<M>;
27
+ /**
28
+ * Render the title. Off when an identity bar above already shows it — which
29
+ * is every full-page use — and on inside a dialog that has no bar.
30
+ */
31
+ showTitle?: boolean;
32
+ className?: string;
33
+ }
34
+ /**
35
+ * What the record *is*, above the fields that say what it holds.
36
+ *
37
+ * A record's picture, name, state and labels are the things you opened the page
38
+ * to see; on the plain grid they were fields like any other, so an order's
39
+ * status sat in the second cell of the first row at the same size and weight as
40
+ * its shipping cost, and its tags were a wrapped list of grey text.
41
+ *
42
+ * Every role here comes from `admin.display` and nothing is derived except the
43
+ * title, which was already derived for the identity bar. A collection that
44
+ * declares no display block therefore gets no header at all rather than a
45
+ * heuristic guess at which of its enums is a "status" — the panel does not know
46
+ * what these records are, and a wrong guess here is louder than no guess.
47
+ *
48
+ * A role filled by a *property* renders through {@link PropertyPreview}, so a
49
+ * status keeps its enum's colour and an image stays a storage thumbnail. A role
50
+ * filled by a resolver has no property behind it and renders as what it
51
+ * returned.
52
+ */
53
+ export declare function EntityDisplayHeader<M extends Record<string, unknown>>({ entity, collection, showTitle, className }: EntityDisplayHeaderProps<M>): React.JSX.Element | null;