@flowerforce/flower-react 4.0.10-beta.0 → 4.0.11-beta.1
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 +206 -85
- package/dist/index.esm.js +204 -85
- 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 +2 -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,7 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
191
209
|
if (!isInitialized)
|
192
210
|
return;
|
193
211
|
if (isDisabled) {
|
194
|
-
dispatch(
|
212
|
+
dispatch({ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/next`, payload: { flowName, disabled: true } });
|
195
213
|
// eslint-disable-next-line no-underscore-dangle, no-undef
|
196
214
|
/* istanbul ignore next */
|
197
215
|
if (wsDevtools &&
|
@@ -214,7 +232,7 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
214
232
|
flowerCore.devtoolState &&
|
215
233
|
_get(flowerCore.devtoolState, '__FLOWER_DEVTOOLS__')) {
|
216
234
|
if (isInitialized === current)
|
217
|
-
return; // salto il primo
|
235
|
+
return; // salto il primo evento
|
218
236
|
flowerCore.Emitter.emit('flower-devtool-from-client', {
|
219
237
|
source: 'flower-client',
|
220
238
|
action: 'SET_SELECTED',
|
@@ -223,15 +241,55 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
223
241
|
time: new Date()
|
224
242
|
});
|
225
243
|
}
|
226
|
-
}, [
|
227
|
-
|
228
|
-
|
244
|
+
}, [dispatch, flowName, current, isDisabled, wsDevtools, isInitialized]);
|
245
|
+
};
|
246
|
+
|
247
|
+
const useSelectorsClient = (flowName) => {
|
248
|
+
const { useSelector } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
249
|
+
const isInitialized = useSelector(makeSelectStartNodeId(flowName));
|
250
|
+
const current = useSelector(makeSelectCurrentNodeId(flowName));
|
251
|
+
const isDisabled = useSelector(makeSelectCurrentNodeDisabled(flowName));
|
252
|
+
const prevFlowerNodeId = useSelector(makeSelectPrevNodeRetain(flowName));
|
253
|
+
return {
|
254
|
+
isInitialized,
|
229
255
|
current,
|
230
256
|
isDisabled,
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
257
|
+
prevFlowerNodeId
|
258
|
+
};
|
259
|
+
};
|
260
|
+
|
261
|
+
/*
|
262
|
+
* FlowerClient
|
263
|
+
*/
|
264
|
+
const FlowerClient = ({ children, name, destroyOnUnmount = true, persist = false, startId = null, initialState = {}, initialData }) => {
|
265
|
+
const flowName = name;
|
266
|
+
const _persist = persist || !destroyOnUnmount;
|
267
|
+
const one = React.useRef(false);
|
268
|
+
const [wsDevtools, setWsDevtools] = React.useState(flowerCore.devtoolState && _get(flowerCore.devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
|
269
|
+
const nodes = React.useMemo(() => generateNodesForFlowerJson(React.Children.toArray(children)), [children]);
|
270
|
+
const nodeById = React.useMemo(() => _keyBy(React.Children.toArray(children), 'props.id'), [children]);
|
271
|
+
const { current, isDisabled, isInitialized, prevFlowerNodeId } = useSelectorsClient(flowName);
|
272
|
+
useInitNodes({
|
273
|
+
name,
|
274
|
+
nodes,
|
275
|
+
one,
|
276
|
+
initialData,
|
277
|
+
initialState,
|
278
|
+
persist: _persist,
|
279
|
+
startId
|
280
|
+
});
|
281
|
+
useDestroyFlow({ flowName, one, persist: _persist });
|
282
|
+
useInitDevtools({ devtoolState: flowerCore.devtoolState, setWsDevtools, flowName });
|
283
|
+
useClientInitEvent({ flowName, isInitialized, wsDevtools });
|
284
|
+
useSetHistoryEvent({ flowName, isInitialized, wsDevtools });
|
285
|
+
useSetCurrentEvent({ current, flowName, isInitialized, wsDevtools });
|
286
|
+
useFlowerNavigateEvent({
|
287
|
+
current,
|
288
|
+
flowName,
|
289
|
+
isDisabled,
|
290
|
+
isInitialized,
|
291
|
+
wsDevtools
|
292
|
+
});
|
235
293
|
const currentNodeId = prevFlowerNodeId || current;
|
236
294
|
const contextValues = React.useMemo(() => ({
|
237
295
|
name: flowName,
|
@@ -245,10 +303,10 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
|
|
245
303
|
React.createElement(flowerReactContext.FlowerReactProvider, { value: prevContextValues }, nodeById[currentNodeId]),
|
246
304
|
React.createElement(flowerReactContext.FlowerReactProvider, { value: contextValues }, !isDisabled && current !== currentNodeId && nodeById[current]))) : null;
|
247
305
|
};
|
248
|
-
const component$
|
249
|
-
component$
|
306
|
+
const component$8 = React.memo(FlowerClient);
|
307
|
+
component$8.displayName = 'Flower';
|
250
308
|
// workaround for let typescript read JSX component as a valid JSX element using react 19(?)
|
251
|
-
const Flower = component$
|
309
|
+
const Flower = component$8;
|
252
310
|
|
253
311
|
const FlowAction = ({ children, onEnter, onExit }) => {
|
254
312
|
React.useEffect(() => {
|
@@ -259,13 +317,12 @@ const FlowAction = ({ children, onEnter, onExit }) => {
|
|
259
317
|
}, [onEnter, onExit]);
|
260
318
|
return React.createElement(React.Fragment, null, children);
|
261
319
|
};
|
262
|
-
const component$
|
263
|
-
component$
|
264
|
-
const FlowerAction = component$
|
320
|
+
const component$7 = React.memo(FlowAction);
|
321
|
+
component$7.displayName = 'FlowerAction';
|
322
|
+
const FlowerAction = component$7;
|
265
323
|
|
266
324
|
const _FlowerComponent = ({ children }) => children;
|
267
|
-
|
268
|
-
const FlowerComponent = component$7;
|
325
|
+
React.memo(_FlowerComponent);
|
269
326
|
|
270
327
|
const _FlowerFlow = ({ children, onEnter, onExit }) => {
|
271
328
|
React.useEffect(() => {
|
@@ -311,6 +368,43 @@ const makeActionPayloadOnReset = makeActionPayload(ACTION_TYPES.reset, PAYLOAD_K
|
|
311
368
|
const makeActionPayloadOnNode = makeActionPayload(ACTION_TYPES.jump, PAYLOAD_KEYS_NEEDED.jump);
|
312
369
|
const makeActionPayloadOnNext = makeActionPayload(ACTION_TYPES.next, PAYLOAD_KEYS_NEEDED.next);
|
313
370
|
const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLOAD_KEYS_NEEDED.restart);
|
371
|
+
const handleHistoryStackChange = (currentIndex, currentNode, flowName, withUrl) => {
|
372
|
+
const historyNode = `/${flowName}/${currentNode.nodeId}`;
|
373
|
+
if (currentNode.nodeType === 'FlowerAction')
|
374
|
+
return currentIndex;
|
375
|
+
const nextIndex = currentIndex + 1;
|
376
|
+
if (history.state?.index !== nextIndex) {
|
377
|
+
window.history.pushState({
|
378
|
+
index: nextIndex,
|
379
|
+
stack: [...(window.history.state.stack ?? []), historyNode]
|
380
|
+
}, '', withUrl ? historyNode : '');
|
381
|
+
}
|
382
|
+
return nextIndex;
|
383
|
+
};
|
384
|
+
|
385
|
+
const useHistorySync = ({ backAction, nextAction }) => {
|
386
|
+
const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
387
|
+
const { index, isActive, setIndex, withUrl } = flowerReactHistoryContext.useHistoryContext();
|
388
|
+
React.useEffect(() => {
|
389
|
+
if (!isActive)
|
390
|
+
return;
|
391
|
+
const initialIndex = window.history.state?.index ?? 0;
|
392
|
+
setIndex(initialIndex);
|
393
|
+
window.history.replaceState({ index: initialIndex, stack: [...(window.history.state?.stack ?? [])] }, withUrl ? '/' : '', '');
|
394
|
+
const onPopState = (event) => {
|
395
|
+
const newIndex = window.history.state?.index ?? 0;
|
396
|
+
if (newIndex > index) {
|
397
|
+
nextAction();
|
398
|
+
}
|
399
|
+
if (newIndex < index) {
|
400
|
+
backAction();
|
401
|
+
}
|
402
|
+
setIndex(newIndex);
|
403
|
+
};
|
404
|
+
window.addEventListener('popstate', onPopState);
|
405
|
+
return () => window.removeEventListener('popstate', onPopState);
|
406
|
+
}, [dispatch, backAction, nextAction]);
|
407
|
+
};
|
314
408
|
|
315
409
|
/** This hook allows you to read flow informations, such as the flowName and ID of the current node.
|
316
410
|
*
|
@@ -332,10 +426,18 @@ const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLO
|
|
332
426
|
*/
|
333
427
|
const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
334
428
|
const { name: flowNameDefault, initialData } = React.useContext(flowerReactContext.FlowerReactContext);
|
429
|
+
const { index, isActive, setIndex, withUrl } = flowerReactHistoryContext.useHistoryContext();
|
335
430
|
const { store, dispatch, useSelector } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
|
336
431
|
const flowName = (customFlowName || name || flowNameDefault);
|
337
432
|
const nodeId = useSelector(makeSelectCurrentNodeId(flowName ?? ''));
|
433
|
+
const currentNode = useSelector(makeSelectIsCurrentNode(flowName ?? ''));
|
338
434
|
const startId = useSelector(makeSelectStartNodeId(flowName ?? ''));
|
435
|
+
React.useEffect(() => {
|
436
|
+
if (currentNode.nodeType === 'FlowerAction')
|
437
|
+
return;
|
438
|
+
window.history.replaceState({ ...window.history.state }, '', withUrl ? `/${flowName}/${nodeId}` : '');
|
439
|
+
}, [nodeId, flowName, withUrl, currentNode]);
|
440
|
+
const stack = React.useMemo(() => window.history.state?.stack?.map((path) => path.split('/')[1]), [window.history.state?.stack]);
|
339
441
|
const emitNavigateEvent = React.useCallback(
|
340
442
|
//TODO check this function is needed
|
341
443
|
(params) => {
|
@@ -357,18 +459,37 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
|
357
459
|
const { type, payload } = makeActionPayloadOnNext(flowName, params);
|
358
460
|
dispatch({
|
359
461
|
type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`,
|
360
|
-
payload: {
|
361
|
-
...payload,
|
362
|
-
data: store.getState()
|
363
|
-
}
|
462
|
+
payload: { ...payload, data: store.getState() }
|
364
463
|
});
|
464
|
+
if (isActive) {
|
465
|
+
setIndex(handleHistoryStackChange(index, currentNode, flowName, withUrl));
|
466
|
+
}
|
365
467
|
emitNavigateEvent({ type, payload });
|
366
468
|
}, [dispatch, emitNavigateEvent, flowName, store]);
|
469
|
+
/**
|
470
|
+
* By doing this, we have a full control over flower navigation from both our buttons and browser back and forward navigation
|
471
|
+
* In order to trigger back correctly, trigger a real history back
|
472
|
+
* If you use replaceState({ index: 2 }) while at index 3,
|
473
|
+
* you visually move to step 2, but the browser still sees you at step 3.
|
474
|
+
* As a result:
|
475
|
+
* - The browser's Forward button stays disabled
|
476
|
+
* - No popstate event is triggered
|
477
|
+
* - The history flow is broken
|
478
|
+
* Use history.back() instead to preserve proper browser navigation.
|
479
|
+
*/
|
480
|
+
const interceptBack = () => {
|
481
|
+
window.history.back();
|
482
|
+
};
|
367
483
|
const back = React.useCallback((param) => {
|
368
|
-
const { type, payload } = makeActionPayloadOnBack(flowName, param);
|
369
|
-
dispatch({
|
484
|
+
const { type, payload } = makeActionPayloadOnBack(isActive ? (stack?.[stack?.length - 1] ?? flowName) : flowName, param);
|
485
|
+
dispatch({
|
486
|
+
type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`,
|
487
|
+
payload
|
488
|
+
});
|
489
|
+
setIndex(index - 1);
|
370
490
|
emitNavigateEvent({ type, payload });
|
371
|
-
}, [dispatch, emitNavigateEvent, flowName]);
|
491
|
+
}, [dispatch, emitNavigateEvent, flowName, stack]);
|
492
|
+
useHistorySync({ backAction: back, nextAction: next });
|
372
493
|
const restart = React.useCallback((param) => {
|
373
494
|
const { type, payload } = makeActionPayloadOnRestart(flowName, param);
|
374
495
|
dispatch({ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
|
@@ -377,10 +498,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
|
377
498
|
const reset = React.useCallback((param) => {
|
378
499
|
const { type, payload } = makeActionPayloadOnReset(flowName, typeof param === 'string'
|
379
500
|
? { node: param, initialData }
|
380
|
-
: {
|
381
|
-
...param,
|
382
|
-
initialData
|
383
|
-
});
|
501
|
+
: { ...param, initialData });
|
384
502
|
dispatch({ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
|
385
503
|
emitNavigateEvent({ type, payload });
|
386
504
|
}, [dispatch, emitNavigateEvent, flowName, initialData]);
|
@@ -403,7 +521,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
|
|
403
521
|
startId,
|
404
522
|
next,
|
405
523
|
jump,
|
406
|
-
back,
|
524
|
+
back: isActive ? interceptBack : back,
|
407
525
|
reset,
|
408
526
|
restart
|
409
527
|
};
|
@@ -647,9 +765,12 @@ Object.defineProperty(exports, "createApi", {
|
|
647
765
|
enumerable: true,
|
648
766
|
get: function () { return flowerReactStore.createApi; }
|
649
767
|
});
|
768
|
+
Object.defineProperty(exports, "HistoryContextProvider", {
|
769
|
+
enumerable: true,
|
770
|
+
get: function () { return flowerReactHistoryContext.HistoryContextProvider; }
|
771
|
+
});
|
650
772
|
exports.Flower = Flower;
|
651
773
|
exports.FlowerAction = FlowerAction;
|
652
|
-
exports.FlowerComponent = FlowerComponent;
|
653
774
|
exports.FlowerFlow = FlowerFlow;
|
654
775
|
exports.FlowerNavigate = FlowerNavigate;
|
655
776
|
exports.FlowerNode = FlowerNode;
|