@capillarytech/cap-ui-utils 3.0.16 → 3.0.17
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.
- package/index.js +1 -0
- package/package.json +1 -1
- package/utils/useMFEModuleHeader.js +133 -0
package/index.js
CHANGED
|
@@ -13,3 +13,4 @@ export { default as decompressJsonObject } from "./utils/zlibDataDecompress";
|
|
|
13
13
|
export { default as getEnvVariable } from "./utils/getEnvVariables";
|
|
14
14
|
export { formatDateWithTimezone, getTimezoneTooltip, hasTimezoneFeatureAccess } from "./utils/timezone";
|
|
15
15
|
export { default as MFEEventBus } from "./utils/mfeEventBus";
|
|
16
|
+
export { default as useMFEModuleHeader } from "./utils/useMFEModuleHeader";
|
package/package.json
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import MFEEventBus from './mfeEventBus';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Emit the module header for a given path. For route-based settings, the
|
|
6
|
+
* settings route shows the settings header (with a back button); everything
|
|
7
|
+
* else shows the main header. This runs on every navigation so both the MFE
|
|
8
|
+
* back button and the browser back button land on the correct header.
|
|
9
|
+
* No-op when settings is not route-based (header is registered once on mount).
|
|
10
|
+
*/
|
|
11
|
+
const emitHeaderForPath = (cfg, pathname) => {
|
|
12
|
+
const { header, settingsRoute, settingsHeader } = cfg;
|
|
13
|
+
if (!settingsRoute) return;
|
|
14
|
+
if (pathname.startsWith(settingsRoute)) {
|
|
15
|
+
MFEEventBus.emit('module:header-register', {
|
|
16
|
+
...header,
|
|
17
|
+
...settingsHeader,
|
|
18
|
+
showSettings: false,
|
|
19
|
+
showBackButton: true,
|
|
20
|
+
});
|
|
21
|
+
} else {
|
|
22
|
+
MFEEventBus.emit('module:header-register', header);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const emitRouteChange = pathname =>
|
|
27
|
+
MFEEventBus.emit('module:route-change', { pathname });
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* useMFEModuleHeader
|
|
31
|
+
*
|
|
32
|
+
* Common hook wrapping the MFEEventBus module-header wiring duplicated across
|
|
33
|
+
* every remote app. Registers the module header, keeps it in sync on
|
|
34
|
+
* navigation (including browser back), and handles the settings button — for
|
|
35
|
+
* both modal-style settings (callback) and route-based settings (own URL).
|
|
36
|
+
*
|
|
37
|
+
* Router-version agnostic: it never calls useLocation/useNavigate itself
|
|
38
|
+
* (those throw without the matching Router context and break the rules of
|
|
39
|
+
* hooks when called conditionally). The caller passes the router primitives:
|
|
40
|
+
* - React Router v5: pass `history` (history.location/.listen/.push/.goBack)
|
|
41
|
+
* - React Router v6: pass `location` (from useLocation) and `navigate`
|
|
42
|
+
* (from useNavigate)
|
|
43
|
+
* If both are passed, `history` (v5) takes precedence.
|
|
44
|
+
*
|
|
45
|
+
* Mount the hook in a component that stays rendered while the module is active
|
|
46
|
+
* AND is an ancestor of the header listener, so the listener subscribes before
|
|
47
|
+
* these emits fire — the bus has no replay for late subscribers.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} config
|
|
50
|
+
* @param {object} [config.history] React Router v5 history object
|
|
51
|
+
* @param {object} [config.location] React Router v6 location ({ pathname })
|
|
52
|
+
* @param {function} [config.navigate] React Router v6 navigate function
|
|
53
|
+
* @param {object} config.header { name, description, showBorder, showSettings }
|
|
54
|
+
* @param {string[]} [config.landingRoutes] Exact paths where the header is shown
|
|
55
|
+
* @param {function} [config.onSettingsClick] Modal settings handler (no route change)
|
|
56
|
+
* @param {string} [config.settingsRoute] Route-based settings URL (enables push/back)
|
|
57
|
+
* @param {object} [config.settingsHeader] { name, description } shown on settingsRoute
|
|
58
|
+
*/
|
|
59
|
+
export default function useMFEModuleHeader(config) {
|
|
60
|
+
const {
|
|
61
|
+
history,
|
|
62
|
+
location,
|
|
63
|
+
navigate,
|
|
64
|
+
header,
|
|
65
|
+
landingRoutes,
|
|
66
|
+
settingsRoute,
|
|
67
|
+
} = config;
|
|
68
|
+
|
|
69
|
+
const isRouteBased = Boolean(settingsRoute);
|
|
70
|
+
const showSettings = Boolean(header && header.showSettings);
|
|
71
|
+
|
|
72
|
+
// Latest config in a ref so callers can pass inline objects/handlers without
|
|
73
|
+
// memoizing: the long-lived listeners always read fresh values and never
|
|
74
|
+
// re-subscribe on identity changes (no stale closure, no churn).
|
|
75
|
+
const cfgRef = useRef(config);
|
|
76
|
+
cfgRef.current = config;
|
|
77
|
+
|
|
78
|
+
// Header register (non-route-based) + routes, once on mount.
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!isRouteBased) {
|
|
81
|
+
MFEEventBus.emit('module:header-register', header);
|
|
82
|
+
}
|
|
83
|
+
if (landingRoutes && landingRoutes.length) {
|
|
84
|
+
MFEEventBus.emit('module:header-routes', {
|
|
85
|
+
landingRoutes,
|
|
86
|
+
...(isRouteBased ? { settingsRoute } : {}),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return () => MFEEventBus.emit('module:header-clear', {});
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
// v5 navigation: single history.listen drives both header + route-change.
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!history) return undefined;
|
|
95
|
+
const { pathname } = history.location;
|
|
96
|
+
emitHeaderForPath(cfgRef.current, pathname);
|
|
97
|
+
emitRouteChange(pathname);
|
|
98
|
+
return history.listen(loc => {
|
|
99
|
+
emitHeaderForPath(cfgRef.current, loc.pathname);
|
|
100
|
+
emitRouteChange(loc.pathname);
|
|
101
|
+
});
|
|
102
|
+
}, [history]);
|
|
103
|
+
|
|
104
|
+
// v6 navigation: re-run on pathname change (skipped when v5 history is used).
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (history || !location) return undefined;
|
|
107
|
+
emitHeaderForPath(cfgRef.current, location.pathname);
|
|
108
|
+
emitRouteChange(location.pathname);
|
|
109
|
+
return undefined;
|
|
110
|
+
}, [history, location && location.pathname]);
|
|
111
|
+
|
|
112
|
+
// Settings button.
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!showSettings) return undefined;
|
|
115
|
+
return MFEEventBus.on('module:settings-click', () => {
|
|
116
|
+
if (isRouteBased) {
|
|
117
|
+
if (history) history.push(settingsRoute);
|
|
118
|
+
else if (navigate) navigate(settingsRoute);
|
|
119
|
+
} else if (cfgRef.current.onSettingsClick) {
|
|
120
|
+
cfgRef.current.onSettingsClick();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}, [history, navigate, settingsRoute, showSettings]);
|
|
124
|
+
|
|
125
|
+
// Back button (route-based settings only).
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!isRouteBased) return undefined;
|
|
128
|
+
return MFEEventBus.on('module:back-click', () => {
|
|
129
|
+
if (history) history.goBack();
|
|
130
|
+
else if (navigate) navigate(-1);
|
|
131
|
+
});
|
|
132
|
+
}, [history, navigate, settingsRoute]);
|
|
133
|
+
}
|