@rebasepro/admin 0.9.0 → 0.9.1-canary.0de22e0

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 (264) hide show
  1. package/README.md +7 -7
  2. package/dist/{CollectionEditorDialog-B-7uW3iJ.js → CollectionEditorDialog-D7odTn0N.js} +150 -36
  3. package/dist/CollectionEditorDialog-D7odTn0N.js.map +1 -0
  4. package/dist/{PropertyEditView-7KMTcrqi.js → PropertyEditView-i51tFOfM.js} +6 -6
  5. package/dist/PropertyEditView-i51tFOfM.js.map +1 -0
  6. package/dist/{RouterCollectionsStudioView-epqIbZrJ.js → RouterCollectionsStudioView-B9AjTCjT.js} +5 -5
  7. package/dist/RouterCollectionsStudioView-B9AjTCjT.js.map +1 -0
  8. package/dist/collection_editor/api/generateCollectionApi.d.ts +4 -4
  9. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +1 -1
  10. package/dist/collection_editor/ui/collection_editor/SwitchControl.d.ts +1 -1
  11. package/dist/collection_editor/useSafeSnackbarController.d.ts +1 -1
  12. package/dist/collection_editor_ui.js +5 -5
  13. package/dist/collection_editor_ui.js.map +1 -1
  14. package/dist/components/CollectionTableBinding/CollectionTableBindingProps.d.ts +1 -1
  15. package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +1 -1
  16. package/dist/components/CollectionTableBinding/internal/popup_field/PopupFormField.d.ts +1 -1
  17. package/dist/components/CollectionTableBinding/table_bindings.d.ts +1 -1
  18. package/dist/components/CollectionViewBinding/hooks/useCollectionInlineEditor.d.ts +1 -1
  19. package/dist/components/{RebaseCMS.d.ts → RebaseAdmin.d.ts} +2 -2
  20. package/dist/components/RebaseLayout.d.ts +5 -4
  21. package/dist/components/RebaseShell.d.ts +1 -0
  22. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  23. package/dist/components/SelectableTable/SelectableTableContext.d.ts +1 -1
  24. package/dist/components/SidePanelProvider.d.ts +1 -1
  25. package/dist/components/field_configs.d.ts +17 -0
  26. package/dist/components/index.d.ts +1 -1
  27. package/dist/contexts/BreacrumbsContext.d.ts +1 -1
  28. package/dist/editor.js +1 -1
  29. package/dist/editor.js.map +1 -1
  30. package/dist/export-BM6HPujv.js +518 -0
  31. package/dist/export-BM6HPujv.js.map +1 -0
  32. package/dist/form/components/LocalChangesMenu.d.ts +1 -1
  33. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  34. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
  35. package/dist/{history-7X5bON_n.js → history-CtIPJxvQ.js} +6 -6
  36. package/dist/history-CtIPJxvQ.js.map +1 -0
  37. package/dist/hooks/useSelectionDialog.d.ts +1 -1
  38. package/dist/import-DzQnT03v.js +328 -0
  39. package/dist/import-DzQnT03v.js.map +1 -0
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js +3300 -2
  42. package/dist/index.js.map +1 -0
  43. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  44. package/dist/preview/util.d.ts +1 -0
  45. package/dist/types/components/formex.d.ts +1 -1
  46. package/dist/util/view_mode.d.ts +54 -0
  47. package/dist/{src-sUZfe29F.js → util-XFwQ9FEc.js} +17554 -21391
  48. package/dist/util-XFwQ9FEc.js.map +1 -0
  49. package/package.json +12 -17
  50. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  51. package/src/collection_editor/api/generateCollectionApi.ts +4 -4
  52. package/src/collection_editor/ui/AddKanbanColumnAction.tsx +1 -1
  53. package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +1 -1
  54. package/src/collection_editor/ui/EditorCollectionAction.tsx +1 -1
  55. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +1 -1
  56. package/src/collection_editor/ui/EditorEntityAction.tsx +1 -1
  57. package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +1 -1
  58. package/src/collection_editor/ui/KanbanSetupAction.tsx +1 -1
  59. package/src/collection_editor/ui/MissingReferenceWidget.tsx +1 -1
  60. package/src/collection_editor/ui/NewCollectionButton.tsx +1 -1
  61. package/src/collection_editor/ui/NewCollectionCard.tsx +1 -1
  62. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +1 -1
  63. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +1 -1
  64. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +3 -3
  65. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +3 -3
  66. package/src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx +2 -2
  67. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +2 -2
  68. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +139 -13
  69. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +1 -1
  70. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +1 -1
  71. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +1 -1
  72. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +2 -2
  73. package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +2 -2
  74. package/src/collection_editor/ui/collection_editor/EntityActionsSelectDialog.tsx +1 -1
  75. package/src/collection_editor/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +1 -1
  76. package/src/collection_editor/ui/collection_editor/EnumForm.tsx +2 -2
  77. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +2 -2
  78. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +2 -2
  79. package/src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx +2 -2
  80. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +2 -2
  81. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  82. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -1
  83. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
  84. package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +2 -2
  85. package/src/collection_editor/ui/collection_editor/SwitchControl.tsx +1 -1
  86. package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +1 -1
  87. package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportMapping.tsx +2 -2
  88. package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +1 -1
  89. package/src/collection_editor/ui/collection_editor/properties/BooleanPropertyField.tsx +1 -1
  90. package/src/collection_editor/ui/collection_editor/properties/CommonPropertyFields.tsx +3 -3
  91. package/src/collection_editor/ui/collection_editor/properties/DateTimePropertyField.tsx +2 -2
  92. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +3 -3
  93. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  94. package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +2 -2
  95. package/src/collection_editor/ui/collection_editor/properties/NumberPropertyField.tsx +2 -2
  96. package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -2
  97. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +1 -1
  98. package/src/collection_editor/ui/collection_editor/properties/RepeatPropertyField.tsx +1 -1
  99. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +2 -2
  100. package/src/collection_editor/ui/collection_editor/properties/StringPropertyField.tsx +2 -2
  101. package/src/collection_editor/ui/collection_editor/properties/UrlPropertyField.tsx +1 -1
  102. package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +1 -1
  103. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +1 -1
  104. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -1
  105. package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +1 -1
  106. package/src/collection_editor/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
  107. package/src/collection_editor/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +2 -2
  108. package/src/collection_editor/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +2 -2
  109. package/src/collection_editor/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +2 -2
  110. package/src/collection_editor/useSafeSnackbarController.ts +1 -1
  111. package/src/components/AdminModeSyncer.tsx +1 -1
  112. package/src/components/ArrayContainer.tsx +1 -1
  113. package/src/components/ClearFilterSortButton.tsx +1 -1
  114. package/src/components/CollectionPanel.tsx +1 -1
  115. package/src/components/CollectionTableBinding/CollectionRowActions.tsx +3 -3
  116. package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +3 -3
  117. package/src/components/CollectionTableBinding/CollectionTableBindingProps.tsx +1 -1
  118. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +1 -1
  119. package/src/components/CollectionTableBinding/column_utils.tsx +1 -1
  120. package/src/components/CollectionTableBinding/fields/TableMultipleRelationField.tsx +2 -2
  121. package/src/components/CollectionTableBinding/fields/TableReferenceField.tsx +2 -2
  122. package/src/components/CollectionTableBinding/fields/TableRelationField.tsx +2 -2
  123. package/src/components/CollectionTableBinding/fields/TableStorageUpload.tsx +1 -1
  124. package/src/components/CollectionTableBinding/internal/CollectionTableToolbar.tsx +1 -1
  125. package/src/components/CollectionTableBinding/internal/EntityTableCell.tsx +2 -2
  126. package/src/components/CollectionTableBinding/internal/EntityTableCellActions.tsx +1 -1
  127. package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +4 -4
  128. package/src/components/CollectionTableBinding/table_bindings.tsx +1 -1
  129. package/src/components/CollectionViewBinding/BoardCardBinding.tsx +2 -2
  130. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +4 -4
  131. package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +1 -1
  132. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +3 -3
  133. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +3 -3
  134. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +77 -127
  135. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +5 -9
  136. package/src/components/CollectionViewBinding/EntityCardBinding.tsx +3 -3
  137. package/src/components/CollectionViewBinding/FiltersDialog.tsx +19 -1
  138. package/src/components/CollectionViewBinding/SplitListView.tsx +114 -63
  139. package/src/components/CollectionViewBinding/ViewModeToggle.tsx +1 -1
  140. package/src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts +2 -2
  141. package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +2 -2
  142. package/src/components/CollectionViewBinding/useBoardDataController.tsx +9 -3
  143. package/src/components/CollectionViewBinding/usePreviewSlots.ts +1 -1
  144. package/src/components/DefaultAppBar.tsx +3 -3
  145. package/src/components/DefaultDrawer.tsx +3 -3
  146. package/src/components/DeleteEntityDialog.tsx +1 -1
  147. package/src/components/DetailViewBinding.tsx +7 -5
  148. package/src/components/DrawerNavigationGroup.tsx +2 -2
  149. package/src/components/EditFormActions.tsx +6 -6
  150. package/src/components/EditViewBinding.tsx +9 -8
  151. package/src/components/EntityPreviewBinding.tsx +5 -4
  152. package/src/components/EntityViewBinding.tsx +2 -2
  153. package/src/components/HomePage/ContentHomePage.tsx +6 -5
  154. package/src/components/HomePage/FavouritesView.tsx +2 -2
  155. package/src/components/HomePage/NavigationCardBinding.tsx +3 -3
  156. package/src/components/HomePage/NavigationGroup.tsx +1 -1
  157. package/src/components/HomePage/RenameGroupDialog.tsx +1 -1
  158. package/src/components/JsonPreviewBinding.tsx +1 -1
  159. package/src/components/{RebaseCMS.tsx → RebaseAdmin.tsx} +3 -3
  160. package/src/components/RebaseAuthGate.tsx +2 -2
  161. package/src/components/RebaseLayout.tsx +7 -13
  162. package/src/components/RebaseNavigation.tsx +18 -3
  163. package/src/components/RebaseRouteDefs.tsx +3 -3
  164. package/src/components/RebaseShell.tsx +2 -1
  165. package/src/components/ReferenceTable/SelectionTableBinding.tsx +7 -6
  166. package/src/components/ReferenceWidget.tsx +1 -1
  167. package/src/components/RelationSelector.tsx +1 -1
  168. package/src/components/SearchIconsView.tsx +2 -2
  169. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  170. package/src/components/SelectableTable/SelectableTableContext.tsx +1 -1
  171. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +1 -1
  172. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +1 -1
  173. package/src/components/SelectableTable/filters/FilterFieldBinding.tsx +1 -1
  174. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
  175. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -1
  176. package/src/components/SideDialogs.tsx +1 -1
  177. package/src/components/SidePanelBinding.tsx +47 -9
  178. package/src/components/SidePanelProvider.tsx +15 -12
  179. package/src/components/UserSelector.tsx +1 -1
  180. package/src/components/admin/CreationResultDialog.tsx +6 -3
  181. package/src/components/app/Scaffold.tsx +6 -2
  182. package/src/components/common/default_entity_actions.tsx +123 -25
  183. package/src/components/field_configs.tsx +42 -25
  184. package/src/components/history/EntityHistoryEntry.tsx +1 -1
  185. package/src/components/history/EntityHistoryView.tsx +5 -5
  186. package/src/components/history/LastEditedByIndicator.tsx +2 -2
  187. package/src/components/index.ts +1 -1
  188. package/src/contexts/BreacrumbsContext.tsx +1 -1
  189. package/src/data_export/export/BasicExportAction.tsx +1 -1
  190. package/src/data_export/export/ExportCollectionAction.tsx +1 -1
  191. package/src/data_import/components/DataNewPropertiesMapping.tsx +1 -1
  192. package/src/data_import/components/ImportFileUpload.tsx +1 -1
  193. package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +3 -5
  194. package/src/data_import/components/ImportSaveInProgress.tsx +1 -1
  195. package/src/data_import/import/ImportCollectionAction.tsx +8 -8
  196. package/src/data_import/utils/data.ts +2 -2
  197. package/src/data_import/utils/get_properties_mapping.ts +1 -1
  198. package/src/editor/components/image-bubble.tsx +1 -1
  199. package/src/editor/components/table-bubble.tsx +1 -1
  200. package/src/editor/editor.tsx +1 -1
  201. package/src/editor/selectors/color-selector.tsx +1 -1
  202. package/src/editor/selectors/link-selector.tsx +1 -1
  203. package/src/editor/selectors/node-selector.tsx +2 -2
  204. package/src/editor/selectors/text-buttons.tsx +1 -1
  205. package/src/form/EntityForm.tsx +8 -5
  206. package/src/form/EntityFormActions.tsx +1 -1
  207. package/src/form/EntityFormBinding.tsx +7 -5
  208. package/src/form/PropertyFieldBinding.tsx +3 -3
  209. package/src/form/components/ErrorFocus.tsx +1 -1
  210. package/src/form/components/LocalChangesMenu.tsx +3 -3
  211. package/src/form/components/StorageItemPreview.tsx +1 -1
  212. package/src/form/components/StorageUploadProgress.tsx +3 -3
  213. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +3 -2
  214. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -3
  215. package/src/form/field_bindings/BlockFieldBinding.tsx +4 -3
  216. package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
  217. package/src/form/field_bindings/KeyValueFieldBinding.tsx +4 -3
  218. package/src/form/field_bindings/MapFieldBinding.tsx +3 -2
  219. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -3
  220. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
  221. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +3 -2
  222. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
  223. package/src/form/field_bindings/ReferenceFieldBinding.tsx +5 -4
  224. package/src/form/field_bindings/RelationFieldBinding.tsx +3 -2
  225. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -2
  226. package/src/form/field_bindings/SelectFieldBinding.tsx +2 -1
  227. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +5 -4
  228. package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -1
  229. package/src/form/field_bindings/TextFieldBinding.tsx +2 -1
  230. package/src/form/field_bindings/UserSelectFieldBinding.tsx +2 -1
  231. package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
  232. package/src/form/form_utils.ts +1 -1
  233. package/src/hooks/navigation/useNavigationResolution.ts +19 -4
  234. package/src/hooks/useBuildSidePanel.tsx +10 -2
  235. package/src/hooks/useCMSContext.tsx +1 -1
  236. package/src/hooks/useHistory.ts +1 -1
  237. package/src/hooks/useResolvedNavigationFrom.tsx +4 -2
  238. package/src/hooks/useSelectionDialog.tsx +3 -3
  239. package/src/index.ts +3 -3
  240. package/src/preview/PropertyPreview.tsx +2 -2
  241. package/src/preview/components/DatePreview.tsx +1 -1
  242. package/src/preview/components/EnumValuesChip.tsx +1 -1
  243. package/src/preview/components/ImagePreview.tsx +2 -2
  244. package/src/preview/components/ReferencePreview.tsx +3 -3
  245. package/src/preview/components/RelationPreview.tsx +3 -3
  246. package/src/preview/components/StorageThumbnail.tsx +1 -1
  247. package/src/preview/components/UrlComponentPreview.tsx +2 -9
  248. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +1 -1
  249. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -1
  250. package/src/preview/property_previews/ArrayOneOfPreview.tsx +1 -1
  251. package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
  252. package/src/preview/property_previews/NumberPropertyPreview.tsx +1 -1
  253. package/src/preview/util.ts +9 -0
  254. package/src/routes/CustomViewRoute.tsx +2 -1
  255. package/src/routes/RebaseRoute.tsx +21 -26
  256. package/src/types/components/formex.tsx +1 -1
  257. package/src/util/navigation_utils.ts +2 -5
  258. package/src/util/previews.ts +4 -2
  259. package/src/util/view_mode.ts +102 -0
  260. package/dist/CollectionEditorDialog-B-7uW3iJ.js.map +0 -1
  261. package/dist/PropertyEditView-7KMTcrqi.js.map +0 -1
  262. package/dist/RouterCollectionsStudioView-epqIbZrJ.js.map +0 -1
  263. package/dist/history-7X5bON_n.js.map +0 -1
  264. package/dist/src-sUZfe29F.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,2 +1,3300 @@
1
- import { $ as RelationPreview, $n as SelectableTable, $t as BlockFieldBinding, A as useCollectionDataController, An as removeInitialAndTrailingSlashes, Ar as renderSkeletonIcon, At as useImportConfig, B as ArrayOfMapsPreview, Bn as useUrlController, Bt as getFieldId, C as downloadEntitiesExport, Cn as editEntityAction, Cr as ArrayPropertyPreview, Ct as useCollectionEditorController, D as ImportDataPreview, Dn as getCollectionBySlugWithin, Dr as StorageThumbnailInternal, Dt as flattenEntry, E as ImportCollectionAction, En as addInitialSlash, Er as StorageThumbnail, Et as convertDataToEntity, F as extractTouchedValues, Fn as useHistory, Fr as ImagePreview, Ft as PropertyConfigBadge, G as DatePreview, Gn as useSideDialogsController, Gt as SelectFieldBinding, H as UserPreview, Hn as useNavigationStateController, Ht as TextFieldBinding, I as getChanges, In as resolveNavigationFrom, Ir as EmptyValue, It as DEFAULT_FIELD_CONFIGS, J as ArrayOneOfPreview, Jn as useBuildSidePanel, Jt as MultiSelectFieldBinding, K as KeyValuePreview, Kn as buildSidePanelsFromUrl, Kt as RepeatFieldBinding, L as getInitialEntityValues, Ln as useResolvedNavigationFrom, Lt as getDefaultFieldConfig, M as mapPropertyToConfig, Mn as removeTrailingSlash, Mr as renderSkeletonText, Mt as ImportFileUpload, N as EntityFormBinding, Nn as resolveCollectionPathIds, Nr as UrlComponentPreview, Nt as convertFileToJson, O as PropertySelectEntry, On as getCollectionPathsCombinations, Or as SkeletonPropertyComponent, Ot as processValueMapping, P as EntityForm, Pn as useBuildCollectionRegistryController, Pr as sanitizeUrl, Pt as unflattenObject, Q as ArrayOfStorageComponentsPreview, Qn as CollectionTableBinding, Qt as DateTimeFieldBinding, R as removeEmptyContainers, Rn as useCMSContext, Rt as getDefaultFieldId, S as downloadDataAsCsv, Sn as deleteEntityAction, Sr as isRelationProperty, St as CollectionViewActions, T as getEntityJsonExportableData, Tn as CreationResultDialog, Tr as EnumValuesChip, Tt as DataNewPropertiesMapping, U as NumberPropertyPreview, Un as CollectionRegistryContext, Ut as SwitchFieldBinding, V as PropertyPreview, Vn as NavigationStateContext, Vt as VectorFieldBinding, W as BooleanPreview, Wn as useCollectionRegistryController, Wt as StorageUploadFieldBinding, X as ArrayPropertyEnumPreview, Xn as useSelectionController, Xt as MapFieldBinding, Y as ArrayOfStringsPreview, Yn as mergeEntityActions, Yt as MarkdownEditorFieldBinding, Z as ArrayEnumPreview, Zn as VirtualTableInput, Zt as KeyValueFieldBinding, _ as toSerializableProperty, _n as useBuildNavigationStateController, _r as getIconForWidget, _t as SidePanelProvider, a as namespaceToPropertiesPath, an as FormLayout, ar as useSelectionDialog, at as RebaseLayout, b as BasicExportAction, bn as useResolvedCollections, br as getResolvedPropertyInPath, bt as CollectionCardViewBinding, c as DEFAULT_COLLECTION_GENERATION_ENDPOINT, cn as PropertyIdCopyTooltip, cr as EntityViewBinding, ct as RebaseAuthGate, d as useJsonCollectionsConfigController, dn as SearchIconsView, dr as EntityPreviewBinding, dt as Drawer, en as ArrayOfReferencesFieldBinding, er as CollectionRowActions, et as CollectionPanel, f as fromSerializableCollectionConfig, fn as FieldCaption, fr as getEntityPreviewKeys, ft as DefaultDrawer, g as toSerializableProperties, gn as useBuildUrlController, gr as getIconForProperty, gt as AdminModeSyncer, h as toSerializableCollectionConfig, hn as useBreadcrumbsController, hr as getDefaultPropertiesOrder, ht as useApp, i as namespaceToPropertiesOrderPath, in as ReadOnlyFieldBinding, ir as useSidePanel, it as RebaseRoute, j as mapPropertiesToConfigs, jn as removeInitialSlash, jr as renderSkeletonImageThumbnail, jt as ImportSaveInProgress, k as createStaticDataController, kn as getLastSegment, kr as renderSkeletonCaptionText, kt as getInferenceType, l as buildCollectionGenerationCallback, ln as LabelWithIcon, lr as ArrayOfReferencesPreview, lt as RebaseCMS, m as fromSerializableProperty, mn as SideDialogs, mr as getBracketNotation, mt as AppBar, n as getFullIdPath, nn as ArrayCustomShapedFieldBinding, nr as SelectableTableContext, nt as RebaseRouteDefs, o as MissingReferenceWidget, on as FormEntry, or as resolveEntityAction, ot as RebaseNavigation, p as fromSerializableProperties, pn as SelectionTableBinding, pr as getEntityTitlePropertyKey, pt as DrawerFooterActions, q as MapPropertyPreview, qn as getEntityViewWidth, qt as ReferenceFieldBinding, r as idToPropertiesPath, rn as PropertyFieldBinding, rr as SidePanelControllerContext, rt as CustomViewRoute, s as CollectionGenerationApiError, sn as LabelWithIconAndTooltip, sr as resolveEntityView, st as useLocalCollectionsConfigController, t as getFullId, tn as ArrayContainer, tr as useClearRestoreValue, tt as RebaseShell, u as validateCollectionJson, un as FieldHelperText, ur as ReferencePreview, ut as Scaffold, v as useCollectionsConfigController, vn as useTopLevelNavigation, vr as getPropertiesWithPropertiesOrder, vt as ReferenceWidget, w as getEntityCSVExportableData, wn as resetPasswordAction, wr as StringPropertyPreview, wt as ImportNewPropertyFieldPreview, x as downloadBlob, xn as copyEntityAction, xr as isReferenceProperty, xt as EntityCardBinding, y as ExportCollectionAction, yn as useResolvedViews, yr as getPropertyInPath, yt as CollectionViewBinding, z as zodToFormErrors, zn as UrlContext, zt as getFieldConfig } from "./src-sUZfe29F.js";
2
- export { AdminModeSyncer, AppBar, ArrayContainer, ArrayCustomShapedFieldBinding, ArrayEnumPreview, ArrayOfMapsPreview, ArrayOfReferencesFieldBinding, ArrayOfReferencesPreview, ArrayOfStorageComponentsPreview, ArrayOfStringsPreview, ArrayOneOfPreview, ArrayPropertyEnumPreview, ArrayPropertyPreview, BasicExportAction, BlockFieldBinding, BooleanPreview, CollectionCardViewBinding, CollectionGenerationApiError, CollectionPanel, CollectionRegistryContext, CollectionRowActions, CollectionTableBinding, CollectionViewActions, CollectionViewBinding, CreationResultDialog, CustomViewRoute, DEFAULT_COLLECTION_GENERATION_ENDPOINT, DEFAULT_FIELD_CONFIGS, DataNewPropertiesMapping, DatePreview, DateTimeFieldBinding, DefaultDrawer, Drawer, DrawerFooterActions, EmptyValue, EntityCardBinding, EntityForm, EntityFormBinding, EntityPreviewBinding, EntityViewBinding, EnumValuesChip, ExportCollectionAction, FieldCaption, FieldHelperText, FormEntry, FormLayout, ImagePreview, ImportCollectionAction, ImportDataPreview, ImportFileUpload, ImportNewPropertyFieldPreview, ImportSaveInProgress, KeyValueFieldBinding, KeyValuePreview, LabelWithIcon, LabelWithIconAndTooltip, MapFieldBinding, MapPropertyPreview, MarkdownEditorFieldBinding, MissingReferenceWidget, MultiSelectFieldBinding, NavigationStateContext, NumberPropertyPreview, PropertyConfigBadge, PropertyFieldBinding, PropertyIdCopyTooltip, PropertyPreview, PropertySelectEntry, ReadOnlyFieldBinding, RebaseAuthGate, RebaseCMS, RebaseLayout, RebaseNavigation, RebaseRoute, RebaseRouteDefs, RebaseShell, ReferenceFieldBinding, ReferencePreview, ReferenceWidget, RelationPreview, RepeatFieldBinding, Scaffold, SearchIconsView, SelectFieldBinding, SelectableTable, SelectableTableContext, SelectionTableBinding, SideDialogs, SidePanelControllerContext, SidePanelProvider, SkeletonPropertyComponent, StorageThumbnail, StorageThumbnailInternal, StorageUploadFieldBinding, StringPropertyPreview, SwitchFieldBinding, TextFieldBinding, UrlComponentPreview, UrlContext, UserPreview, VectorFieldBinding, VirtualTableInput, addInitialSlash, buildCollectionGenerationCallback, buildSidePanelsFromUrl, convertDataToEntity, convertFileToJson, copyEntityAction, createStaticDataController, deleteEntityAction, downloadBlob, downloadDataAsCsv, downloadEntitiesExport, editEntityAction, extractTouchedValues, flattenEntry, fromSerializableCollectionConfig, fromSerializableProperties, fromSerializableProperty, getBracketNotation, getChanges, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultFieldConfig, getDefaultFieldId, getDefaultPropertiesOrder, getEntityCSVExportableData, getEntityJsonExportableData, getEntityPreviewKeys, getEntityTitlePropertyKey, getEntityViewWidth, getFieldConfig, getFieldId, getFullId, getFullIdPath, getIconForProperty, getIconForWidget, getInferenceType, getInitialEntityValues, getLastSegment, getPropertiesWithPropertiesOrder, getPropertyInPath, getResolvedPropertyInPath, idToPropertiesPath, isReferenceProperty, isRelationProperty, mapPropertiesToConfigs, mapPropertyToConfig, mergeEntityActions, namespaceToPropertiesOrderPath, namespaceToPropertiesPath, processValueMapping, removeEmptyContainers, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, renderSkeletonCaptionText, renderSkeletonIcon, renderSkeletonImageThumbnail, renderSkeletonText, resetPasswordAction, resolveCollectionPathIds, resolveEntityAction, resolveEntityView, resolveNavigationFrom, sanitizeUrl, toSerializableCollectionConfig, toSerializableProperties, toSerializableProperty, unflattenObject, useApp, useBreadcrumbsController, useBuildCollectionRegistryController, useBuildNavigationStateController, useBuildSidePanel, useBuildUrlController, useCMSContext, useClearRestoreValue, useCollectionDataController, useCollectionEditorController, useCollectionRegistryController, useCollectionsConfigController, useHistory, useImportConfig, useJsonCollectionsConfigController, useLocalCollectionsConfigController, useNavigationStateController, useResolvedCollections, useResolvedNavigationFrom, useResolvedViews, useSelectionController, useSelectionDialog, useSideDialogsController, useSidePanel, useTopLevelNavigation, useUrlController, validateCollectionJson, zodToFormErrors };
1
+ import { $ as useHistory, $n as renderSkeletonImageThumbnail, $t as SelectableTable, A as VectorFieldBinding, An as SidePanelControllerContext, At as unflattenObject, B as KeyValueFieldBinding, Bn as getPropertiesWithPropertiesOrder, Bt as getCollectionPathsCombinations, C as zodToFormErrors, Cn as ArrayPropertyEnumPreview, Ct as flattenEntry, D as getDefaultFieldId, Dn as ArrayOfReferencesPreview, Dt as ImportSaveInProgress, E as getDefaultFieldConfig, En as RelationPreview, Et as useImportConfig, F as RepeatFieldBinding, Fn as getEntityTitlePropertyKey, Ft as useBreadcrumbsController, G as SelectionTableBinding, Gn as ArrayPropertyPreview, Gt as resolveCollectionPathIds, H as BlockFieldBinding, Hn as getResolvedPropertyInPath, Ht as removeInitialAndTrailingSlashes, I as ReferenceFieldBinding, In as getBracketNotation, It as BreadcrumbsProvider, J as useBuildNavigationStateController, Jn as StorageThumbnail, Jt as resolveEntityAction, K as SideDialogs, Kn as StringPropertyPreview, Kt as resolveOpenEntityMode, L as MultiSelectFieldBinding, Ln as getDefaultPropertiesOrder, Lt as mergeEntityActions, M as SwitchFieldBinding, Mn as CollectionRegistryContext, Mt as PropertyConfigBadge, N as StorageUploadFieldBinding, Nn as useCollectionRegistryController, Nt as SearchIconsView, O as getFieldConfig, On as ReferencePreview, Ot as ImportFileUpload, P as SelectFieldBinding, Pn as getEntityPreviewKeys, Pt as FieldCaption, Q as useBuildCollectionRegistryController, Qn as renderSkeletonIcon, Qt as CollectionTableBinding, R as MarkdownEditorFieldBinding, Rn as getIconForProperty, Rt as addInitialSlash, S as removeEmptyContainers, Sn as ArrayOfStringsPreview, St as convertDataToEntity, T as DEFAULT_FIELD_CONFIGS, Tn as ArrayOfStorageComponentsPreview, Tt as getInferenceType, U as ArrayOfReferencesFieldBinding, Un as isReferenceProperty, Ut as removeInitialSlash, V as DateTimeFieldBinding, Vn as getPropertyInPath, Vt as getLastSegment, W as useSelectionDialog, Wn as isRelationProperty, Wt as removeTrailingSlash, X as useResolvedViews, Xn as SkeletonPropertyComponent, Xt as useSelectionController, Y as useTopLevelNavigation, Yn as StorageThumbnailInternal, Yt as resolveEntityView, Z as useResolvedCollections, Zn as renderSkeletonCaptionText, Zt as VirtualTableInput, _ as EntityFormBinding, _n as BooleanPreview, _t as useCollectionEditorController, a as namespaceToPropertiesPath, an as useNavigationStateController, ar as LabelWithIconAndTooltip, at as EditViewBinding, b as getChanges, bn as MapPropertyPreview, bt as ImportNewPropertyFieldPreview, c as buildCollectionGenerationCallback, cn as SelectableTableContext, cr as FormLayout, ct as deleteEntityAction, d as fromSerializableProperties, dn as ReadOnlyFieldBinding, dt as CreationResultDialog, en as CollectionRowActions, er as renderSkeletonText, et as resolveNavigationFrom, f as fromSerializableProperty, fn as FieldHelperText, ft as DetailViewBinding, g as useCollectionsConfigController, gn as NumberPropertyPreview, gt as CollectionViewActions, h as toSerializableProperty, hn as UserPreview, ht as EntityCardBinding, i as namespaceToPropertiesOrderPath, in as NavigationStateContext, ir as EmptyValue, it as useBuildSidePanel, j as TextFieldBinding, jn as useSidePanel, jt as ArrayContainer, k as getFieldId, kn as EntityPreviewBinding, kt as convertFileToJson, l as validateCollectionJson, ln as ArrayCustomShapedFieldBinding, lr as FormEntry, lt as editEntityAction, m as toSerializableProperties, mn as PropertyPreview, mt as CollectionCardViewBinding, n as getFullIdPath, nn as UrlContext, nr as ImagePreview, nt as buildSidePanelsFromUrl, o as CollectionGenerationApiError, on as useSideDialogsController, or as PropertyIdCopyTooltip, ot as CollectionViewBinding, p as toSerializableCollectionConfig, pn as ArrayOfMapsPreview, pt as EntityViewBinding, q as useBuildUrlController, qn as EnumValuesChip, qt as resolveViewMode, r as idToPropertiesPath, rn as useUrlController, rr as sanitizeUrl, rt as getEntityViewWidth, s as DEFAULT_COLLECTION_GENERATION_ENDPOINT, sn as SideDialogsControllerContext, sr as LabelWithIcon, st as copyEntityAction, t as getFullId, tn as useCMSContext, tr as UrlComponentPreview, tt as useResolvedNavigationFrom, u as fromSerializableCollectionConfig, un as useClearRestoreValue, ut as resetPasswordAction, v as EntityForm, vn as DatePreview, vt as ConfigControllerProvider, w as PropertyFieldBinding, wn as ArrayEnumPreview, wt as processValueMapping, x as getInitialEntityValues, xn as ArrayOneOfPreview, xt as DataNewPropertiesMapping, y as extractTouchedValues, yn as KeyValuePreview, yt as useCollectionEditorDialogsState, z as MapFieldBinding, zn as getIconForWidget, zt as getCollectionBySlugWithin } from "./util-XFwQ9FEc.js";
2
+ import { i as PropertySelectEntry, n as ImportCollectionAction, r as ImportDataPreview } from "./import-DzQnT03v.js";
3
+ import { a as downloadDataAsCsv, c as getEntityJsonExportableData, i as downloadBlob, n as ExportCollectionAction, o as downloadEntitiesExport, r as BasicExportAction, s as getEntityCSVExportableData } from "./export-BM6HPujv.js";
4
+ import React, { Suspense, lazy, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
+ import { deepEqual } from "fast-equals";
6
+ import { Avatar, Button, Card, ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircularProgressCenter, Collapse, Container, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, IconButton, LogOutIcon, Markdown, Menu, MenuIcon, MenuItem, MoonIcon, PencilIcon, SearchBar, SettingsIcon, Sheet, Skeleton, StarIcon, SunIcon, SunMoonIcon, TextField, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
7
+ import { buildRoutedRebaseData, getLastSegment as getLastSegment$1, getNavigationEntriesFromPath, getReferenceFrom, resolveDataSource } from "@rebasepro/common";
8
+ import { CollectionResolverRegistrationContext, CollectionScopeProvider, CustomizationControllerContext, ErrorView, IconForView, LanguageToggle, LoginView, NavigationBlockerProvider, NotFoundPage, RebaseDataContext, RebaseLogo, RebaseRoutes, SchemaDriftBanner, StudioBridgeRegistryProvider, UIReferenceView, UnsavedChangesDialog, UserSettingsView, buildCollapsedDefaults, useAdminModeController, useAnalyticsController, useAuthController, useBridgeRegistration, useBuildLocalConfigurationPersistence, useCollapsedGroups, useComponentOverride, useCustomizationController, useData, useDataSources, useLargeLayout, useModeController, useNavigationBlocker, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useTranslation, useUserConfigurationPersistence } from "@rebasepro/app";
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ import { prettifyIdentifier, toArray } from "@rebasepro/utils";
11
+ import { Link, NavLink, Outlet, Route, useLocation, useNavigate } from "react-router-dom";
12
+ import { DndContext, DragOverlay, KeyboardSensor, MeasuringStrategy, MouseSensor, TouchSensor, closestCenter, closestCorners, getFirstCollision, pointerWithin, rectIntersection, useDndMonitor, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
13
+ import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers";
14
+ import { SortableContext, arrayMove, defaultAnimateLayoutChanges, rectSortingStrategy, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
15
+ import { CSS } from "@dnd-kit/utilities";
16
+ import Fuse from "fuse.js";
17
+ //#region src/routes/CustomViewRoute.tsx
18
+ function CustomViewRoute({ view }) {
19
+ const breadcrumbs = useBreadcrumbsController();
20
+ const urlController = useUrlController();
21
+ const breadcrumbsRef = useRef(breadcrumbs);
22
+ breadcrumbsRef.current = breadcrumbs;
23
+ useEffect(() => {
24
+ breadcrumbsRef.current.set({ breadcrumbs: [{
25
+ title: view.name,
26
+ url: urlController.buildAppUrlPath(view.slug)
27
+ }] });
28
+ }, [view.slug, urlController]);
29
+ if (typeof view.view === "function") {
30
+ const ViewComponent = view.view;
31
+ return /* @__PURE__ */ jsx(ViewComponent, {});
32
+ }
33
+ return /* @__PURE__ */ jsx(Fragment, { children: view.view });
34
+ }
35
+ //#endregion
36
+ //#region src/routes/RebaseRoute.tsx
37
+ function RebaseRoute() {
38
+ const location = useLocation();
39
+ const collectionRegistry = useCollectionRegistryController();
40
+ const urlController = useUrlController();
41
+ const breadcrumbs = useBreadcrumbsController();
42
+ const userConfigPersistence = useUserConfigurationPersistence();
43
+ const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
44
+ const hash = location.hash;
45
+ const isSidePanel = hash.includes("#side");
46
+ const isNew = hash.includes("#new") || hash.includes("#new_side");
47
+ const isCopy = hash.includes("#copy");
48
+ const isFullScreen = hash.includes("#full");
49
+ const pathname = location.pathname;
50
+ const navigationEntries = getNavigationEntriesFromPath({
51
+ path: urlController.urlPathToDataPath(pathname),
52
+ collections: collectionRegistry.collections ?? []
53
+ });
54
+ useEffect(() => {
55
+ const isViewingCollection = navigationEntries[navigationEntries.length - 1]?.type === "collection";
56
+ breadcrumbs.set({ breadcrumbs: navigationEntries.map((entry, index) => {
57
+ const isLastEntry = index === navigationEntries.length - 1;
58
+ if (entry.type === "entity") return {
59
+ title: String(entry.entityId),
60
+ url: urlController.buildUrlCollectionPath(entry.path)
61
+ };
62
+ else if (entry.type === "custom_view") return {
63
+ title: String(entry.view.name ?? entry.view.key),
64
+ url: urlController.buildUrlCollectionPath(entry.path)
65
+ };
66
+ else if (entry.type === "collection") {
67
+ const showCount = isLastEntry && isViewingCollection;
68
+ return {
69
+ title: entry.collection.name,
70
+ url: urlController.buildUrlCollectionPath(entry.path),
71
+ id: entry.path,
72
+ ...showCount ? { count: null } : {}
73
+ };
74
+ } else throw new Error("Unexpected navigation entry type");
75
+ }) });
76
+ }, [navigationEntries.map((entry) => entry.path).join(",")]);
77
+ if (isNew) return /* @__PURE__ */ jsx(EntityFullScreenRoute, {
78
+ pathname,
79
+ navigationEntries,
80
+ isNew: true,
81
+ isCopy: false
82
+ });
83
+ if (navigationEntries.length === 1 && navigationEntries[0].type === "collection") {
84
+ let collection;
85
+ collection = collectionRegistry.getCollection(navigationEntries[0].id);
86
+ if (!collection) collection = collectionRegistry.getCollection(navigationEntries[0].slug);
87
+ if (!collection) {
88
+ if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
89
+ return null;
90
+ }
91
+ return /* @__PURE__ */ jsx(ResolvedCollectionView, {
92
+ ...collection,
93
+ parentCollectionSlugs: [],
94
+ parentEntityIds: [],
95
+ path: collection.slug,
96
+ updateUrl: true,
97
+ Actions: toArray(collection.Actions)
98
+ }, `collection_view_${collection.slug}`);
99
+ }
100
+ if (isSidePanel) {
101
+ if ([...navigationEntries].reverse().find((entry) => entry.type === "collection")) {
102
+ let collection;
103
+ const firstEntry = navigationEntries[0];
104
+ collection = collectionRegistry.getCollection(firstEntry.id);
105
+ if (!collection) collection = collectionRegistry.getCollection(firstEntry.slug);
106
+ if (!collection) {
107
+ if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
108
+ return null;
109
+ }
110
+ return /* @__PURE__ */ jsx(ResolvedCollectionView, {
111
+ ...collection,
112
+ parentCollectionSlugs: [],
113
+ parentEntityIds: [],
114
+ path: collection.slug,
115
+ updateUrl: true,
116
+ Actions: toArray(collection.Actions)
117
+ }, `collection_view_${collection.slug}`);
118
+ }
119
+ }
120
+ const lastEntityEntry = navigationEntries.find((entry) => entry.type === "entity");
121
+ const firstCollectionEntry = navigationEntries[0];
122
+ if (!isFullScreen && !isCopy && firstCollectionEntry?.type === "collection" && lastEntityEntry?.type === "entity" && (navigationEntries.length === 2 || navigationEntries.length === 3)) {
123
+ let collection;
124
+ collection = collectionRegistry.getCollection(firstCollectionEntry.id);
125
+ if (!collection) collection = collectionRegistry.getCollection(firstCollectionEntry.slug);
126
+ const effectiveOpenMode = collection ? resolveOpenEntityMode({
127
+ collection,
128
+ viewMode: resolveViewMode({
129
+ collection,
130
+ search: location.search,
131
+ savedViewMode: userConfigPersistence?.getCollectionConfig(collection.slug)?.defaultViewMode
132
+ })
133
+ }) : void 0;
134
+ if (collection && effectiveOpenMode === "split") {
135
+ let selectedTab;
136
+ if (navigationEntries.length === 3) {
137
+ const thirdEntry = navigationEntries[2];
138
+ if (thirdEntry.type === "collection") selectedTab = thirdEntry.collection.slug;
139
+ else if (thirdEntry.type === "custom_view") selectedTab = thirdEntry.view.key;
140
+ }
141
+ if (!selectedTab && navigationEntries.length === 2 && lastEntityEntry) {
142
+ const entityIdStr = String(lastEntityEntry.entityId);
143
+ const entityIdIdx = pathname.lastIndexOf(`/${entityIdStr}`);
144
+ if (entityIdIdx >= 0) {
145
+ const afterEntity = pathname.substring(entityIdIdx + 1 + entityIdStr.length);
146
+ const trailingSegment = afterEntity.startsWith("/") ? afterEntity.substring(1) : afterEntity;
147
+ if (trailingSegment.length > 0) selectedTab = trailingSegment;
148
+ }
149
+ }
150
+ return /* @__PURE__ */ jsx(ResolvedCollectionView, {
151
+ ...collection,
152
+ parentCollectionSlugs: [],
153
+ parentEntityIds: [],
154
+ path: collection.slug,
155
+ updateUrl: true,
156
+ selectedEntityId: lastEntityEntry.entityId,
157
+ selectedTab,
158
+ Actions: toArray(collection.Actions)
159
+ }, `collection_view_${collection.slug}`);
160
+ }
161
+ }
162
+ return /* @__PURE__ */ jsx(EntityFullScreenRoute, {
163
+ pathname,
164
+ navigationEntries,
165
+ isNew,
166
+ isCopy
167
+ });
168
+ }
169
+ function getSelectedTabFromUrl(isNew, lastCustomView) {
170
+ if (isNew) return;
171
+ else if (lastCustomView) {
172
+ if (lastCustomView.type === "custom_view") return lastCustomView.view.key;
173
+ else if (lastCustomView.type === "collection") return lastCustomView.id ?? lastCustomView.slug;
174
+ }
175
+ }
176
+ function EntityFullScreenRoute({ pathname, navigationEntries, isNew, isCopy }) {
177
+ const collectionRegistry = useCollectionRegistryController();
178
+ const urlController = useUrlController();
179
+ const navigate = useNavigate();
180
+ const location = useLocation();
181
+ const defaultValues = location.state?.defaultValues;
182
+ const hash = location.hash;
183
+ const navigationPath = urlController.urlPathToDataPath(pathname);
184
+ const blocked = useRef(false);
185
+ const lastEntityEntry = [...navigationEntries].reverse().find((entry) => entry.type === "entity");
186
+ const lastCustomView = [...lastEntityEntry ? navigationEntries.slice(navigationEntries.indexOf(lastEntityEntry) + 1) : []].reverse().find((entry) => entry.type === "custom_view" || entry.type === "collection");
187
+ const entityId = lastEntityEntry && "entityId" in lastEntityEntry ? lastEntityEntry.entityId : void 0;
188
+ let urlTab = getSelectedTabFromUrl(isNew, lastCustomView);
189
+ if (!urlTab && entityId && !isNew) {
190
+ const entityIdStr = String(entityId);
191
+ const entityIdIdx = pathname.lastIndexOf(`/${entityIdStr}`);
192
+ if (entityIdIdx >= 0) {
193
+ const afterEntity = pathname.substring(entityIdIdx + 1 + entityIdStr.length);
194
+ const trailingSegment = afterEntity.startsWith("/") ? afterEntity.substring(1) : afterEntity;
195
+ if (trailingSegment.length > 0 && trailingSegment !== "edit") urlTab = trailingSegment;
196
+ }
197
+ }
198
+ const [selectedTab, setSelectedTab] = useState(urlTab);
199
+ const parentCollectionSlugs = collectionRegistry.getParentCollectionSlugs(navigationPath);
200
+ const parentEntityIds = collectionRegistry.getParentEntityIds(navigationPath);
201
+ useEffect(() => {
202
+ if (urlTab !== selectedTab) setSelectedTab(urlTab);
203
+ }, [urlTab]);
204
+ const basePath = !entityId || isNew ? pathname : pathname.substring(0, pathname.lastIndexOf(`/${entityId}`));
205
+ const entityPath = basePath + `/${entityId}`;
206
+ const blocker = useNavigationBlocker(useCallback(({ currentLocation, nextLocation }) => {
207
+ if (nextLocation.pathname.startsWith(entityPath)) return false;
208
+ const nextHash = nextLocation.hash;
209
+ if (nextHash === "#side" || nextHash === "#new_side") return false;
210
+ const currentHash = currentLocation.hash;
211
+ if ((currentHash === "#side" || currentHash === "#new_side") && (nextLocation.pathname === basePath || nextLocation.pathname.startsWith(entityPath))) return false;
212
+ return blocked.current;
213
+ }, [entityPath, basePath]));
214
+ const lastCollectionEntry = [...navigationEntries].reverse().find((entry) => entry.type === "collection");
215
+ if (isNew && !lastCollectionEntry) {
216
+ if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
217
+ throw new Error("INTERNAL: No collection found in the navigation");
218
+ }
219
+ if (!isNew && !lastEntityEntry) {
220
+ if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
221
+ return /* @__PURE__ */ jsx(NotFoundPage, {});
222
+ }
223
+ const rawCollection = isNew ? lastCollectionEntry && "collection" in lastCollectionEntry ? lastCollectionEntry.collection : void 0 : lastEntityEntry && "parentCollection" in lastEntityEntry ? lastEntityEntry.parentCollection : void 0;
224
+ const collection = collectionRegistry.getCollection(rawCollection.slug) || rawCollection;
225
+ const fullIdPath = isNew ? lastCollectionEntry.slug : lastEntityEntry.slug;
226
+ const collectionPath = urlController.resolveDatabasePathsFrom(fullIdPath);
227
+ const isEditRoute = pathname.endsWith("/edit") || pathname.split("/").pop() === "edit";
228
+ if (collection.defaultEntityAction === "view" && !isNew && !isCopy && entityId && !isEditRoute) return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(DetailViewBinding, {
229
+ entityId,
230
+ collection,
231
+ layout: "full_screen",
232
+ path: collectionPath,
233
+ selectedTab: selectedTab ?? void 0,
234
+ onEditClick: () => {
235
+ navigate(urlController.buildUrlCollectionPath(`${collectionPath}/${entityId}`) + "/edit" + hash);
236
+ },
237
+ onTabChange: (params) => {
238
+ setSelectedTab(params.selectedTab);
239
+ const newSelectedTab = params.selectedTab;
240
+ if (newSelectedTab) navigate(`${basePath}/${entityId}/${newSelectedTab}${hash}`, { replace: true });
241
+ else navigate(`${basePath}/${entityId}${hash}`, { replace: true });
242
+ },
243
+ parentCollectionSlugs,
244
+ parentEntityIds
245
+ }, collection.slug + "_view_" + entityId) });
246
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EditViewBinding, {
247
+ entityId: isNew ? void 0 : entityId,
248
+ collection,
249
+ layout: "full_screen",
250
+ path: collectionPath,
251
+ copy: isCopy,
252
+ selectedTab: selectedTab ?? void 0,
253
+ defaultValues: isNew ? defaultValues : void 0,
254
+ onValuesModified: (modified) => blocked.current = modified,
255
+ navigateBack: () => {
256
+ navigate(urlController.buildUrlCollectionPath(`${collectionPath}/${entityId}`) + hash);
257
+ },
258
+ onSaved: (params) => {
259
+ const newSelectedTab = params.selectedTab;
260
+ const newEntityId = params.entityId;
261
+ const savedHash = isNew ? "" : hash;
262
+ if (newSelectedTab) navigate(`${basePath}/${newEntityId}/${newSelectedTab}${savedHash}`, { replace: true });
263
+ else navigate(`${basePath}/${newEntityId}${savedHash}`, { replace: true });
264
+ },
265
+ onTabChange: (params) => {
266
+ setSelectedTab(params.selectedTab);
267
+ if (isNew) return;
268
+ const newSelectedTab = params.selectedTab;
269
+ if (newSelectedTab) navigate(`${basePath}/${entityId}/${newSelectedTab}${hash}`, { replace: true });
270
+ else navigate(`${basePath}/${entityId}${hash}`, { replace: true });
271
+ },
272
+ parentCollectionSlugs,
273
+ parentEntityIds
274
+ }, collection.slug + "_" + (isNew ? "new" : isCopy ? entityId + "_copy" : entityId)), /* @__PURE__ */ jsx(UnsavedChangesDialog, {
275
+ open: blocker?.state === "blocked",
276
+ handleOk: () => blocker?.proceed?.(),
277
+ handleCancel: () => blocker?.reset?.(),
278
+ body: "You have unsaved changes in this entity."
279
+ })] });
280
+ }
281
+ //#endregion
282
+ //#region src/components/ReferenceWidget.tsx
283
+ /**
284
+ * This field allows selecting reference/s.
285
+ */
286
+ function ReferenceWidget({ name, multiselect = false, path, disabled, value, onReferenceSelected, onMultipleReferenceSelected, previewProperties, fixedFilter, size, className, includeId, includeEntityLink }) {
287
+ const collectionRegistryController = useCollectionRegistryController();
288
+ const collection = useMemo(() => {
289
+ return collectionRegistryController.getCollection(path);
290
+ }, [path, collectionRegistryController.getCollection]);
291
+ const onSingleEntitySelected = useCallback((entity) => {
292
+ if (disabled) return;
293
+ if (onReferenceSelected) {
294
+ const reference = entity ? getReferenceFrom(entity) : null;
295
+ onReferenceSelected?.({
296
+ reference,
297
+ entity
298
+ });
299
+ }
300
+ }, [disabled, onReferenceSelected]);
301
+ const onMultipleEntitiesSelected = useCallback((entities) => {
302
+ if (disabled) return;
303
+ if (onMultipleReferenceSelected) onMultipleReferenceSelected({
304
+ references: entities ? entities.map((e) => getReferenceFrom(e)) : null,
305
+ entities
306
+ });
307
+ }, [disabled, onReferenceSelected]);
308
+ const referenceDialogController = useSelectionDialog({
309
+ multiselect,
310
+ path,
311
+ collection,
312
+ onSingleEntitySelected,
313
+ onMultipleEntitiesSelected,
314
+ fixedFilter
315
+ });
316
+ useCallback((e) => {
317
+ e.stopPropagation();
318
+ if (multiselect) onMultipleEntitiesSelected([]);
319
+ else onSingleEntitySelected(null);
320
+ }, [onReferenceSelected]);
321
+ let child;
322
+ const onEntryClick = () => {
323
+ if (disabled) return;
324
+ referenceDialogController.open();
325
+ };
326
+ if (Array.isArray(value)) child = /* @__PURE__ */ jsx("div", {
327
+ className: "flex flex-col gap-4",
328
+ children: value.map((ref, index) => {
329
+ return /* @__PURE__ */ jsx(ReferencePreview, {
330
+ onClick: onEntryClick,
331
+ reference: ref,
332
+ disabled,
333
+ previewProperties,
334
+ size,
335
+ includeId,
336
+ includeEntityLink
337
+ }, `reference_preview_${index}`);
338
+ })
339
+ });
340
+ else if (value?.isEntityReference && value?.isEntityReference()) child = /* @__PURE__ */ jsx(ReferencePreview, {
341
+ reference: value,
342
+ onClick: onEntryClick,
343
+ disabled,
344
+ previewProperties,
345
+ size,
346
+ includeId,
347
+ includeEntityLink
348
+ });
349
+ return /* @__PURE__ */ jsxs("div", {
350
+ className: cls("text-sm font-medium", "min-w-80 flex flex-col gap-4", "relative transition-colors duration-200 ease-in rounded-xs font-medium", disabled ? "bg-opacity-50" : "hover:bg-opacity-75", "dark:text-white/50 text-text-primary/50 dark:text-text-primary-dark/50 dark:text-white/50", className),
351
+ children: [child, !value && /* @__PURE__ */ jsx("div", {
352
+ className: "justify-center text-left",
353
+ children: /* @__PURE__ */ jsxs(Button, {
354
+ disabled,
355
+ onClick: onEntryClick,
356
+ children: ["Edit ", name]
357
+ })
358
+ })]
359
+ });
360
+ }
361
+ //#endregion
362
+ //#region src/hooks/useBuildSideDialogsController.tsx
363
+ function useBuildSideDialogsController() {
364
+ const location = useLocation();
365
+ const navigate = useNavigate();
366
+ const locationRef = useRef(location);
367
+ locationRef.current = location;
368
+ const [sidePanels, setSidePanels] = useState([]);
369
+ const sidePanelsRef = useRef(sidePanels);
370
+ const routesStore = useRef({});
371
+ const routesCount = useRef(0);
372
+ const updateSidePanels = useCallback((newPanels) => {
373
+ sidePanelsRef.current = newPanels;
374
+ setSidePanels(newPanels);
375
+ }, []);
376
+ useEffect(() => {
377
+ const newPanels = (location.state?.panels ?? []).map((key) => routesStore.current[key]).filter((p) => Boolean(p));
378
+ if (!deepEqual(sidePanelsRef.current.map((p) => p.key), newPanels.map((p) => p.key))) {
379
+ updateSidePanels(newPanels);
380
+ if (newPanels.length === 0) routesCount.current = 0;
381
+ }
382
+ }, [location]);
383
+ const close = useCallback(() => {
384
+ const currentPanels = sidePanelsRef.current;
385
+ if (currentPanels.length === 0) return;
386
+ const lastSidePanel = currentPanels[currentPanels.length - 1];
387
+ const updatedPanels = [...currentPanels.slice(0, -1)];
388
+ updateSidePanels(updatedPanels);
389
+ if (routesCount.current > 0) {
390
+ if (lastSidePanel.urlPath) navigate(-1);
391
+ routesCount.current--;
392
+ } else if (lastSidePanel.parentUrlPath) {
393
+ const baseLocation = locationRef.current.state?.base_location ?? locationRef.current;
394
+ navigate(lastSidePanel.parentUrlPath, {
395
+ replace: true,
396
+ state: {
397
+ base_location: baseLocation,
398
+ panels: updatedPanels.map((p) => p.key)
399
+ }
400
+ });
401
+ }
402
+ }, [navigate]);
403
+ const open = useCallback((panelProps) => {
404
+ const newPanels = Array.isArray(panelProps) ? panelProps : [panelProps];
405
+ newPanels.forEach((panel) => {
406
+ routesStore.current[panel.key] = panel;
407
+ });
408
+ routesCount.current = routesCount.current + newPanels.length;
409
+ const baseLocation = locationRef.current.state?.base_location ?? locationRef.current;
410
+ const updatedPanels = [...sidePanelsRef.current, ...newPanels];
411
+ updateSidePanels(updatedPanels);
412
+ newPanels.forEach((panel) => {
413
+ if (panel.urlPath) navigate(panel.urlPath, { state: {
414
+ base_location: baseLocation,
415
+ panels: updatedPanels.map((p) => p.key)
416
+ } });
417
+ });
418
+ }, [navigate]);
419
+ const replace = useCallback((panelProps) => {
420
+ const newPanels = Array.isArray(panelProps) ? panelProps : [panelProps];
421
+ newPanels.forEach((panel) => {
422
+ routesStore.current[panel.key] = panel;
423
+ });
424
+ const baseLocation = locationRef.current.state?.base_location ?? locationRef.current;
425
+ const updatedPanels = [...sidePanelsRef.current.slice(0, -newPanels.length), ...newPanels];
426
+ updateSidePanels(updatedPanels);
427
+ newPanels.forEach((panel) => {
428
+ if (panel.urlPath) navigate(panel.urlPath, {
429
+ replace: true,
430
+ state: {
431
+ base_location: baseLocation,
432
+ panels: updatedPanels.map((p) => p.key)
433
+ }
434
+ });
435
+ });
436
+ }, [navigate]);
437
+ return useMemo(() => ({
438
+ sidePanels,
439
+ setSidePanels: updateSidePanels,
440
+ close,
441
+ open,
442
+ replace
443
+ }), [
444
+ sidePanels,
445
+ updateSidePanels,
446
+ close,
447
+ open,
448
+ replace
449
+ ]);
450
+ }
451
+ //#endregion
452
+ //#region src/components/SidePanelProvider.tsx
453
+ /**
454
+ * Provider that builds the SidePanelController and makes it available
455
+ * via the SidePanelControllerContext from @rebasepro/app.
456
+ *
457
+ * After the CMS extraction refactor, `useBuildSidePanel` lives
458
+ * in the CMS package while the context it feeds into lives in core.
459
+ * This provider bridges the two: place it inside the `<Rebase>` tree and
460
+ * above any component that calls `useSidePanel()`.
461
+ *
462
+ * Also auto-registers the side entity controller and breadcrumbs into the
463
+ * self-assembling Studio bridge (when a StudioBridgeRegistryProvider is
464
+ * mounted above in the tree).
465
+ *
466
+ * @example
467
+ * ```tsx
468
+ * <Rebase ...>
469
+ * {({ loading }) => (
470
+ * <SidePanelProvider>
471
+ * <RebaseRoutes>
472
+ * ...
473
+ * </RebaseRoutes>
474
+ * </SidePanelProvider>
475
+ * )}
476
+ * </Rebase>
477
+ * ```
478
+ *
479
+ * @group Components
480
+ */
481
+ function SidePanelProvider({ children }) {
482
+ const collectionRegistryController = useCollectionRegistryController();
483
+ const urlController = useUrlController();
484
+ const navigationStateController = useNavigationStateController();
485
+ const sideDialogsController = useBuildSideDialogsController();
486
+ const sidePanelController = useBuildSidePanel(collectionRegistryController, urlController, navigationStateController, sideDialogsController, useAuthController());
487
+ return /* @__PURE__ */ jsx(NavigationBlockerProvider, { children: /* @__PURE__ */ jsx(BreadcrumbsProvider, { children: /* @__PURE__ */ jsx(SideDialogsControllerContext.Provider, {
488
+ value: sideDialogsController,
489
+ children: /* @__PURE__ */ jsxs(SidePanelControllerContext.Provider, {
490
+ value: sidePanelController,
491
+ children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar$1, { sidePanelController }), children]
492
+ })
493
+ }) }) });
494
+ }
495
+ /**
496
+ * Internal component that auto-registers side entity and breadcrumbs
497
+ * into the Studio bridge. Must be a child of BreadcrumbsProvider.
498
+ */
499
+ function BridgeAutoRegistrar$1({ sidePanelController }) {
500
+ const breadcrumbs = useBreadcrumbsController();
501
+ useBridgeRegistration("sidePanelController", sidePanelController);
502
+ useBridgeRegistration("breadcrumbs", breadcrumbs);
503
+ return null;
504
+ }
505
+ //#endregion
506
+ //#region src/components/AdminModeSyncer.tsx
507
+ /**
508
+ * A highly customizable utility component that observes react-router routes and
509
+ * actively shifts the internal Rebase `adminModeController` context to match
510
+ * the user's active window segment (e.g., Content vs Studio mode).
511
+ *
512
+ * Placing this anywhere safely inside the Rebase layout automatically triggers
513
+ * UI mode synchronization.
514
+ */
515
+ function AdminModeSyncer({ devViews }) {
516
+ const location = useLocation();
517
+ const urlController = useUrlController();
518
+ const adminModeController = useAdminModeController();
519
+ const currentModeRef = React.useRef(adminModeController.mode);
520
+ currentModeRef.current = adminModeController.mode;
521
+ const setModeRef = React.useRef(adminModeController.setMode);
522
+ setModeRef.current = adminModeController.setMode;
523
+ useEffect(() => {
524
+ const path = location.pathname;
525
+ const isContentRoute = urlController.isUrlCollectionPath(path) || path === urlController.basePath;
526
+ const studioHomePath = urlController.basePath === "/" ? "/s" : `${urlController.basePath}/s`;
527
+ if ((path === studioHomePath || path.startsWith(studioHomePath + "/") || devViews?.some((view) => {
528
+ const viewPath = urlController.buildAppUrlPath(view.slug);
529
+ return path.startsWith(`${viewPath}/`) || path === viewPath;
530
+ })) && currentModeRef.current !== "studio") setModeRef.current("studio");
531
+ else if (isContentRoute && currentModeRef.current !== "content") setModeRef.current("content");
532
+ }, [
533
+ location.pathname,
534
+ urlController,
535
+ devViews
536
+ ]);
537
+ return null;
538
+ }
539
+ //#endregion
540
+ //#region src/components/app/useApp.tsx
541
+ var AppContext = React.createContext({
542
+ hasDrawer: false,
543
+ drawerHovered: false,
544
+ drawerOpen: false,
545
+ openDrawer: () => {
546
+ throw new Error("openDrawer not implemented");
547
+ },
548
+ closeDrawer: () => {
549
+ throw new Error("closeDrawer not implemented");
550
+ },
551
+ closeHover: () => {
552
+ throw new Error("closeHover not implemented");
553
+ },
554
+ autoOpenDrawer: false
555
+ });
556
+ function useApp() {
557
+ return React.useContext(AppContext);
558
+ }
559
+ //#endregion
560
+ //#region src/components/DefaultAppBar.tsx
561
+ /**
562
+ * This component renders the main app bar of Rebase.
563
+ * You will likely not need to use this component directly.
564
+ *
565
+
566
+ */
567
+ var DefaultAppBar = function DefaultAppBar({ title, endAdornment, startAdornment, dropDownActions, includeModeToggle = true, includeLanguageToggle = true, includeUserMenu = true, className, style, user: userProp, logo: logoProp }) {
568
+ const { hasDrawer, drawerOpen, logo: appLogo } = useApp();
569
+ const logo = logoProp ?? appLogo;
570
+ const navigation = useUrlController();
571
+ const breadcrumbs = useBreadcrumbsController();
572
+ const authController = useAuthController();
573
+ useAdminModeController();
574
+ const { mode, setMode } = useModeController();
575
+ const navigate = useNavigate();
576
+ const largeLayout = useLargeLayout();
577
+ const user = userProp ?? authController.user;
578
+ const { t } = useTranslation();
579
+ let avatarComponent;
580
+ if (user) {
581
+ const initial = user?.displayName ? user.displayName[0].toUpperCase() : user?.email ? user.email[0].toUpperCase() : "A";
582
+ avatarComponent = /* @__PURE__ */ jsx(Avatar, {
583
+ src: user.photoURL ?? void 0,
584
+ children: initial
585
+ });
586
+ } else if (user === void 0 || authController.initialLoading) avatarComponent = /* @__PURE__ */ jsx("div", {
587
+ className: "p-1 flex justify-center",
588
+ children: /* @__PURE__ */ jsx(Skeleton, { className: "w-10 h-10 rounded-full" })
589
+ });
590
+ else avatarComponent = null;
591
+ return /* @__PURE__ */ jsxs("div", {
592
+ style,
593
+ role: "banner",
594
+ className: cls("w-full h-14 transition-all ease-in duration-75 absolute top-0 max-w-full overflow-x-auto no-scrollbar", "flex flex-row gap-2 px-4 items-center", "backdrop-blur-sm bg-surface-50/95 dark:bg-surface-900/80", {
595
+ "pl-[19rem]": drawerOpen && largeLayout,
596
+ "pl-24": hasDrawer && !(drawerOpen && largeLayout),
597
+ "z-10": largeLayout,
598
+ "duration-100": drawerOpen && largeLayout
599
+ }, className),
600
+ children: [
601
+ navigation && (!hasDrawer || title) && /* @__PURE__ */ jsx("div", {
602
+ className: "mr-2 hidden lg:block",
603
+ children: /* @__PURE__ */ jsx(Link, {
604
+ className: "visited:text-inherit dark:visited:text-inherit block",
605
+ to: navigation?.basePath ?? "/",
606
+ children: /* @__PURE__ */ jsxs("div", {
607
+ className: "flex flex-row gap-4",
608
+ children: [!hasDrawer && (logo ? /* @__PURE__ */ jsx("img", {
609
+ src: logo,
610
+ alt: "Logo",
611
+ className: cls("w-[32px] h-[32px] object-contain")
612
+ }) : /* @__PURE__ */ jsx(RebaseLogo, {
613
+ width: "32px",
614
+ height: "32px"
615
+ })), typeof title === "string" ? /* @__PURE__ */ jsx(Typography, {
616
+ variant: "subtitle1",
617
+ noWrap: true,
618
+ children: title
619
+ }) : title]
620
+ })
621
+ })
622
+ }),
623
+ /* @__PURE__ */ jsx("div", {
624
+ className: "mr-8 hidden lg:block",
625
+ children: /* @__PURE__ */ jsx("nav", {
626
+ "aria-label": "Breadcrumb",
627
+ children: /* @__PURE__ */ jsx("div", {
628
+ className: "flex flex-row gap-2 items-center",
629
+ children: breadcrumbs.breadcrumbs.map((breadcrumb, index) => {
630
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [index > 0 && /* @__PURE__ */ jsx(Typography, {
631
+ variant: "caption",
632
+ color: "secondary",
633
+ children: "/"
634
+ }), /* @__PURE__ */ jsx(Link, {
635
+ className: "visited:text-inherit dark:visited:text-inherit block",
636
+ to: breadcrumb.url,
637
+ children: /* @__PURE__ */ jsxs("div", {
638
+ className: "flex flex-row items-center gap-2 whitespace-nowrap",
639
+ children: [/* @__PURE__ */ jsx(Typography, {
640
+ variant: "body2",
641
+ children: breadcrumb.title
642
+ }), breadcrumb.count !== void 0 && (breadcrumb.count !== null ? /* @__PURE__ */ jsx("span", {
643
+ className: "text-xs text-surface-accent-500 dark:text-surface-accent-400 bg-surface-100 dark:bg-surface-800 px-1 py-0 rounded",
644
+ children: breadcrumb.count
645
+ }) : /* @__PURE__ */ jsx(Skeleton, { className: "w-8 h-4 rounded-md" }))]
646
+ })
647
+ }, index)] }, breadcrumb.url + "_" + index);
648
+ })
649
+ })
650
+ })
651
+ }),
652
+ startAdornment,
653
+ /* @__PURE__ */ jsx("div", { className: "grow" }),
654
+ endAdornment && /* @__PURE__ */ jsx(ErrorBoundary, { children: endAdornment }),
655
+ includeLanguageToggle && /* @__PURE__ */ jsx(LanguageToggle, {}),
656
+ includeModeToggle && /* @__PURE__ */ jsxs(Menu, {
657
+ trigger: /* @__PURE__ */ jsx(IconButton, {
658
+ color: "inherit",
659
+ "aria-label": "Toggle theme",
660
+ children: mode === "dark" ? /* @__PURE__ */ jsx(MoonIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(SunIcon, { size: iconSize.small })
661
+ }),
662
+ children: [
663
+ /* @__PURE__ */ jsxs(MenuItem, {
664
+ onClick: () => setMode("dark"),
665
+ children: [
666
+ /* @__PURE__ */ jsx(MoonIcon, { size: iconSize.smallest }),
667
+ " ",
668
+ t("dark_mode")
669
+ ]
670
+ }),
671
+ /* @__PURE__ */ jsxs(MenuItem, {
672
+ onClick: () => setMode("light"),
673
+ children: [
674
+ /* @__PURE__ */ jsx(SunIcon, { size: iconSize.smallest }),
675
+ " ",
676
+ t("light_mode"),
677
+ " "
678
+ ]
679
+ }),
680
+ /* @__PURE__ */ jsxs(MenuItem, {
681
+ onClick: () => setMode("system"),
682
+ children: [
683
+ " ",
684
+ /* @__PURE__ */ jsx(SunMoonIcon, { size: iconSize.smallest }),
685
+ t("system_mode")
686
+ ]
687
+ })
688
+ ]
689
+ }),
690
+ includeUserMenu && /* @__PURE__ */ jsxs(Menu, {
691
+ trigger: /* @__PURE__ */ jsx("div", {
692
+ "aria-label": "User menu",
693
+ role: "button",
694
+ children: avatarComponent
695
+ }),
696
+ children: [
697
+ user && /* @__PURE__ */ jsxs("div", {
698
+ className: "px-4 py-2 mb-2",
699
+ children: [user.displayName && /* @__PURE__ */ jsx(Typography, {
700
+ variant: "body1",
701
+ color: "secondary",
702
+ children: user.displayName
703
+ }), user.email && /* @__PURE__ */ jsx(Typography, {
704
+ variant: "body2",
705
+ color: "secondary",
706
+ children: user.email
707
+ })]
708
+ }),
709
+ dropDownActions,
710
+ !dropDownActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(MenuItem, {
711
+ onClick: () => navigate("/settings"),
712
+ children: [/* @__PURE__ */ jsx(SettingsIcon, {}), t("account_settings")]
713
+ }), /* @__PURE__ */ jsxs(MenuItem, {
714
+ onClick: async () => {
715
+ await authController.signOut();
716
+ navigate("/");
717
+ },
718
+ children: [/* @__PURE__ */ jsx(LogOutIcon, {}), t("log_out")]
719
+ })] })
720
+ ]
721
+ })
722
+ ]
723
+ });
724
+ };
725
+ //#endregion
726
+ //#region src/components/app/AppBar.tsx
727
+ /**
728
+ * This component renders the main app bar of Rebase.
729
+ */
730
+ function AppBar({ children, ...props }) {
731
+ return /* @__PURE__ */ jsx(Fragment, { children: children ?? /* @__PURE__ */ jsx(DefaultAppBar, { ...props }) });
732
+ }
733
+ AppBar.componentType = "AppBar";
734
+ //#endregion
735
+ //#region src/components/DrawerNavigationItem.tsx
736
+ function DrawerNavigationItem({ name, icon, drawerOpen, adminMenuOpen, tooltipsOpen, url, onClick }) {
737
+ const listItem = /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(NavLink, {
738
+ onClick,
739
+ style: {
740
+ width: "100%",
741
+ transition: drawerOpen ? "width 150ms ease-in" : void 0
742
+ },
743
+ className: ({ isActive }) => cls("rounded-lg truncate group/nav", "hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white", "flex flex-row items-center", drawerOpen ? "pr-4 h-[30px]" : "h-[30px]", "font-medium text-[13px]", isActive ? "bg-primary/8 dark:bg-primary/10 text-primary dark:text-primary [&_div]:text-primary" : ""),
744
+ to: url,
745
+ children: [/* @__PURE__ */ jsx("div", {
746
+ className: "shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4 group-hover/nav:text-primary transition-colors duration-150",
747
+ children: icon
748
+ }), /* @__PURE__ */ jsx("div", {
749
+ className: cls("text-text-primary dark:text-surface-200", drawerOpen ? "opacity-100" : "opacity-0 hidden", "font-inherit truncate space-x-2"),
750
+ children: name
751
+ })]
752
+ }) });
753
+ return /* @__PURE__ */ jsx(Tooltip, {
754
+ open: drawerOpen || adminMenuOpen ? false : tooltipsOpen,
755
+ side: "right",
756
+ title: name,
757
+ children: listItem
758
+ });
759
+ }
760
+ //#endregion
761
+ //#region src/components/DrawerNavigationGroup.tsx
762
+ /**
763
+ * Shared drawer navigation group component used by both DefaultDrawer and RebaseCloudDrawer.
764
+ * Renders a collapsible group with header and navigation items.
765
+ */
766
+ function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, drawerOpen, tooltipsOpen, adminMenuOpen, headerActions, onItemClick, hideHeader }) {
767
+ const { t } = useTranslation();
768
+ const ResolvedDrawerNavigationItem = useComponentOverride("Shell.DrawerNavigationItem", DrawerNavigationItem);
769
+ return /* @__PURE__ */ jsxs("div", {
770
+ className: "my-2 mx-2 flex flex-col",
771
+ children: [!hideHeader && /* @__PURE__ */ jsxs("div", {
772
+ className: cls("pl-3 pr-2 py-0.5 flex flex-row items-center transition-colors", drawerOpen ? "cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-800/40 rounded-lg" : "opacity-0 invisible pointer-events-none"),
773
+ onClick: drawerOpen ? onToggleCollapsed : void 0,
774
+ children: [
775
+ /* @__PURE__ */ jsx(ChevronDownIcon, {
776
+ size: iconSize.small,
777
+ className: cls("text-surface-400 dark:text-surface-400 transition-transform duration-200 mr-1", collapsed ? "-rotate-90" : "rotate-0")
778
+ }),
779
+ /* @__PURE__ */ jsx(Typography, {
780
+ variant: "caption",
781
+ color: "secondary",
782
+ className: "font-semibold text-[11px] uppercase tracking-wider flex-grow line-clamp-1 text-surface-400 dark:text-surface-400",
783
+ children: group || t("views_group")
784
+ }),
785
+ headerActions && /* @__PURE__ */ jsx("div", {
786
+ onClick: (e) => e.stopPropagation(),
787
+ children: headerActions
788
+ })
789
+ ]
790
+ }), /* @__PURE__ */ jsx("div", {
791
+ className: cls("transition-all duration-200 ease-in-out", "overflow-hidden", !hideHeader && "dark:bg-transparent", "rounded-lg", !hideHeader && collapsed ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"),
792
+ children: entries.map((entry) => /* @__PURE__ */ jsx(ResolvedDrawerNavigationItem, {
793
+ icon: /* @__PURE__ */ jsx(IconForView, {
794
+ collectionOrView: entry.collection ?? entry.view,
795
+ size: "small"
796
+ }),
797
+ tooltipsOpen: !collapsed && tooltipsOpen,
798
+ adminMenuOpen,
799
+ drawerOpen,
800
+ onClick: () => onItemClick?.(entry),
801
+ url: entry.url,
802
+ name: entry.name
803
+ }, entry.id))
804
+ })]
805
+ }, `drawer_group_${group}`);
806
+ }
807
+ //#endregion
808
+ //#region src/components/DefaultDrawer.tsx
809
+ /**
810
+ * Default drawer used in the CMS.
811
+ *
812
+ * When no `children` are provided, renders the full CMS navigation
813
+ * (collection groups, mode switch, plugin slots).
814
+ *
815
+ * When `children` **are** provided, renders the shared drawer shell
816
+ * (logo, scrollable area, footer actions, collapse toggle) with the
817
+ * custom content injected in the scrollable area. This lets consumers
818
+ * like the SaaS dashboard reuse the identical structural layout while
819
+ * supplying their own navigation items.
820
+ *
821
+ * @group Core
822
+ */
823
+ function DefaultDrawer({ title, logo, logoDestination, children, footerActions, className, style }) {
824
+ const { drawerHovered, drawerOpen, openDrawer, closeDrawer, logo: appLogo } = useApp();
825
+ const resolvedLogo = logo ?? appLogo;
826
+ const scrollRef = React.useRef(null);
827
+ const [scrolled, setScrolled] = React.useState(false);
828
+ const handleScroll = () => {
829
+ if (scrollRef.current) setScrolled(scrollRef.current.scrollTop > 0);
830
+ };
831
+ return /* @__PURE__ */ jsxs("div", {
832
+ role: "navigation",
833
+ "aria-label": "Main navigation",
834
+ className: cls("flex flex-col h-full relative grow w-full", className),
835
+ style,
836
+ children: [
837
+ /* @__PURE__ */ jsx(DrawerLogo, {
838
+ logo: resolvedLogo,
839
+ title,
840
+ logoDestination,
841
+ drawerOpen,
842
+ drawerHovered
843
+ }),
844
+ /* @__PURE__ */ jsx("div", {
845
+ ref: scrollRef,
846
+ onScroll: handleScroll,
847
+ className: "flex-grow min-h-0 overflow-y-auto overflow-x-hidden no-scrollbar px-2",
848
+ style: { maskImage: scrolled ? "linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%)" : "linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%)" },
849
+ children: children ?? /* @__PURE__ */ jsx(CMSNavigationContent, {})
850
+ }),
851
+ footerActions !== null && (footerActions !== void 0 ? footerActions : /* @__PURE__ */ jsx(DrawerFooterActions, {
852
+ drawerOpen,
853
+ drawerHovered
854
+ })),
855
+ /* @__PURE__ */ jsx(DrawerToggle, {
856
+ drawerOpen,
857
+ drawerHovered,
858
+ openDrawer,
859
+ closeDrawer
860
+ })
861
+ ]
862
+ });
863
+ }
864
+ /**
865
+ * Default CMS navigation content — rendered inside DefaultDrawer when no
866
+ * custom `children` are provided. Contains the mode switch, navigation
867
+ * groups, and header / footer plugin slots.
868
+ */
869
+ function CMSNavigationContent() {
870
+ const { drawerHovered, drawerOpen, closeDrawer, closeHover } = useApp();
871
+ const [adminMenuOpen, setAdminMenuOpen] = React.useState(false);
872
+ const analyticsController = useAnalyticsController();
873
+ const navigationState = useNavigationStateController();
874
+ const context = useRebaseContext();
875
+ const tooltipsOpen = drawerHovered && !drawerOpen && !adminMenuOpen;
876
+ const largeLayout = useLargeLayout();
877
+ const adminModeController = useAdminModeController();
878
+ const registry = useRebaseRegistry();
879
+ const ResolvedDrawerNavigationGroup = useComponentOverride("Shell.DrawerNavigationGroup", DrawerNavigationGroup);
880
+ const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
881
+ const studioViewSlugs = useMemo(() => {
882
+ const slugs = /* @__PURE__ */ new Set();
883
+ (registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
884
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
885
+ return slugs;
886
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
887
+ const filteredEntries = adminModeController.mode === "studio" ? allNavigationEntries.filter((e) => e.type === "view" && studioViewSlugs.has(e.slug) || e.type === "admin") : allNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
888
+ const entryGroups = new Set(filteredEntries.map((e) => e.group).filter(Boolean));
889
+ const orderedGroups = navigationState.topLevelNavigation?.groups ?? [];
890
+ const groupsToRender = [...orderedGroups.filter((g) => entryGroups.has(g)), ...[...entryGroups].filter((g) => !orderedGroups.includes(g))];
891
+ const { isGroupCollapsed, toggleGroupCollapsed } = useCollapsedGroups(groupsToRender, "drawer", useMemo(() => buildCollapsedDefaults(registry.cmsConfig?.navigationGroupMappings, "drawer"), [registry.cmsConfig?.navigationGroupMappings]));
892
+ const headerSlot = useSlot("navigation.header", {
893
+ drawerOpen,
894
+ drawerHovered,
895
+ context
896
+ });
897
+ const footerSlot = useSlot("navigation.footer", {
898
+ drawerOpen,
899
+ drawerHovered,
900
+ context
901
+ });
902
+ if (!navigationState.topLevelNavigation) return null;
903
+ const onItemClick = (view) => {
904
+ const eventName = view.type === "collection" ? "drawer_navigate_to_collection" : view.type === "view" ? "drawer_navigate_to_view" : "unmapped_event";
905
+ analyticsController.onAnalyticsEvent?.(eventName, { url: view.url });
906
+ if (!largeLayout) closeDrawer();
907
+ else if (!drawerOpen) closeHover();
908
+ };
909
+ const drawerVisuallyOpen = drawerOpen || drawerHovered;
910
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
911
+ registry.studioConfig && /* @__PURE__ */ jsx(DrawerModeSwitch, {
912
+ drawerOpen,
913
+ drawerHovered
914
+ }),
915
+ headerSlot,
916
+ groupsToRender.map((group) => {
917
+ return /* @__PURE__ */ jsx(ResolvedDrawerNavigationGroup, {
918
+ group,
919
+ entries: filteredEntries.filter((e) => e.group === group),
920
+ collapsed: isGroupCollapsed(group),
921
+ onToggleCollapsed: () => toggleGroupCollapsed(group),
922
+ drawerOpen: drawerVisuallyOpen,
923
+ tooltipsOpen,
924
+ adminMenuOpen,
925
+ onItemClick
926
+ }, `drawer_group_${group}`);
927
+ }),
928
+ footerSlot
929
+ ] });
930
+ }
931
+ function DrawerLogo({ logo, title, logoDestination = "/", drawerOpen, drawerHovered }) {
932
+ return /* @__PURE__ */ jsxs("div", {
933
+ className: "flex flex-row items-center shrink-0 pt-4 pb-0 px-2",
934
+ children: [/* @__PURE__ */ jsx(Link, {
935
+ className: "shrink-0 flex items-center justify-center w-[56px] h-[40px]",
936
+ to: logoDestination,
937
+ children: logo ? /* @__PURE__ */ jsx("img", {
938
+ src: logo,
939
+ alt: "Logo",
940
+ className: "w-[28px] h-[28px] object-contain"
941
+ }) : /* @__PURE__ */ jsx(RebaseLogo, {
942
+ width: "28px",
943
+ height: "28px"
944
+ })
945
+ }), /* @__PURE__ */ jsx("div", {
946
+ className: cls("flex flex-row items-center overflow-hidden transition-all duration-200 ease-in-out", drawerOpen || drawerHovered && !drawerOpen ? "opacity-100 w-full ml-1" : "opacity-0 w-0 ml-0"),
947
+ children: title && /* @__PURE__ */ jsx(Link, {
948
+ className: "visited:text-inherit dark:visited:text-inherit block truncate",
949
+ to: logoDestination,
950
+ children: typeof title === "string" ? /* @__PURE__ */ jsx(Typography, {
951
+ variant: "subtitle1",
952
+ noWrap: true,
953
+ className: "truncate",
954
+ children: title
955
+ }) : title
956
+ })
957
+ })]
958
+ });
959
+ }
960
+ /**
961
+ * Toggle button at the bottom of the drawer.
962
+ * Uses double-chevron icons to indicate collapse/expand direction.
963
+ */
964
+ function DrawerToggle({ drawerOpen, drawerHovered, openDrawer, closeDrawer }) {
965
+ const isExpanded = drawerOpen;
966
+ const isHovered = drawerHovered && !drawerOpen;
967
+ const isFloating = isHovered;
968
+ const showFullContent = isExpanded || isHovered;
969
+ const { t } = useTranslation();
970
+ return /* @__PURE__ */ jsx("div", {
971
+ className: "shrink-0 mt-auto px-4 pt-0.5 pb-2",
972
+ children: /* @__PURE__ */ jsx(Tooltip, {
973
+ title: isExpanded ? t("collapse") : t("expand"),
974
+ side: "right",
975
+ sideOffset: 12,
976
+ asChild: true,
977
+ open: isFloating ? false : void 0,
978
+ children: /* @__PURE__ */ jsxs("div", {
979
+ className: cls("flex flex-row items-center rounded-lg cursor-pointer", "hover:bg-surface-accent-100 dark:hover:bg-surface-800", "transition-colors duration-150", "py-2"),
980
+ role: "button",
981
+ tabIndex: 0,
982
+ "aria-expanded": isExpanded,
983
+ "aria-label": isExpanded ? t("collapse") : t("expand"),
984
+ onClick: () => isExpanded ? closeDrawer() : openDrawer(),
985
+ onKeyDown: (e) => {
986
+ if (e.key === "Enter" || e.key === " ") {
987
+ e.preventDefault();
988
+ if (isExpanded) closeDrawer();
989
+ else openDrawer();
990
+ }
991
+ },
992
+ children: [/* @__PURE__ */ jsx("div", {
993
+ className: "shrink-0 flex items-center justify-center w-[44px] h-[24px] text-surface-500 dark:text-surface-400",
994
+ children: isExpanded ? /* @__PURE__ */ jsx(ChevronsLeftIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(ChevronsRightIcon, { size: iconSize.small })
995
+ }), /* @__PURE__ */ jsx("div", {
996
+ className: cls("overflow-hidden transition-all duration-200 ease-in-out", showFullContent ? "opacity-100 w-auto" : "opacity-0 w-0"),
997
+ children: /* @__PURE__ */ jsx(Typography, {
998
+ variant: "body2",
999
+ className: "text-surface-500 dark:text-surface-400 select-none whitespace-nowrap",
1000
+ children: isExpanded ? t("collapse") : t("expand")
1001
+ })
1002
+ })]
1003
+ })
1004
+ })
1005
+ });
1006
+ }
1007
+ /**
1008
+ * Segmented Content / Studio switch rendered inside the drawer.
1009
+ * Animates in/out with the drawer open state via opacity + max-height.
1010
+ */
1011
+ function DrawerModeSwitch({ drawerOpen, drawerHovered }) {
1012
+ const adminModeController = useAdminModeController();
1013
+ const urlController = useUrlController();
1014
+ const navigate = useNavigate();
1015
+ return /* @__PURE__ */ jsx("div", {
1016
+ className: cls("shrink-0 overflow-hidden transition-all duration-200 ease-in-out px-3", drawerOpen || drawerHovered ? "opacity-100 h-7 mt-2 mb-0" : "opacity-0 pointer-events-none h-7 mt-2 mb-0"),
1017
+ children: /* @__PURE__ */ jsxs("div", {
1018
+ role: "group",
1019
+ "aria-label": "Content mode",
1020
+ className: "flex bg-surface-100 dark:bg-surface-900 rounded-lg p-0.5 border border-surface-200 dark:border-surface-700/60",
1021
+ children: [/* @__PURE__ */ jsx("button", {
1022
+ onClick: () => {
1023
+ adminModeController.setMode("content");
1024
+ navigate(urlController.basePath ?? "/");
1025
+ },
1026
+ "aria-pressed": adminModeController.mode === "content",
1027
+ className: cls("flex-1 px-3 py-0.5 text-xs font-semibold rounded-md transition-all text-center", adminModeController.mode === "content" ? "bg-white dark:bg-surface-800 shadow-sm text-primary dark:text-primary-400" : "text-surface-500 hover:text-surface-900 dark:hover:text-white"),
1028
+ children: "Content"
1029
+ }), /* @__PURE__ */ jsx("button", {
1030
+ onClick: () => {
1031
+ adminModeController.setMode("studio");
1032
+ navigate(urlController.basePath === "/" ? "/s" : `${urlController.basePath ?? ""}/s`);
1033
+ },
1034
+ "aria-pressed": adminModeController.mode === "studio",
1035
+ className: cls("flex-1 px-3 py-0.5 text-xs font-semibold rounded-md transition-all text-center", adminModeController.mode === "studio" ? "bg-white dark:bg-surface-800 shadow-sm text-primary dark:text-primary-400" : "text-surface-500 hover:text-surface-900 dark:hover:text-white"),
1036
+ children: "Studio"
1037
+ })]
1038
+ })
1039
+ });
1040
+ }
1041
+ /**
1042
+ * Footer actions rendered at the bottom of the drawer, above the collapse/expand toggle.
1043
+ * Replaces the app bar icons (language, theme, user avatar) with a drawer-native layout
1044
+ * that adapts between collapsed (icons only) and expanded (full labels + user bar) states.
1045
+ *
1046
+ * @group Core
1047
+ */
1048
+ function DrawerFooterActions({ drawerOpen, drawerHovered, dropDownActions, user: userProp }) {
1049
+ const authController = useAuthController();
1050
+ const { mode, setMode } = useModeController();
1051
+ const navigate = useNavigate();
1052
+ const { t } = useTranslation();
1053
+ const user = userProp ?? authController.user;
1054
+ const showFullContent = drawerOpen || drawerHovered;
1055
+ const portalRef = React.useRef(null);
1056
+ let avatarComponent;
1057
+ if (user) {
1058
+ const initial = user?.displayName ? user.displayName[0].toUpperCase() : user?.email ? user.email[0].toUpperCase() : "A";
1059
+ avatarComponent = /* @__PURE__ */ jsx(Avatar, {
1060
+ src: user.photoURL ?? void 0,
1061
+ className: "w-8 h-8 text-xs",
1062
+ children: initial
1063
+ });
1064
+ } else if (user === void 0 || authController.initialLoading) avatarComponent = /* @__PURE__ */ jsx(Skeleton, { className: "w-8 h-8 rounded-full" });
1065
+ else avatarComponent = null;
1066
+ return /* @__PURE__ */ jsx("div", {
1067
+ className: "shrink-0 pt-2 pb-0",
1068
+ ref: portalRef,
1069
+ children: avatarComponent && /* @__PURE__ */ jsxs("div", {
1070
+ className: "flex items-center px-[16px] py-1",
1071
+ children: [/* @__PURE__ */ jsxs(Menu, {
1072
+ trigger: /* @__PURE__ */ jsx("div", {
1073
+ className: cls("shrink-0 flex items-center justify-center w-[44px] cursor-pointer", "rounded-md py-1", "hover:bg-surface-accent-100 dark:hover:bg-surface-800", "transition-colors duration-150"),
1074
+ role: "button",
1075
+ tabIndex: 0,
1076
+ "aria-label": t("user_menu") || "User menu",
1077
+ children: avatarComponent
1078
+ }),
1079
+ side: "top",
1080
+ align: "start",
1081
+ children: [
1082
+ user && /* @__PURE__ */ jsxs("div", {
1083
+ className: "px-4 py-2 mb-1",
1084
+ children: [user.displayName && /* @__PURE__ */ jsx(Typography, {
1085
+ variant: "body1",
1086
+ color: "secondary",
1087
+ children: user.displayName
1088
+ }), user.email && /* @__PURE__ */ jsx(Typography, {
1089
+ variant: "body2",
1090
+ color: "secondary",
1091
+ children: user.email
1092
+ })]
1093
+ }),
1094
+ dropDownActions,
1095
+ !dropDownActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(MenuItem, {
1096
+ onClick: () => navigate("/settings"),
1097
+ children: [/* @__PURE__ */ jsx(SettingsIcon, {}), t("account_settings")]
1098
+ }), /* @__PURE__ */ jsxs(MenuItem, {
1099
+ onClick: async () => {
1100
+ await authController.signOut();
1101
+ navigate("/");
1102
+ },
1103
+ children: [/* @__PURE__ */ jsx(LogOutIcon, {}), t("log_out")]
1104
+ })] })
1105
+ ]
1106
+ }), showFullContent && /* @__PURE__ */ jsxs("div", {
1107
+ className: "flex items-center gap-0",
1108
+ children: [/* @__PURE__ */ jsx(LanguageToggle, {}), /* @__PURE__ */ jsxs(Menu, {
1109
+ trigger: /* @__PURE__ */ jsx(IconButton, {
1110
+ color: "inherit",
1111
+ "aria-label": t("toggle_theme") || "Toggle theme",
1112
+ className: "text-surface-500 dark:text-surface-400",
1113
+ children: mode === "dark" ? /* @__PURE__ */ jsx(MoonIcon, { size: iconSize.small }) : mode === "light" ? /* @__PURE__ */ jsx(SunIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(SunMoonIcon, { size: iconSize.small })
1114
+ }),
1115
+ portalContainer: portalRef.current,
1116
+ side: "top",
1117
+ children: [
1118
+ /* @__PURE__ */ jsxs(MenuItem, {
1119
+ onClick: () => setMode("dark"),
1120
+ children: [
1121
+ /* @__PURE__ */ jsx(MoonIcon, { size: iconSize.smallest }),
1122
+ " ",
1123
+ t("dark_mode")
1124
+ ]
1125
+ }),
1126
+ /* @__PURE__ */ jsxs(MenuItem, {
1127
+ onClick: () => setMode("light"),
1128
+ children: [
1129
+ /* @__PURE__ */ jsx(SunIcon, { size: iconSize.smallest }),
1130
+ " ",
1131
+ t("light_mode")
1132
+ ]
1133
+ }),
1134
+ /* @__PURE__ */ jsxs(MenuItem, {
1135
+ onClick: () => setMode("system"),
1136
+ children: [
1137
+ /* @__PURE__ */ jsx(SunMoonIcon, { size: iconSize.smallest }),
1138
+ " ",
1139
+ t("system_mode")
1140
+ ]
1141
+ })
1142
+ ]
1143
+ })]
1144
+ })]
1145
+ })
1146
+ });
1147
+ }
1148
+ //#endregion
1149
+ //#region src/components/app/Drawer.tsx
1150
+ /**
1151
+ * This component is in charge of rendering the drawer.
1152
+ * If you add this component under your {@link Scaffold}, it will be rendered
1153
+ * as a drawer, and the open and close functionality will be handled automatically.
1154
+ * If you want to customise the drawer, you can create your own component and pass it as a child.
1155
+ * For custom drawers, you can use the {@link useApp} to open and close the drawer.
1156
+ *
1157
+ */
1158
+ function Drawer({ children, title, logo, logoDestination, footerActions, className, style }) {
1159
+ return /* @__PURE__ */ jsx(Fragment, { children: children ?? /* @__PURE__ */ jsx(DefaultDrawer, {
1160
+ title,
1161
+ logo,
1162
+ logoDestination,
1163
+ footerActions,
1164
+ className,
1165
+ style
1166
+ }) });
1167
+ }
1168
+ Drawer.componentType = "Drawer";
1169
+ /**
1170
+ * This view acts as a scaffold for Rebase.
1171
+ *
1172
+ * It is in charge of displaying the navigation drawer, top bar and main
1173
+ * collection views.
1174
+ * This component needs a parent {@link Rebase}
1175
+ *
1176
+ * @param props
1177
+
1178
+ * @group Core
1179
+ */
1180
+ var Scaffold = React.memo(function Scaffold(props) {
1181
+ const { children, autoOpenDrawer, logo, className, style, padding = true } = props;
1182
+ const drawerChildren = React.Children.toArray(children).filter((child) => child.type.componentType === "Drawer");
1183
+ if (drawerChildren.length > 1) throw Error("Only one Drawer component is allowed in Scaffold");
1184
+ const appBarChildren = React.Children.toArray(children).filter((child) => child.type.componentType === "AppBar");
1185
+ if (appBarChildren.length > 1) throw Error("Only one AppBar component is allowed in Scaffold");
1186
+ const otherChildren = React.Children.toArray(children).filter((child) => child.type.componentType !== "Drawer" && child.type.componentType !== "AppBar");
1187
+ const includeDrawer = drawerChildren.length > 0;
1188
+ const largeLayout = useLargeLayout();
1189
+ const [drawerOpen, setDrawerOpen] = React.useState(false);
1190
+ const [onHover, setOnHover] = React.useState(false);
1191
+ const setOnHoverTrue = useCallback(() => setOnHover(true), []);
1192
+ const setOnHoverFalse = useCallback(() => {
1193
+ if (document.querySelector("[data-radix-popper-content-wrapper]")) return;
1194
+ setOnHover(false);
1195
+ }, []);
1196
+ const handleDrawerOpen = useCallback(() => {
1197
+ setDrawerOpen(true);
1198
+ }, []);
1199
+ const handleDrawerClose = useCallback(() => {
1200
+ setDrawerOpen(false);
1201
+ }, []);
1202
+ const computedDrawerOpen = drawerOpen;
1203
+ const computedDrawerHovered = Boolean(largeLayout && onHover);
1204
+ const isStudioDark = useAdminModeController().mode === "studio";
1205
+ const hasAppBar = Boolean(appBarChildren.length > 0);
1206
+ const appContextValue = useMemo(() => ({
1207
+ hasDrawer: Boolean(includeDrawer),
1208
+ drawerOpen: computedDrawerOpen,
1209
+ drawerHovered: computedDrawerHovered,
1210
+ openDrawer: handleDrawerOpen,
1211
+ closeDrawer: handleDrawerClose,
1212
+ closeHover: setOnHoverFalse,
1213
+ logo
1214
+ }), [
1215
+ includeDrawer,
1216
+ computedDrawerOpen,
1217
+ computedDrawerHovered,
1218
+ handleDrawerOpen,
1219
+ handleDrawerClose,
1220
+ setOnHoverFalse,
1221
+ logo
1222
+ ]);
1223
+ return /* @__PURE__ */ jsx(AppContext.Provider, {
1224
+ value: appContextValue,
1225
+ children: /* @__PURE__ */ jsxs("div", {
1226
+ className: cls("flex h-screen w-screen overflow-hidden", "bg-surface-50 dark:bg-surface-900", "text-surface-900 dark:text-white", className),
1227
+ style: {
1228
+ paddingTop: "env(safe-area-inset-top)",
1229
+ paddingLeft: "env(safe-area-inset-left)",
1230
+ paddingRight: "env(safe-area-inset-right)",
1231
+ paddingBottom: "env(safe-area-inset-bottom)",
1232
+ height: "100dvh",
1233
+ ...style
1234
+ },
1235
+ children: [
1236
+ appBarChildren,
1237
+ /* @__PURE__ */ jsx(DrawerWrapper, {
1238
+ displayed: includeDrawer,
1239
+ onMouseEnter: setOnHoverTrue,
1240
+ onMouseMove: setOnHoverTrue,
1241
+ onMouseLeave: setOnHoverFalse,
1242
+ open: drawerOpen,
1243
+ hovered: onHover,
1244
+ isStudioDark,
1245
+ setDrawerOpen,
1246
+ children: includeDrawer && drawerChildren
1247
+ }),
1248
+ /* @__PURE__ */ jsxs("main", {
1249
+ className: "flex flex-col grow overflow-auto",
1250
+ children: [hasAppBar && /* @__PURE__ */ jsx(DrawerHeader, {}), /* @__PURE__ */ jsx("div", {
1251
+ className: cls(defaultBorderMixin, "bg-surface-50 dark:bg-surface-800", "grow overflow-auto m-0", {
1252
+ "mt-1 lg:m-0 lg:mx-2 lg:mb-2 lg:rounded-lg lg:border-t lg:border-x lg:border-solid": padding,
1253
+ "lg:mt-2": !hasAppBar && padding,
1254
+ "lg:mt-4": !hasAppBar && !padding,
1255
+ "border-t": hasAppBar && !padding
1256
+ }),
1257
+ children: /* @__PURE__ */ jsx(ErrorBoundary, { children: otherChildren })
1258
+ })]
1259
+ })
1260
+ ]
1261
+ })
1262
+ });
1263
+ }, deepEqual);
1264
+ var DrawerHeader = () => {
1265
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-14" });
1266
+ };
1267
+ function DrawerWrapper(props) {
1268
+ const layoutWidth = !props.displayed ? 0 : props.open ? 280 : 72;
1269
+ const visualWidth = !props.displayed ? 0 : props.open || props.hovered ? 280 : 72;
1270
+ const isFloating = props.hovered && !props.open;
1271
+ const darkBg = "dark:bg-surface-900";
1272
+ const darkBgFloating = "dark:bg-surface-900";
1273
+ const { t } = useTranslation();
1274
+ const innerDrawer = /* @__PURE__ */ jsx("div", {
1275
+ className: cls("h-full overflow-hidden", defaultBorderMixin, isFloating ? `absolute top-0 left-0 bottom-0 z-50 bg-surface-50 ${darkBgFloating} shadow-lg border-r` : `relative bg-surface-50 ${darkBg}`),
1276
+ style: {
1277
+ width: visualWidth,
1278
+ transition: "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms"
1279
+ },
1280
+ children: /* @__PURE__ */ jsx("div", {
1281
+ className: "flex flex-col h-full",
1282
+ children: props.children
1283
+ })
1284
+ });
1285
+ if (!useLargeLayout()) {
1286
+ if (!props.displayed) return null;
1287
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconButton, {
1288
+ color: "inherit",
1289
+ "aria-label": t("open_menu"),
1290
+ onClick: () => props.setDrawerOpen(true),
1291
+ className: "absolute sm:top-2 sm:left-4 top-1 left-2",
1292
+ children: /* @__PURE__ */ jsx(MenuIcon, {})
1293
+ }), /* @__PURE__ */ jsx(Sheet, {
1294
+ side: "left",
1295
+ transparent: true,
1296
+ open: props.open,
1297
+ onOpenChange: props.setDrawerOpen,
1298
+ title: t("navigation_drawer"),
1299
+ overlayClassName: "bg-white/80 dark:bg-surface-900/80",
1300
+ children: innerDrawer
1301
+ })] });
1302
+ }
1303
+ return /* @__PURE__ */ jsx("div", {
1304
+ className: "z-20 relative flex-shrink-0 overflow-visible",
1305
+ onMouseEnter: props.onMouseEnter,
1306
+ onMouseMove: props.onMouseMove,
1307
+ onMouseLeave: props.onMouseLeave,
1308
+ style: {
1309
+ width: layoutWidth,
1310
+ minWidth: layoutWidth,
1311
+ transition: "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms"
1312
+ },
1313
+ children: innerDrawer
1314
+ });
1315
+ }
1316
+ //#endregion
1317
+ //#region src/components/RebaseAdmin.tsx
1318
+ /**
1319
+ * Declarative component to configure the CMS in Rebase.
1320
+ * Renders nothing — purely registers config into the RebaseRegistry.
1321
+ *
1322
+ * When `collectionEditor` is provided, the built-in visual schema editor
1323
+ * is auto-wired as a native feature (slots, provider, Studio view) without
1324
+ * needing any external plugin.
1325
+ */
1326
+ function RebaseAdmin({ collections, views, homePage, entityViews, entityActions, collectionEditor, navigationGroupMappings, basePath }) {
1327
+ const dispatch = useRebaseRegistryDispatch();
1328
+ useLayoutEffect(() => {
1329
+ dispatch.registerCMS({
1330
+ collections,
1331
+ views,
1332
+ homePage,
1333
+ entityViews,
1334
+ entityActions,
1335
+ collectionEditor,
1336
+ navigationGroupMappings,
1337
+ basePath
1338
+ });
1339
+ return () => dispatch.unregisterCMS();
1340
+ }, [
1341
+ dispatch,
1342
+ collections,
1343
+ views,
1344
+ homePage,
1345
+ entityViews,
1346
+ entityActions,
1347
+ collectionEditor,
1348
+ navigationGroupMappings,
1349
+ basePath
1350
+ ]);
1351
+ return null;
1352
+ }
1353
+ //#endregion
1354
+ //#region src/components/RebaseAuthGate.tsx
1355
+ /**
1356
+ * Auth gate component that handles the authentication flow.
1357
+ *
1358
+ * - Shows a loading spinner while `authController.initialLoading` is true.
1359
+ * - Shows the login view when no user is authenticated.
1360
+ * - Renders `children` when a user is authenticated.
1361
+ *
1362
+ * **Independently usable**: Use this alone when you want auth gating
1363
+ * without the full CMS layout or navigation.
1364
+ *
1365
+ * @example
1366
+ * ```tsx
1367
+ * <RebaseAuthGate>
1368
+ * <MyCustomApp />
1369
+ * </RebaseAuthGate>
1370
+ * ```
1371
+ */
1372
+ function RebaseAuthGate({ children }) {
1373
+ const registry = useRebaseRegistry();
1374
+ const authController = useAuthController();
1375
+ if (authController?.initialLoading) return /* @__PURE__ */ jsx(CircularProgressCenter, { size: "large" });
1376
+ if (!authController?.user) return /* @__PURE__ */ jsx(Fragment, { children: registry.authConfig?.loginView ?? /* @__PURE__ */ jsx(LoginView, { authController }) });
1377
+ return /* @__PURE__ */ jsx(Fragment, { children });
1378
+ }
1379
+ //#endregion
1380
+ //#region src/collection_editor/useLocalCollectionsConfigController.tsx
1381
+ function useLocalCollectionsConfigController(clientOrUrl, baseCollections = [], options) {
1382
+ const parsedCollections = baseCollections;
1383
+ const optionsRef = useRef(options);
1384
+ optionsRef.current = options;
1385
+ const request = async (endpoint, payload) => {
1386
+ try {
1387
+ let token = optionsRef.current?.getAuthToken ? await optionsRef.current.getAuthToken() : null;
1388
+ let baseUrl = typeof clientOrUrl === "string" ? clientOrUrl : "";
1389
+ if (typeof clientOrUrl === "object" && clientOrUrl !== null) {
1390
+ baseUrl = clientOrUrl.baseUrl || baseUrl;
1391
+ if (!token && clientOrUrl.resolveToken) token = await clientOrUrl.resolveToken();
1392
+ }
1393
+ const headers = { "Content-Type": "application/json" };
1394
+ if (token) headers["Authorization"] = `Bearer ${token}`;
1395
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/schema-editor${endpoint}`, {
1396
+ method: "POST",
1397
+ headers,
1398
+ body: JSON.stringify(payload)
1399
+ });
1400
+ if (!response.ok) {
1401
+ const text = await response.text();
1402
+ let err = {};
1403
+ try {
1404
+ err = JSON.parse(text);
1405
+ } catch (e) {}
1406
+ if (Object.keys(err).length === 0) err = { message: text };
1407
+ console.error("dev server error payload:", err);
1408
+ const errObj = err.error;
1409
+ const errMessage = typeof errObj === "object" && errObj !== null ? errObj.message : typeof errObj === "string" ? errObj : err.message;
1410
+ throw new Error(errMessage || "Error communicating with local dev server");
1411
+ }
1412
+ } catch (e) {
1413
+ console.error("fetch request failed", e);
1414
+ throw e;
1415
+ }
1416
+ };
1417
+ return useMemo(() => ({
1418
+ loading: false,
1419
+ readOnly: options?.readOnly ?? false,
1420
+ readOnlyReason: "Local collection editing is only available in development mode.",
1421
+ collections: parsedCollections,
1422
+ getCollection: (id) => {
1423
+ const found = parsedCollections.find((c) => c.id === id || c.slug === id);
1424
+ if (found) return found;
1425
+ throw Error(`Collection ${id} not found in local mode`);
1426
+ },
1427
+ saveCollection: async ({ id, collectionData }) => {
1428
+ await request("/collection/save", {
1429
+ collectionId: id,
1430
+ collectionData
1431
+ });
1432
+ },
1433
+ updateCollection: async ({ id, collectionData }) => {
1434
+ await request("/collection/save", {
1435
+ collectionId: id,
1436
+ collectionData
1437
+ });
1438
+ },
1439
+ deleteCollection: async ({ id }) => {
1440
+ await request("/collection/delete", { collectionId: id });
1441
+ },
1442
+ saveProperty: async ({ path, propertyKey, property, newPropertiesOrder }) => {
1443
+ await request("/property/save", {
1444
+ collectionId: path,
1445
+ propertyKey,
1446
+ propertyConfig: property
1447
+ });
1448
+ if (newPropertiesOrder) await request("/collection/save", {
1449
+ collectionId: path,
1450
+ collectionData: { propertiesOrder: newPropertiesOrder }
1451
+ });
1452
+ },
1453
+ deleteProperty: async ({ path, propertyKey, newPropertiesOrder }) => {
1454
+ await request("/property/delete", {
1455
+ collectionId: path,
1456
+ propertyKey
1457
+ });
1458
+ if (newPropertiesOrder) await request("/collection/save", {
1459
+ collectionId: path,
1460
+ collectionData: { propertiesOrder: newPropertiesOrder }
1461
+ });
1462
+ },
1463
+ updatePropertiesOrder: async ({ collection, fullPath, newPropertiesOrder }) => {
1464
+ await request("/collection/save", {
1465
+ collectionId: collection.id || fullPath.split("/").pop(),
1466
+ collectionData: { propertiesOrder: newPropertiesOrder }
1467
+ });
1468
+ },
1469
+ updateKanbanColumnsOrder: async () => {},
1470
+ navigationEntries: [],
1471
+ saveNavigationEntries: async () => {}
1472
+ }), [
1473
+ clientOrUrl,
1474
+ parsedCollections,
1475
+ options?.readOnly,
1476
+ options?.getAuthToken
1477
+ ]);
1478
+ }
1479
+ //#endregion
1480
+ //#region src/components/RebaseNavigation.tsx
1481
+ var EMPTY_PLUGINS = [];
1482
+ var EMPTY_COLLECTIONS = [];
1483
+ var CollectionsStudioView = lazy(() => import("./RouterCollectionsStudioView-B9AjTCjT.js").then((n) => n.n).then((m) => ({ default: m.RouterCollectionsStudioView })));
1484
+ /**
1485
+ * Navigation layer — builds and provides all CMS navigation controllers:
1486
+ * collection registry, URL controller, navigation state, side entity,
1487
+ * and the self-assembling Studio bridge.
1488
+ *
1489
+ * Also handles the collection editor config controller when enabled.
1490
+ *
1491
+ * **Independently usable**: Use this when you need CMS navigation
1492
+ * (entity tables, side panels) in a custom layout.
1493
+ *
1494
+ * @example
1495
+ * ```tsx
1496
+ * <RebaseNavigation>
1497
+ * <MyCustomLayout>
1498
+ * <CollectionViewBinding ... />
1499
+ * </MyCustomLayout>
1500
+ * </RebaseNavigation>
1501
+ * ```
1502
+ */
1503
+ function RebaseNavigation({ children }) {
1504
+ const registry = useRebaseRegistry();
1505
+ const context = useRebaseContext();
1506
+ const adminModeController = useAdminModeController();
1507
+ const userConfigPersistence = useBuildLocalConfigurationPersistence();
1508
+ const collectionEditorConfig = registry.cmsConfig?.collectionEditor;
1509
+ const collectionEditorEnabled = Boolean(collectionEditorConfig) || Boolean(registry.studioConfig);
1510
+ const collectionEditorOptions = useMemo(() => {
1511
+ if (collectionEditorConfig === true || !collectionEditorConfig) return {};
1512
+ return collectionEditorConfig;
1513
+ }, [collectionEditorConfig]);
1514
+ const cmsCollections = registry.cmsConfig?.collections ?? EMPTY_COLLECTIONS;
1515
+ const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
1516
+ const dataSources = useDataSources();
1517
+ const collectionRegistryController = useBuildCollectionRegistryController({
1518
+ userConfigPersistence,
1519
+ dataSources: dataSources.registry
1520
+ });
1521
+ const defaultData = useData();
1522
+ const getCollectionRef = useRef(collectionRegistryController.getCollection);
1523
+ getCollectionRef.current = collectionRegistryController.getCollection;
1524
+ useContext(CollectionResolverRegistrationContext)(useCallback((slug) => getCollectionRef.current(slug), []));
1525
+ const routedData = useMemo(() => buildRoutedRebaseData({
1526
+ defaultData,
1527
+ sources: dataSources.sources,
1528
+ resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
1529
+ }), [defaultData, dataSources]);
1530
+ const urlController = useBuildUrlController({
1531
+ basePath: useMemo(() => {
1532
+ const raw = registry.cmsConfig?.basePath;
1533
+ if (!raw || raw === "/") return "/";
1534
+ const trimmed = raw.replace(/^\/+/, "").replace(/\/+$/, "");
1535
+ return trimmed ? `/${trimmed}` : "/";
1536
+ }, [registry.cmsConfig?.basePath]),
1537
+ baseCollectionPath: "/c",
1538
+ collectionRegistryController
1539
+ });
1540
+ const rebaseClient = useRebaseClient();
1541
+ const authController = useAuthController();
1542
+ const resolvedCollections = useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]);
1543
+ useEffect(() => {
1544
+ if (process.env.NODE_ENV === "production") return;
1545
+ const { registry, sources } = dataSources;
1546
+ const missing = Object.values(registry).filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key]).map((d) => d.key);
1547
+ if (missing.length > 0) console.warn(`[Rebase] These data source(s) declare a direct/custom transport but have no client-side driver and will fall back to the default data source: ${missing.map((k) => `"${k}"`).join(", ")}. Provide a \`driver\` for them in \`dataSources\` on <Rebase>.`);
1548
+ }, [dataSources]);
1549
+ const internalConfigController = useLocalCollectionsConfigController(rebaseClient, resolvedCollections, collectionEditorEnabled ? {
1550
+ readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
1551
+ getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
1552
+ } : { readOnly: true });
1553
+ const schemaView = useMemo(() => {
1554
+ if (!collectionEditorEnabled) return void 0;
1555
+ const studioTools = registry.studioConfig?.tools ?? [
1556
+ "sql",
1557
+ "js",
1558
+ "rls",
1559
+ "schema",
1560
+ "storage"
1561
+ ];
1562
+ if (!registry.studioConfig || !studioTools.includes("schema")) return void 0;
1563
+ return {
1564
+ slug: "schema",
1565
+ name: "Edit collections",
1566
+ group: "Database",
1567
+ icon: "LayoutList",
1568
+ nestedRoutes: true,
1569
+ view: /* @__PURE__ */ jsx(Suspense, {
1570
+ fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
1571
+ children: /* @__PURE__ */ jsx(CollectionsStudioView, { configController: internalConfigController })
1572
+ })
1573
+ };
1574
+ }, [
1575
+ collectionEditorEnabled,
1576
+ registry.studioConfig,
1577
+ internalConfigController
1578
+ ]);
1579
+ const devViews = useMemo(() => {
1580
+ const base = registry.studioConfig?.devViews ?? [];
1581
+ if (schemaView) return [...base, schemaView];
1582
+ return base;
1583
+ }, [registry.studioConfig?.devViews, schemaView]);
1584
+ const cmsViews = registry.cmsConfig?.views;
1585
+ const mergedViews = useMemo(() => {
1586
+ if (!cmsViews) return devViews;
1587
+ if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
1588
+ if (typeof cmsViews === "function") return async (params) => {
1589
+ return [...await cmsViews(params) ?? [], ...devViews];
1590
+ };
1591
+ return [...cmsViews, ...devViews];
1592
+ }, [cmsViews, devViews]);
1593
+ const parentCustomizationController = useCustomizationController();
1594
+ const navigationStateController = useBuildNavigationStateController({
1595
+ plugins: parentCustomizationController.plugins ?? EMPTY_PLUGINS,
1596
+ collections: collectionsBuilder,
1597
+ views: mergedViews,
1598
+ navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
1599
+ authController: context.authController,
1600
+ data: routedData,
1601
+ collectionRegistryController,
1602
+ urlController,
1603
+ adminMode: adminModeController?.mode
1604
+ });
1605
+ const enrichedCustomizationController = useMemo(() => {
1606
+ const cmsEntityViews = registry.cmsConfig?.entityViews ?? [];
1607
+ const cmsEntityActions = registry.cmsConfig?.entityActions ?? [];
1608
+ if (cmsEntityViews.length === 0 && cmsEntityActions.length === 0) return parentCustomizationController;
1609
+ return {
1610
+ ...parentCustomizationController,
1611
+ entityViews: [...parentCustomizationController.entityViews ?? [], ...cmsEntityViews.filter((v) => !(parentCustomizationController.entityViews ?? []).some((ev) => ev.key === v.key))],
1612
+ entityActions: [...parentCustomizationController.entityActions ?? [], ...cmsEntityActions.filter((a) => !(parentCustomizationController.entityActions ?? []).some((ea) => ea.key === a.key))]
1613
+ };
1614
+ }, [
1615
+ parentCustomizationController,
1616
+ registry.cmsConfig?.entityViews,
1617
+ registry.cmsConfig?.entityActions
1618
+ ]);
1619
+ const navigationContent = /* @__PURE__ */ jsx(RebaseDataContext.Provider, {
1620
+ value: routedData,
1621
+ children: /* @__PURE__ */ jsx(CustomizationControllerContext.Provider, {
1622
+ value: enrichedCustomizationController,
1623
+ children: /* @__PURE__ */ jsx(StudioBridgeRegistryProvider, { children: /* @__PURE__ */ jsx(CollectionRegistryContext.Provider, {
1624
+ value: collectionRegistryController,
1625
+ children: /* @__PURE__ */ jsx(UrlContext.Provider, {
1626
+ value: urlController,
1627
+ children: /* @__PURE__ */ jsx(NavigationStateContext.Provider, {
1628
+ value: navigationStateController,
1629
+ children: /* @__PURE__ */ jsxs(SidePanelProvider, { children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar, {
1630
+ collectionRegistryController,
1631
+ urlController,
1632
+ navigationStateController
1633
+ }), children] })
1634
+ })
1635
+ })
1636
+ }) })
1637
+ })
1638
+ });
1639
+ if (collectionEditorEnabled) return /* @__PURE__ */ jsx(ConfigControllerProvider, {
1640
+ collectionConfigController: internalConfigController,
1641
+ pathSuggestions: collectionEditorOptions?.pathSuggestions,
1642
+ children: navigationContent
1643
+ });
1644
+ return navigationContent;
1645
+ }
1646
+ /**
1647
+ * Internal component that auto-registers CMS controllers into the
1648
+ * self-assembling Studio bridge. Must be rendered inside both the
1649
+ * navigation contexts and the StudioBridgeRegistryProvider.
1650
+ */
1651
+ function BridgeAutoRegistrar({ collectionRegistryController, urlController, navigationStateController }) {
1652
+ useBridgeRegistration("collectionRegistry", collectionRegistryController);
1653
+ useBridgeRegistration("urlController", urlController);
1654
+ useBridgeRegistration("navigationState", navigationStateController);
1655
+ return null;
1656
+ }
1657
+ //#endregion
1658
+ //#region src/components/RebaseLayout.tsx
1659
+ /**
1660
+ * Layout layer — provides the Scaffold, Drawer, and SideDialogs.
1661
+ *
1662
+ * Wraps the route outlet with the standard Rebase admin layout.
1663
+ * Override the drawer via props; pass `appBar` to add a top bar (there is
1664
+ * none by default — the drawer owns navigation and user actions).
1665
+ *
1666
+ * **Independently usable**: Use this when you want the Rebase layout
1667
+ * without its default route definitions.
1668
+ *
1669
+ * @example
1670
+ * ```tsx
1671
+ * <RebaseLayout title="My Admin" appBar={<AppBar/>}>
1672
+ * <Route path="/" element={<MyHomePage />} />
1673
+ * <Route path="/custom" element={<CustomView />} />
1674
+ * </RebaseLayout>
1675
+ * ```
1676
+ */
1677
+ function RebaseLayout(props) {
1678
+ const { title = "Rebase", appBar, drawer, autoOpenDrawer = false, devViews = [] } = props;
1679
+ const adminModeController = useAdminModeController();
1680
+ const ResolvedDrawer = useComponentOverride("Shell.Drawer", Drawer);
1681
+ const ActiveDrawer = drawer ?? /* @__PURE__ */ jsx(ResolvedDrawer, {
1682
+ title,
1683
+ logoDestination: adminModeController.mode === "studio" ? "/s" : "/"
1684
+ });
1685
+ return /* @__PURE__ */ jsxs(Scaffold, {
1686
+ autoOpenDrawer,
1687
+ children: [
1688
+ /* @__PURE__ */ jsx(AdminModeSyncer, { devViews }),
1689
+ appBar,
1690
+ ActiveDrawer,
1691
+ /* @__PURE__ */ jsx(Outlet, {}),
1692
+ /* @__PURE__ */ jsx(SideDialogs, {})
1693
+ ]
1694
+ });
1695
+ }
1696
+ //#endregion
1697
+ //#region src/components/HomePage/FavouritesView.tsx
1698
+ function NavigationChip({ entry }) {
1699
+ const navigate = useNavigate();
1700
+ const userConfigurationPersistence = useUserConfigurationPersistence();
1701
+ if (!userConfigurationPersistence) return null;
1702
+ const favourite = userConfigurationPersistence.favouritePaths.includes(entry.slug);
1703
+ const onIconClick = (e) => {
1704
+ e.preventDefault();
1705
+ e.stopPropagation();
1706
+ if (favourite) userConfigurationPersistence.setFavouritePaths(userConfigurationPersistence.favouritePaths.filter((p) => p !== entry.slug));
1707
+ else userConfigurationPersistence.setFavouritePaths([...userConfigurationPersistence.favouritePaths, entry.slug]);
1708
+ };
1709
+ return /* @__PURE__ */ jsx(Chip, {
1710
+ onClick: () => navigate(entry.url),
1711
+ icon: /* @__PURE__ */ jsx(StarIcon, {
1712
+ onClick: onIconClick,
1713
+ size: "small",
1714
+ className: favourite ? "text-secondary" : "text-surface-400 dark:text-surface-500"
1715
+ }),
1716
+ children: entry.name
1717
+ }, entry.slug);
1718
+ }
1719
+ function FavouritesView({ hidden }) {
1720
+ const navigationStateController = useNavigationStateController();
1721
+ const userConfigurationPersistence = useUserConfigurationPersistence();
1722
+ if (!userConfigurationPersistence) return null;
1723
+ const favouriteCollections = (userConfigurationPersistence?.favouritePaths ?? []).map((path) => navigationStateController.topLevelNavigation?.navigationEntries.find((entry) => entry.slug === path)).filter(Boolean);
1724
+ return /* @__PURE__ */ jsx(Collapse, {
1725
+ in: favouriteCollections.length > 0,
1726
+ children: /* @__PURE__ */ jsx("div", {
1727
+ className: "flex flex-row flex-wrap gap-2 pb-2 min-h-[32px]",
1728
+ children: favouriteCollections.map((entry) => /* @__PURE__ */ jsx(NavigationChip, { entry }, entry.slug))
1729
+ })
1730
+ });
1731
+ }
1732
+ //#endregion
1733
+ //#region src/components/HomePage/NavigationGroup.tsx
1734
+ function NavigationGroup({ children, group, minimised, isPreview, isPotentialCardDropTarget, onEditGroup, dndDisabled, collapsed, onToggleCollapsed }) {
1735
+ const { t } = useTranslation();
1736
+ const [isHovered, setIsHovered] = useState(false);
1737
+ const currentGroupName = group ?? t("views_group");
1738
+ const showCaret = !isPreview && !!onToggleCollapsed;
1739
+ const TitleContent = /* @__PURE__ */ jsxs("div", {
1740
+ className: cls("flex items-center", isPreview ? "px-1 py-0.5" : ""),
1741
+ children: [/* @__PURE__ */ jsx(Typography, {
1742
+ variant: isPreview ? "body2" : "caption",
1743
+ component: "h2",
1744
+ color: "secondary",
1745
+ className: cls("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
1746
+ children: currentGroupName
1747
+ }), !isPreview && onEditGroup && !dndDisabled && /* @__PURE__ */ jsx(IconButton, {
1748
+ size: "smallest",
1749
+ onClick: (e) => {
1750
+ e.stopPropagation();
1751
+ onEditGroup(currentGroupName);
1752
+ },
1753
+ className: cls("ml-2 ", isHovered ? "opacity-100" : "opacity-0", "transition-opacity duration-100"),
1754
+ children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
1755
+ })]
1756
+ });
1757
+ return /* @__PURE__ */ jsxs("div", {
1758
+ className: cls(!isPotentialCardDropTarget ? "my-10" : "my-6", "transition-all duration-200 ease-in-out"),
1759
+ children: [
1760
+ isPreview && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
1761
+ className: cls("flex items-center justify-between w-full", "p-4 py-2"),
1762
+ onMouseEnter: () => setIsHovered(true),
1763
+ onMouseLeave: () => setIsHovered(false),
1764
+ children: TitleContent
1765
+ }), children] }),
1766
+ !isPreview && showCaret && /* @__PURE__ */ jsx(ExpandablePanel, {
1767
+ invisible: true,
1768
+ expanded: !collapsed,
1769
+ onExpandedChange: (open) => {
1770
+ if (open !== !collapsed) onToggleCollapsed?.();
1771
+ },
1772
+ className: cls("mt-6"),
1773
+ titleClassName: cls("min-h-0 p-0 border-none", "rounded flex items-center justify-between w-full", "hover:bg-transparent", "cursor-pointer select-none", collapsed && "bg-surface-100 dark:bg-surface-900/50"),
1774
+ innerClassName: cls("mt-4", !minimised ? "pt-0" : ""),
1775
+ title: /* @__PURE__ */ jsx("div", {
1776
+ onMouseEnter: () => setIsHovered(true),
1777
+ onMouseLeave: () => setIsHovered(false),
1778
+ className: "flex items-center",
1779
+ children: TitleContent
1780
+ }),
1781
+ children: minimised ? /* @__PURE__ */ jsx("div", {
1782
+ className: cls("mt-4 p-8 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg"),
1783
+ style: { minHeight: "50px" }
1784
+ }) : /* @__PURE__ */ jsx("div", {
1785
+ className: cls("mt-4", !minimised ? "pt-0" : ""),
1786
+ children
1787
+ })
1788
+ }),
1789
+ !isPreview && !showCaret && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
1790
+ className: cls("flex items-center justify-between w-full", "mt-6"),
1791
+ onMouseEnter: () => setIsHovered(true),
1792
+ onMouseLeave: () => setIsHovered(false),
1793
+ children: TitleContent
1794
+ }), !collapsed && (minimised ? /* @__PURE__ */ jsx("div", {
1795
+ className: cls("mt-4 p-8 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg"),
1796
+ style: { minHeight: "50px" }
1797
+ }) : /* @__PURE__ */ jsx("div", {
1798
+ className: cls("mt-4", !minimised ? "pt-0" : ""),
1799
+ children
1800
+ }))] })
1801
+ ]
1802
+ });
1803
+ }
1804
+ //#endregion
1805
+ //#region src/components/HomePage/NavigationCard.tsx
1806
+ var NavigationCard = React.memo(function NavigationCard({ name, description, icon, actions, additionalContent, onClick, shrink }) {
1807
+ return /* @__PURE__ */ jsx(Card, {
1808
+ className: cls("group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out", "hover:bg-primary/5 dark:hover:bg-primary/5", shrink && "w-full max-w-full min-h-0 scale-75"),
1809
+ onClick: () => {
1810
+ onClick?.();
1811
+ },
1812
+ children: /* @__PURE__ */ jsxs("div", {
1813
+ className: "flex flex-col h-full",
1814
+ children: [
1815
+ /* @__PURE__ */ jsxs("div", {
1816
+ className: "flex items-center w-full justify-between mb-1",
1817
+ children: [/* @__PURE__ */ jsxs("div", {
1818
+ className: "flex items-center gap-3",
1819
+ children: [/* @__PURE__ */ jsx("div", {
1820
+ className: "flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary",
1821
+ children: icon
1822
+ }), /* @__PURE__ */ jsx(Typography, {
1823
+ variant: "subtitle1",
1824
+ component: "h2",
1825
+ children: name
1826
+ })]
1827
+ }), /* @__PURE__ */ jsx("div", {
1828
+ className: "flex items-center gap-0.5",
1829
+ onClick: (event) => {
1830
+ event.preventDefault();
1831
+ event.stopPropagation();
1832
+ },
1833
+ children: actions
1834
+ })]
1835
+ }),
1836
+ /* @__PURE__ */ jsx("div", {
1837
+ className: "pl-8",
1838
+ children: description && /* @__PURE__ */ jsx(Typography, {
1839
+ variant: "caption",
1840
+ color: "secondary",
1841
+ component: "div",
1842
+ children: /* @__PURE__ */ jsx(Markdown, {
1843
+ source: description,
1844
+ size: "small"
1845
+ })
1846
+ })
1847
+ }),
1848
+ additionalContent && /* @__PURE__ */ jsx("div", {
1849
+ className: "pl-8 pointer-events-none",
1850
+ children: additionalContent
1851
+ }),
1852
+ /* @__PURE__ */ jsx("div", { className: "grow" })
1853
+ ]
1854
+ })
1855
+ });
1856
+ });
1857
+ //#endregion
1858
+ //#region src/components/HomePage/NavigationCardBinding.tsx
1859
+ /**
1860
+ * This is the component used in the home page to render a card for each
1861
+ * collection or view.
1862
+ * @group Components
1863
+ * @param view
1864
+ * @param path
1865
+ * @param collection
1866
+ * @param url
1867
+ * @param name
1868
+ * @param description
1869
+ * @param onClick
1870
+
1871
+ */
1872
+ function NavigationCardBinding({ slug, collection, view, url, name, description, onClick, type, shrink }) {
1873
+ const userConfigurationPersistence = useUserConfigurationPersistence();
1874
+ const collectionIcon = /* @__PURE__ */ jsx(IconForView, {
1875
+ collectionOrView: collection ?? view,
1876
+ size: "small"
1877
+ });
1878
+ const navigate = useNavigate();
1879
+ const context = useRebaseContext();
1880
+ const favourite = (userConfigurationPersistence?.favouritePaths ?? []).includes(slug);
1881
+ const actionsArray = userConfigurationPersistence ? [/* @__PURE__ */ jsx(IconButton, {
1882
+ size: "small",
1883
+ onClick: (e) => {
1884
+ e.preventDefault();
1885
+ e.stopPropagation();
1886
+ if (favourite) userConfigurationPersistence.setFavouritePaths(userConfigurationPersistence.favouritePaths.filter((p) => p !== slug));
1887
+ else userConfigurationPersistence.setFavouritePaths([...userConfigurationPersistence.favouritePaths, slug]);
1888
+ },
1889
+ children: /* @__PURE__ */ jsx(StarIcon, { className: favourite ? "text-secondary" : "text-surface-400 dark:text-surface-500" })
1890
+ }, "favourite")] : [];
1891
+ const pluginCardActions = useSlot("home.collection.actions", {
1892
+ slug,
1893
+ collection,
1894
+ context
1895
+ });
1896
+ const pluginCardInsights = useSlot("home.card.insight", {
1897
+ slug,
1898
+ collection,
1899
+ context
1900
+ });
1901
+ return /* @__PURE__ */ jsx(NavigationCard, {
1902
+ icon: collectionIcon,
1903
+ name,
1904
+ description,
1905
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [actionsArray, pluginCardActions] }),
1906
+ additionalContent: pluginCardInsights.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: pluginCardInsights }) : void 0,
1907
+ onClick: () => {
1908
+ onClick?.();
1909
+ navigate(url);
1910
+ if (userConfigurationPersistence) userConfigurationPersistence.setRecentlyVisitedPaths([slug, ...(userConfigurationPersistence.recentlyVisitedPaths ?? []).filter((p) => p !== slug)]);
1911
+ },
1912
+ shrink
1913
+ });
1914
+ }
1915
+ //#endregion
1916
+ //#region src/components/HomePage/HomePageDnD.tsx
1917
+ var animateLayoutChanges = (args) => defaultAnimateLayoutChanges({
1918
+ ...args,
1919
+ wasDragging: true
1920
+ });
1921
+ var dropAnimationConfig = {};
1922
+ var cloneSerializableNavigationEntry = (entry) => {
1923
+ return {
1924
+ id: entry.id,
1925
+ slug: entry.slug,
1926
+ url: entry.url,
1927
+ name: entry.name,
1928
+ type: entry.type,
1929
+ collection: entry.collection ? { ...entry.collection } : void 0,
1930
+ view: entry.view ? { ...entry.view } : void 0,
1931
+ ...entry.group && { group: entry.group },
1932
+ ...entry.description && { description: entry.description }
1933
+ };
1934
+ };
1935
+ var cloneItemsForDnd = (items) => items.map((g) => ({
1936
+ name: g.name,
1937
+ entries: g.entries.map(cloneSerializableNavigationEntry)
1938
+ }));
1939
+ function SortableNavigationCard({ entry, onClick }) {
1940
+ const { setNodeRef, listeners, attributes, transform, transition, isDragging } = useSortable({
1941
+ id: entry.url,
1942
+ animateLayoutChanges
1943
+ });
1944
+ return /* @__PURE__ */ jsx("div", {
1945
+ ref: setNodeRef,
1946
+ style: {
1947
+ transform: transform ? CSS.Transform.toString(transform) : void 0,
1948
+ transition,
1949
+ opacity: isDragging ? 0 : 1
1950
+ },
1951
+ ...attributes,
1952
+ ...listeners,
1953
+ children: /* @__PURE__ */ jsx(NavigationCardBinding, {
1954
+ ...entry,
1955
+ onClick
1956
+ })
1957
+ });
1958
+ }
1959
+ function NavigationGroupDroppable({ id, itemIds, children, isPotentialCardDropTarget = false }) {
1960
+ const { setNodeRef } = useDroppable({ id });
1961
+ return /* @__PURE__ */ jsx("div", {
1962
+ ref: setNodeRef,
1963
+ className: cls(isPotentialCardDropTarget ? "p-2 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg" : void 0, "transition-all duration-200 ease-in-out"),
1964
+ children: /* @__PURE__ */ jsx(SortableContext, {
1965
+ items: itemIds,
1966
+ strategy: rectSortingStrategy,
1967
+ children
1968
+ })
1969
+ });
1970
+ }
1971
+ function SortableNavigationGroup({ groupName, children, disabled }) {
1972
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
1973
+ id: groupName,
1974
+ animateLayoutChanges,
1975
+ disabled
1976
+ });
1977
+ return /* @__PURE__ */ jsx("div", {
1978
+ ref: setNodeRef,
1979
+ style: {
1980
+ transform: transform ? CSS.Transform.toString(transform) : void 0,
1981
+ transition,
1982
+ opacity: isDragging ? 0 : 1
1983
+ },
1984
+ ...attributes,
1985
+ ...listeners,
1986
+ children
1987
+ });
1988
+ }
1989
+ function useHomePageDnd({ items, setItems, disabled, onCardMovedBetweenGroups, onGroupMoved, onNewGroupDrop, onPersist }) {
1990
+ const dndItems = items;
1991
+ const setDndItems = setItems;
1992
+ const [activeId, setActiveId] = useState(null);
1993
+ const [activeIsGroup, setActiveIsGroup] = useState(false);
1994
+ const [currentDraggingGroupId, setCurrentDraggingGroupId] = useState(null);
1995
+ const [dndKitActiveNode, setDndKitActiveNode] = useState(null);
1996
+ const [isDraggingCardOnly, setIsDraggingCardOnly] = useState(false);
1997
+ const [dialogOpenForGroup, setDialogOpenForGroup] = useState(null);
1998
+ const [isHoveringNewGroupDropZone, setIsHoveringNewGroupDropZone] = useState(false);
1999
+ const [pendingNewGroupName, setPendingNewGroupName] = useState(null);
2000
+ const [stateBeforeNewGroup, setStateBeforeNewGroup] = useState(null);
2001
+ const preDragItemsRef = useRef(null);
2002
+ const interimItemsRef = useRef(null);
2003
+ useEffect(() => {
2004
+ interimItemsRef.current = dndItems;
2005
+ }, [dndItems]);
2006
+ const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 10 } }), useSensor(TouchSensor, { activationConstraint: {
2007
+ delay: 150,
2008
+ tolerance: 5
2009
+ } }), useSensor(KeyboardSensor));
2010
+ const dndContainers = dndItems.map((g) => g.name);
2011
+ const findDndContainer = useCallback((id) => {
2012
+ if (!id) return void 0;
2013
+ const group = dndItems.find((g) => g.name === id);
2014
+ if (group) return group.name;
2015
+ for (const g of dndItems) if (g.entries.some((e) => e.url === id)) return g.name;
2016
+ }, [dndItems]);
2017
+ const lastOverId = useRef(null);
2018
+ const recentlyMovedToNewContainer = useRef(false);
2019
+ const collisionDetection = useCallback((args) => {
2020
+ if (disabled || !activeId) return [];
2021
+ if (activeIsGroup) {
2022
+ const groups = args.droppableContainers.filter((c) => dndItems.some((g) => g.name === c.id));
2023
+ if (!groups.length) return [];
2024
+ return closestCenter({
2025
+ ...args,
2026
+ droppableContainers: groups
2027
+ });
2028
+ }
2029
+ const pointer = pointerWithin(args);
2030
+ if (pointer.length) {
2031
+ const zone = pointer.find((c) => c.id === "new-group-drop-zone");
2032
+ if (zone) return [zone];
2033
+ const container = pointer.find((c) => dndItems.some((g) => g.name === c.id));
2034
+ if (container) {
2035
+ const itemsIn = dndItems.find((g) => g.name === container.id)?.entries;
2036
+ if (itemsIn?.length) {
2037
+ const closest = closestCorners({
2038
+ ...args,
2039
+ droppableContainers: args.droppableContainers.filter((c) => itemsIn.some((e) => e.url === c.id))
2040
+ });
2041
+ if (closest.length) return closest;
2042
+ }
2043
+ return [container];
2044
+ }
2045
+ const first = getFirstCollision(pointer, "id");
2046
+ if (first) return [{ id: first }];
2047
+ }
2048
+ const rects = rectIntersection(args);
2049
+ const zoneRect = rects.find((c) => c.id === "new-group-drop-zone");
2050
+ if (zoneRect) return [zoneRect];
2051
+ let overId = getFirstCollision(rects, "id");
2052
+ if (overId != null) {
2053
+ if (dndItems.some((g) => g.name === overId)) {
2054
+ const itemsIn = dndItems.find((g) => g.name === overId)?.entries;
2055
+ if (itemsIn?.length) {
2056
+ const closestItem = closestCorners({
2057
+ ...args,
2058
+ droppableContainers: args.droppableContainers.filter((c) => itemsIn.some((e) => e.url === c.id))
2059
+ })[0]?.id;
2060
+ if (closestItem) overId = closestItem;
2061
+ }
2062
+ }
2063
+ lastOverId.current = overId;
2064
+ return [{ id: overId }];
2065
+ }
2066
+ if (recentlyMovedToNewContainer.current && lastOverId.current && !activeIsGroup) return [{ id: lastOverId.current }];
2067
+ return [];
2068
+ }, [
2069
+ activeId,
2070
+ dndItems,
2071
+ disabled,
2072
+ activeIsGroup
2073
+ ]);
2074
+ const handleDragStart = ({ active }) => {
2075
+ setDndKitActiveNode(active);
2076
+ if (disabled) return;
2077
+ preDragItemsRef.current = cloneItemsForDnd(dndItems);
2078
+ const isGroup = dndItems.some((g) => g.name === active.id);
2079
+ if (!active.data.current) active.data.current = {};
2080
+ active.data.current.type = isGroup ? "group" : "item";
2081
+ setActiveId(active.id);
2082
+ setActiveIsGroup(isGroup);
2083
+ setIsDraggingCardOnly(!isGroup);
2084
+ if (isGroup) setCurrentDraggingGroupId(active.id);
2085
+ recentlyMovedToNewContainer.current = false;
2086
+ };
2087
+ const handleDragOver = ({ active, over }) => {
2088
+ if (disabled || !over) return;
2089
+ const activeIdNow = active.id;
2090
+ const overIdNow = over.id;
2091
+ if (activeIdNow === overIdNow) return;
2092
+ if (activeIsGroup) return;
2093
+ const activeCont = findDndContainer(activeIdNow);
2094
+ const overCont = findDndContainer(overIdNow);
2095
+ if (!activeCont) return;
2096
+ if (overCont && activeCont !== overCont) {
2097
+ recentlyMovedToNewContainer.current = true;
2098
+ lastOverId.current = overIdNow;
2099
+ setDndItems((current) => {
2100
+ const newState = cloneItemsForDnd(current);
2101
+ const srcIdx = newState.findIndex((g) => g.name === activeCont);
2102
+ const tgtIdx = newState.findIndex((g) => g.name === overCont);
2103
+ if (srcIdx === -1 || tgtIdx === -1) return current;
2104
+ const src = newState[srcIdx];
2105
+ const tgt = newState[tgtIdx];
2106
+ const idxInSrc = src.entries.findIndex((e) => e.url === activeIdNow);
2107
+ if (idxInSrc === -1) return current;
2108
+ const [moved] = src.entries.splice(idxInSrc, 1);
2109
+ if (overIdNow === overCont) tgt.entries.push(moved);
2110
+ else {
2111
+ const overIdx = tgt.entries.findIndex((e) => e.url === overIdNow);
2112
+ if (overIdx !== -1) tgt.entries.splice(overIdx, 0, moved);
2113
+ else tgt.entries.push(moved);
2114
+ }
2115
+ return newState;
2116
+ });
2117
+ } else if (activeCont === overCont) recentlyMovedToNewContainer.current = false;
2118
+ };
2119
+ const handleDragEnd = ({ active, over }) => {
2120
+ if (disabled || !over) {
2121
+ resetDragState();
2122
+ return;
2123
+ }
2124
+ const activeIdNow = active.id;
2125
+ const overIdNow = over.id;
2126
+ if (activeIsGroup) {
2127
+ if (activeIdNow !== overIdNow && dndItems.some((g) => g.name === overIdNow)) {
2128
+ const from = dndItems.findIndex((g) => g.name === activeIdNow);
2129
+ const to = dndItems.findIndex((g) => g.name === overIdNow);
2130
+ if (from !== -1 && to !== -1) {
2131
+ const newState = arrayMove(dndItems, from, to);
2132
+ setDndItems(newState);
2133
+ onPersist?.(newState);
2134
+ onGroupMoved?.(activeIdNow, from, to);
2135
+ }
2136
+ }
2137
+ } else {
2138
+ const findContainerInState = (id, state) => {
2139
+ const group = state.find((g) => g.name === id);
2140
+ if (group) return group.name;
2141
+ for (const g of state) if (g.entries.some((e) => e.url === id)) return g.name;
2142
+ };
2143
+ const sourceState = preDragItemsRef.current || dndItems;
2144
+ const activeCont = findContainerInState(activeIdNow, sourceState);
2145
+ findDndContainer(overIdNow);
2146
+ if (overIdNow === "new-group-drop-zone") {
2147
+ if (activeCont) {
2148
+ setStateBeforeNewGroup(cloneItemsForDnd(dndItems));
2149
+ const newState = cloneItemsForDnd(dndItems);
2150
+ const srcIdx = newState.findIndex((g) => g.name === activeCont);
2151
+ if (srcIdx !== -1) {
2152
+ const src = newState[srcIdx];
2153
+ const idxInSrc = src.entries.findIndex((e) => e.url === activeIdNow);
2154
+ if (idxInSrc !== -1) {
2155
+ const [dragged] = src.entries.splice(idxInSrc, 1);
2156
+ if (src.entries.length === 0) newState.splice(srcIdx, 1);
2157
+ let tentative = "New Group";
2158
+ let counter = 1;
2159
+ while (newState.some((g) => g.name === tentative)) tentative = `New Group ${counter++}`;
2160
+ newState.push({
2161
+ name: tentative,
2162
+ entries: [dragged]
2163
+ });
2164
+ setDndItems(newState);
2165
+ setPendingNewGroupName(tentative);
2166
+ setDialogOpenForGroup(tentative);
2167
+ onNewGroupDrop?.();
2168
+ }
2169
+ }
2170
+ }
2171
+ } else {
2172
+ const overCont = findDndContainer(overIdNow);
2173
+ if (activeCont === overCont) {
2174
+ const grpIdx = dndItems.findIndex((g) => g.name === activeCont);
2175
+ if (grpIdx !== -1) {
2176
+ const group = dndItems[grpIdx];
2177
+ const oldIdx = group.entries.findIndex((e) => e.url === activeIdNow);
2178
+ let newIdx = group.entries.findIndex((e) => e.url === overIdNow);
2179
+ if (newIdx === -1 && overIdNow === activeCont) newIdx = group.entries.length - 1;
2180
+ if (oldIdx !== -1 && newIdx !== -1 && oldIdx !== newIdx) {
2181
+ const reordered = arrayMove(group.entries, oldIdx, newIdx);
2182
+ const newState = [...dndItems];
2183
+ newState[grpIdx] = {
2184
+ ...group,
2185
+ entries: reordered
2186
+ };
2187
+ setDndItems(newState);
2188
+ onPersist?.(newState);
2189
+ }
2190
+ }
2191
+ } else if (overCont && activeCont !== overCont) {
2192
+ const finalState = cloneItemsForDnd(sourceState);
2193
+ const finalOverId = lastOverId.current || overIdNow;
2194
+ const cleanOverCont = findContainerInState(finalOverId, sourceState) || overCont;
2195
+ const srcIdx = finalState.findIndex((g) => g.name === activeCont);
2196
+ const tgtIdx = finalState.findIndex((g) => g.name === cleanOverCont);
2197
+ if (srcIdx !== -1 && tgtIdx !== -1) {
2198
+ const src = finalState[srcIdx];
2199
+ const tgt = finalState[tgtIdx];
2200
+ const idxInSrc = src.entries.findIndex((e) => e.url === activeIdNow);
2201
+ if (idxInSrc !== -1) {
2202
+ const [moved] = src.entries.splice(idxInSrc, 1);
2203
+ if (finalOverId === cleanOverCont) tgt.entries.push(moved);
2204
+ else {
2205
+ const overIdx = tgt.entries.findIndex((e) => e.url === finalOverId);
2206
+ if (overIdx !== -1) tgt.entries.splice(overIdx, 0, moved);
2207
+ else tgt.entries.push(moved);
2208
+ }
2209
+ if (src.entries.length === 0) finalState.splice(srcIdx, 1);
2210
+ setDndItems(finalState);
2211
+ onPersist?.(finalState);
2212
+ onCardMovedBetweenGroups?.(moved);
2213
+ }
2214
+ }
2215
+ } else if (recentlyMovedToNewContainer.current) console.error("Move between containers detected but conditions not met", {
2216
+ activeCont,
2217
+ overCont,
2218
+ activeIdNow,
2219
+ overIdNow
2220
+ });
2221
+ }
2222
+ }
2223
+ resetDragState();
2224
+ };
2225
+ const resetDragState = () => {
2226
+ setDndKitActiveNode(null);
2227
+ setActiveId(null);
2228
+ setActiveIsGroup(false);
2229
+ setCurrentDraggingGroupId(null);
2230
+ setIsDraggingCardOnly(false);
2231
+ recentlyMovedToNewContainer.current = false;
2232
+ };
2233
+ const handleDragCancel = () => resetDragState();
2234
+ const handleRenameGroup = (oldName, newName) => {
2235
+ setDndItems((current) => {
2236
+ const idx = current.findIndex((g) => g.name === oldName);
2237
+ if (idx === -1) return current;
2238
+ if (current.some((g) => g.name === newName && g.name !== oldName)) return current;
2239
+ const updated = [...current];
2240
+ updated[idx] = {
2241
+ ...updated[idx],
2242
+ name: newName
2243
+ };
2244
+ onPersist?.(updated);
2245
+ return updated;
2246
+ });
2247
+ setPendingNewGroupName(null);
2248
+ setStateBeforeNewGroup(null);
2249
+ setDialogOpenForGroup(null);
2250
+ };
2251
+ const handleDialogClose = () => {
2252
+ if (pendingNewGroupName && dialogOpenForGroup === pendingNewGroupName && stateBeforeNewGroup) setDndItems(stateBeforeNewGroup);
2253
+ setPendingNewGroupName(null);
2254
+ setStateBeforeNewGroup(null);
2255
+ setDialogOpenForGroup(null);
2256
+ };
2257
+ return {
2258
+ sensors,
2259
+ collisionDetection,
2260
+ onDragStart: handleDragStart,
2261
+ onDragOver: handleDragOver,
2262
+ onDragEnd: handleDragEnd,
2263
+ onDragCancel: handleDragCancel,
2264
+ dropAnimation: dropAnimationConfig,
2265
+ activeItemForOverlay: disabled || !activeId || activeIsGroup ? null : dndItems.flatMap((g) => g.entries).find((e) => e.url === activeId) || null,
2266
+ activeGroupData: disabled || !activeId || !activeIsGroup ? null : dndItems.find((g) => g.name === activeId) || null,
2267
+ draggingGroupId: currentDraggingGroupId,
2268
+ containers: dndContainers,
2269
+ dndKitActiveNode,
2270
+ isDraggingCardOnly,
2271
+ dialogOpenForGroup,
2272
+ setDialogOpenForGroup,
2273
+ handleRenameGroup,
2274
+ handleDialogClose,
2275
+ isHoveringNewGroupDropZone,
2276
+ setIsHoveringNewGroupDropZone
2277
+ };
2278
+ }
2279
+ function NewGroupDropZone({ disabled, setIsHovering }) {
2280
+ const { setNodeRef, isOver } = useDroppable({
2281
+ id: "new-group-drop-zone",
2282
+ disabled
2283
+ });
2284
+ const [isVisible, setIsVisible] = useState(false);
2285
+ useDndMonitor({
2286
+ onDragStart({ active }) {
2287
+ if (disabled) return;
2288
+ const tp = active.data.current?.type;
2289
+ setIsVisible(tp === "item");
2290
+ },
2291
+ onDragEnd() {
2292
+ setIsVisible(false);
2293
+ },
2294
+ onDragCancel() {
2295
+ setIsVisible(false);
2296
+ }
2297
+ });
2298
+ useEffect(() => {
2299
+ setIsHovering(isOver && isVisible);
2300
+ }, [
2301
+ isOver,
2302
+ isVisible,
2303
+ setIsHovering
2304
+ ]);
2305
+ if (!isVisible || disabled) return null;
2306
+ return /* @__PURE__ */ jsx("div", {
2307
+ ref: setNodeRef,
2308
+ className: cls("fixed right-8 top-1/2 -translate-y-1/2 w-[200px] h-[120px] border border-dashed rounded-lg flex items-center justify-center transition-all", isOver ? "bg-surface-accent-100 dark:bg-surface-accent-800 border-surface-300 dark:border-surface-600" : "bg-surface-50 dark:bg-surface-900 border-surface-200 dark:border-surface-700"),
2309
+ children: /* @__PURE__ */ jsx("div", {
2310
+ className: "text-center p-4",
2311
+ children: /* @__PURE__ */ jsx("span", {
2312
+ className: "block font-medium text-sm",
2313
+ children: "Drop here to create a new group"
2314
+ })
2315
+ })
2316
+ });
2317
+ }
2318
+ //#endregion
2319
+ //#region src/components/HomePage/RenameGroupDialog.tsx
2320
+ function RenameGroupDialog({ open, initialName, existingGroupNames, onClose, onRename }) {
2321
+ const { t } = useTranslation();
2322
+ const [name, setName] = useState(initialName);
2323
+ const [error, setError] = useState(null);
2324
+ const inputRef = useRef(null);
2325
+ useEffect(() => {
2326
+ if (open) {
2327
+ setName(initialName);
2328
+ setError(null);
2329
+ setTimeout(() => {
2330
+ if (inputRef.current) {
2331
+ inputRef.current.focus();
2332
+ inputRef.current.select();
2333
+ }
2334
+ }, 100);
2335
+ }
2336
+ }, [initialName, open]);
2337
+ const handleNameChange = (event) => {
2338
+ const newName = event.target.value;
2339
+ setName(newName);
2340
+ if (!newName.trim()) setError(t("group_name_empty_error"));
2341
+ else if (existingGroupNames.includes(newName.trim())) setError(t("group_name_exists_error"));
2342
+ else setError(null);
2343
+ };
2344
+ const handleSave = () => {
2345
+ const trimmedName = name.trim();
2346
+ if (!trimmedName) {
2347
+ setError(t("group_name_empty_error"));
2348
+ return;
2349
+ }
2350
+ if (existingGroupNames.includes(trimmedName)) {
2351
+ setError(t("group_name_exists_error"));
2352
+ return;
2353
+ }
2354
+ if (!error) {
2355
+ onRename(trimmedName);
2356
+ onClose();
2357
+ }
2358
+ };
2359
+ const handleKeyDown = (event) => {
2360
+ if (event.key === "Enter") {
2361
+ event.preventDefault();
2362
+ const trimmedName = name.trim();
2363
+ let currentError = null;
2364
+ if (!trimmedName) currentError = t("group_name_empty_error");
2365
+ else if (existingGroupNames.includes(trimmedName)) currentError = t("group_name_exists_error");
2366
+ if (!currentError && trimmedName) handleSave();
2367
+ else if (currentError) setError(currentError);
2368
+ }
2369
+ };
2370
+ if (!open) return null;
2371
+ return /* @__PURE__ */ jsxs(Dialog, {
2372
+ open,
2373
+ children: [
2374
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("rename_group") }),
2375
+ /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsx(TextField, {
2376
+ inputRef,
2377
+ label: t("group_name_label"),
2378
+ value: name,
2379
+ onChange: handleNameChange,
2380
+ onKeyDown: handleKeyDown,
2381
+ error: !!error,
2382
+ "aria-describedby": error ? "group-name-error" : void 0
2383
+ }), error && /* @__PURE__ */ jsx("p", {
2384
+ id: "group-name-error",
2385
+ style: { display: "none" },
2386
+ children: error
2387
+ })] }),
2388
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
2389
+ onClick: onClose,
2390
+ variant: "text",
2391
+ children: t("cancel")
2392
+ }), /* @__PURE__ */ jsx(Button, {
2393
+ onClick: handleSave,
2394
+ disabled: !!error || !name.trim(),
2395
+ children: t("save")
2396
+ })] })
2397
+ ]
2398
+ });
2399
+ }
2400
+ //#endregion
2401
+ //#region src/components/HomePage/ContentHomePage.tsx
2402
+ var DEFAULT_GROUP_NAME = "Views";
2403
+ var ADMIN_GROUP_NAME = "Admin";
2404
+ function ContentHomePage({ additionalActions, additionalChildrenStart, additionalChildrenEnd, sections, hiddenGroups }) {
2405
+ const context = useCMSContext();
2406
+ const { navigationStateController } = context;
2407
+ const customizationController = useCustomizationController();
2408
+ const adminModeController = useAdminModeController();
2409
+ const registry = useRebaseRegistry();
2410
+ const { resolvedSlots } = customizationController;
2411
+ const breadcrumbs = useBreadcrumbsController();
2412
+ const { t } = useTranslation();
2413
+ useEffect(() => {
2414
+ breadcrumbs.set({ breadcrumbs: [] });
2415
+ }, [breadcrumbs.set]);
2416
+ const { allowDragAndDrop = false, navigationEntries: unFilteredNavigationEntries = [], groups: unFilteredGroupOrder = [], onNavigationEntriesUpdate = () => {} } = navigationStateController.topLevelNavigation || {};
2417
+ const studioViewSlugs = useMemo(() => {
2418
+ const slugs = /* @__PURE__ */ new Set();
2419
+ (registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
2420
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
2421
+ return slugs;
2422
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
2423
+ const rawNavigationEntries = useMemo(() => {
2424
+ if (adminModeController.mode === "studio") return unFilteredNavigationEntries.filter((e) => e.type === "view" && studioViewSlugs.has(e.slug) || e.type === "admin");
2425
+ return unFilteredNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
2426
+ }, [
2427
+ unFilteredNavigationEntries,
2428
+ adminModeController.mode,
2429
+ studioViewSlugs
2430
+ ]);
2431
+ const groupOrderFromNavController = useMemo(() => {
2432
+ const entryGroups = new Set(rawNavigationEntries.map((e) => e.group).filter(Boolean));
2433
+ return unFilteredGroupOrder.filter((g) => entryGroups.has(g));
2434
+ }, [unFilteredGroupOrder, rawNavigationEntries]);
2435
+ const fuse = useRef(null);
2436
+ const [filteredUrls, setFilteredUrls] = useState(null);
2437
+ const performingSearch = Boolean(filteredUrls);
2438
+ const filteredNavigationEntries = useMemo(() => {
2439
+ return filteredUrls ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url)) : rawNavigationEntries;
2440
+ }, [filteredUrls, rawNavigationEntries]);
2441
+ useEffect(() => {
2442
+ fuse.current = new Fuse(rawNavigationEntries, { keys: [
2443
+ "name",
2444
+ "description",
2445
+ "group",
2446
+ "path"
2447
+ ] });
2448
+ }, [rawNavigationEntries]);
2449
+ const updateSearch = useCallback((v) => {
2450
+ if (!v?.trim()) {
2451
+ setFilteredUrls(null);
2452
+ return;
2453
+ }
2454
+ const results = fuse.current?.search(v.trim());
2455
+ setFilteredUrls(results ? results.map((x) => x.item.url) : []);
2456
+ }, []);
2457
+ const [items, setItems] = useState([]);
2458
+ const [adminGroupData, setAdminGroupData] = useState(null);
2459
+ const isDndDirtyRef = useRef(false);
2460
+ const dndDirtyTimeoutRef = useRef(null);
2461
+ const processedGroups = useMemo(() => {
2462
+ const src = filteredNavigationEntries;
2463
+ const entriesByGroup = {};
2464
+ src.forEach((e) => {
2465
+ const g = e.type === "admin" ? ADMIN_GROUP_NAME : e.group ?? "Views";
2466
+ (entriesByGroup[g] ??= []).push(e);
2467
+ });
2468
+ const hasPluginAdditionalCards = customizationController.resolvedSlots.some((s) => s.slot === "home.cards");
2469
+ let allProcessed;
2470
+ if (performingSearch) allProcessed = [...new Set(src.map((e) => e.group ?? "Views"))].map((name) => ({
2471
+ name,
2472
+ entries: entriesByGroup[name] || []
2473
+ })).filter((g) => g.entries.length);
2474
+ else {
2475
+ allProcessed = groupOrderFromNavController.map((g) => ({
2476
+ name: g,
2477
+ entries: entriesByGroup[g] || []
2478
+ }));
2479
+ Object.keys(entriesByGroup).forEach((g) => {
2480
+ if (!groupOrderFromNavController.includes(g)) allProcessed.push({
2481
+ name: g,
2482
+ entries: entriesByGroup[g]
2483
+ });
2484
+ });
2485
+ if (hasPluginAdditionalCards && !allProcessed.some((g) => g.name === "Views")) allProcessed.push({
2486
+ name: DEFAULT_GROUP_NAME,
2487
+ entries: []
2488
+ });
2489
+ allProcessed = allProcessed.filter((g) => g.entries.length || g.name === "Views" && hasPluginAdditionalCards);
2490
+ }
2491
+ return {
2492
+ adminGroupData: allProcessed.find((g) => g.name === "Admin") || null,
2493
+ items: allProcessed.filter((g) => g.name !== "Admin" && !hiddenGroups?.includes(g.name))
2494
+ };
2495
+ }, [
2496
+ filteredNavigationEntries,
2497
+ performingSearch,
2498
+ groupOrderFromNavController,
2499
+ customizationController.resolvedSlots,
2500
+ hiddenGroups
2501
+ ]);
2502
+ useEffect(() => {
2503
+ if (isDndDirtyRef.current) return;
2504
+ setAdminGroupData(processedGroups.adminGroupData);
2505
+ setItems(processedGroups.items);
2506
+ }, [processedGroups]);
2507
+ const updateItems = (updater) => {
2508
+ setItems(updater);
2509
+ };
2510
+ const persistNavigationGroups = (latest) => {
2511
+ isDndDirtyRef.current = true;
2512
+ if (dndDirtyTimeoutRef.current) clearTimeout(dndDirtyTimeoutRef.current);
2513
+ dndDirtyTimeoutRef.current = setTimeout(() => {
2514
+ isDndDirtyRef.current = false;
2515
+ }, 1e3);
2516
+ const draggable = latest.map((g) => ({
2517
+ name: g.name,
2518
+ entries: g.entries.map((e) => e.slug)
2519
+ }));
2520
+ onNavigationEntriesUpdate(adminGroupData ? [...draggable, {
2521
+ name: adminGroupData.name,
2522
+ entries: adminGroupData.entries.map((e) => e.slug)
2523
+ }] : draggable);
2524
+ };
2525
+ const { isGroupCollapsed, toggleGroupCollapsed } = useCollapsedGroups(useMemo(() => [...items.map((item) => item.name), ...adminGroupData ? [adminGroupData.name] : []], [items, adminGroupData]), "home", useMemo(() => buildCollapsedDefaults(registry.cmsConfig?.navigationGroupMappings, "home"), [registry.cmsConfig?.navigationGroupMappings]));
2526
+ const { sensors, collisionDetection, onDragStart, onDragOver, onDragEnd, dropAnimation, activeItemForOverlay, activeGroupData, draggingGroupId, containers, dndKitActiveNode, onDragCancel, isDraggingCardOnly, dialogOpenForGroup, setDialogOpenForGroup, handleRenameGroup, handleDialogClose, isHoveringNewGroupDropZone, setIsHoveringNewGroupDropZone } = useHomePageDnd({
2527
+ items,
2528
+ setItems: updateItems,
2529
+ disabled: !allowDragAndDrop || performingSearch,
2530
+ onPersist: persistNavigationGroups,
2531
+ onGroupMoved: (g) => context.analyticsController?.onAnalyticsEvent?.("home_move_group", { name: g }),
2532
+ onCardMovedBetweenGroups: (card) => context.analyticsController?.onAnalyticsEvent?.("home_move_card", { id: card.id }),
2533
+ onNewGroupDrop: () => context.analyticsController?.onAnalyticsEvent?.("home_drop_new_group")
2534
+ });
2535
+ const { containerRef, direction } = useRestoreScroll();
2536
+ const dndDisabled = !allowDragAndDrop || performingSearch;
2537
+ const dndModifiers = dndKitActiveNode?.data.current?.type === "group" ? [restrictToVerticalAxis, restrictToWindowEdges] : [restrictToWindowEdges];
2538
+ const sectionProps = { context };
2539
+ const additionalPluginChildrenStart = useSlot("home.children.start", sectionProps);
2540
+ const additionalPluginChildrenEnd = useSlot("home.children.end", sectionProps);
2541
+ const additionalPluginActions = useSlot("home.actions", sectionProps);
2542
+ const homeCardContributions = useMemo(() => {
2543
+ return resolvedSlots.filter((s) => s.slot === "home.cards").sort((a, b) => (a.order ?? 50) - (b.order ?? 50));
2544
+ }, [resolvedSlots]);
2545
+ return /* @__PURE__ */ jsxs("div", {
2546
+ ref: containerRef,
2547
+ className: "py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800",
2548
+ children: [/* @__PURE__ */ jsxs(Container, {
2549
+ maxWidth: "6xl",
2550
+ children: [
2551
+ /* @__PURE__ */ jsx("div", {
2552
+ className: "mb-4 flex flex-col gap-2",
2553
+ children: /* @__PURE__ */ jsx(SchemaDriftBanner, {})
2554
+ }),
2555
+ /* @__PURE__ */ jsxs("div", {
2556
+ className: "w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4",
2557
+ style: { top: direction === "down" ? -84 : 0 },
2558
+ children: [
2559
+ /* @__PURE__ */ jsx(SearchBar, {
2560
+ onTextSearch: updateSearch,
2561
+ placeholder: t("search_collections"),
2562
+ autoFocus: true,
2563
+ size: "small",
2564
+ innerClassName: "w-full",
2565
+ className: "w-full grow"
2566
+ }),
2567
+ additionalActions,
2568
+ additionalPluginActions
2569
+ ]
2570
+ }),
2571
+ /* @__PURE__ */ jsx(FavouritesView, { hidden: performingSearch }),
2572
+ additionalChildrenStart,
2573
+ additionalPluginChildrenStart,
2574
+ /* @__PURE__ */ jsxs(DndContext, {
2575
+ sensors,
2576
+ collisionDetection,
2577
+ measuring: { droppable: {
2578
+ strategy: MeasuringStrategy.Always,
2579
+ frequency: 500
2580
+ } },
2581
+ onDragStart,
2582
+ onDragOver,
2583
+ onDragEnd,
2584
+ onDragCancel,
2585
+ modifiers: dndModifiers,
2586
+ children: [
2587
+ /* @__PURE__ */ jsx(SortableContext, {
2588
+ items: containers,
2589
+ strategy: verticalListSortingStrategy,
2590
+ children: items.map((groupData, groupIndex) => {
2591
+ const groupKey = groupData.name;
2592
+ const entriesInGroup = groupData.entries;
2593
+ const homeCardsProps = {
2594
+ group: groupKey === "Views" ? void 0 : groupKey,
2595
+ context
2596
+ };
2597
+ const additionalCards = homeCardContributions.map((s, i) => /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(s.Component, {
2598
+ ...homeCardsProps,
2599
+ ...s.props ?? {}
2600
+ }) }, `home_cards_${groupKey}_${i}`));
2601
+ if (entriesInGroup.length === 0 && (additionalCards.length === 0 || performingSearch)) return null;
2602
+ return /* @__PURE__ */ jsx(SortableNavigationGroup, {
2603
+ groupName: groupKey,
2604
+ disabled: dndDisabled,
2605
+ children: /* @__PURE__ */ jsx(NavigationGroup, {
2606
+ group: groupKey === "Views" ? void 0 : groupKey,
2607
+ minimised: draggingGroupId === groupKey && !isDraggingCardOnly,
2608
+ isPotentialCardDropTarget: isDraggingCardOnly,
2609
+ dndDisabled,
2610
+ onEditGroup: () => {
2611
+ if (dndDisabled) return;
2612
+ setDialogOpenForGroup(groupKey);
2613
+ },
2614
+ collapsed: isGroupCollapsed(groupKey),
2615
+ onToggleCollapsed: () => toggleGroupCollapsed(groupKey),
2616
+ children: /* @__PURE__ */ jsx(NavigationGroupDroppable, {
2617
+ id: groupKey,
2618
+ itemIds: entriesInGroup.map((e) => e.url),
2619
+ isPotentialCardDropTarget: isDraggingCardOnly,
2620
+ children: /* @__PURE__ */ jsxs("div", {
2621
+ className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ",
2622
+ children: [entriesInGroup.map((entry) => /* @__PURE__ */ jsx(SortableNavigationCard, {
2623
+ entry,
2624
+ onClick: () => {
2625
+ let event = "unmapped_event";
2626
+ if (entry.type === "collection") event = "home_navigate_to_collection";
2627
+ else if (entry.type === "view") event = "home_navigate_to_view";
2628
+ else if (entry.type === "admin") event = "home_navigate_to_admin_view";
2629
+ context.analyticsController?.onAnalyticsEvent?.(event, { path: entry.slug });
2630
+ }
2631
+ }, entry.url)), !performingSearch && groupKey.toLowerCase() !== "Admin".toLowerCase() && additionalCards]
2632
+ })
2633
+ })
2634
+ })
2635
+ }, `group-${groupIndex}`);
2636
+ })
2637
+ }, containers.join(",")),
2638
+ /* @__PURE__ */ jsx(NewGroupDropZone, {
2639
+ disabled: dndDisabled,
2640
+ setIsHovering: setIsHoveringNewGroupDropZone
2641
+ }),
2642
+ /* @__PURE__ */ jsx(DragOverlay, {
2643
+ adjustScale: false,
2644
+ dropAnimation,
2645
+ children: activeGroupData && draggingGroupId === activeGroupData.name ? /* @__PURE__ */ jsx("div", {
2646
+ className: "rounded-lg bg-transparent",
2647
+ style: {
2648
+ padding: 0,
2649
+ margin: 0
2650
+ },
2651
+ children: /* @__PURE__ */ jsx(NavigationGroup, {
2652
+ group: activeGroupData.name === "Views" ? void 0 : activeGroupData.name,
2653
+ isPreview: false,
2654
+ minimised: true
2655
+ })
2656
+ }) : activeItemForOverlay ? /* @__PURE__ */ jsx(NavigationCardBinding, {
2657
+ ...activeItemForOverlay,
2658
+ shrink: isHoveringNewGroupDropZone
2659
+ }) : null
2660
+ })
2661
+ ]
2662
+ }),
2663
+ !performingSearch && adminGroupData && /* @__PURE__ */ jsx(NavigationGroup, {
2664
+ group: adminGroupData.name,
2665
+ collapsed: isGroupCollapsed(adminGroupData.name),
2666
+ onToggleCollapsed: () => toggleGroupCollapsed(adminGroupData.name),
2667
+ children: /* @__PURE__ */ jsx("div", {
2668
+ className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ",
2669
+ children: adminGroupData.entries.map((entry) => /* @__PURE__ */ jsx(NavigationCardBinding, {
2670
+ ...entry,
2671
+ onClick: () => {
2672
+ let event = "unmapped_event";
2673
+ if (entry.type === "collection") event = "home_navigate_to_collection";
2674
+ else if (entry.type === "view") event = "home_navigate_to_view";
2675
+ else if (entry.type === "admin") event = "home_navigate_to_admin_view";
2676
+ context.analyticsController?.onAnalyticsEvent?.(event, { path: entry.slug });
2677
+ }
2678
+ }, entry.url))
2679
+ })
2680
+ }),
2681
+ sections && sections.map((section) => /* @__PURE__ */ jsx(NavigationGroup, {
2682
+ group: section.title,
2683
+ children: section.children
2684
+ }, section.key)),
2685
+ additionalPluginChildrenEnd,
2686
+ additionalChildrenEnd
2687
+ ]
2688
+ }), dialogOpenForGroup && /* @__PURE__ */ jsx(RenameGroupDialog, {
2689
+ open: true,
2690
+ initialName: dialogOpenForGroup,
2691
+ existingGroupNames: items.map((g) => g.name).filter((n) => n !== dialogOpenForGroup),
2692
+ onClose: handleDialogClose,
2693
+ onRename: (newName) => {
2694
+ handleRenameGroup(dialogOpenForGroup, newName);
2695
+ }
2696
+ })]
2697
+ });
2698
+ }
2699
+ //#endregion
2700
+ //#region src/components/CollectionEditorDialogs.tsx
2701
+ var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-D7odTn0N.js").then((n) => n.r).then((m) => ({ default: m.CollectionEditorDialog })));
2702
+ var PropertyFormDialog = lazy(() => import("./PropertyEditView-i51tFOfM.js").then((n) => n.t).then((m) => ({ default: m.PropertyFormDialog })));
2703
+ /**
2704
+ * Renders the CollectionEditorDialog and PropertyFormDialog inside
2705
+ * the RebaseShell tree where CMS-internal contexts
2706
+ * (CollectionRegistryContext, NavigationStateContext, UrlContext)
2707
+ * are available.
2708
+ *
2709
+ * Both dialog components are loaded dynamically via React.lazy so that
2710
+ * production CMS bundles that never open the collection editor don't pay
2711
+ * the download/parse cost up-front.
2712
+ *
2713
+ * The dialog state (open/close, props) is managed by ConfigControllerProvider
2714
+ * (a root-scope plugin provider that wraps the shell) and exposed via
2715
+ * CollectionEditorDialogsContext.
2716
+ */
2717
+ function CollectionEditorDialogs() {
2718
+ const state = useCollectionEditorDialogsState();
2719
+ if (!state) return null;
2720
+ const { collectionDialogProps, propertyDialogProps } = state;
2721
+ if (!(collectionDialogProps?.open || propertyDialogProps?.open)) return null;
2722
+ return /* @__PURE__ */ jsxs(Suspense, {
2723
+ fallback: null,
2724
+ children: [collectionDialogProps?.open && /* @__PURE__ */ jsx(CollectionEditorDialog, {
2725
+ open: false,
2726
+ isNewCollection: false,
2727
+ configController: void 0,
2728
+ handleClose: () => {},
2729
+ ...collectionDialogProps
2730
+ }), propertyDialogProps?.open && /* @__PURE__ */ jsx(PropertyFormDialog, {
2731
+ open: false,
2732
+ existingProperty: false,
2733
+ autoOpenTypeSelect: false,
2734
+ inArray: false,
2735
+ allowDataInference: false,
2736
+ propertyConfigs: {},
2737
+ ...propertyDialogProps
2738
+ })]
2739
+ });
2740
+ }
2741
+ //#endregion
2742
+ //#region src/components/RebaseRouteDefs.tsx
2743
+ function SettingsView() {
2744
+ const { t } = useTranslation();
2745
+ const breadcrumbs = useBreadcrumbsController();
2746
+ useEffect(() => {
2747
+ breadcrumbs.set({ breadcrumbs: [{
2748
+ title: t("account_settings"),
2749
+ url: "/settings"
2750
+ }] });
2751
+ }, []);
2752
+ return /* @__PURE__ */ jsx(UserSettingsView, {});
2753
+ }
2754
+ /**
2755
+ * Route definitions for the CMS.
2756
+ *
2757
+ * Defines all standard routes: home, studio home, collection view,
2758
+ * settings, debug, custom views, and a catch-all 404.
2759
+ *
2760
+ * **Independently usable**: Use this when you want Rebase routes
2761
+ * inside your own layout/navigation setup.
2762
+ *
2763
+ * @example
2764
+ * ```tsx
2765
+ * <RebaseNavigation>
2766
+ * <RebaseRouteDefs layout={<MyCustomLayout />}>
2767
+ * <Route path="/my-custom" element={<MyView />} />
2768
+ * </RebaseRouteDefs>
2769
+ * </RebaseNavigation>
2770
+ * ```
2771
+ */
2772
+ function RebaseRouteDefs({ children, layout }) {
2773
+ const registry = useRebaseRegistry();
2774
+ const navigationStateController = useNavigationStateController();
2775
+ const ResolvedHomePage = useComponentOverride("HomePage", ContentHomePage);
2776
+ const cmsHomePage = registry.cmsConfig?.homePage ?? /* @__PURE__ */ jsx(Suspense, {
2777
+ fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
2778
+ children: /* @__PURE__ */ jsx(ResolvedHomePage, {})
2779
+ });
2780
+ const studioHomePage = registry.studioConfig?.homePage;
2781
+ const combinedViews = useMemo(() => [...navigationStateController.views ?? [], ...navigationStateController.adminViews ?? []], [navigationStateController.views, navigationStateController.adminViews]);
2782
+ const routeContents = /* @__PURE__ */ jsxs(Fragment, { children: [
2783
+ /* @__PURE__ */ jsx(Route, {
2784
+ path: "/",
2785
+ element: cmsHomePage
2786
+ }),
2787
+ registry.studioConfig && /* @__PURE__ */ jsx(Route, {
2788
+ path: "/s",
2789
+ element: studioHomePage
2790
+ }),
2791
+ /* @__PURE__ */ jsx(Route, {
2792
+ path: "/c/*",
2793
+ element: /* @__PURE__ */ jsx(RebaseRoute, {})
2794
+ }),
2795
+ /* @__PURE__ */ jsx(Route, {
2796
+ path: "/settings",
2797
+ element: /* @__PURE__ */ jsx(SettingsView, {})
2798
+ }),
2799
+ /* @__PURE__ */ jsx(Route, {
2800
+ path: "/debug/ui",
2801
+ element: /* @__PURE__ */ jsx(UIReferenceView, {})
2802
+ }),
2803
+ combinedViews.map((view) => {
2804
+ return (Array.isArray(view.slug) ? view.slug : [view.slug]).flatMap((slug) => {
2805
+ const routes = [/* @__PURE__ */ jsx(Route, {
2806
+ path: slug,
2807
+ element: /* @__PURE__ */ jsx(CustomViewRoute, { view })
2808
+ }, slug)];
2809
+ if (view.nestedRoutes) routes.push(/* @__PURE__ */ jsx(Route, {
2810
+ path: slug + "/*",
2811
+ element: /* @__PURE__ */ jsx(CustomViewRoute, { view })
2812
+ }, slug + "/*"));
2813
+ return routes;
2814
+ });
2815
+ }),
2816
+ children,
2817
+ /* @__PURE__ */ jsx(Route, {
2818
+ path: "*",
2819
+ element: navigationStateController.loading ? /* @__PURE__ */ jsx(CircularProgressCenter, {}) : /* @__PURE__ */ jsx(NotFoundPage, {})
2820
+ })
2821
+ ] });
2822
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(RebaseRoutes, { children: layout ? /* @__PURE__ */ jsx(Route, {
2823
+ element: layout,
2824
+ children: routeContents
2825
+ }) : /* @__PURE__ */ jsx(Route, {
2826
+ element: /* @__PURE__ */ jsx(Fragment, { children: routeContents }),
2827
+ children: routeContents
2828
+ }) }), /* @__PURE__ */ jsx(CollectionEditorDialogs, {})] });
2829
+ }
2830
+ //#endregion
2831
+ //#region src/components/RebaseShell.tsx
2832
+ /**
2833
+ * Convenience component that composes all four CMS layers:
2834
+ *
2835
+ * ```
2836
+ * <RebaseAuthGate>
2837
+ * <RebaseNavigation>
2838
+ * <RebaseRouteDefs layout={<RebaseLayout>}>
2839
+ * {children}
2840
+ * </RebaseRouteDefs>
2841
+ * </RebaseNavigation>
2842
+ * </RebaseAuthGate>
2843
+ * ```
2844
+ *
2845
+ * Each layer is independently usable — see their individual docs.
2846
+ * RebaseShell is sugar that composes them all with sensible defaults.
2847
+ */
2848
+ function RebaseShell(props) {
2849
+ const { title = "Rebase", appBar, drawer, autoOpenDrawer = false, children } = props;
2850
+ const registry = useRebaseRegistry();
2851
+ return /* @__PURE__ */ jsx(RebaseAuthGate, { children: /* @__PURE__ */ jsx(RebaseNavigation, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
2852
+ fullPage: true,
2853
+ children: /* @__PURE__ */ jsx(RebaseRouteDefs, {
2854
+ layout: /* @__PURE__ */ jsx(RebaseLayout, {
2855
+ title,
2856
+ appBar,
2857
+ drawer,
2858
+ autoOpenDrawer,
2859
+ devViews: useMemo(() => {
2860
+ return registry.studioConfig?.devViews ?? [];
2861
+ }, [registry.studioConfig?.devViews])
2862
+ }),
2863
+ children
2864
+ })
2865
+ }) }) });
2866
+ }
2867
+ //#endregion
2868
+ //#region src/components/CollectionPanel.tsx
2869
+ /**
2870
+ * A high-level, reusable wrapper for embedding a Rebase collection view
2871
+ * inside custom pages (dashboards, home pages, entity detail views, etc.).
2872
+ *
2873
+ * Usage:
2874
+ * ```tsx
2875
+ * import { CollectionPanel } from "@rebasepro/admin";
2876
+ *
2877
+ * function MyDashboard() {
2878
+ * return (
2879
+ * <div>
2880
+ * <CollectionPanel path="tasks" title="Pending Tasks" />
2881
+ * <CollectionPanel
2882
+ * path="clients"
2883
+ * viewMode="table"
2884
+ * collectionOverrides={{
2885
+ * defaultFilter: { status: ["!=", "completed"] }
2886
+ * }}
2887
+ * />
2888
+ * </div>
2889
+ * );
2890
+ * }
2891
+ * ```
2892
+ *
2893
+ * @group Components
2894
+ */
2895
+ function CollectionPanelInner({ mergedCollection, path, title, updateUrl, className }) {
2896
+ const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
2897
+ return /* @__PURE__ */ jsxs("div", {
2898
+ className,
2899
+ children: [title !== false && /* @__PURE__ */ jsx(Typography, {
2900
+ variant: "subtitle2",
2901
+ className: "font-bold mb-2 text-surface-700 dark:text-surface-300",
2902
+ children: title ?? mergedCollection.name
2903
+ }), /* @__PURE__ */ jsx(ResolvedCollectionView, {
2904
+ ...mergedCollection,
2905
+ path,
2906
+ updateUrl
2907
+ })]
2908
+ });
2909
+ }
2910
+ function CollectionPanel(props) {
2911
+ const { path, viewMode, sort, limit, openEntityMode, className, collectionOverrides } = props;
2912
+ const registeredCollection = useCollectionRegistryController().getCollection(path);
2913
+ const mergedCollection = useMemo(() => {
2914
+ if (!registeredCollection) return void 0;
2915
+ const propOverrides = {};
2916
+ if (viewMode) propOverrides.defaultViewMode = viewMode;
2917
+ if (sort) propOverrides.sort = sort;
2918
+ if (limit) propOverrides.pagination = limit;
2919
+ if (openEntityMode) propOverrides.openEntityMode = openEntityMode;
2920
+ return {
2921
+ ...registeredCollection,
2922
+ ...collectionOverrides ?? {},
2923
+ ...propOverrides
2924
+ };
2925
+ }, [
2926
+ registeredCollection,
2927
+ collectionOverrides,
2928
+ viewMode,
2929
+ sort,
2930
+ limit,
2931
+ openEntityMode
2932
+ ]);
2933
+ if (!mergedCollection) return /* @__PURE__ */ jsx("div", {
2934
+ className,
2935
+ children: /* @__PURE__ */ jsxs(Typography, {
2936
+ variant: "body2",
2937
+ color: "secondary",
2938
+ children: [
2939
+ "Collection \"",
2940
+ path,
2941
+ "\" not found."
2942
+ ]
2943
+ })
2944
+ });
2945
+ return /* @__PURE__ */ jsx(CollectionScopeProvider, {
2946
+ collection: mergedCollection,
2947
+ children: /* @__PURE__ */ jsx(CollectionPanelInner, {
2948
+ ...props,
2949
+ mergedCollection
2950
+ })
2951
+ });
2952
+ }
2953
+ //#endregion
2954
+ //#region src/util/propertyConfigMapper.ts
2955
+ /**
2956
+ * Map Rebase property types to headless-supported types.
2957
+ * `vector` and `binary` are not directly renderable, so we fall back to `string`.
2958
+ */
2959
+ var TYPE_MAP = {
2960
+ string: "string",
2961
+ number: "number",
2962
+ boolean: "boolean",
2963
+ date: "date",
2964
+ geopoint: "geopoint",
2965
+ reference: "reference",
2966
+ relation: "relation",
2967
+ array: "array",
2968
+ map: "map",
2969
+ vector: "string",
2970
+ binary: "string"
2971
+ };
2972
+ /**
2973
+ * Convert a single Property to a CollectionPropertyConfig.
2974
+ */
2975
+ function mapPropertyToConfig(property) {
2976
+ const base = {
2977
+ type: TYPE_MAP[property.type] ?? "string",
2978
+ name: property.name,
2979
+ description: property.description,
2980
+ columnWidth: property.ui?.columnWidth,
2981
+ hideFromCollection: property.ui?.hideFromCollection
2982
+ };
2983
+ if (property.type === "string") {
2984
+ if (property.enum) base.enum = mapEnumValues(property.enum);
2985
+ if (property.ui?.multiline) base.multiline = true;
2986
+ if (property.ui?.previewAsTag) base.previewAsTag = true;
2987
+ if (property.ui?.url) base.url = property.ui.url;
2988
+ if (property.ui?.markdown) base.markdown = true;
2989
+ if (property.storage) base.storage = true;
2990
+ if (property.email) base.email = true;
2991
+ }
2992
+ if (property.type === "number" && property.enum) base.enum = mapEnumValues(property.enum);
2993
+ if (property.type === "array" && property.of && !Array.isArray(property.of)) base.of = mapPropertyToConfig(property.of);
2994
+ if (property.type === "map" && property.properties) {
2995
+ base.properties = mapPropertiesToConfigs(property.properties);
2996
+ if (property.propertiesOrder) base.propertiesOrder = property.propertiesOrder;
2997
+ }
2998
+ if (property.type === "date" && property.mode) base.mode = property.mode;
2999
+ if (property.ui?.Preview) {}
3000
+ return base;
3001
+ }
3002
+ /**
3003
+ * Convert a Properties record to a CollectionPropertyConfig record.
3004
+ */
3005
+ function mapPropertiesToConfigs(properties) {
3006
+ const result = {};
3007
+ for (const [key, property] of Object.entries(properties)) result[key] = mapPropertyToConfig(property);
3008
+ return result;
3009
+ }
3010
+ /**
3011
+ * Convert Rebase EnumValues to CollectionEnumValueConfig format.
3012
+ * Handles both array form (EnumValueConfig[]) and record form.
3013
+ */
3014
+ function mapEnumValues(enumValues) {
3015
+ if (!enumValues) return void 0;
3016
+ if (Array.isArray(enumValues)) {
3017
+ const result = {};
3018
+ for (const item of enumValues) result[String(item.id)] = normalizeEnumValueConfig(item);
3019
+ return result;
3020
+ }
3021
+ const result = {};
3022
+ for (const [key, value] of Object.entries(enumValues)) result[key] = typeof value === "string" ? value : normalizeEnumValueConfig(value);
3023
+ return result;
3024
+ }
3025
+ function normalizeEnumValueConfig(config) {
3026
+ return {
3027
+ label: config.label,
3028
+ color: typeof config.color === "string" ? config.color : void 0,
3029
+ disabled: config.disabled
3030
+ };
3031
+ }
3032
+ //#endregion
3033
+ //#region src/util/dataControllerAdapter.ts
3034
+ /**
3035
+ * Adapts an `EntityTableController<M>` (from @rebasepro/types) to a
3036
+ * `CollectionDataController<T>` (from @rebasepro/ui), bridging the
3037
+ * entity-aware data layer to the headless collection view.
3038
+ */
3039
+ /**
3040
+ * Unwrap `Entity<M>` objects to flat rows for the headless view.
3041
+ * The entity system uses `{ id, path, values: M }`; `values` is already the
3042
+ * flat row `M` carrying its real primary key, so unwrapping is just `.values` —
3043
+ * no `id`/`path` merge (those are view-model metadata, not row columns).
3044
+ */
3045
+ function flattenEntities(entities) {
3046
+ return entities.map((entity) => entity.values);
3047
+ }
3048
+ /**
3049
+ * React hook that adapts a EntityTableController to a CollectionDataController.
3050
+ *
3051
+ * Usage:
3052
+ * ```tsx
3053
+ * const tableController = useDataTableController({ path, collection });
3054
+ * const dataController = useCollectionDataController(tableController);
3055
+ *
3056
+ * <CollectionView dataController={dataController} ... />
3057
+ * ```
3058
+ */
3059
+ function useCollectionDataController(tableController) {
3060
+ const flatData = useMemo(() => flattenEntities(tableController.data), [tableController.data]);
3061
+ return useMemo(() => ({
3062
+ data: flatData,
3063
+ loading: tableController.dataLoading,
3064
+ noMoreToLoad: tableController.noMoreToLoad,
3065
+ error: tableController.dataLoadingError,
3066
+ filterValues: tableController.filterValues,
3067
+ setFilterValues: tableController.setFilterValues,
3068
+ sortBy: tableController.sortBy,
3069
+ setSortBy: tableController.setSortBy,
3070
+ searchString: tableController.searchString,
3071
+ setSearchString: tableController.setSearchString,
3072
+ clearFilter: tableController.clearFilter,
3073
+ initialScroll: tableController.initialScroll,
3074
+ onScroll: tableController.onScroll,
3075
+ paginationEnabled: tableController.paginationEnabled,
3076
+ pageSize: tableController.pageSize,
3077
+ itemCount: tableController.itemCount,
3078
+ setItemCount: tableController.setItemCount
3079
+ }), [flatData, tableController]);
3080
+ }
3081
+ /**
3082
+ * Non-hook version for cases where you already have the data
3083
+ * and just need to build a static controller.
3084
+ */
3085
+ function createStaticDataController(data, options) {
3086
+ return {
3087
+ data,
3088
+ loading: options?.loading ?? false,
3089
+ noMoreToLoad: true,
3090
+ error: options?.error
3091
+ };
3092
+ }
3093
+ //#endregion
3094
+ //#region src/collection_editor/useJsonCollectionsConfigController.tsx
3095
+ /**
3096
+ * A `CollectionsConfigController` implementation that persists collection
3097
+ * configurations as JSON via a pluggable `JsonCollectionStore` adapter.
3098
+ *
3099
+ * Use this when you want to store collection schemas in a database, API,
3100
+ * localStorage, or any other JSON-compatible backend — as opposed to the
3101
+ * default code-based persistence.
3102
+ *
3103
+ * @example
3104
+ * ```tsx
3105
+ * const store: JsonCollectionStore = {
3106
+ * load: async () => { const res = await fetch('/api/schemas'); return res.json(); },
3107
+ * save: async (slug, data) => { await fetch(`/api/schemas/${slug}`, { method: 'PUT', body: JSON.stringify(data) }); },
3108
+ * delete: async (slug) => { await fetch(`/api/schemas/${slug}`, { method: 'DELETE' }); },
3109
+ * };
3110
+ *
3111
+ * const configController = useJsonCollectionsConfigController({ store });
3112
+ * ```
3113
+ */
3114
+ function useJsonCollectionsConfigController({ store, readOnly = false, autoLoad = true }) {
3115
+ const [collections, setCollections] = useState([]);
3116
+ const [loading, setLoading] = useState(autoLoad);
3117
+ const [navigationEntries, setNavigationEntries] = useState([]);
3118
+ const storeRef = useRef(store);
3119
+ storeRef.current = store;
3120
+ useEffect(() => {
3121
+ if (!autoLoad) return;
3122
+ let cancelled = false;
3123
+ (async () => {
3124
+ try {
3125
+ const [serialized, navEntries] = await Promise.all([storeRef.current.load(), storeRef.current.loadNavigationEntries?.() ?? Promise.resolve([])]);
3126
+ if (cancelled) return;
3127
+ setCollections(serialized.map(fromSerializableCollectionConfig));
3128
+ setNavigationEntries(navEntries);
3129
+ } catch (e) {
3130
+ console.error("useJsonCollectionsConfigController: failed to load collections", e);
3131
+ } finally {
3132
+ if (!cancelled) setLoading(false);
3133
+ }
3134
+ })();
3135
+ return () => {
3136
+ cancelled = true;
3137
+ };
3138
+ }, [autoLoad]);
3139
+ /**
3140
+ * Update local state and persist a collection to the store.
3141
+ */
3142
+ const persistCollection = useCallback(async (collection) => {
3143
+ const serializable = toSerializableCollectionConfig(collection);
3144
+ await storeRef.current.save(collection.slug, serializable);
3145
+ }, []);
3146
+ /**
3147
+ * Find collection by id (slug).
3148
+ */
3149
+ const getCollection = useCallback((id) => {
3150
+ const found = collections.find((c) => c.slug === id);
3151
+ if (found) return found;
3152
+ throw new Error(`Collection "${id}" not found`);
3153
+ }, [collections]);
3154
+ const saveCollection = useCallback(async ({ id, collectionData }) => {
3155
+ const collection = collectionData;
3156
+ await persistCollection(collection);
3157
+ setCollections((prev) => {
3158
+ const idx = prev.findIndex((c) => c.slug === id);
3159
+ if (idx >= 0) {
3160
+ const next = [...prev];
3161
+ next[idx] = collection;
3162
+ return next;
3163
+ }
3164
+ return [...prev, collection];
3165
+ });
3166
+ }, [persistCollection]);
3167
+ const updateCollection = useCallback(async ({ id, collectionData, previousId }) => {
3168
+ setCollections((prev) => {
3169
+ const lookupId = previousId ?? id;
3170
+ const idx = prev.findIndex((c) => c.slug === lookupId);
3171
+ if (idx < 0) return prev;
3172
+ const merged = {
3173
+ ...prev[idx],
3174
+ ...collectionData
3175
+ };
3176
+ const next = [...prev];
3177
+ next[idx] = merged;
3178
+ persistCollection(merged).catch((e) => console.error("useJsonCollectionsConfigController: failed to update collection", e));
3179
+ if (previousId && previousId !== id) storeRef.current.delete(previousId).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete old slug", e));
3180
+ return next;
3181
+ });
3182
+ }, [persistCollection]);
3183
+ const deleteCollection = useCallback(async ({ id }) => {
3184
+ await storeRef.current.delete(id);
3185
+ setCollections((prev) => prev.filter((c) => c.slug !== id));
3186
+ }, []);
3187
+ const saveProperty = useCallback(async ({ path, propertyKey, property, newPropertiesOrder }) => {
3188
+ setCollections((prev) => {
3189
+ const idx = prev.findIndex((c) => c.slug === path);
3190
+ if (idx < 0) return prev;
3191
+ const collection = { ...prev[idx] };
3192
+ collection.properties = {
3193
+ ...collection.properties,
3194
+ [propertyKey]: property
3195
+ };
3196
+ if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
3197
+ const next = [...prev];
3198
+ next[idx] = collection;
3199
+ persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to save property", e));
3200
+ return next;
3201
+ });
3202
+ }, [persistCollection]);
3203
+ const deleteProperty = useCallback(async ({ path, propertyKey, newPropertiesOrder }) => {
3204
+ setCollections((prev) => {
3205
+ const idx = prev.findIndex((c) => c.slug === path);
3206
+ if (idx < 0) return prev;
3207
+ const collection = { ...prev[idx] };
3208
+ const { [propertyKey]: _removed, ...remaining } = collection.properties;
3209
+ collection.properties = remaining;
3210
+ if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
3211
+ const next = [...prev];
3212
+ next[idx] = collection;
3213
+ persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete property", e));
3214
+ return next;
3215
+ });
3216
+ }, [persistCollection]);
3217
+ const updatePropertiesOrder = useCallback(async ({ collection, newPropertiesOrder }) => {
3218
+ const updated = {
3219
+ ...collection,
3220
+ propertiesOrder: newPropertiesOrder
3221
+ };
3222
+ setCollections((prev) => {
3223
+ const idx = prev.findIndex((c) => c.slug === collection.slug);
3224
+ if (idx < 0) return prev;
3225
+ const next = [...prev];
3226
+ next[idx] = updated;
3227
+ return next;
3228
+ });
3229
+ await persistCollection(updated);
3230
+ }, [persistCollection]);
3231
+ const updateKanbanColumnsOrder = useCallback(async ({ collection, kanbanColumnProperty, newColumnsOrder }) => {}, []);
3232
+ const saveNavigationEntriesHandler = useCallback(async (entries) => {
3233
+ setNavigationEntries(entries);
3234
+ if (storeRef.current.saveNavigationEntries) await storeRef.current.saveNavigationEntries(entries);
3235
+ }, []);
3236
+ return useMemo(() => ({
3237
+ loading,
3238
+ readOnly,
3239
+ readOnlyReason: readOnly ? "Configuration is read-only." : void 0,
3240
+ collections,
3241
+ getCollection,
3242
+ saveCollection,
3243
+ updateCollection,
3244
+ deleteCollection,
3245
+ saveProperty,
3246
+ deleteProperty,
3247
+ updatePropertiesOrder,
3248
+ updateKanbanColumnsOrder,
3249
+ navigationEntries,
3250
+ saveNavigationEntries: saveNavigationEntriesHandler
3251
+ }), [
3252
+ loading,
3253
+ readOnly,
3254
+ collections,
3255
+ getCollection,
3256
+ saveCollection,
3257
+ updateCollection,
3258
+ deleteCollection,
3259
+ saveProperty,
3260
+ deleteProperty,
3261
+ updatePropertiesOrder,
3262
+ updateKanbanColumnsOrder,
3263
+ navigationEntries,
3264
+ saveNavigationEntriesHandler
3265
+ ]);
3266
+ }
3267
+ //#endregion
3268
+ //#region src/collection_editor/ui/MissingReferenceWidget.tsx
3269
+ function MissingReferenceWidget({ path: pathProp }) {
3270
+ const registry = useCollectionRegistryController();
3271
+ const path = getLastSegment$1(pathProp);
3272
+ const parentCollectionSlugs = registry.getParentCollectionSlugs(pathProp);
3273
+ const parentEntityIds = registry.getParentEntityIds(pathProp);
3274
+ const collectionEditor = useCollectionEditorController();
3275
+ const { t } = useTranslation();
3276
+ return /* @__PURE__ */ jsxs("div", {
3277
+ className: "p-1 flex flex-col items-center",
3278
+ children: [/* @__PURE__ */ jsx(ErrorView, { error: t("studio_missing_reference_error", { path }) }), /* @__PURE__ */ jsx(Button, {
3279
+ className: "mx-2",
3280
+ size: "small",
3281
+ onClick: () => {
3282
+ collectionEditor.createCollection({
3283
+ initialValues: {
3284
+ path,
3285
+ name: prettifyIdentifier(path)
3286
+ },
3287
+ parentCollectionSlugs,
3288
+ parentEntityIds,
3289
+ redirect: false,
3290
+ sourceClick: "missing_reference"
3291
+ });
3292
+ },
3293
+ children: t("studio_missing_reference_create")
3294
+ })]
3295
+ });
3296
+ }
3297
+ //#endregion
3298
+ export { AdminModeSyncer, AppBar, ArrayContainer, ArrayCustomShapedFieldBinding, ArrayEnumPreview, ArrayOfMapsPreview, ArrayOfReferencesFieldBinding, ArrayOfReferencesPreview, ArrayOfStorageComponentsPreview, ArrayOfStringsPreview, ArrayOneOfPreview, ArrayPropertyEnumPreview, ArrayPropertyPreview, BasicExportAction, BlockFieldBinding, BooleanPreview, CollectionCardViewBinding, CollectionGenerationApiError, CollectionPanel, CollectionRegistryContext, CollectionRowActions, CollectionTableBinding, CollectionViewActions, CollectionViewBinding, CreationResultDialog, CustomViewRoute, DEFAULT_COLLECTION_GENERATION_ENDPOINT, DEFAULT_FIELD_CONFIGS, DataNewPropertiesMapping, DatePreview, DateTimeFieldBinding, DefaultDrawer, Drawer, DrawerFooterActions, EmptyValue, EntityCardBinding, EntityForm, EntityFormBinding, EntityPreviewBinding, EntityViewBinding, EnumValuesChip, ExportCollectionAction, FieldCaption, FieldHelperText, FormEntry, FormLayout, ImagePreview, ImportCollectionAction, ImportDataPreview, ImportFileUpload, ImportNewPropertyFieldPreview, ImportSaveInProgress, KeyValueFieldBinding, KeyValuePreview, LabelWithIcon, LabelWithIconAndTooltip, MapFieldBinding, MapPropertyPreview, MarkdownEditorFieldBinding, MissingReferenceWidget, MultiSelectFieldBinding, NavigationStateContext, NumberPropertyPreview, PropertyConfigBadge, PropertyFieldBinding, PropertyIdCopyTooltip, PropertyPreview, PropertySelectEntry, ReadOnlyFieldBinding, RebaseAdmin, RebaseAuthGate, RebaseLayout, RebaseNavigation, RebaseRoute, RebaseRouteDefs, RebaseShell, ReferenceFieldBinding, ReferencePreview, ReferenceWidget, RelationPreview, RepeatFieldBinding, Scaffold, SearchIconsView, SelectFieldBinding, SelectableTable, SelectableTableContext, SelectionTableBinding, SideDialogs, SidePanelControllerContext, SidePanelProvider, SkeletonPropertyComponent, StorageThumbnail, StorageThumbnailInternal, StorageUploadFieldBinding, StringPropertyPreview, SwitchFieldBinding, TextFieldBinding, UrlComponentPreview, UrlContext, UserPreview, VectorFieldBinding, VirtualTableInput, addInitialSlash, buildCollectionGenerationCallback, buildSidePanelsFromUrl, convertDataToEntity, convertFileToJson, copyEntityAction, createStaticDataController, deleteEntityAction, downloadBlob, downloadDataAsCsv, downloadEntitiesExport, editEntityAction, extractTouchedValues, flattenEntry, fromSerializableCollectionConfig, fromSerializableProperties, fromSerializableProperty, getBracketNotation, getChanges, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultFieldConfig, getDefaultFieldId, getDefaultPropertiesOrder, getEntityCSVExportableData, getEntityJsonExportableData, getEntityPreviewKeys, getEntityTitlePropertyKey, getEntityViewWidth, getFieldConfig, getFieldId, getFullId, getFullIdPath, getIconForProperty, getIconForWidget, getInferenceType, getInitialEntityValues, getLastSegment, getPropertiesWithPropertiesOrder, getPropertyInPath, getResolvedPropertyInPath, idToPropertiesPath, isReferenceProperty, isRelationProperty, mapPropertiesToConfigs, mapPropertyToConfig, mergeEntityActions, namespaceToPropertiesOrderPath, namespaceToPropertiesPath, processValueMapping, removeEmptyContainers, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, renderSkeletonCaptionText, renderSkeletonIcon, renderSkeletonImageThumbnail, renderSkeletonText, resetPasswordAction, resolveCollectionPathIds, resolveEntityAction, resolveEntityView, resolveNavigationFrom, sanitizeUrl, toSerializableCollectionConfig, toSerializableProperties, toSerializableProperty, unflattenObject, useApp, useBreadcrumbsController, useBuildCollectionRegistryController, useBuildNavigationStateController, useBuildSidePanel, useBuildUrlController, useCMSContext, useClearRestoreValue, useCollectionDataController, useCollectionEditorController, useCollectionRegistryController, useCollectionsConfigController, useHistory, useImportConfig, useJsonCollectionsConfigController, useLocalCollectionsConfigController, useNavigationStateController, useResolvedCollections, useResolvedNavigationFrom, useResolvedViews, useSelectionController, useSelectionDialog, useSideDialogsController, useSidePanel, useTopLevelNavigation, useUrlController, validateCollectionJson, zodToFormErrors };
3299
+
3300
+ //# sourceMappingURL=index.js.map