@capillarytech/cap-ui-utils 3.0.17-alpha.0 → 3.0.18
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/package.json +1 -1
- package/utils/useMFEModuleHeader.js +75 -38
package/package.json
CHANGED
|
@@ -2,28 +2,36 @@ import { useEffect, useRef } from 'react';
|
|
|
2
2
|
import MFEEventBus from './mfeEventBus';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Register the module header for a given path. Resolution order:
|
|
6
|
+
* 1. settingsRoute prefix -> settings header (back button, no settings icon)
|
|
7
|
+
* 2. first matching pathConfigs entry -> its header
|
|
8
|
+
* 3. fallback `header`
|
|
9
|
+
* Only used when the header is path-aware (settingsRoute or pathConfigs set);
|
|
10
|
+
* a purely static header is registered once on mount instead.
|
|
10
11
|
*/
|
|
11
|
-
const
|
|
12
|
-
const { header, settingsRoute, settingsHeader } = cfg;
|
|
13
|
-
if (
|
|
14
|
-
if (pathname.startsWith(settingsRoute)) {
|
|
12
|
+
const registerHeaderForPath = (cfg, pathname) => {
|
|
13
|
+
const { pathConfigs, header, settingsRoute, settingsHeader } = cfg;
|
|
14
|
+
if (settingsRoute && pathname.startsWith(settingsRoute)) {
|
|
15
15
|
MFEEventBus.emit('module:header-register', {
|
|
16
16
|
...header,
|
|
17
17
|
...settingsHeader,
|
|
18
18
|
showSettings: false,
|
|
19
19
|
showBackButton: true,
|
|
20
20
|
});
|
|
21
|
-
|
|
22
|
-
MFEEventBus.emit('module:header-register', header);
|
|
21
|
+
return;
|
|
23
22
|
}
|
|
23
|
+
if (pathConfigs && pathConfigs.length) {
|
|
24
|
+
const hit = pathConfigs.find(({ match }) =>
|
|
25
|
+
typeof match === 'function' ? match(pathname) : pathname.startsWith(match));
|
|
26
|
+
if (hit) {
|
|
27
|
+
MFEEventBus.emit('module:header-register', hit.header);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (header) MFEEventBus.emit('module:header-register', header);
|
|
24
32
|
};
|
|
25
33
|
|
|
26
|
-
const
|
|
34
|
+
const emitRouteChangeFor = pathname =>
|
|
27
35
|
MFEEventBus.emit('module:route-change', { pathname });
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -31,12 +39,13 @@ const emitRouteChange = pathname =>
|
|
|
31
39
|
*
|
|
32
40
|
* Common hook wrapping the MFEEventBus module-header wiring duplicated across
|
|
33
41
|
* every remote app. Registers the module header, keeps it in sync on
|
|
34
|
-
* navigation (including browser back), and handles the settings
|
|
35
|
-
*
|
|
42
|
+
* navigation (including browser back), and handles the settings/back buttons.
|
|
43
|
+
* Scales from a single static header (launchpad-style) up to a per-path header
|
|
44
|
+
* map with a header dropdown, extra buttons and arbitrary custom events (neo).
|
|
36
45
|
*
|
|
37
|
-
* Router-version agnostic: it never calls useLocation/useNavigate itself
|
|
38
|
-
*
|
|
39
|
-
*
|
|
46
|
+
* Router-version agnostic: it never calls useLocation/useNavigate itself (those
|
|
47
|
+
* throw without the matching Router context and break the rules of hooks when
|
|
48
|
+
* called conditionally). The caller passes the router primitives:
|
|
40
49
|
* - React Router v5: pass `history` (history.location/.listen/.push/.goBack)
|
|
41
50
|
* - React Router v6: pass `location` (from useLocation) and `navigate`
|
|
42
51
|
* (from useNavigate)
|
|
@@ -50,11 +59,20 @@ const emitRouteChange = pathname =>
|
|
|
50
59
|
* @param {object} [config.history] React Router v5 history object
|
|
51
60
|
* @param {object} [config.location] React Router v6 location ({ pathname })
|
|
52
61
|
* @param {function} [config.navigate] React Router v6 navigate function
|
|
53
|
-
* @param {object}
|
|
62
|
+
* @param {object} [config.header] Default header, e.g.
|
|
63
|
+
* { name, description, showBorder, showSettings, ...extra }
|
|
64
|
+
* Any extra fields (showSelect, selectOptions,
|
|
65
|
+
* extraButton, settingsRoute, ...) pass straight
|
|
66
|
+
* through to the host.
|
|
67
|
+
* @param {Array} [config.pathConfigs] Path-aware header map, first match wins:
|
|
68
|
+
* [{ match: string|((pathname)=>boolean), header }]
|
|
54
69
|
* @param {string[]} [config.landingRoutes] Exact paths where the header is shown
|
|
55
|
-
* @param {function} [config.onSettingsClick]
|
|
56
|
-
* @param {string} [config.settingsRoute] Route-based settings URL (enables push
|
|
70
|
+
* @param {function} [config.onSettingsClick] Settings handler (modal / custom nav)
|
|
71
|
+
* @param {string} [config.settingsRoute] Route-based settings URL (enables push + back)
|
|
57
72
|
* @param {object} [config.settingsHeader] { name, description } shown on settingsRoute
|
|
73
|
+
* @param {object} [config.listeners] { eventName: (payload)=>void } custom bus listeners
|
|
74
|
+
* @param {boolean} [config.emitRouteChange] Emit module:route-change on nav (default true).
|
|
75
|
+
* Set false to preserve repos that never emitted it.
|
|
58
76
|
*/
|
|
59
77
|
export default function useMFEModuleHeader(config) {
|
|
60
78
|
const {
|
|
@@ -64,10 +82,15 @@ export default function useMFEModuleHeader(config) {
|
|
|
64
82
|
header,
|
|
65
83
|
landingRoutes,
|
|
66
84
|
settingsRoute,
|
|
85
|
+
pathConfigs,
|
|
86
|
+
onSettingsClick,
|
|
87
|
+
listeners,
|
|
88
|
+
emitRouteChange = true,
|
|
67
89
|
} = config;
|
|
68
90
|
|
|
69
|
-
const
|
|
70
|
-
const
|
|
91
|
+
const isPathAware = Boolean(settingsRoute) || Boolean(pathConfigs && pathConfigs.length);
|
|
92
|
+
const hasSettingsClick = Boolean(settingsRoute) || Boolean(onSettingsClick);
|
|
93
|
+
const listenerNames = listeners ? Object.keys(listeners).sort().join(',') : '';
|
|
71
94
|
|
|
72
95
|
// Latest config in a ref so callers can pass inline objects/handlers without
|
|
73
96
|
// memoizing: the long-lived listeners always read fresh values and never
|
|
@@ -75,59 +98,73 @@ export default function useMFEModuleHeader(config) {
|
|
|
75
98
|
const cfgRef = useRef(config);
|
|
76
99
|
cfgRef.current = config;
|
|
77
100
|
|
|
78
|
-
//
|
|
101
|
+
// Static header register (path-unaware) + routes, once on mount.
|
|
79
102
|
useEffect(() => {
|
|
80
|
-
if (!
|
|
103
|
+
if (!isPathAware && header) {
|
|
81
104
|
MFEEventBus.emit('module:header-register', header);
|
|
82
105
|
}
|
|
83
106
|
if (landingRoutes && landingRoutes.length) {
|
|
84
107
|
MFEEventBus.emit('module:header-routes', {
|
|
85
108
|
landingRoutes,
|
|
86
|
-
...(
|
|
109
|
+
...(settingsRoute ? { settingsRoute } : {}),
|
|
87
110
|
});
|
|
88
111
|
}
|
|
89
112
|
return () => MFEEventBus.emit('module:header-clear', {});
|
|
90
113
|
}, []);
|
|
91
114
|
|
|
92
|
-
// v5 navigation: single history.listen drives
|
|
115
|
+
// v5 navigation: single history.listen drives path-aware header + route-change.
|
|
93
116
|
useEffect(() => {
|
|
94
117
|
if (!history) return undefined;
|
|
95
118
|
const { pathname } = history.location;
|
|
96
|
-
|
|
97
|
-
emitRouteChange(pathname);
|
|
119
|
+
if (isPathAware) registerHeaderForPath(cfgRef.current, pathname);
|
|
120
|
+
if (emitRouteChange) emitRouteChangeFor(pathname);
|
|
98
121
|
return history.listen(loc => {
|
|
99
|
-
|
|
100
|
-
emitRouteChange(loc.pathname);
|
|
122
|
+
if (isPathAware) registerHeaderForPath(cfgRef.current, loc.pathname);
|
|
123
|
+
if (emitRouteChange) emitRouteChangeFor(loc.pathname);
|
|
101
124
|
});
|
|
102
125
|
}, [history]);
|
|
103
126
|
|
|
104
127
|
// v6 navigation: re-run on pathname change (skipped when v5 history is used).
|
|
105
128
|
useEffect(() => {
|
|
106
129
|
if (history || !location) return undefined;
|
|
107
|
-
|
|
108
|
-
emitRouteChange(location.pathname);
|
|
130
|
+
if (isPathAware) registerHeaderForPath(cfgRef.current, location.pathname);
|
|
131
|
+
if (emitRouteChange) emitRouteChangeFor(location.pathname);
|
|
109
132
|
return undefined;
|
|
110
133
|
}, [history, location && location.pathname]);
|
|
111
134
|
|
|
112
135
|
// Settings button.
|
|
113
136
|
useEffect(() => {
|
|
114
|
-
if (!
|
|
137
|
+
if (!hasSettingsClick) return undefined;
|
|
115
138
|
return MFEEventBus.on('module:settings-click', () => {
|
|
116
|
-
if (
|
|
139
|
+
if (settingsRoute) {
|
|
117
140
|
if (history) history.push(settingsRoute);
|
|
118
141
|
else if (navigate) navigate(settingsRoute);
|
|
119
142
|
} else if (cfgRef.current.onSettingsClick) {
|
|
120
143
|
cfgRef.current.onSettingsClick();
|
|
121
144
|
}
|
|
122
145
|
});
|
|
123
|
-
}, [history, navigate, settingsRoute,
|
|
146
|
+
}, [history, navigate, settingsRoute, hasSettingsClick]);
|
|
124
147
|
|
|
125
|
-
// Back button (route-based settings
|
|
148
|
+
// Back button (route-based settings or path-aware headers with a back button).
|
|
126
149
|
useEffect(() => {
|
|
127
|
-
if (!
|
|
150
|
+
if (!isPathAware) return undefined;
|
|
128
151
|
return MFEEventBus.on('module:back-click', () => {
|
|
129
152
|
if (history) history.goBack();
|
|
130
153
|
else if (navigate) navigate(-1);
|
|
131
154
|
});
|
|
132
|
-
}, [history, navigate,
|
|
155
|
+
}, [history, navigate, isPathAware]);
|
|
156
|
+
|
|
157
|
+
// Arbitrary custom event listeners. Handlers read from the ref so they can be
|
|
158
|
+
// inline; re-subscribes only when the set of event names changes.
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const map = cfgRef.current.listeners;
|
|
161
|
+
if (!map) return undefined;
|
|
162
|
+
const unsubs = Object.keys(map).map(name =>
|
|
163
|
+
MFEEventBus.on(name, payload => {
|
|
164
|
+
const fn = cfgRef.current.listeners && cfgRef.current.listeners[name];
|
|
165
|
+
if (fn) fn(payload);
|
|
166
|
+
}));
|
|
167
|
+
return () => unsubs.forEach(unsub => unsub());
|
|
168
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
169
|
+
}, [listenerNames]);
|
|
133
170
|
}
|