@rebasepro/admin 0.13.1-canary.gef9608c → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/{CollectionEditorDialog-7b-kSLno.js → CollectionEditorDialog-CSheud_E.js} +19 -9
  2. package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
  3. package/dist/{PropertyEditView-C-31uvpG.js → PropertyEditView-CWZlC1gq.js} +5 -5
  4. package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-BJylABq3.js → RouterCollectionsStudioView-CwTpdzWF.js} +9 -13
  6. package/dist/RouterCollectionsStudioView-CwTpdzWF.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/ClearFilterSortButton.d.ts +7 -1
  10. package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
  11. package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
  12. package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +101 -0
  13. package/dist/components/CollectionViewBinding/SlotValue.d.ts +26 -0
  14. package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
  15. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -6
  16. package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +1 -1
  17. package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +26 -1
  18. package/dist/components/DetailViewBinding.d.ts +7 -0
  19. package/dist/components/DrawerNavigationGroup.d.ts +4 -2
  20. package/dist/components/DrawerNavigationItem.d.ts +13 -5
  21. package/dist/components/EditViewBinding.d.ts +22 -7
  22. package/dist/components/EntityIdentityBar.d.ts +24 -1
  23. package/dist/components/RelationSelector.d.ts +13 -0
  24. package/dist/data_export/export/export.d.ts +7 -0
  25. package/dist/data_export/export/fetch_export_data.d.ts +48 -0
  26. package/dist/data_export/export/index.d.ts +1 -0
  27. package/dist/data_import/utils/csv.d.ts +30 -0
  28. package/dist/data_import/utils/index.d.ts +2 -0
  29. package/dist/data_import/utils/save_entities.d.ts +38 -0
  30. package/dist/data_import/utils/transforms.d.ts +9 -0
  31. package/dist/editor.js +3 -0
  32. package/dist/editor.js.map +1 -1
  33. package/dist/{export-C-XG3aRh.js → export-L53WektO.js} +175 -39
  34. package/dist/export-L53WektO.js.map +1 -0
  35. package/dist/form/form_utils.d.ts +38 -1
  36. package/dist/{history-DfMuvPfr.js → history-D5SKygpJ.js} +2 -2
  37. package/dist/{history-DfMuvPfr.js.map → history-D5SKygpJ.js.map} +1 -1
  38. package/dist/{import-B0GyllB0.js → import-C_4edkoD.js} +5 -4
  39. package/dist/import-C_4edkoD.js.map +1 -0
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +96 -80
  42. package/dist/index.js.map +1 -1
  43. package/dist/preview/components/date_fns_locales.d.ts +9 -0
  44. package/dist/util/view_constants.d.ts +24 -0
  45. package/dist/util/view_mode.d.ts +18 -0
  46. package/dist/{util-DHNXodBo.js → util-C-D5yD2n.js} +2173 -673
  47. package/dist/util-C-D5yD2n.js.map +1 -0
  48. package/package.json +10 -9
  49. package/src/collection_editor/serializable_types.ts +1 -0
  50. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +8 -3
  51. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -2
  52. package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +1 -0
  53. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +9 -8
  54. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
  55. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +5 -2
  56. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
  57. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
  58. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -0
  59. package/src/collection_editor/useJsonCollectionsConfigController.tsx +92 -84
  60. package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
  61. package/src/components/ClearFilterSortButton.tsx +16 -7
  62. package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +2 -2
  63. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
  64. package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
  65. package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
  66. package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
  67. package/src/components/CollectionViewBinding/BoardCardBinding.tsx +18 -1
  68. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
  69. package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
  70. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +816 -335
  71. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
  72. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +26 -6
  73. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
  74. package/src/components/CollectionViewBinding/EntityCardBinding.tsx +45 -10
  75. package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
  76. package/src/components/CollectionViewBinding/SearchHighlight.tsx +311 -0
  77. package/src/components/CollectionViewBinding/SlotValue.tsx +96 -0
  78. package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
  79. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -9
  80. package/src/components/CollectionViewBinding/SplitListShowButton.tsx +3 -3
  81. package/src/components/CollectionViewBinding/SplitListView.tsx +36 -9
  82. package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
  83. package/src/components/CollectionViewBinding/useBoardDataController.tsx +11 -3
  84. package/src/components/CollectionViewBinding/usePreviewSlots.ts +76 -24
  85. package/src/components/CollectionViewBinding/utils.ts +7 -4
  86. package/src/components/DefaultDrawer.tsx +7 -0
  87. package/src/components/DetailViewBinding.tsx +33 -9
  88. package/src/components/DrawerNavigationGroup.tsx +12 -32
  89. package/src/components/DrawerNavigationItem.tsx +33 -12
  90. package/src/components/EditViewBinding.tsx +175 -28
  91. package/src/components/EntityIdentityBar.tsx +109 -17
  92. package/src/components/EntityInspector.tsx +24 -6
  93. package/src/components/EntityViewBinding.tsx +4 -2
  94. package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
  95. package/src/components/RelationSelector.tsx +61 -11
  96. package/src/components/SearchIconsView.tsx +14 -33
  97. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
  98. package/src/components/SidePanelBinding.tsx +36 -25
  99. package/src/components/UserSelector.tsx +52 -9
  100. package/src/components/history/LastEditedByIndicator.tsx +7 -12
  101. package/src/data_export/export/ExportCollectionAction.tsx +101 -48
  102. package/src/data_export/export/export.ts +63 -16
  103. package/src/data_export/export/fetch_export_data.ts +90 -0
  104. package/src/data_export/export/index.ts +1 -0
  105. package/src/data_import/components/DataNewPropertiesMapping.tsx +20 -3
  106. package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -0
  107. package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
  108. package/src/data_import/import/ImportCollectionAction.tsx +13 -2
  109. package/src/data_import/utils/csv.ts +153 -0
  110. package/src/data_import/utils/data.ts +16 -2
  111. package/src/data_import/utils/file_to_json.ts +77 -6
  112. package/src/data_import/utils/index.ts +2 -0
  113. package/src/data_import/utils/save_entities.ts +138 -0
  114. package/src/data_import/utils/transforms.ts +23 -0
  115. package/src/editor/components/image-bubble.tsx +2 -0
  116. package/src/editor/selectors/link-selector.tsx +1 -0
  117. package/src/form/EntityForm.tsx +12 -3
  118. package/src/form/EntityFormBinding.tsx +8 -12
  119. package/src/form/PropertyFieldBinding.tsx +2 -2
  120. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
  121. package/src/form/field_bindings/BinaryFieldBinding.tsx +1 -0
  122. package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
  123. package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
  124. package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
  125. package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
  126. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +15 -2
  127. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  128. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
  129. package/src/form/field_bindings/TextFieldBinding.tsx +18 -0
  130. package/src/form/field_bindings/VectorFieldBinding.tsx +1 -0
  131. package/src/form/form_utils.ts +78 -0
  132. package/src/index.ts +9 -1
  133. package/src/preview/PropertyPreview.tsx +7 -3
  134. package/src/preview/components/DatePreview.tsx +15 -2
  135. package/src/preview/components/ReferencePreview.tsx +5 -8
  136. package/src/preview/components/RelationPreview.tsx +8 -7
  137. package/src/preview/components/date_fns_locales.ts +125 -0
  138. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
  139. package/src/util/navigation_utils.ts +4 -2
  140. package/src/util/property_utils.tsx +6 -1
  141. package/src/util/view_constants.ts +27 -0
  142. package/src/util/view_mode.ts +36 -0
  143. package/dist/CollectionEditorDialog-7b-kSLno.js.map +0 -1
  144. package/dist/PropertyEditView-C-31uvpG.js.map +0 -1
  145. package/dist/RouterCollectionsStudioView-BJylABq3.js.map +0 -1
  146. package/dist/export-C-XG3aRh.js.map +0 -1
  147. package/dist/import-B0GyllB0.js.map +0 -1
  148. package/dist/util-DHNXodBo.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-DHNXodBo.js";
3
- import { s as AIModifiedPathsProvider } from "./PropertyEditView-C-31uvpG.js";
4
- import { t as CollectionEditor } from "./CollectionEditorDialog-7b-kSLno.js";
2
+ import { ln as useUrlController } from "./util-C-D5yD2n.js";
3
+ import { s as AIModifiedPathsProvider } from "./PropertyEditView-CWZlC1gq.js";
4
+ import { t as CollectionEditor } from "./CollectionEditorDialog-CSheud_E.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-BJylABq3.js.map
188
+ //# sourceMappingURL=RouterCollectionsStudioView-CwTpdzWF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouterCollectionsStudioView-CwTpdzWF.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-DHNXodBo.js";
2
- import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-C-31uvpG.js";
3
- import { n as CollectionEditorDialog } from "./CollectionEditorDialog-7b-kSLno.js";
4
- import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-BJylABq3.js";
1
+ import { ln as useUrlController } from "./util-C-D5yD2n.js";
2
+ import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-CWZlC1gq.js";
3
+ import { n as CollectionEditorDialog } from "./CollectionEditorDialog-CSheud_E.js";
4
+ import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-CwTpdzWF.js";
5
5
  import "react";
6
6
  import { useSnackbarController } from "@rebasepro/app";
7
7
  import { jsx } from "react/jsx-runtime";
@@ -1,5 +1,11 @@
1
1
  import { EntityTableController } from "@rebasepro/admin-types";
2
- export declare function ClearFilterSortButton({ tableController, enabled }: {
2
+ export declare function ClearFilterSortButton({ tableController, enabled, compact }: {
3
3
  enabled: boolean;
4
4
  tableController: EntityTableController;
5
+ /**
6
+ * Toolbar showing icon-only controls (split view, or a layout too narrow
7
+ * for the filters button's label). Only decides the icon size, which
8
+ * follows that button — see `CollectionViewStartActions`.
9
+ */
10
+ compact?: boolean;
5
11
  }): import("react").JSX.Element | null;
@@ -2,6 +2,8 @@ import React from "react";
2
2
  import { Entity } from "@rebasepro/types";
3
3
  import { CollectionSize, AdminCollection } from "@rebasepro/admin-types";
4
4
  export type EntityCardBindingProps<M extends Record<string, unknown> = Record<string, unknown>> = {
5
+ /** The active search, so a card can mark the hit and say where it was. */
6
+ searchString?: string;
5
7
  entity: Entity<M>;
6
8
  collection: AdminCollection<M>;
7
9
  onClick?: (entity: Entity<M>) => void;
@@ -18,4 +20,4 @@ export type EntityCardBindingProps<M extends Record<string, unknown> = Record<st
18
20
  * Card component for displaying a entity in a grid view.
19
21
  * Shows thumbnail, title, and preview properties via the shared slot system.
20
22
  */
21
- export declare function EntityCardBinding<M extends Record<string, unknown> = Record<string, unknown>>({ entity, collection, onClick, selected, highlighted, onSelectionChange, selectionEnabled, size }: EntityCardBindingProps<M>): React.JSX.Element;
23
+ export declare function EntityCardBinding<M extends Record<string, unknown> = Record<string, unknown>>({ entity, collection, onClick, selected, highlighted, onSelectionChange, selectionEnabled, size, searchString }: EntityCardBindingProps<M>): React.JSX.Element;
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+ import type { SearchMatch } from "@rebasepro/types";
3
+ /**
4
+ * The one place a view asks "why is this row here?" and gets something to draw.
5
+ *
6
+ * List, cards and board all render the same title-and-subtitle slots over the
7
+ * same machinery. An explanation that shows up in one of them and not the others
8
+ * reads as randomness rather than as a feature, so the decision and the markup
9
+ * live here and each view calls them.
10
+ */
11
+ /**
12
+ * Terms to mark inside values the row already shows, plus the single match
13
+ * worth putting where the subtitle would go — or nothing, when the row is not
14
+ * the product of a search or the hit is already visible.
15
+ *
16
+ * Prefers the server's `_matches`, which knows the stemming the index used, and
17
+ * falls back to reading the row directly. That fallback is not belt-and-braces:
18
+ * rows reach the panel over REST, the realtime socket and the offline cache,
19
+ * and only one of those carries query metadata.
20
+ */
21
+ export declare function useSearchExplanation(entity: {
22
+ values?: Record<string, unknown>;
23
+ searchMatches?: SearchMatch[];
24
+ }, properties: Record<string, unknown> | undefined, shownKeys: (string | undefined)[], searchString: string | undefined): {
25
+ terms: string[];
26
+ offSlot: SearchMatch | undefined;
27
+ };
28
+ /**
29
+ * Which field the hit was in, and the text around it.
30
+ *
31
+ * Sits where the subtitle would have gone. Deliberately quiet — a small amber
32
+ * caption and one line — because it is context for a result, not the result. It
33
+ * has to survive a narrow column, so it truncates rather than wraps.
34
+ */
35
+ export declare function MatchExplanation({ match, label }: {
36
+ match: SearchMatch;
37
+ label: string;
38
+ }): React.JSX.Element;
39
+ /**
40
+ * What to call a matched field on screen.
41
+ *
42
+ * The path is what the config and the server speak — `questionnaire.
43
+ * certifications` — and is unreadable in a list. Resolve it to the property's
44
+ * declared `name`, walking into `map` properties for a dotted path, and fall
45
+ * back to the last segment humanised, so an unnamed field still reads as words
46
+ * rather than as a path.
47
+ */
48
+ export declare function fieldLabel(properties: Record<string, unknown> | undefined, path: string): string;
@@ -0,0 +1,101 @@
1
+ import React from "react";
2
+ import type { SearchMatch } from "@rebasepro/types";
3
+ /**
4
+ * Pull the first mark near the front of the snippet.
5
+ *
6
+ * The snippet renders on one truncated line whose width is not knowable here —
7
+ * a list column is wide, the same list beside an open record is not. Whatever
8
+ * precedes the mark competes with it for that width, and when the mark loses
9
+ * the reader gets a fragment of their record with nothing highlighted in it,
10
+ * which reads as the feature being broken rather than as a narrow column.
11
+ *
12
+ * `ts_headline` centres its fragment on the match, so roughly half the window
13
+ * is lead-in; that is the right shape for a paragraph and the wrong one for a
14
+ * line. Trimming here rather than asking Postgres for a smaller window keeps
15
+ * the trailing context, which is what actually explains the hit — and applies
16
+ * the same shape to the snippets built in the browser, so a row looks the same
17
+ * whichever path produced it.
18
+ */
19
+ export declare function trimSnippetLead(text: string, lead?: number): string;
20
+ export declare function Snippet({ text, className }: {
21
+ text: string;
22
+ className?: string;
23
+ }): React.JSX.Element;
24
+ /**
25
+ * Mark every occurrence of the searched terms inside a plain value.
26
+ *
27
+ * Used for values the list already renders, where the server's snippet would be
28
+ * the wrong thing to show: the snippet is truncated to a fragment and folded to
29
+ * the indexed form, while the cell should keep the record's real text. So the
30
+ * terms are re-matched here, on the display value.
31
+ *
32
+ * Deliberately dumber than the server's matching, and it has to be: this cannot
33
+ * stem or unaccent without reimplementing the text search configuration in the
34
+ * browser and getting it subtly different. It marks what it can find as a
35
+ * prefix, case- and accent-insensitively, and marks nothing when it cannot —
36
+ * a highlight that is merely absent reads as "no hit in this field", which is
37
+ * the honest answer for a stem the browser cannot reproduce.
38
+ */
39
+ export declare function Highlighted({ text, terms, className }: {
40
+ text: string;
41
+ terms: string[];
42
+ className?: string;
43
+ }): React.JSX.Element;
44
+ interface Segment {
45
+ text: string;
46
+ hit: boolean;
47
+ }
48
+ /**
49
+ * Split `text` into hit / non-hit runs.
50
+ *
51
+ * Matches on the *folded* form but slices the original, so the marked text
52
+ * keeps its accents and case — `Gestión`, not `gestion`. Folding is
53
+ * length-preserving for the diacritics this handles, which is what makes the
54
+ * indices transferable; a fold that changed length would misalign the slices.
55
+ */
56
+ export declare function splitOnTerms(text: string, terms: string[]): Segment[];
57
+ /**
58
+ * The terms a searcher typed, as things to look for in a rendered value.
59
+ *
60
+ * Quoted phrases stay whole; `-excluded` terms are dropped, since marking a
61
+ * word the user asked *not* to see would be actively wrong. `websearch_to_tsquery`
62
+ * reads the same syntax on the server.
63
+ */
64
+ export declare function searchTerms(searchString: string | undefined): string[];
65
+ /**
66
+ * The best match to show when the term is nowhere the list already displays.
67
+ *
68
+ * `shownKeys` are the property paths the row is already rendering, so a match
69
+ * there needs no explaining — {@link Highlighted} marks it in place and the
70
+ * subtitle keeps its own job. Anything else is invisible, and one of those is
71
+ * what the subtitle slot gets replaced with.
72
+ *
73
+ * The first such match wins, and the server returns them in the order the
74
+ * collection declared its `search` fields — so the field an author put first is
75
+ * the one a reader sees, rather than whichever the database aggregated first.
76
+ */
77
+ export declare function offSlotMatch(matches: SearchMatch[] | undefined, shownKeys: (string | undefined)[]): SearchMatch | undefined;
78
+ /**
79
+ * Where a search term appears in a row, computed from the row itself.
80
+ *
81
+ * The server can say this too — `_matches`, from `ts_headline` — and its answer
82
+ * is better, because it knows the stemming the index used. This exists because
83
+ * the admin cannot rely on getting it: rows reach the panel over three
84
+ * different transports (REST, the realtime socket, the offline cache) and only
85
+ * one of them carries query metadata. A feature that explains a result on some
86
+ * loads and not others is worse than one that always gives a slightly simpler
87
+ * answer.
88
+ *
89
+ * So: use the server's when it is there, fall back to this. Both produce the
90
+ * same `{ field, snippet }` shape, and this one marks with the same `<mark>`.
91
+ */
92
+ export declare function localRowMatch(values: Record<string, unknown> | undefined, properties: Record<string, unknown> | undefined, terms: string[], shownKeys: (string | undefined)[]): SearchMatch | undefined;
93
+ /**
94
+ * The text around the first hit, with every hit in view marked.
95
+ *
96
+ * Trimmed to a readable window rather than the whole field: a match in a long
97
+ * body should show the sentence it is in, not the first line of the document,
98
+ * and certainly not the document.
99
+ */
100
+ export declare function snippetAround(text: string, terms: string[]): string | undefined;
101
+ export {};
@@ -35,3 +35,29 @@ export interface SlotValueProps {
35
35
  * seven versions.
36
36
  */
37
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;
@@ -8,6 +8,12 @@ export type SortButtonProps<M extends Record<string, unknown>> = {
8
8
  * the options offered.
9
9
  */
10
10
  properties: Properties;
11
+ /**
12
+ * Toolbar showing icon-only controls (split view, or a layout too narrow
13
+ * for the filters button's label). Only decides the icon size, which
14
+ * follows that button — see `CollectionViewStartActions`.
15
+ */
16
+ compact?: boolean;
11
17
  };
12
18
  /**
13
19
  * Toolbar control that orders the collection by one property.
@@ -21,4 +27,4 @@ export type SortButtonProps<M extends Record<string, unknown>> = {
21
27
  * is one click rather than a trip through a direction selector; clicking a
22
28
  * different one starts it ascending. The popover stays open through both.
23
29
  */
24
- export declare function SortButton<M extends Record<string, unknown>>({ tableController, properties }: SortButtonProps<M>): React.JSX.Element | null;
30
+ export declare function SortButton<M extends Record<string, unknown>>({ tableController, properties, compact }: SortButtonProps<M>): React.JSX.Element | null;
@@ -3,10 +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 a
7
- * double chevron pointing at the list it folds away: closing the list and
8
- * opening the record full screen are the same thing, so there is one control
9
- * and one route (`#full`) rather than two. Full screen carries the mirrored
10
- * chevron to bring the list back.
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.
11
15
  */
12
- 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;
@@ -3,7 +3,7 @@ import React from "react";
3
3
  * Brings the list back beside a record that was opened full screen.
4
4
  *
5
5
  * The mirror of {@link SplitListCloseButton}: the same edge of the same bar,
6
- * the same double chevron pointing the other way, and the same single route —
6
+ * the same pane glyph opening rather than closing, and the same single route —
7
7
  * showing the list is dropping the `#full` hash. It replaces the back arrow
8
8
  * where it applies, because the list it unfolds already holds the record.
9
9
  */
@@ -1,6 +1,6 @@
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
  /**
@@ -17,6 +17,16 @@ export interface PreviewSlot {
17
17
  property?: Property;
18
18
  propertyKey?: string;
19
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;
20
30
  }
21
31
  /**
22
32
  * Date slot extends the base with a pre-formatted string.
@@ -72,6 +82,19 @@ export interface EntityPreviewSlots {
72
82
  status: PreviewSlot | undefined;
73
83
  /** META — date / timestamp */
74
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;
75
98
  /** Entity ID (always available) */
76
99
  entityId: string | number;
77
100
  }
@@ -88,6 +111,8 @@ export interface CollectionSlotKeys {
88
111
  relationKeys: string[];
89
112
  statusKey: string | undefined;
90
113
  dateKey: string | undefined;
114
+ /** Declared only — see {@link EntityPreviewSlots.tags}. */
115
+ tagsKey: string | undefined;
91
116
  }
92
117
  /**
93
118
  * Resolve which property key fills each slot for a given collection.
@@ -24,6 +24,13 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
24
24
  parentEntityIds: string[];
25
25
  onTabChange?: (props: OnTabChangeParams<M>) => void;
26
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;
27
34
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
28
35
  barActions?: (params: BarActionsParams) => React.ReactNode;
29
36
  /**
@@ -45,8 +45,10 @@ export interface DrawerNavigationGroupProps {
45
45
  /**
46
46
  * Lucide icon name for the group header, from `NavigationGroupMapping.icon`.
47
47
  *
48
- * Supplying one moves the visual anchor from the rows to the group: the header
49
- * 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`.
50
52
  */
51
53
  icon?: string;
52
54
  }
@@ -1,5 +1,9 @@
1
1
  import React from "react";
2
- export declare function DrawerNavigationItem({ name, icon, drawerOpen, 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
9
  /** @deprecated No longer read: the tooltip is uncontrolled. */
@@ -10,9 +14,13 @@ export declare function DrawerNavigationItem({ name, icon, drawerOpen, url, onCl
10
14
  url: string;
11
15
  onClick?: () => void;
12
16
  /**
13
- * Render as a child of an icon-bearing group: no icon of its own, indented to
14
- * sit under the group's label. Set by {@link DrawerNavigationGroup} only when
15
- * 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.
16
23
  */
17
24
  indented?: boolean;
18
- }): React.JSX.Element;
25
+ };
26
+ export declare function DrawerNavigationItem({ name, icon, drawerOpen, url, onClick, indented }: DrawerNavigationItemProps): React.JSX.Element;
@@ -10,14 +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;
13
15
  /**
14
- * Whether `values` differ from what is stored. The side panel's "open full
15
- * screen" hands the in-flight edit to the full-screen route through the
16
- * memory cache, and a record loaded from that cache opens *dirty*: without
17
- * this it stashed the values unconditionally, so expanding a record you had
18
- * only looked at arrived saying "Unsaved changes" with Save enabled.
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.
19
24
  */
20
- dirty: boolean;
25
+ carryEdit?: () => void;
21
26
  };
22
27
  export type OnTabChangeParams<M extends Record<string, unknown>> = {
23
28
  path: string;
@@ -41,6 +46,16 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
41
46
  onSaved?: (params: OnUpdateParams) => void;
42
47
  onTabChange?: (props: OnTabChangeParams<M>) => void;
43
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;
44
59
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
45
60
  barActions?: (params: BarActionsParams) => React.ReactNode;
46
61
  /**
@@ -67,7 +82,7 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
67
82
  * a record is opened.
68
83
  */
69
84
  export declare function EditViewBinding<M extends Record<string, unknown>>({ entityId, ...props }: EditViewBindingProps<M>): React.JSX.Element;
70
- 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, onShowList, 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> & {
71
86
  entity?: Entity<M>;
72
87
  initialDirtyValues?: Partial<M>;
73
88
  dataLoading: boolean;
@@ -29,6 +29,29 @@ export interface EntityIdentityBarProps {
29
29
  * action is "done with this record" rather than "keep editing".
30
30
  */
31
31
  onSaveAndClose?: () => void;
32
+ /**
33
+ * Where {@link onSaveAndClose} is offered.
34
+ *
35
+ * `"button"` — its own filled button, and Save demotes to text. For the
36
+ * overlays, where you opened the record to do one thing to it and closing is
37
+ * the dominant intent.
38
+ *
39
+ * `"menu"` — a `▾` welded to the Save button. For the split, where the list
40
+ * is right there and `j`/`k` walk from record to record: there, saving and
41
+ * *staying* is the common case, so it keeps the filled button and dismissing
42
+ * is one step further in. The gesture still exists, so muscle memory carried
43
+ * over from the side panel finds it.
44
+ */
45
+ saveAndClosePlacement?: "button" | "menu";
46
+ /**
47
+ * Dismiss this record, from the ✕ at the bar's trailing edge.
48
+ *
49
+ * Fenced off from Save by a separator: it is chrome, not the last item in
50
+ * the action row. The panel that supplies it decides what dismissing means —
51
+ * in the split it is a navigation, which the layout's unsaved-changes
52
+ * blocker already guards, so the ✕ cannot drop an edit without asking.
53
+ */
54
+ onClose?: () => void;
32
55
  onBack?: () => void;
33
56
  onInspect?: () => void;
34
57
  /** Opens the inspector straight on its history tab. */
@@ -69,4 +92,4 @@ export interface EntityIdentityBarProps {
69
92
  * editing. They live here instead, and the left half of a bar that was
70
93
  * previously empty now does the work.
71
94
  */
72
- export declare function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, externalLink, recordActions, pluginActions, trailing, leading }: EntityIdentityBarProps): React.JSX.Element;
95
+ export declare function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, saveAndClosePlacement, onClose, onBack, onInspect, onViewHistory, externalLink, recordActions, pluginActions, trailing, leading }: EntityIdentityBarProps): React.JSX.Element;
@@ -47,7 +47,20 @@ export interface RelationSelectorProps {
47
47
  pageSize?: number;
48
48
  emptyPlaceholder?: string;
49
49
  searchPlaceholder?: string;
50
+ /** Shown in the open list when a search matched nothing. */
50
51
  noResultsText?: string;
52
+ /**
53
+ * Shown on the closed trigger when nothing is selected. Says only that the
54
+ * field is empty — see the note by `resolvedPlaceholder` for why it cannot
55
+ * say whether there is anything to pick.
56
+ */
57
+ emptyText?: string;
58
+ /**
59
+ * Shown in the open list when the target collection turned out to hold no
60
+ * rows at all, as opposed to a search simply not matching. Defaults to a
61
+ * sentence naming the target collection.
62
+ */
63
+ emptyCollectionText?: string;
51
64
  loadingText?: string;
52
65
  }
53
66
  export declare const RelationSelector: React.ForwardRefExoticComponent<RelationSelectorProps & React.RefAttributes<HTMLButtonElement>>;
@@ -17,6 +17,13 @@ export interface DownloadEntitiesExportParams<M extends Record<string, unknown>>
17
17
  export declare function downloadEntitiesExport<M extends Record<string, unknown>>({ data, additionalData, properties, propertiesOrder, name, flattenArrays, additionalHeaders, exportType, dateExportType }: DownloadEntitiesExportParams<M>): void;
18
18
  export declare function getEntityCSVExportableData(data: Entity<Record<string, unknown>>[], additionalData: Record<string, unknown>[] | undefined, properties: Properties, headers: Header[], dateExportType: "timestamp" | "string"): unknown[][];
19
19
  export declare function getEntityJsonExportableData(data: Entity<Record<string, unknown>>[], additionalData: Record<string, unknown>[] | undefined, properties: Properties, dateExportType: "timestamp" | "string"): Record<string, unknown>[];
20
+ /**
21
+ * Neutralise a cell that a spreadsheet would otherwise evaluate, by prefixing a
22
+ * single quote — the standard OWASP mitigation. The apostrophe is consumed by
23
+ * the spreadsheet as "treat the rest as text", so the value reads back intact.
24
+ */
25
+ export declare function escapeCsvFormula(value: string): string;
26
+ export declare function entryToCSVRow(entry: unknown[]): string;
20
27
  export declare function downloadBlob(content: BlobPart[], filename: string, contentType: string): void;
21
28
  export declare function downloadDataAsCsv(data: object[], name: string): void;
22
29
  export {};