@rebasepro/app 0.17.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +4 -1
  2. package/dist/components/index.d.ts +0 -3
  3. package/dist/contexts/AuthControllerContext.d.ts +2 -1
  4. package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
  5. package/dist/contexts/DialogsProvider.d.ts +2 -1
  6. package/dist/contexts/RebaseDataContext.d.ts +10 -1
  7. package/dist/contexts/StorageSourceContext.d.ts +11 -1
  8. package/dist/core/RebaseProps.d.ts +2 -12
  9. package/dist/debug/index.d.ts +19 -0
  10. package/dist/debug.js +3631 -0
  11. package/dist/hooks/data/delete.d.ts +16 -7
  12. package/dist/hooks/data/save.d.ts +18 -4
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/useAdminModeController.d.ts +5 -4
  15. package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
  16. package/dist/hooks/useStudioBridge.d.ts +33 -0
  17. package/dist/index.es.js +2599 -6980
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/vitePlugin.d.ts +22 -0
  20. package/dist/vitePlugin.js +40 -1
  21. package/package.json +42 -31
  22. package/src/auth/api.ts +0 -134
  23. package/src/auth/index.ts +0 -26
  24. package/src/auth/types.ts +0 -115
  25. package/src/auth/useRebaseAuthController.ts +0 -345
  26. package/src/collections/collection_view_config.ts +0 -76
  27. package/src/collections/entity-display-cache.ts +0 -195
  28. package/src/collections/entity-display.ts +0 -77
  29. package/src/collections/entity_image_preview.ts +0 -61
  30. package/src/collections/filter-operator-resolution.ts +0 -119
  31. package/src/collections/form-layout.ts +0 -394
  32. package/src/collections/index.ts +0 -13
  33. package/src/collections/navigation_from_path.ts +0 -123
  34. package/src/collections/navigation_utils.ts +0 -171
  35. package/src/collections/parent_references_from_path.ts +0 -59
  36. package/src/collections/property-path.ts +0 -30
  37. package/src/collections/property_presentation.ts +0 -264
  38. package/src/collections/summary-property.ts +0 -114
  39. package/src/collections/title-property.ts +0 -312
  40. package/src/components/AIIcon.tsx +0 -47
  41. package/src/components/AuthSimulationMenu.tsx +0 -0
  42. package/src/components/ConfirmationDialog.tsx +0 -48
  43. package/src/components/Debug/UIReferenceView.tsx +0 -1053
  44. package/src/components/Debug/UIStyleGuide.tsx +0 -159
  45. package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
  46. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
  47. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
  48. package/src/components/Debug/collection-views/index.ts +0 -3
  49. package/src/components/Debug/collection-views/sample_data.ts +0 -44
  50. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
  51. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
  52. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
  53. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
  54. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
  55. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
  56. package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
  57. package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
  58. package/src/components/ErrorTooltip.tsx +0 -12
  59. package/src/components/ErrorView.tsx +0 -94
  60. package/src/components/LanguageToggle.tsx +0 -59
  61. package/src/components/LoginView/LoginView.tsx +0 -1235
  62. package/src/components/LoginView/index.ts +0 -2
  63. package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
  64. package/src/components/NotFoundPage.tsx +0 -27
  65. package/src/components/RebaseAuth.tsx +0 -27
  66. package/src/components/RebaseLogo.tsx +0 -88
  67. package/src/components/SchemaDriftBanner.tsx +0 -102
  68. package/src/components/UnsavedChangesDialog.tsx +0 -46
  69. package/src/components/UserDisplay.tsx +0 -54
  70. package/src/components/UserSelectPopover.tsx +0 -399
  71. package/src/components/UserSettingsView.tsx +0 -318
  72. package/src/components/common/index.ts +0 -6
  73. package/src/components/common/table_height.tsx +0 -21
  74. package/src/components/common/types.tsx +0 -66
  75. package/src/components/common/useColumnsIds.tsx +0 -261
  76. package/src/components/common/useDataTableController.tsx +0 -571
  77. package/src/components/common/useDebouncedData.ts +0 -49
  78. package/src/components/common/useScrollRestoration.tsx +0 -68
  79. package/src/components/index.tsx +0 -26
  80. package/src/contexts/AdminModeController.tsx +0 -11
  81. package/src/contexts/AnalyticsContext.tsx +0 -4
  82. package/src/contexts/AuthControllerContext.tsx +0 -4
  83. package/src/contexts/CollectionResolverContext.tsx +0 -24
  84. package/src/contexts/CollectionScopeContext.tsx +0 -68
  85. package/src/contexts/ComponentOverrideContext.tsx +0 -81
  86. package/src/contexts/CustomizationControllerContext.tsx +0 -4
  87. package/src/contexts/DataDriverContext.tsx +0 -4
  88. package/src/contexts/DataSourcesContext.tsx +0 -36
  89. package/src/contexts/DatabaseAdminContext.tsx +0 -4
  90. package/src/contexts/DialogsProvider.tsx +0 -56
  91. package/src/contexts/EffectiveRoleController.tsx +0 -12
  92. package/src/contexts/ModeController.tsx +0 -11
  93. package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
  94. package/src/contexts/RebaseDataContext.tsx +0 -4
  95. package/src/contexts/SnackbarProvider.tsx +0 -15
  96. package/src/contexts/StorageSourceContext.tsx +0 -4
  97. package/src/contexts/StorageSourcesContext.tsx +0 -30
  98. package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
  99. package/src/contexts/index.ts +0 -18
  100. package/src/core/PluginLifecycleManager.tsx +0 -96
  101. package/src/core/PluginProviderStack.tsx +0 -40
  102. package/src/core/Rebase.tsx +0 -488
  103. package/src/core/RebaseProps.tsx +0 -333
  104. package/src/core/RebaseRouter.tsx +0 -18
  105. package/src/core/RebaseRoutes.tsx +0 -24
  106. package/src/core/index.tsx +0 -5
  107. package/src/hooks/ApiConfigContext.tsx +0 -83
  108. package/src/hooks/data/collectionQuery.ts +0 -55
  109. package/src/hooks/data/delete.ts +0 -56
  110. package/src/hooks/data/save.ts +0 -78
  111. package/src/hooks/data/useCollection.tsx +0 -183
  112. package/src/hooks/data/useData.tsx +0 -18
  113. package/src/hooks/data/useDataOrder.ts +0 -26
  114. package/src/hooks/data/useFetch.tsx +0 -156
  115. package/src/hooks/data/useRelationSelector.tsx +0 -280
  116. package/src/hooks/index.tsx +0 -45
  117. package/src/hooks/useAdminModeController.tsx +0 -23
  118. package/src/hooks/useAnalyticsController.tsx +0 -8
  119. package/src/hooks/useAuthController.tsx +0 -15
  120. package/src/hooks/useAuthSubscription.ts +0 -130
  121. package/src/hooks/useBackendStorageSource.ts +0 -295
  122. package/src/hooks/useBridgeRegistration.tsx +0 -32
  123. package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
  124. package/src/hooks/useBuildAdminModeController.tsx +0 -29
  125. package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
  126. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
  127. package/src/hooks/useBuildModeController.tsx +0 -73
  128. package/src/hooks/useClipboard.tsx +0 -158
  129. package/src/hooks/useCollapsedGroups.ts +0 -116
  130. package/src/hooks/useComponentOverride.tsx +0 -59
  131. package/src/hooks/useCustomizationController.tsx +0 -14
  132. package/src/hooks/useDialogsController.tsx +0 -15
  133. package/src/hooks/useEffectiveRoleController.tsx +0 -10
  134. package/src/hooks/useLargeLayout.tsx +0 -65
  135. package/src/hooks/useModeController.tsx +0 -23
  136. package/src/hooks/useNavigationBlocker.tsx +0 -108
  137. package/src/hooks/usePermissions.ts +0 -45
  138. package/src/hooks/useRebaseClient.tsx +0 -10
  139. package/src/hooks/useRebaseContext.tsx +0 -84
  140. package/src/hooks/useRebaseRegistry.tsx +0 -91
  141. package/src/hooks/useResolvedComponent.tsx +0 -195
  142. package/src/hooks/useSlot.tsx +0 -64
  143. package/src/hooks/useSnackbarController.tsx +0 -80
  144. package/src/hooks/useStorageSource.tsx +0 -11
  145. package/src/hooks/useStudioBridge.tsx +0 -258
  146. package/src/hooks/useTranslation.ts +0 -38
  147. package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
  148. package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
  149. package/src/i18n/RebaseI18nProvider.tsx +0 -159
  150. package/src/index.ts +0 -32
  151. package/src/internal/common.tsx +0 -28
  152. package/src/internal/useRestoreScroll.tsx +0 -69
  153. package/src/locales/de.ts +0 -941
  154. package/src/locales/en.ts +0 -1021
  155. package/src/locales/es.ts +0 -962
  156. package/src/locales/fr.ts +0 -941
  157. package/src/locales/hi.ts +0 -941
  158. package/src/locales/it.ts +0 -941
  159. package/src/locales/pt.ts +0 -949
  160. package/src/util/constants.ts +0 -7
  161. package/src/util/createFormexStub.tsx +0 -66
  162. package/src/util/entity_cache.ts +0 -242
  163. package/src/util/enums.ts +0 -34
  164. package/src/util/icon_list.ts +0 -22
  165. package/src/util/icons.tsx +0 -129
  166. package/src/util/index.ts +0 -13
  167. package/src/util/local_storage.ts +0 -66
  168. package/src/util/previews.ts +0 -127
  169. package/src/util/useStorageUploadController.tsx +0 -354
  170. package/src/vitePlugin.ts +0 -215
  171. /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
  172. /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
  173. /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
  174. /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
  175. /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
  176. /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
  177. /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
  178. /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
  179. /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
  180. /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
  181. /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
  182. /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
  183. /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
  184. /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
  185. /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
@@ -1,108 +0,0 @@
1
- import React, { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
2
- import { Blocker, BlockerFunction, useBlocker } from "react-router";
3
-
4
- const IDLE_BLOCKER: Blocker = {
5
- state: "unblocked",
6
- proceed: undefined,
7
- reset: undefined,
8
- location: undefined
9
- };
10
-
11
- type NavigationBlockerContextValue = {
12
- register: (id: string, predicate: BlockerFunction) => void;
13
- unregister: (id: string) => void;
14
- blocker: Blocker;
15
- /** Id of the registration that caused the current block, if any. */
16
- blockedBy: string | null;
17
- };
18
-
19
- const NavigationBlockerContext = createContext<NavigationBlockerContextValue | null>(null);
20
-
21
- /**
22
- * Owns the single React Router blocker for the whole app.
23
- *
24
- * React Router only honours **one** blocker at a time: `shouldBlockNavigation`
25
- * picks the last-registered blocker function and silently ignores every other
26
- * one (it only logs a dev warning). Because blockers register in a `useEffect`,
27
- * the winner is whichever surface mounted most recently — so an unsaved-changes
28
- * guard could be disabled by an unrelated component mounting after it.
29
- *
30
- * This provider registers the only `useBlocker` in the tree and multiplexes it,
31
- * so every surface that needs to guard navigation gets a say regardless of
32
- * mount order. Surfaces register through {@link useNavigationBlocker}.
33
- */
34
- export function NavigationBlockerProvider({ children }: { children: React.ReactNode }) {
35
-
36
- const predicates = useRef(new Map<string, BlockerFunction>());
37
-
38
- // State, not a ref: the owning registration only renders its dialog once
39
- // this has propagated, so it has to go through React.
40
- const [blockedBy, setBlockedBy] = useState<string | null>(null);
41
-
42
- const shouldBlock = useCallback<BlockerFunction>((args) => {
43
- for (const [id, predicate] of predicates.current) {
44
- if (predicate(args)) {
45
- setBlockedBy(id);
46
- return true;
47
- }
48
- }
49
- setBlockedBy(null);
50
- return false;
51
- }, []);
52
-
53
- const blocker = useBlocker(shouldBlock);
54
-
55
- const register = useCallback((id: string, predicate: BlockerFunction) => {
56
- predicates.current.set(id, predicate);
57
- }, []);
58
-
59
- const unregister = useCallback((id: string) => {
60
- predicates.current.delete(id);
61
- setBlockedBy((current) => (current === id ? null : current));
62
- }, []);
63
-
64
- const value = useMemo<NavigationBlockerContextValue>(() => ({
65
- register,
66
- unregister,
67
- blocker,
68
- blockedBy
69
- }), [register, unregister, blocker, blockedBy]);
70
-
71
- return (
72
- <NavigationBlockerContext.Provider value={value}>
73
- {children}
74
- </NavigationBlockerContext.Provider>
75
- );
76
- }
77
-
78
- /**
79
- * Guard navigation with `predicate`, sharing the app-wide blocker.
80
- *
81
- * Returns a {@link Blocker} that is only ever in the `blocked` state when *this*
82
- * registration is what blocked the navigation — so several guards can coexist
83
- * without each of them popping a dialog.
84
- *
85
- * Returns an idle blocker when no {@link NavigationBlockerProvider} is mounted
86
- * above, rather than competing for React Router's single blocker slot.
87
- */
88
- export function useNavigationBlocker(predicate: BlockerFunction): Blocker {
89
-
90
- const context = useContext(NavigationBlockerContext);
91
- const id = useId();
92
-
93
- // Keep the latest predicate without re-registering on every render.
94
- const predicateRef = useRef(predicate);
95
- predicateRef.current = predicate;
96
-
97
- const register = context?.register;
98
- const unregister = context?.unregister;
99
-
100
- useEffect(() => {
101
- if (!register || !unregister) return;
102
- register(id, (args) => predicateRef.current(args));
103
- return () => unregister(id);
104
- }, [register, unregister, id]);
105
-
106
- if (!context) return IDLE_BLOCKER;
107
- return context.blockedBy === id ? context.blocker : IDLE_BLOCKER;
108
- }
@@ -1,45 +0,0 @@
1
-
2
- import { useAuthController } from "./useAuthController";
3
- import { Entity } from "@rebasepro/types";
4
- import { canCreateEntity, canEditEntity, canDeleteEntity, canReadCollection } from "@rebasepro/common";
5
- import { useCallback, useMemo } from "react";
6
- import type { AdminCollection } from "@rebasepro/cms-types";
7
-
8
- /**
9
- * Hook to evaluate roles and permissions for the current user.
10
- * It abstracts away the need to pass `authController` to permission evaluation functions.
11
- */
12
- export function usePermissions() {
13
- const authController = useAuthController();
14
-
15
- const canCreate = useCallback(
16
- (collection: AdminCollection<any>, path: string) =>
17
- canCreateEntity(collection, authController, path, null),
18
- [authController]
19
- );
20
-
21
- const canEdit = useCallback(
22
- (collection: AdminCollection<any>, path: string, entity: Entity<any> | null) =>
23
- canEditEntity(collection, authController, path, entity),
24
- [authController]
25
- );
26
-
27
- const canDelete = useCallback(
28
- (collection: AdminCollection<any>, path: string, entity: Entity<any> | null) =>
29
- canDeleteEntity(collection, authController, path, entity),
30
- [authController]
31
- );
32
-
33
- const canRead = useCallback(
34
- (collection: AdminCollection<any>) =>
35
- canReadCollection(collection, authController),
36
- [authController]
37
- );
38
-
39
- return useMemo(() => ({
40
- canCreate,
41
- canEdit,
42
- canDelete,
43
- canRead
44
- }), [canCreate, canEdit, canDelete, canRead]);
45
- }
@@ -1,10 +0,0 @@
1
- import { useContext } from "react";
2
- import { RebaseClientInstanceContext } from "../contexts/RebaseClientInstanceContext";
3
-
4
- /**
5
- * Hook to retrieve the full RebaseClient instance from context.
6
- * Returns `undefined` if no client was provided to `<Rebase>`.
7
- */
8
- export function useRebaseClient<T = any>(): T | undefined {
9
- return useContext(RebaseClientInstanceContext) as T | undefined;
10
- }
@@ -1,84 +0,0 @@
1
- import { User } from "@rebasepro/types";
2
- import { AuthController, RebaseContext } from "@rebasepro/cms-types";
3
- import { wrapAsSdkData } from "@rebasepro/common";
4
- import { useAuthController } from "./useAuthController";
5
- import { useRebaseClient } from "./useRebaseClient";
6
- import { useData } from "./data/useData";
7
- import { useStorageSource } from "./useStorageSource";
8
- import { useSnackbarController } from "./useSnackbarController";
9
- import { useUserConfigurationPersistence } from "./useUserConfigurationPersistence";
10
- import { useDialogsController } from "./useDialogsController";
11
- import { useCustomizationController } from "./useCustomizationController";
12
- import { useAnalyticsController } from "./useAnalyticsController";
13
- import { useEffectiveRoleController } from "./useEffectiveRoleController";
14
- import React, { useEffect, useContext } from "react";
15
-
16
-
17
- // DatabaseAdmin is provided by <Rebase> via DatabaseAdminContext.
18
- import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
19
-
20
- /**
21
- * Hook to retrieve the {@link RebaseContext}.
22
- *
23
- * Consider that in order to use this hook you need to have a parent
24
- * `Rebase` component.
25
- *
26
- * @see RebaseContext
27
- * @group Hooks and utilities
28
- */
29
- export const useRebaseContext = <USER extends User = User, AuthControllerType extends AuthController<USER> = AuthController<USER>>(): RebaseContext<USER, AuthControllerType> => {
30
-
31
- const client = useRebaseClient<any>();
32
-
33
- const authController = useAuthController<USER, AuthControllerType>();
34
- // `context.data` is the flat SDK view — identical in shape to the backend
35
- // `context.data` and the frontend SDK client, so callbacks behave the same
36
- // everywhere. The admin reads Entities via `useData()` directly.
37
- const entityData = useData();
38
- const data = React.useMemo(() => wrapAsSdkData(entityData), [entityData]);
39
- const storageSource = useStorageSource();
40
- const snackbarController = useSnackbarController();
41
- const userConfigPersistence = useUserConfigurationPersistence();
42
- const dialogsController = useDialogsController();
43
- const customizationController = useCustomizationController();
44
- const analyticsController = useAnalyticsController();
45
- const effectiveRoleController = useEffectiveRoleController();
46
-
47
-
48
- // Will get `databaseAdmin` from context
49
- const databaseAdmin = useContext(DatabaseAdminContext);
50
-
51
- const rebaseContextRef = React.useRef<RebaseContext<USER, AuthControllerType>>({
52
- authController,
53
- data,
54
- storageSource,
55
- snackbarController,
56
- userConfigPersistence,
57
- dialogsController,
58
- customizationController,
59
- analyticsController,
60
- effectiveRoleController,
61
- databaseAdmin,
62
- client: client! // Client should be provided
63
- });
64
-
65
- React.useEffect(() => {
66
- rebaseContextRef.current = {
67
- authController,
68
- data,
69
- storageSource,
70
- snackbarController,
71
- userConfigPersistence,
72
- dialogsController,
73
- customizationController,
74
- analyticsController,
75
- effectiveRoleController,
76
- databaseAdmin,
77
- client: client!
78
- };
79
- }, [authController, data, storageSource, snackbarController, userConfigPersistence,
80
- dialogsController, customizationController, analyticsController,
81
- effectiveRoleController, databaseAdmin, client]);
82
-
83
- return rebaseContextRef.current;
84
- }
@@ -1,91 +0,0 @@
1
- import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from "react";
2
- import type { RebaseRegistryController, RebaseCMSConfig, RebaseStudioConfig, RebaseAuthViewConfig } from "@rebasepro/cms-types";
3
-
4
- /**
5
- * Split into two contexts to prevent infinite re-render loops:
6
- * - DispatchContext: stable register/unregister functions (never changes identity)
7
- * - StateContext: the current config values (changes when modules register)
8
- *
9
- * Feature components (RebaseAuth, RebaseCMS, RebaseStudio) only consume
10
- * the dispatch context, so registering does NOT cause them to re-render.
11
- * RebaseShell consumes state context to read the collected configs.
12
- */
13
-
14
- interface RegistryDispatch {
15
- registerAdmin: (config: RebaseCMSConfig) => void;
16
- unregisterAdmin: () => void;
17
- registerStudio: (config: RebaseStudioConfig) => void;
18
- unregisterStudio: () => void;
19
- registerAuth: (config: RebaseAuthViewConfig) => void;
20
- unregisterAuth: () => void;
21
- }
22
-
23
- interface RegistryState {
24
- cmsConfig: RebaseCMSConfig | null;
25
- studioConfig: RebaseStudioConfig | null;
26
- authConfig: RebaseAuthViewConfig | null;
27
- }
28
-
29
- const RegistryDispatchContext = createContext<RegistryDispatch | undefined>(undefined);
30
- const RegistryStateContext = createContext<RegistryState>({
31
- cmsConfig: null,
32
- studioConfig: null,
33
- authConfig: null
34
- });
35
-
36
- export function RebaseRegistryProvider({ children }: { children: React.ReactNode }) {
37
- const [cmsConfig, setCmsConfig] = useState<RebaseCMSConfig | null>(null);
38
- const [studioConfig, setStudioConfig] = useState<RebaseStudioConfig | null>(null);
39
- const [authConfig, setAuthConfig] = useState<RebaseAuthViewConfig | null>(null);
40
-
41
- // Dispatch functions are stable — never change identity
42
- const dispatch = useMemo<RegistryDispatch>(() => ({
43
- registerAdmin: (config: RebaseCMSConfig) => setCmsConfig(config),
44
- unregisterAdmin: () => setCmsConfig(null),
45
- registerStudio: (config: RebaseStudioConfig) => setStudioConfig(config),
46
- unregisterStudio: () => setStudioConfig(null),
47
- registerAuth: (config: RebaseAuthViewConfig) => setAuthConfig(config),
48
- unregisterAuth: () => setAuthConfig(null)
49
- }), []);
50
-
51
- const state = useMemo<RegistryState>(() => ({
52
- cmsConfig,
53
- studioConfig,
54
- authConfig
55
- }), [cmsConfig, studioConfig, authConfig]);
56
-
57
- return (
58
- <RegistryDispatchContext.Provider value={dispatch}>
59
- <RegistryStateContext.Provider value={state}>
60
- {children}
61
- </RegistryStateContext.Provider>
62
- </RegistryDispatchContext.Provider>
63
- );
64
- }
65
-
66
- /**
67
- * Returns the full registry (state + dispatch).
68
- * Use this in RebaseShell where you need to read configs.
69
- */
70
- export function useRebaseRegistry(): RebaseRegistryController {
71
- const dispatch = useContext(RegistryDispatchContext);
72
- const state = useContext(RegistryStateContext);
73
- if (!dispatch) {
74
- throw new Error("useRebaseRegistry must be used within RebaseRegistryProvider");
75
- }
76
- return useMemo(() => ({ ...state,
77
- ...dispatch }), [state, dispatch]);
78
- }
79
-
80
- /**
81
- * Returns only the stable dispatch functions.
82
- * Use this in feature components (RebaseAuth, RebaseCMS, RebaseStudio)
83
- * to avoid re-render loops.
84
- */
85
- export function useRebaseRegistryDispatch(): RegistryDispatch {
86
- const dispatch = useContext(RegistryDispatchContext);
87
- if (!dispatch) {
88
- throw new Error("useRebaseRegistryDispatch must be used within RebaseRegistryProvider");
89
- }
90
- return dispatch;
91
- }
@@ -1,195 +0,0 @@
1
- import React, { useMemo } from "react";
2
- import { lazyChunk } from "@rebasepro/ui";
3
- import type { ComponentRef, LazyComponentRef } from "@rebasepro/types";
4
- import { isLazyComponentRef } from "@rebasepro/types";
5
-
6
- /**
7
- * Internal cache for resolved lazy components.
8
- *
9
- * `React.lazy()` must return the SAME wrapper across renders — if we call
10
- * `lazy(loader)` on every render, React will treat each result as a new
11
- * component type and unmount/remount on each render cycle.
12
- *
13
- * This WeakMap keeps a stable mapping from a `ComponentRef` (object/function)
14
- * to the `React.lazy()` wrapper it produced. Strings are keyed by a separate
15
- * plain Map since they can't be WeakMap keys.
16
- */
17
-
18
- const lazyCache = new WeakMap<object | ((...args: any[]) => any), React.ComponentType<any>>();
19
-
20
- /**
21
- * Resolves a `ComponentRef` into a renderable `React.ComponentType`.
22
- *
23
- * This hook handles all three forms of `ComponentRef`:
24
- *
25
- * 1. **`LazyComponentRef`** (produced by the Vite plugin from string paths):
26
- * Wraps the lazy loader with `React.lazy()` for automatic code-splitting.
27
- *
28
- * 2. **`() => Promise<{ default: ComponentType }>`** (manual lazy import):
29
- * Also wraps with `React.lazy()`. Distinguished from regular components
30
- * by checking that the function has no parameters and is not a known
31
- * React internal (no `$$typeof`).
32
- *
33
- * 3. **Direct `React.ComponentType`**:
34
- * Returned as-is.
35
- *
36
- * If the ref is `undefined` or a raw string (which should never happen at
37
- * runtime in the browser since the Vite plugin transforms strings), returns `undefined`.
38
- *
39
- * The returned component is stable across re-renders as long as the `ref`
40
- * is referentially stable.
41
- *
42
- * **Usage:** Wrap the rendered component in `<Suspense>` to handle the
43
- * loading state of lazy components.
44
- *
45
- * @example
46
- * ```tsx
47
- * const ResolvedField = useResolvedComponent(property.ui?.Field);
48
- * if (!ResolvedField) return null;
49
- * return (
50
- * <Suspense fallback={<CircularProgress />}>
51
- * <ResolvedField {...fieldProps} />
52
- * </Suspense>
53
- * );
54
- * ```
55
- * */
56
- export function useResolvedComponent<P = unknown>(
57
- ref: ComponentRef<P> | undefined
58
- ): React.ComponentType<P> | undefined {
59
- return useMemo(() => resolveComponentRef(ref), [ref]);
60
- }
61
-
62
- /**
63
- * Wraps a loader function with `React.lazy()`, caching the result so the
64
- * same loader always returns the same lazy component identity.
65
- */
66
- function getOrCreateLazy<P>(
67
- key: object | ((...args: any[]) => any),
68
- loader: () => Promise<{ default: React.ComponentType<P> }>
69
- ): React.ComponentType<P> {
70
- const cached = lazyCache.get(key);
71
- if (cached) return cached as React.ComponentType<P>;
72
-
73
- // SAFETY: React.lazy returns LazyExoticComponent which is structurally compatible with ComponentType<P>
74
- // `lazyChunk`, not `lazy`: a custom view is a chunk like any other, and a
75
- // tab left open across a deploy cannot fetch it either.
76
- const LazyComponent = lazyChunk(loader) as React.ComponentType<P>;
77
- lazyCache.set(key, LazyComponent);
78
- return LazyComponent;
79
- }
80
-
81
- /**
82
- * Pure function version of the resolver, for use outside React components.
83
- * Same resolution logic as `useResolvedComponent`.
84
- *
85
- * Results are cached per reference identity — calling this multiple times
86
- * with the same `ref` object returns the same `React.ComponentType`.
87
- */
88
- export function resolveComponentRef<P = unknown>(
89
- ref: ComponentRef<P> | undefined
90
- ): React.ComponentType<P> | undefined {
91
- if (ref == null) return undefined;
92
-
93
- // 1. String — should not happen at runtime (Vite transforms them).
94
- // Log a warning and bail out.
95
- if (typeof ref === "string") {
96
- console.warn(
97
- `[Rebase] Encountered a raw string ComponentRef ("${ref}") at runtime. ` +
98
- "This usually means the Vite transform plugin did not process this file. " +
99
- "Ensure the file is inside the configured collectionsDir."
100
- );
101
- return undefined;
102
- }
103
-
104
- // 2. LazyComponentRef — produced by the Vite plugin from string paths.
105
- // The object has { __rebaseLazy: true, load: () => import(...) }.
106
- if (isLazyComponentRef(ref)) {
107
- return getOrCreateLazy<P>(
108
- ref as object,
109
- // `load()` resolves to `ComponentLike<P>` — the structural stand-in
110
- // that lets `ComponentRef` live in the React-free core. This function
111
- // is the one place that boundary is crossed, so it is the one place
112
- // the widening is asserted: `ComponentLike` accepts a return type of
113
- // `unknown` where React wants `ReactNode`. What actually arrives is
114
- // whatever the collection file pointed at, and rendering it is
115
- // React's problem either way.
116
- () => (ref as LazyComponentRef<P>).load() as Promise<{ default: React.ComponentType<P> }>
117
- );
118
- }
119
-
120
- // 3. Function — either a React component or a lazy import loader.
121
- if (typeof ref === "function") {
122
- const fn = ref as (...args: any[]) => any;
123
-
124
- // Class components (React.Component / PureComponent) have this flag
125
- if (fn.prototype?.isReactComponent) {
126
- return ref as React.ComponentType<P>;
127
- }
128
-
129
- // React internals (forwardRef, memo, etc.) carry $$typeof
130
- if ("$$typeof" in fn) {
131
- return ref as React.ComponentType<P>;
132
- }
133
-
134
- // A function with declared parameters (fn.length > 0) is a regular
135
- // component that accepts props — return as-is.
136
- if (fn.length > 0) {
137
- return ref as React.ComponentType<P>;
138
- }
139
-
140
- // Zero-parameter function — either a React component that takes no props,
141
- // or a lazy loader `() => import(...)`.
142
- //
143
- // Nothing here can be decided with certainty from the outside, so the
144
- // signals are ordered by how much they can be trusted, and the strongest
145
- // one is what the function *does*, not what it is called.
146
- //
147
- // The name alone is not usable evidence, which is what this used to rely on.
148
- // `admin: { Preview: () => import("./BodyPartsPreview") }` gives the arrow
149
- // the *inferred* name "Preview" — JS names an anonymous function after the
150
- // property key it is assigned to — so the "starts with an uppercase letter,
151
- // therefore a component" rule matched every lazy loader written in the
152
- // documented way. The thunk was then handed to React as a component, React
153
- // called it, got a Promise back, and rendered nothing. That is what a blank
154
- // custom preview column in the demo was.
155
- const source = (() => {
156
- try {
157
- return Function.prototype.toString.call(fn);
158
- } catch {
159
- return "";
160
- }
161
- })();
162
-
163
- // A dynamic module load in the body is the strongest signal, and it outranks
164
- // the name: a toolchain cannot rename it away without breaking the fetch,
165
- // whereas the name is often not the author's at all.
166
- //
167
- // Both spellings are checked because the body reaching us depends on the
168
- // transform. Vite and any ESM build keep `import(...)`; ts-jest and other
169
- // CommonJS transforms rewrite it to `require(...)`. Matching only the first
170
- // passes in the browser and fails in the test suite, which is precisely how
171
- // this went unnoticed. A zero-argument *component* containing either call is
172
- // not a thing — components import at module scope.
173
- const loadsAChunk = /\b(?:import|require)\s*\(/.test(source);
174
-
175
- if (!loadsAChunk && fn.name && /^[A-Z]/.test(fn.name)) {
176
- // Named like a component and does not load a chunk: a component.
177
- return ref as React.ComponentType<P>;
178
- }
179
-
180
- // Treat as a lazy loader — wrap with React.lazy, cached by identity.
181
- return getOrCreateLazy<P>(
182
- fn,
183
- ref as () => Promise<{ default: React.ComponentType<P> }>
184
- );
185
- }
186
-
187
- // 4. React wrapper objects (React.forwardRef, React.memo) —
188
- // These are objects with $$typeof but are not functions.
189
- if (typeof ref === "object" && "$$typeof" in (ref as object)) {
190
- return ref as unknown as React.ComponentType<P>;
191
- }
192
-
193
- // 5. Unknown shape — return undefined to be safe
194
- return undefined;
195
- }
@@ -1,64 +0,0 @@
1
- import type { SlotName, SlotRegistry } from "@rebasepro/cms-types";
2
- import React, { useMemo } from "react";
3
- ;
4
- import { useCustomizationController } from "./useCustomizationController";
5
- import { ErrorBoundary } from "@rebasepro/ui";
6
-
7
- /**
8
- * Hook that retrieves and renders all slot contributions for a given slot name.
9
- *
10
- * @param slot - The slot name to render contributions for.
11
- * @param props - Props passed to each slot component.
12
- * @returns An array of rendered React nodes, each wrapped in an ErrorBoundary.
13
- *
14
- * @example
15
- * ```tsx
16
- * const actions = useSlot("home.actions", { context });
17
- * return <div>{actions}</div>;
18
- * ```
19
- *
20
- * @group Hooks
21
- */
22
- export function useSlot<K extends SlotName>(
23
- slot: K,
24
- props: SlotRegistry[K]
25
- ): React.ReactNode[] {
26
- const { resolvedSlots } = useCustomizationController();
27
-
28
- const propsRef = React.useRef(props);
29
- const currentProps = props as unknown as Record<string, unknown>;
30
- const prevProps = propsRef.current as unknown as Record<string, unknown>;
31
-
32
- let changed = false;
33
- if (currentProps !== prevProps) {
34
- const keys = Object.keys(currentProps);
35
- const prevKeys = Object.keys(prevProps);
36
- if (keys.length !== prevKeys.length) {
37
- changed = true;
38
- } else {
39
- for (let i = 0; i < keys.length; i++) {
40
- if (currentProps[keys[i]] !== prevProps[keys[i]]) {
41
- changed = true;
42
- break;
43
- }
44
- }
45
- }
46
- }
47
-
48
- if (changed) {
49
- propsRef.current = props;
50
- }
51
-
52
- const stableProps = propsRef.current;
53
-
54
- return useMemo(() => {
55
- return resolvedSlots
56
- .filter(s => s.slot === slot)
57
- .sort((a, b) => (a.order ?? 50) - (b.order ?? 50))
58
- .map((s, i) => (
59
- <ErrorBoundary key={`${slot}_${i}`}>
60
- <s.Component {...(stableProps as unknown as Record<string, unknown>)} {...(s.props ?? {})}/>
61
- </ErrorBoundary>
62
- ));
63
- }, [resolvedSlots, slot, stableProps]);
64
- }
@@ -1,80 +0,0 @@
1
- import type { SnackbarAction, SnackbarMessageType } from "@rebasepro/cms-types";
2
- import React, { useCallback, useMemo } from "react";
3
- import { useSnackbar } from "notistack";
4
- import { Button } from "@rebasepro/ui";
5
-
6
- /**
7
- * Hook to retrieve the SnackbarContext.
8
- *
9
- * Consider that in order to use this hook you need to have a parent
10
- * `Rebase`
11
- *
12
- * @see SnackbarController
13
- * @group Hooks and utilities
14
- */
15
- export const useSnackbarController = () => {
16
-
17
- const {
18
- enqueueSnackbar,
19
- closeSnackbar
20
- } = useSnackbar();
21
-
22
- const open = useCallback((props: {
23
- type: SnackbarMessageType;
24
- title?: string;
25
- message: React.ReactNode;
26
- autoHideDuration?: number;
27
- action?: SnackbarAction;
28
- }) => {
29
- const {
30
- type,
31
- message,
32
- autoHideDuration,
33
- action
34
- } = props;
35
- enqueueSnackbar({
36
- message: props.title ? <div className={"flex flex-col"}>
37
- <strong>{props.title}</strong>
38
- {message}
39
- </div> : message,
40
- variant: type,
41
- autoHideDuration,
42
- // The provider sets `preventDuplicate`, which collapses two
43
- // snackbars carrying the same message into one. That is right for a
44
- // confirmation and wrong for an action: deleting two rows in a row
45
- // produces the same "Deleted" text twice, and swallowing the second
46
- // would leave the second deletion with no way to undo it. A key
47
- // makes each one distinct.
48
- ...(action ? { key: nextActionKey() } : {}),
49
- action: action
50
- ? (key) => (
51
- <Button variant={"text"}
52
- size={"small"}
53
- className={"text-inherit"}
54
- onClick={() => {
55
- action.onClick();
56
- // The offer has been taken; leaving it up
57
- // invites a second click that undoes nothing,
58
- // or worse, undoes something else.
59
- closeSnackbar(key);
60
- }}>
61
- {action.label}
62
- </Button>
63
- )
64
- : undefined
65
- })
66
- }, [enqueueSnackbar, closeSnackbar]);
67
-
68
- const close = useCallback(() => {
69
- closeSnackbar();
70
- }, [closeSnackbar]);
71
-
72
- return useMemo(() => ({
73
- open,
74
- close
75
- }), [open, close]);
76
-
77
- };
78
-
79
- let actionKeySeq = 0;
80
- const nextActionKey = () => `rebase-snackbar-action-${actionKeySeq++}`;