@flowerforce/flower-react 4.0.11-beta.0 → 4.0.11-beta.2
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/CHANGELOG.md +48 -0
- package/dist/index.cjs.js +280 -85
- package/dist/index.esm.js +276 -85
- package/dist/src/abacProvider/abacProvider.d.ts +18 -0
- package/dist/src/abacProvider/index.d.ts +1 -0
- package/dist/src/components/Flower.d.ts +3 -15
- package/dist/src/components/hooks/eventsHandlers.d.ts +5 -0
- package/dist/src/components/hooks/index.d.ts +5 -0
- package/dist/src/components/hooks/types/index.d.ts +29 -0
- package/dist/src/components/hooks/useBrowserNavigationSync.d.ts +12 -0
- package/dist/src/components/hooks/useDestroyFlow.d.ts +2 -0
- package/dist/src/components/hooks/useInitDevtools.d.ts +2 -0
- package/dist/src/components/hooks/useInitNodes.d.ts +2 -0
- package/dist/src/components/hooks/useSelectorsClient.d.ts +6 -0
- package/dist/src/components/useFlower/utils.d.ts +2 -0
- package/dist/src/features/selectors/selectors.d.ts +175 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/types/Flower.d.ts +15 -0
- package/dist/src/types/utilsTypes.d.ts +4 -0
- package/dist/src/utils.d.ts +2 -10
- package/package.json +12 -7
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,51 @@
|
|
1
|
+
## 3.7.2 (2025-04-23)
|
2
|
+
|
3
|
+
|
4
|
+
### 🩹 Fixes
|
5
|
+
|
6
|
+
- package lock ([a3bb210](https://github.com/flowerforce/flower/commit/a3bb210))
|
7
|
+
|
8
|
+
|
9
|
+
### 🧱 Updated Dependencies
|
10
|
+
|
11
|
+
- Updated flower-core to 3.5.2
|
12
|
+
|
13
|
+
## 3.7.1 (2025-04-19)
|
14
|
+
|
15
|
+
|
16
|
+
### 🧱 Updated Dependencies
|
17
|
+
|
18
|
+
- Updated flower-core to 3.5.1
|
19
|
+
|
20
|
+
## 3.7.0 (2025-04-19)
|
21
|
+
|
22
|
+
|
23
|
+
### 🚀 Features
|
24
|
+
|
25
|
+
- remove data blank from init nodes ([#80](https://github.com/flowerforce/flower/pull/80))
|
26
|
+
|
27
|
+
|
28
|
+
### 🧱 Updated Dependencies
|
29
|
+
|
30
|
+
- Updated flower-core to 3.5.0
|
31
|
+
|
32
|
+
## 3.6.0 (2025-04-19)
|
33
|
+
|
34
|
+
|
35
|
+
### 🚀 Features
|
36
|
+
|
37
|
+
- added remove value on hide element ([#68](https://github.com/flowerforce/flower/pull/68))
|
38
|
+
|
39
|
+
|
40
|
+
### 🩹 Fixes
|
41
|
+
|
42
|
+
- avoid validate hidden field ([#67](https://github.com/flowerforce/flower/pull/67))
|
43
|
+
|
44
|
+
|
45
|
+
### 🧱 Updated Dependencies
|
46
|
+
|
47
|
+
- Updated flower-core to 3.4.0
|
48
|
+
|
1
49
|
## 3.5.0 (2024-10-08)
|
2
50
|
|
3
51
|
|
package/dist/index.cjs.js
CHANGED
@@ -5,17 +5,42 @@ var _keyBy = require('lodash/keyBy');
|
|
5
5
|
var flowerCore = require('@flowerforce/flower-core');
|
6
6
|
var flowerReactContext = require('@flowerforce/flower-react-context');
|
7
7
|
var _get = require('lodash/get');
|
8
|
-
var toolkit = require('@reduxjs/toolkit');
|
9
8
|
var flowerReactStore = require('@flowerforce/flower-react-store');
|
9
|
+
var toolkit = require('@reduxjs/toolkit');
|
10
10
|
var reselect = require('reselect');
|
11
|
+
var flowerReactHistoryContext = require('@flowerforce/flower-react-history-context');
|
11
12
|
var flowerReactShared = require('@flowerforce/flower-react-shared');
|
12
13
|
var set = require('lodash/set');
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
const res = flowerCore.CoreUtils.generateNodesForFlowerJson(nodes);
|
17
|
-
return res;
|
15
|
+
const getRulesExists = (rules) => {
|
16
|
+
return Object.keys(rules).length ? flowerCore.FlowUtils.mapEdge(rules) : undefined;
|
18
17
|
};
|
18
|
+
function hasDisplayName(type) {
|
19
|
+
return (!!type &&
|
20
|
+
typeof type === 'object' &&
|
21
|
+
'displayName' in type &&
|
22
|
+
typeof type.displayName === 'string');
|
23
|
+
}
|
24
|
+
const generateNodesForFlowerJson = (nodes) => nodes
|
25
|
+
.filter((child) => typeof child === 'object' && child !== null && 'props' in child)
|
26
|
+
.filter((e) => !!_get(e, 'props.id'))
|
27
|
+
.map((e) => {
|
28
|
+
const rules = flowerCore.FlowUtils.makeRules(e.props.to ?? {});
|
29
|
+
const nextRules = getRulesExists(rules);
|
30
|
+
const children = e.props.data?.children;
|
31
|
+
const nodeType = hasDisplayName(e.type)
|
32
|
+
? e.type.displayName
|
33
|
+
: e.props.as || 'FlowerNode';
|
34
|
+
return {
|
35
|
+
nodeId: e.props.id,
|
36
|
+
nodeType,
|
37
|
+
nodeTitle: _get(e.props, 'data.title'),
|
38
|
+
children,
|
39
|
+
nextRules,
|
40
|
+
retain: e.props.retain,
|
41
|
+
disabled: e.props.disabled
|
42
|
+
};
|
43
|
+
});
|
19
44
|
|
20
45
|
const flowerReducer = toolkit.createSlice({
|
21
46
|
name: flowerCore.REDUCER_NAME.FLOWER_FLOW,
|
@@ -42,60 +67,37 @@ const selectFlowerHistory = (name) => reselect.createSelector(selectFlower(name)
|
|
42
67
|
const makeSelectNodesIds = (name) => reselect.createSelector(selectFlower(name), flowerCore.FlowerCoreStateSelectors.makeSelectNodesIds);
|
43
68
|
const makeSelectStartNodeId = (name) => reselect.createSelector(selectFlower(name), flowerCore.FlowerCoreStateSelectors.makeSelectStartNodeId);
|
44
69
|
const makeSelectCurrentNodeId = (name) => reselect.createSelector(selectFlower(name), makeSelectStartNodeId(name), flowerCore.FlowerCoreStateSelectors.makeSelectCurrentNodeId);
|
70
|
+
const makeSelectIsCurrentNode = (name) => reselect.createSelector(selectFlower(name), makeSelectCurrentNodeId(name), (flower, current) => flower.nodes[current]);
|
45
71
|
const makeSelectPrevNodeRetain = (name) => reselect.createSelector(makeSelectNodesIds(name), selectFlowerHistory(name), makeSelectCurrentNodeId(name), flowerCore.FlowerCoreStateSelectors.makeSelectPrevNodeRetain);
|
46
72
|
const makeSelectCurrentNodeDisabled = (name) => reselect.createSelector(makeSelectNodesIds(name), makeSelectCurrentNodeId(name), flowerCore.FlowerCoreStateSelectors.makeSelectCurrentNodeDisabled);
|
47
73
|
// dati nel flow selezionato
|
48
74
|
const makeSelectData = (name) => reselect.createSelector(selectFlowerDataNode(name), (data) => data?.data ?? {});
|
49
75
|
reselect.createSelector(selectGlobalData, flowerCore.FlowerStateUtils.getAllData);
|
50
76
|
|
51
|
-
|
52
|
-
|
53
|
-
/*
|
54
|
-
* FlowerClient
|
55
|
-
*/
|
56
|
-
const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null, initialState = {}, initialData }) => {
|
57
|
-
const flowName = name;
|
58
|
-
const { dispatch, store, useSelector } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
59
|
-
const one = React.useRef(false);
|
60
|
-
const [wsDevtools, setWsDevtools] = React.useState(flowerCore.devtoolState && _get(flowerCore.devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
|
61
|
-
// TODO could make that transformation in CoreUtils.generateNodesForFlowerJson
|
62
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps, max-len
|
63
|
-
const nodes = React.useMemo(
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
65
|
-
() => convertElements(React.Children.toArray(children)), [children]);
|
66
|
-
const nodeById = React.useMemo(() => _keyBy(React.Children.toArray(children), 'props.id'), [children]);
|
67
|
-
const isInitialized = useSelector(makeSelectStartNodeId(name));
|
68
|
-
const history = useSelector(selectFlowerHistory(name));
|
69
|
-
const current = useSelector(makeSelectCurrentNodeId(flowName));
|
70
|
-
const isDisabled = useSelector(makeSelectCurrentNodeDisabled(flowName));
|
71
|
-
const prevFlowerNodeId = useSelector(makeSelectPrevNodeRetain(flowName));
|
77
|
+
const useInitNodes = ({ one, nodes, name, startId, persist = false, initialData, initialState = {} }) => {
|
78
|
+
const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
72
79
|
React.useEffect(() => {
|
73
80
|
if (nodes.length > 0 && one.current === false) {
|
74
81
|
one.current = true;
|
75
82
|
dispatch(flowerActions.initNodes({
|
76
|
-
name
|
77
|
-
// @ts-expect-error FIX ME
|
83
|
+
name,
|
78
84
|
nodes,
|
79
85
|
startId: startId ?? '',
|
80
|
-
persist
|
86
|
+
persist,
|
81
87
|
initialState
|
82
88
|
}));
|
83
|
-
if (initialData) {
|
84
|
-
dispatch(flowerReactStore.flowerDataActions.initData({
|
85
|
-
rootName: flowName,
|
86
|
-
initialData: initialData ?? {}
|
87
|
-
}));
|
88
|
-
}
|
89
89
|
}
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
90
|
+
if (initialData) {
|
91
|
+
dispatch(flowerReactStore.flowerDataActions.initData({
|
92
|
+
rootName: name,
|
93
|
+
initialData: initialData ?? {}
|
94
|
+
}));
|
95
|
+
}
|
96
|
+
}, [dispatch, name, nodes, startId, initialData, initialState, persist]);
|
97
|
+
};
|
98
|
+
|
99
|
+
const useInitDevtools = ({ devtoolState, setWsDevtools, flowName }) => {
|
100
|
+
const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
99
101
|
React.useEffect(() => {
|
100
102
|
/* istanbul ignore next */
|
101
103
|
const eventCb = (msg) => {
|
@@ -109,32 +111,39 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
109
111
|
dispatch(flowerActions.setCurrentNode({ name: msg.name, node: msg.id }));
|
110
112
|
}
|
111
113
|
// if (msg.action === 'REPLACE_DATA' && msg.name === flowName) {
|
112
|
-
// dispatch(
|
113
|
-
// formActions.replaceData({ flowName: msg.name, value: msg.data })
|
114
|
-
// )
|
114
|
+
// dispatch(actions.replaceData({ flowName: msg.name, value: msg.data }))
|
115
115
|
// }
|
116
116
|
// if (msg.action === 'ADD_DATA' && msg.name === flowName) {
|
117
|
-
// dispatch(
|
117
|
+
// dispatch(actions.addData({ flowName: msg.name, value: msg.data }))
|
118
118
|
// }
|
119
119
|
};
|
120
120
|
/* istanbul ignore next */
|
121
|
-
if (
|
121
|
+
if (devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS__')) {
|
122
122
|
flowerCore.Emitter.on('flower-devtool-to-client', eventCb);
|
123
123
|
}
|
124
124
|
return () => {
|
125
125
|
/* istanbul ignore next */
|
126
|
-
if (
|
126
|
+
if (devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS__')) {
|
127
127
|
flowerCore.Emitter.off('flower-devtool-to-client', eventCb);
|
128
128
|
}
|
129
129
|
};
|
130
130
|
}, [dispatch, flowName]);
|
131
|
+
};
|
132
|
+
|
133
|
+
const useDestroyFlow = ({ flowName, one, persist }) => {
|
134
|
+
const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
131
135
|
React.useEffect(() => () => {
|
132
|
-
|
133
|
-
|
136
|
+
if (persist)
|
137
|
+
return;
|
138
|
+
if (one.current === true) {
|
134
139
|
one.current = false;
|
135
140
|
dispatch(flowerActions.destroy({ name: flowName }));
|
136
141
|
}
|
137
|
-
}, [dispatch, flowName,
|
142
|
+
}, [dispatch, flowName, persist]);
|
143
|
+
};
|
144
|
+
|
145
|
+
const useClientInitEvent = ({ flowName, isInitialized, wsDevtools }) => {
|
146
|
+
const { store } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
138
147
|
React.useEffect(() => {
|
139
148
|
/* istanbul ignore next */
|
140
149
|
if (isInitialized &&
|
@@ -150,7 +159,11 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
150
159
|
getState: store.getState
|
151
160
|
});
|
152
161
|
}
|
153
|
-
}, [
|
162
|
+
}, [flowName, wsDevtools, isInitialized, store]);
|
163
|
+
};
|
164
|
+
const useSetHistoryEvent = ({ flowName, isInitialized, wsDevtools }) => {
|
165
|
+
const { useSelector } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
166
|
+
const history = useSelector(selectFlowerHistory(flowName));
|
154
167
|
React.useEffect(() => {
|
155
168
|
/* istanbul ignore next */
|
156
169
|
if (isInitialized &&
|
@@ -164,7 +177,9 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
164
177
|
history
|
165
178
|
});
|
166
179
|
}
|
167
|
-
}, [
|
180
|
+
}, [flowName, history, wsDevtools, isInitialized]);
|
181
|
+
};
|
182
|
+
const useSetCurrentEvent = ({ current, flowName, isInitialized, wsDevtools }) => {
|
168
183
|
React.useEffect(() => {
|
169
184
|
if (!current)
|
170
185
|
return;
|
@@ -184,6 +199,9 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
184
199
|
});
|
185
200
|
}
|
186
201
|
}, [flowName, current, wsDevtools, isInitialized]);
|
202
|
+
};
|
203
|
+
const useFlowerNavigateEvent = ({ current, flowName, isInitialized, wsDevtools, isDisabled }) => {
|
204
|
+
const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
187
205
|
React.useEffect(() => {
|
188
206
|
if (!current)
|
189
207
|
return;
|
@@ -191,7 +209,10 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
191
209
|
if (!isInitialized)
|
192
210
|
return;
|
193
211
|
if (isDisabled) {
|
194
|
-
dispatch(
|
212
|
+
dispatch({
|
213
|
+
type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/next`,
|
214
|
+
payload: { flowName, disabled: true }
|
215
|
+
});
|
195
216
|
// eslint-disable-next-line no-underscore-dangle, no-undef
|
196
217
|
/* istanbul ignore next */
|
197
218
|
if (wsDevtools &&
|
@@ -214,7 +235,7 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
214
235
|
flowerCore.devtoolState &&
|
215
236
|
_get(flowerCore.devtoolState, '__FLOWER_DEVTOOLS__')) {
|
216
237
|
if (isInitialized === current)
|
217
|
-
return; // salto il primo
|
238
|
+
return; // salto il primo evento
|
218
239
|
flowerCore.Emitter.emit('flower-devtool-from-client', {
|
219
240
|
source: 'flower-client',
|
220
241
|
action: 'SET_SELECTED',
|
@@ -223,15 +244,55 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
223
244
|
time: new Date()
|
224
245
|
});
|
225
246
|
}
|
226
|
-
}, [
|
227
|
-
|
228
|
-
|
247
|
+
}, [dispatch, flowName, current, isDisabled, wsDevtools, isInitialized]);
|
248
|
+
};
|
249
|
+
|
250
|
+
const useSelectorsClient = (flowName) => {
|
251
|
+
const { useSelector } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
252
|
+
const isInitialized = useSelector(makeSelectStartNodeId(flowName));
|
253
|
+
const current = useSelector(makeSelectCurrentNodeId(flowName));
|
254
|
+
const isDisabled = useSelector(makeSelectCurrentNodeDisabled(flowName));
|
255
|
+
const prevFlowerNodeId = useSelector(makeSelectPrevNodeRetain(flowName));
|
256
|
+
return {
|
257
|
+
isInitialized,
|
258
|
+
current,
|
259
|
+
isDisabled,
|
260
|
+
prevFlowerNodeId
|
261
|
+
};
|
262
|
+
};
|
263
|
+
|
264
|
+
/*
|
265
|
+
* FlowerClient
|
266
|
+
*/
|
267
|
+
const FlowerClient = ({ children, name, destroyOnUnmount = true, persist = false, startId = null, initialState = {}, initialData }) => {
|
268
|
+
const flowName = name;
|
269
|
+
const _persist = persist || !destroyOnUnmount;
|
270
|
+
const one = React.useRef(false);
|
271
|
+
const [wsDevtools, setWsDevtools] = React.useState(flowerCore.devtoolState && _get(flowerCore.devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
|
272
|
+
const nodes = React.useMemo(() => generateNodesForFlowerJson(React.Children.toArray(children)), [children]);
|
273
|
+
const nodeById = React.useMemo(() => _keyBy(React.Children.toArray(children), 'props.id'), [children]);
|
274
|
+
const { current, isDisabled, isInitialized, prevFlowerNodeId } = useSelectorsClient(flowName);
|
275
|
+
useInitNodes({
|
276
|
+
name,
|
277
|
+
nodes,
|
278
|
+
one,
|
279
|
+
initialData,
|
280
|
+
initialState,
|
281
|
+
persist: _persist,
|
282
|
+
startId
|
283
|
+
});
|
284
|
+
useDestroyFlow({ flowName, one, persist: _persist });
|
285
|
+
useInitDevtools({ devtoolState: flowerCore.devtoolState, setWsDevtools, flowName });
|
286
|
+
useClientInitEvent({ flowName, isInitialized, wsDevtools });
|
287
|
+
useSetHistoryEvent({ flowName, isInitialized, wsDevtools });
|
288
|
+
useSetCurrentEvent({ current, flowName, isInitialized, wsDevtools });
|
289
|
+
useFlowerNavigateEvent({
|
229
290
|
current,
|
291
|
+
flowName,
|
230
292
|
isDisabled,
|
231
|
-
|
232
|
-
wsDevtools
|
233
|
-
|
234
|
-
]);
|
293
|
+
isInitialized,
|
294
|
+
wsDevtools
|
295
|
+
});
|
235
296
|
const currentNodeId = prevFlowerNodeId || current;
|
236
297
|
const contextValues = React.useMemo(() => ({
|
237
298
|
name: flowName,
|
@@ -245,10 +306,10 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
245
306
|
React.createElement(flowerReactContext.FlowerReactProvider, { value: prevContextValues }, nodeById[currentNodeId]),
|
246
307
|
React.createElement(flowerReactContext.FlowerReactProvider, { value: contextValues }, !isDisabled && current !== currentNodeId && nodeById[current]))) : null;
|
247
308
|
};
|
248
|
-
const component$
|
249
|
-
component$
|
309
|
+
const component$8 = React.memo(FlowerClient);
|
310
|
+
component$8.displayName = 'Flower';
|
250
311
|
// workaround for let typescript read JSX component as a valid JSX element using react 19(?)
|
251
|
-
const Flower = component$
|
312
|
+
const Flower = component$8;
|
252
313
|
|
253
314
|
const FlowAction = ({ children, onEnter, onExit }) => {
|
254
315
|
React.useEffect(() => {
|
@@ -259,13 +320,12 @@ const FlowAction = ({ children, onEnter, onExit }) => {
|
|
259
320
|
}, [onEnter, onExit]);
|
260
321
|
return React.createElement(React.Fragment, null, children);
|
261
322
|
};
|
262
|
-
const component$
|
263
|
-
component$
|
264
|
-
const FlowerAction = component$
|
323
|
+
const component$7 = React.memo(FlowAction);
|
324
|
+
component$7.displayName = 'FlowerAction';
|
325
|
+
const FlowerAction = component$7;
|
265
326
|
|
266
327
|
const _FlowerComponent = ({ children }) => children;
|
267
|
-
|
268
|
-
const FlowerComponent = component$7;
|
328
|
+
React.memo(_FlowerComponent);
|
269
329
|
|
270
330
|
const _FlowerFlow = ({ children, onEnter, onExit }) => {
|
271
331
|
React.useEffect(() => {
|
@@ -311,6 +371,43 @@ const makeActionPayloadOnReset = makeActionPayload(ACTION_TYPES.reset, PAYLOAD_K
|
|
311
371
|
const makeActionPayloadOnNode = makeActionPayload(ACTION_TYPES.jump, PAYLOAD_KEYS_NEEDED.jump);
|
312
372
|
const makeActionPayloadOnNext = makeActionPayload(ACTION_TYPES.next, PAYLOAD_KEYS_NEEDED.next);
|
313
373
|
const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLOAD_KEYS_NEEDED.restart);
|
374
|
+
const handleHistoryStackChange = (currentIndex, currentNode, flowName, withUrl) => {
|
375
|
+
const historyNode = `/${flowName}/${currentNode.nodeId}`;
|
376
|
+
if (currentNode.nodeType === 'FlowerAction')
|
377
|
+
return currentIndex;
|
378
|
+
const nextIndex = currentIndex + 1;
|
379
|
+
if (history.state?.index !== nextIndex) {
|
380
|
+
window.history.pushState({
|
381
|
+
index: nextIndex,
|
382
|
+
stack: [...(window.history.state.stack ?? []), historyNode]
|
383
|
+
}, '', withUrl ? historyNode : '');
|
384
|
+
}
|
385
|
+
return nextIndex;
|
386
|
+
};
|
387
|
+
|
388
|
+
const useHistorySync = ({ backAction, nextAction }) => {
|
389
|
+
const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
390
|
+
const { index, isActive, setIndex, withUrl } = flowerReactHistoryContext.useHistoryContext();
|
391
|
+
React.useEffect(() => {
|
392
|
+
if (!isActive)
|
393
|
+
return;
|
394
|
+
const initialIndex = window.history.state?.index ?? 0;
|
395
|
+
setIndex(initialIndex);
|
396
|
+
window.history.replaceState({ index: initialIndex, stack: [...(window.history.state?.stack ?? [])] }, withUrl ? '/' : '', '');
|
397
|
+
const onPopState = (event) => {
|
398
|
+
const newIndex = window.history.state?.index ?? 0;
|
399
|
+
if (newIndex > index) {
|
400
|
+
nextAction();
|
401
|
+
}
|
402
|
+
if (newIndex < index) {
|
403
|
+
backAction();
|
404
|
+
}
|
405
|
+
setIndex(newIndex);
|
406
|
+
};
|
407
|
+
window.addEventListener('popstate', onPopState);
|
408
|
+
return () => window.removeEventListener('popstate', onPopState);
|
409
|
+
}, [dispatch, backAction, nextAction]);
|
410
|
+
};
|
314
411
|
|
315
412
|
/** This hook allows you to read flow informations, such as the flowName and ID of the current node.
|
316
413
|
*
|
@@ -332,10 +429,18 @@ const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLO
|
|
332
429
|
*/
|
333
430
|
const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
334
431
|
const { name: flowNameDefault, initialData } = React.useContext(flowerReactContext.FlowerReactContext);
|
432
|
+
const { index, isActive, setIndex, withUrl } = flowerReactHistoryContext.useHistoryContext();
|
335
433
|
const { store, dispatch, useSelector } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
336
434
|
const flowName = (customFlowName || name || flowNameDefault);
|
337
435
|
const nodeId = useSelector(makeSelectCurrentNodeId(flowName ?? ''));
|
436
|
+
const currentNode = useSelector(makeSelectIsCurrentNode(flowName ?? ''));
|
338
437
|
const startId = useSelector(makeSelectStartNodeId(flowName ?? ''));
|
438
|
+
React.useEffect(() => {
|
439
|
+
if (currentNode.nodeType === 'FlowerAction')
|
440
|
+
return;
|
441
|
+
window.history.replaceState({ ...window.history.state }, '', withUrl ? `/${flowName}/${nodeId}` : '');
|
442
|
+
}, [nodeId, flowName, withUrl, currentNode]);
|
443
|
+
const stack = React.useMemo(() => window.history.state?.stack?.map((path) => path.split('/')[1]), [window.history.state?.stack]);
|
339
444
|
const emitNavigateEvent = React.useCallback(
|
340
445
|
//TODO check this function is needed
|
341
446
|
(params) => {
|
@@ -357,18 +462,37 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
|
357
462
|
const { type, payload } = makeActionPayloadOnNext(flowName, params);
|
358
463
|
dispatch({
|
359
464
|
type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`,
|
360
|
-
payload: {
|
361
|
-
...payload,
|
362
|
-
data: store.getState()
|
363
|
-
}
|
465
|
+
payload: { ...payload, data: store.getState() }
|
364
466
|
});
|
467
|
+
if (isActive) {
|
468
|
+
setIndex(handleHistoryStackChange(index, currentNode, flowName, withUrl));
|
469
|
+
}
|
365
470
|
emitNavigateEvent({ type, payload });
|
366
471
|
}, [dispatch, emitNavigateEvent, flowName, store]);
|
472
|
+
/**
|
473
|
+
* By doing this, we have a full control over flower navigation from both our buttons and browser back and forward navigation
|
474
|
+
* In order to trigger back correctly, trigger a real history back
|
475
|
+
* If you use replaceState({ index: 2 }) while at index 3,
|
476
|
+
* you visually move to step 2, but the browser still sees you at step 3.
|
477
|
+
* As a result:
|
478
|
+
* - The browser's Forward button stays disabled
|
479
|
+
* - No popstate event is triggered
|
480
|
+
* - The history flow is broken
|
481
|
+
* Use history.back() instead to preserve proper browser navigation.
|
482
|
+
*/
|
483
|
+
const interceptBack = () => {
|
484
|
+
window.history.back();
|
485
|
+
};
|
367
486
|
const back = React.useCallback((param) => {
|
368
|
-
const { type, payload } = makeActionPayloadOnBack(flowName, param);
|
369
|
-
dispatch({
|
487
|
+
const { type, payload } = makeActionPayloadOnBack(isActive ? (stack?.[stack?.length - 1] ?? flowName) : flowName, param);
|
488
|
+
dispatch({
|
489
|
+
type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`,
|
490
|
+
payload
|
491
|
+
});
|
492
|
+
setIndex(index - 1);
|
370
493
|
emitNavigateEvent({ type, payload });
|
371
|
-
}, [dispatch, emitNavigateEvent, flowName]);
|
494
|
+
}, [dispatch, emitNavigateEvent, flowName, stack]);
|
495
|
+
useHistorySync({ backAction: back, nextAction: next });
|
372
496
|
const restart = React.useCallback((param) => {
|
373
497
|
const { type, payload } = makeActionPayloadOnRestart(flowName, param);
|
374
498
|
dispatch({ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
|
@@ -377,10 +501,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
|
377
501
|
const reset = React.useCallback((param) => {
|
378
502
|
const { type, payload } = makeActionPayloadOnReset(flowName, typeof param === 'string'
|
379
503
|
? { node: param, initialData }
|
380
|
-
: {
|
381
|
-
...param,
|
382
|
-
initialData
|
383
|
-
});
|
504
|
+
: { ...param, initialData });
|
384
505
|
dispatch({ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
|
385
506
|
emitNavigateEvent({ type, payload });
|
386
507
|
}, [dispatch, emitNavigateEvent, flowName, initialData]);
|
@@ -403,7 +524,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
|
403
524
|
startId,
|
404
525
|
next,
|
405
526
|
jump,
|
406
|
-
back,
|
527
|
+
back: isActive ? interceptBack : back,
|
407
528
|
reset,
|
408
529
|
restart
|
409
530
|
};
|
@@ -643,13 +764,86 @@ const createSliceWithFlower = (createSliceOptions) => {
|
|
643
764
|
return slice;
|
644
765
|
};
|
645
766
|
|
767
|
+
const AbacContext = React.createContext({
|
768
|
+
can: () => false,
|
769
|
+
allowedActions: () => [],
|
770
|
+
loaded: false,
|
771
|
+
rawRules: null
|
772
|
+
});
|
773
|
+
const AbacProvider = ({ children, rules, rulesPath, fetchOptions, denyByDefault = true }) => {
|
774
|
+
const [loaded, setLoaded] = React.useState(flowerCore.isAbacInitialized());
|
775
|
+
const [localRawRules, setLocalRawRules] = React.useState(flowerCore.getRawRules());
|
776
|
+
React.useEffect(() => {
|
777
|
+
if (rules) {
|
778
|
+
flowerCore.initAbac(rules);
|
779
|
+
setLocalRawRules(rules);
|
780
|
+
setLoaded(true);
|
781
|
+
return;
|
782
|
+
}
|
783
|
+
if (!rulesPath) {
|
784
|
+
setLoaded(false);
|
785
|
+
setLocalRawRules(null);
|
786
|
+
return;
|
787
|
+
}
|
788
|
+
let mounted = true;
|
789
|
+
const init = async () => {
|
790
|
+
try {
|
791
|
+
const res = await fetch(rulesPath, fetchOptions);
|
792
|
+
if (!res.ok)
|
793
|
+
throw new Error(`HTTP ${res.status}`);
|
794
|
+
const json = (await res.json());
|
795
|
+
if (!mounted)
|
796
|
+
return;
|
797
|
+
flowerCore.initAbac(json);
|
798
|
+
setLocalRawRules(json);
|
799
|
+
setLoaded(true);
|
800
|
+
}
|
801
|
+
catch (err) {
|
802
|
+
console.error('ABAC: failed to load rules from', rulesPath, err);
|
803
|
+
setLocalRawRules(null);
|
804
|
+
setLoaded(false);
|
805
|
+
}
|
806
|
+
};
|
807
|
+
init();
|
808
|
+
return () => {
|
809
|
+
mounted = false;
|
810
|
+
};
|
811
|
+
}, [rules, rulesPath, fetchOptions]);
|
812
|
+
const can = React.useCallback((ctx) => {
|
813
|
+
if (!flowerCore.isAbacInitialized())
|
814
|
+
return !denyByDefault;
|
815
|
+
const subject = flowerCore.getSubject();
|
816
|
+
return (flowerCore.getAbacEngine()?.decide({ subject: subject ?? {}, ...ctx }) === 'Permit');
|
817
|
+
}, []);
|
818
|
+
const allowedActions = React.useCallback((resource, actions = ['read', 'create', 'update', 'delete']) => {
|
819
|
+
if (!flowerCore.isAbacInitialized())
|
820
|
+
return denyByDefault ? [] : actions.slice();
|
821
|
+
const subject = flowerCore.getSubject();
|
822
|
+
return flowerCore.getAbacEngine()?.allowedActions({ subject: subject ?? {}, resource, environment: undefined }, actions);
|
823
|
+
}, []);
|
824
|
+
const value = React.useMemo(() => ({
|
825
|
+
can,
|
826
|
+
allowedActions,
|
827
|
+
loaded,
|
828
|
+
rawRules: localRawRules
|
829
|
+
}), [loaded, localRawRules]);
|
830
|
+
return React.createElement(AbacContext.Provider, { value: value }, children);
|
831
|
+
};
|
832
|
+
function useAbac() {
|
833
|
+
return React.useContext(AbacContext);
|
834
|
+
}
|
835
|
+
|
646
836
|
Object.defineProperty(exports, "createApi", {
|
647
837
|
enumerable: true,
|
648
838
|
get: function () { return flowerReactStore.createApi; }
|
649
839
|
});
|
840
|
+
Object.defineProperty(exports, "HistoryContextProvider", {
|
841
|
+
enumerable: true,
|
842
|
+
get: function () { return flowerReactHistoryContext.HistoryContextProvider; }
|
843
|
+
});
|
844
|
+
exports.AbacProvider = AbacProvider;
|
650
845
|
exports.Flower = Flower;
|
651
846
|
exports.FlowerAction = FlowerAction;
|
652
|
-
exports.FlowerComponent = FlowerComponent;
|
653
847
|
exports.FlowerFlow = FlowerFlow;
|
654
848
|
exports.FlowerNavigate = FlowerNavigate;
|
655
849
|
exports.FlowerNode = FlowerNode;
|
@@ -662,5 +856,6 @@ exports.createStoreWithFlower = createStoreWithFlower;
|
|
662
856
|
exports.flowerReducers = flowerReducers;
|
663
857
|
exports.makeSelectData = makeSelectData;
|
664
858
|
exports.reducerFlower = reducerFlower;
|
859
|
+
exports.useAbac = useAbac;
|
665
860
|
exports.useFlower = useFlower;
|
666
861
|
exports.useFlowerNavigate = useFlowerNavigate;
|