@flowerforce/flower-react 3.7.2 → 4.0.1-beta.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 (61) hide show
  1. package/README.md +290 -199
  2. package/dist/index.cjs.js +427 -670
  3. package/dist/index.esm.js +409 -654
  4. package/dist/src/components/Flower.d.ts +3 -15
  5. package/dist/src/components/FlowerAction.d.ts +2 -3
  6. package/dist/src/components/FlowerComponent.d.ts +2 -4
  7. package/dist/src/components/FlowerFlow.d.ts +2 -3
  8. package/dist/src/components/FlowerNavigate/FlowerNavigate.d.ts +3 -0
  9. package/dist/src/components/FlowerNavigate/WrapperComponent.d.ts +3 -3
  10. package/dist/src/components/FlowerNavigate/index.d.ts +2 -4
  11. package/dist/src/components/FlowerNavigate/useFlowerNavigate.d.ts +1 -1
  12. package/dist/src/components/FlowerNode.d.ts +2 -3
  13. package/dist/src/components/FlowerRoute.d.ts +2 -3
  14. package/dist/src/components/FlowerServer.d.ts +2 -3
  15. package/dist/src/components/FlowerStart.d.ts +3 -4
  16. package/dist/src/components/hooks/eventsHandlers.d.ts +5 -0
  17. package/dist/src/components/hooks/index.d.ts +5 -0
  18. package/dist/src/components/hooks/types/index.d.ts +29 -0
  19. package/dist/src/components/hooks/useBrowserNavigationSync.d.ts +12 -0
  20. package/dist/src/components/hooks/useDestroyFlow.d.ts +2 -0
  21. package/dist/src/components/hooks/useInitDevtools.d.ts +2 -0
  22. package/dist/src/components/hooks/useInitNodes.d.ts +2 -0
  23. package/dist/src/components/hooks/useSelectorsClient.d.ts +6 -0
  24. package/dist/src/components/index.d.ts +10 -0
  25. package/dist/src/components/{useFlower.d.ts → useFlower/index.d.ts} +2 -3
  26. package/dist/src/components/useFlower/utils.d.ts +22 -0
  27. package/dist/src/features/index.d.ts +2 -0
  28. package/dist/src/features/reducer/flowerReducer.d.ts +7 -0
  29. package/dist/src/{selectors.d.ts → features/selectors/selectors.d.ts} +669 -383
  30. package/dist/src/index.d.ts +6 -33
  31. package/dist/src/provider/createSliceWithFlower.d.ts +2 -0
  32. package/dist/src/provider/createStoreWithFlower.d.ts +8 -0
  33. package/dist/src/provider/index.d.ts +3 -0
  34. package/dist/src/provider/provider.d.ts +7 -0
  35. package/dist/src/types/FlowContext.d.ts +6 -0
  36. package/dist/src/types/Flower.d.ts +15 -0
  37. package/dist/src/types/FlowerHooks.d.ts +27 -0
  38. package/dist/src/{components/types → types}/FlowerProvider.d.ts +3 -3
  39. package/dist/src/types/index.d.ts +10 -0
  40. package/dist/src/types/middlewares.d.ts +16 -0
  41. package/dist/src/types/utilsTypes.d.ts +4 -0
  42. package/dist/src/utils.d.ts +2 -10
  43. package/package.json +15 -7
  44. package/dist/src/components/FlowerField.d.ts +0 -4
  45. package/dist/src/components/FlowerRule.d.ts +0 -4
  46. package/dist/src/components/FlowerValue.d.ts +0 -4
  47. package/dist/src/components/types/FlowerField.d.ts +0 -124
  48. package/dist/src/components/types/FlowerHooks.d.ts +0 -72
  49. package/dist/src/components/types/FlowerRule.d.ts +0 -35
  50. package/dist/src/components/types/FlowerValue.d.ts +0 -33
  51. package/dist/src/components/useFlowerForm.d.ts +0 -26
  52. package/dist/src/context.d.ts +0 -10
  53. package/dist/src/provider.d.ts +0 -25
  54. package/dist/src/reducer.d.ts +0 -7
  55. /package/dist/src/{components/types → types}/DefaultNode.d.ts +0 -0
  56. /package/dist/src/{components/types → types}/FlowerComponent.d.ts +0 -0
  57. /package/dist/src/{components/types → types}/FlowerFlow.d.ts +0 -0
  58. /package/dist/src/{components/types → types}/FlowerNavigate.d.ts +0 -0
  59. /package/dist/src/{components/types → types}/FlowerNode.d.ts +0 -0
  60. /package/dist/src/{components/types → types}/FlowerRoute.d.ts +0 -0
  61. /package/dist/src/{components/types → types}/FlowerServer.d.ts +0 -0
package/dist/index.esm.js CHANGED
@@ -1,122 +1,103 @@
1
- import React, { createContext, PureComponent, memo, useRef, useState, useMemo, Children, useEffect, useContext, useCallback } from 'react';
1
+ import React, { useEffect, memo, useRef, useState, useMemo, Children, useContext, useCallback } from 'react';
2
2
  import _keyBy from 'lodash/keyBy';
3
- import { CoreUtils, FlowerCoreReducers, FlowerStateUtils, Selectors, devtoolState, Emitter, MatchRules } from '@flowerforce/flower-core';
3
+ import { FlowUtils, FlowerCoreBaseReducers, REDUCER_NAME, FlowerCoreStateSelectors, FlowerStateUtils, Emitter, devtoolState } from '@flowerforce/flower-core';
4
+ import { FlowerReactProvider, FlowerReactContext } from '@flowerforce/flower-react-context';
4
5
  import _get from 'lodash/get';
5
- import { createSlice, configureStore } from '@reduxjs/toolkit';
6
+ import { reducerData, ReduxFlowerProvider, flowerDataActions, middlewares } from '@flowerforce/flower-react-store';
7
+ export { createApi } from '@flowerforce/flower-react-store';
8
+ import { createSlice, configureStore, combineReducers, createAction } from '@reduxjs/toolkit';
6
9
  import { createSelector } from 'reselect';
7
- import { createDispatchHook, createSelectorHook, createStoreHook, Provider as Provider$1 } from 'react-redux';
8
- import debounce from 'lodash/debounce';
9
- import isEqual from 'lodash/isEqual';
10
+ import { useHistoryContext } from '@flowerforce/flower-react-history-context';
11
+ export { HistoryContextProvider } from '@flowerforce/flower-react-history-context';
12
+ import { FlowerRule } from '@flowerforce/flower-react-shared';
13
+ import set from 'lodash/set';
10
14
 
11
- const _context = createContext({});
12
- const context = _context;
13
- const Provider = _context.Provider;
14
- const Consumer = _context.Consumer;
15
-
16
- // eslint-disable-next-line import/prefer-default-export
17
- const convertElements = (nodes) => {
18
- const res = CoreUtils.generateNodesForFlowerJson(nodes);
19
- return res;
15
+ const getRulesExists = (rules) => {
16
+ return Object.keys(rules).length ? FlowUtils.mapEdge(rules) : undefined;
20
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 = 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
+ });
21
44
 
22
45
  const flowerReducer = createSlice({
23
- name: 'flower',
46
+ name: REDUCER_NAME.FLOWER_FLOW,
24
47
  initialState: {},
25
- reducers: FlowerCoreReducers
48
+ reducers: FlowerCoreBaseReducers
26
49
  });
27
- const { actions } = flowerReducer;
50
+ const flowerActions = flowerReducer.actions;
51
+ const flowerFlowReducer = flowerReducer.reducer;
28
52
  const reducerFlower = {
29
- flower: flowerReducer.reducer
53
+ [REDUCER_NAME.FLOWER_FLOW]: flowerFlowReducer
54
+ };
55
+ const flowerReducers = {
56
+ ...reducerFlower,
57
+ ...reducerData
30
58
  };
31
59
 
32
- const { getAllData: mapData } = FlowerStateUtils;
33
- const { selectGlobal } = Selectors;
34
- const selectFlower = (name) => createSelector(selectGlobal, Selectors.selectFlower(name));
35
- const selectFlowerFormNode = (name, id) => createSelector(selectFlower(name), Selectors.selectFlowerFormNode(id));
36
- const selectFlowerHistory = (name) => createSelector(selectFlower(name), Selectors.selectFlowerHistory);
37
- const makeSelectNodesIds = (name) => createSelector(selectFlower(name), Selectors.makeSelectNodesIds);
38
- const makeSelectStartNodeId = (name) => createSelector(selectFlower(name), Selectors.makeSelectStartNodeId);
39
- const makeSelectCurrentNodeId = (name) => createSelector(selectFlower(name), makeSelectStartNodeId(name), Selectors.makeSelectCurrentNodeId);
40
- const makeSelectPrevNodeRetain = (name) => createSelector(makeSelectNodesIds(name), selectFlowerHistory(name), makeSelectCurrentNodeId(name), Selectors.makeSelectPrevNodeRetain);
41
- const makeSelectCurrentNodeDisabled = (name) => createSelector(makeSelectNodesIds(name), makeSelectCurrentNodeId(name), Selectors.makeSelectCurrentNodeDisabled);
42
- // dati nel flow selezionato
43
- const getDataByFlow = (name) => createSelector(selectFlower(name), Selectors.getDataByFlow);
44
- // selettore per recuperare i dati di un flow specifico e id specifico
45
- const getDataFromState = (name, id) => createSelector(getDataByFlow(name), Selectors.getDataFromState(id));
46
- const makeSelectNodeErrors = (name, currentNodeId) => createSelector(selectFlowerFormNode(name, currentNodeId), Selectors.makeSelectNodeErrors);
47
- const makeSelectNodeFieldTouched = (name, currentNodeId, fieldId) => createSelector(selectFlowerFormNode(name, currentNodeId), Selectors.makeSelectNodeFormFieldTouched(fieldId));
48
- const makeSelectNodeFieldFocused = (name, currentNodeId, fieldId) => createSelector(selectFlowerFormNode(name, currentNodeId), Selectors.makeSelectNodeFormFieldFocused(fieldId));
49
- const makeSelectNodeFieldDirty = (name, currentNodeId, fieldId) => createSelector(selectFlowerFormNode(name, currentNodeId), Selectors.makeSelectNodeFormFieldDirty(fieldId));
50
- const makeSelectNodeFormSubmitted = (name, currentNodeId) => createSelector(selectFlowerFormNode(name, currentNodeId), Selectors.makeSelectNodeFormSubmitted);
51
- const getAllData = createSelector(selectGlobal, mapData);
52
- const selectFlowerFormCurrentNode = (name) => createSelector(selectFlower(name), makeSelectCurrentNodeId(name), (data, current) => {
53
- return _get(data, ['form', current]);
54
- });
55
- const makeSelectFieldError = (name, id, validate) => createSelector(getAllData, selectFlowerFormCurrentNode(name), Selectors.makeSelectFieldError(name, id, validate));
56
- const selectorRulesDisabled = (id, rules, keys, flowName, value, currentNode) => createSelector(getAllData, makeSelectNodeErrors(flowName, currentNode), Selectors.selectorRulesDisabled(id, rules, keys, flowName, value));
57
-
58
- //TODO check reduxContext type due to remove all any types
59
- const reduxContext = createContext(null);
60
- const useDispatch = createDispatchHook(reduxContext);
61
- const useSelector = createSelectorHook(reduxContext);
62
- const useStore = createStoreHook(reduxContext);
63
- const store = ({ enableDevtool }) => configureStore({
64
- reducer: reducerFlower,
65
- devTools: enableDevtool ? { name: 'flower' } : false
60
+ const { selectGlobal, selectGlobalData } = FlowerCoreStateSelectors;
61
+ const selectFlower = (name) => createSelector(selectGlobal, FlowerCoreStateSelectors.selectFlower(name));
62
+ const selectFlowerData = selectGlobalData;
63
+ const selectFlowerDataNode = (name) => createSelector(selectFlowerData, (data) => {
64
+ return data[name];
66
65
  });
67
- class FlowerProvider extends PureComponent {
68
- constructor(props) {
69
- super(props);
70
- this.store = store({ enableDevtool: props.enableReduxDevtool });
71
- }
72
- render() {
73
- const { children } = this.props;
74
- return (React.createElement(Provider$1, { context: reduxContext, store: this.store }, children));
75
- }
76
- }
66
+ const selectFlowerHistory = (name) => createSelector(selectFlower(name), FlowerCoreStateSelectors.selectFlowerHistory);
67
+ const makeSelectNodesIds = (name) => createSelector(selectFlower(name), FlowerCoreStateSelectors.makeSelectNodesIds);
68
+ const makeSelectStartNodeId = (name) => createSelector(selectFlower(name), FlowerCoreStateSelectors.makeSelectStartNodeId);
69
+ const makeSelectCurrentNodeId = (name) => createSelector(selectFlower(name), makeSelectStartNodeId(name), FlowerCoreStateSelectors.makeSelectCurrentNodeId);
70
+ const makeSelectIsCurrentNode = (name) => createSelector(selectFlower(name), makeSelectCurrentNodeId(name), (flower, current) => flower.nodes[current]);
71
+ const makeSelectPrevNodeRetain = (name) => createSelector(makeSelectNodesIds(name), selectFlowerHistory(name), makeSelectCurrentNodeId(name), FlowerCoreStateSelectors.makeSelectPrevNodeRetain);
72
+ const makeSelectCurrentNodeDisabled = (name) => createSelector(makeSelectNodesIds(name), makeSelectCurrentNodeId(name), FlowerCoreStateSelectors.makeSelectCurrentNodeDisabled);
73
+ // dati nel flow selezionato
74
+ const makeSelectData = (name) => createSelector(selectFlowerDataNode(name), (data) => data?.data ?? {});
75
+ createSelector(selectGlobalData, FlowerStateUtils.getAllData);
77
76
 
78
- /* eslint-disable no-undef */
79
- /* eslint-disable no-underscore-dangle */
80
- /**
81
- * FlowerClient
82
- */
83
- const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null, initialData = {}, initialState = {} }) => {
84
- const flowName = name;
85
- const dispatch = useDispatch();
86
- const one = useRef(false);
87
- const [wsDevtools, setWsDevtools] = useState(devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
88
- // TODO rivedere il giro, potremmo fare le trasformazioni in CoreUtils.generateNodesForFlowerJson
89
- // eslint-disable-next-line react-hooks/exhaustive-deps, max-len
90
- const nodes = useMemo(() => convertElements(Children.toArray(children)), [children]);
91
- const nodeById = useMemo(() => _keyBy(Children.toArray(children), 'props.id'), [children]);
92
- const isInitialized = useSelector(makeSelectStartNodeId(name));
93
- const history = useSelector(selectFlowerHistory(name));
94
- const current = useSelector(makeSelectCurrentNodeId(flowName));
95
- const isDisabled = useSelector(makeSelectCurrentNodeDisabled(flowName));
96
- const prevFlowerNodeId = useSelector(makeSelectPrevNodeRetain(flowName));
97
- const store = useStore();
77
+ const useInitNodes = ({ one, nodes, name, startId, persist = false, initialData, initialState = {} }) => {
78
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
98
79
  useEffect(() => {
99
80
  if (nodes.length > 0 && one.current === false) {
100
81
  one.current = true;
101
- dispatch(actions.initNodes({
102
- name: flowName,
103
- // @ts-expect-error FIX ME
82
+ dispatch(flowerActions.initNodes({
83
+ name,
104
84
  nodes,
105
85
  startId: startId ?? '',
106
- persist: destroyOnUnmount === false,
107
- initialData,
86
+ persist,
108
87
  initialState
109
88
  }));
110
89
  }
111
- }, [
112
- dispatch,
113
- flowName,
114
- nodes,
115
- startId,
116
- initialData,
117
- destroyOnUnmount,
118
- initialState
119
- ]);
90
+ if (initialData) {
91
+ dispatch(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 } = ReduxFlowerProvider.getReduxHooks();
120
101
  useEffect(() => {
121
102
  /* istanbul ignore next */
122
103
  const eventCb = (msg) => {
@@ -127,14 +108,14 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
127
108
  setWsDevtools(true);
128
109
  }
129
110
  if (msg.action === 'SELECTED_NODE' && msg.name === flowName) {
130
- dispatch(actions.setCurrentNode({ name: msg.name, node: msg.id }));
131
- }
132
- if (msg.action === 'REPLACE_DATA' && msg.name === flowName) {
133
- dispatch(actions.replaceData({ flowName: msg.name, value: msg.data }));
134
- }
135
- if (msg.action === 'ADD_DATA' && msg.name === flowName) {
136
- dispatch(actions.addData({ flowName: msg.name, value: msg.data }));
111
+ dispatch(flowerActions.setCurrentNode({ name: msg.name, node: msg.id }));
137
112
  }
113
+ // if (msg.action === 'REPLACE_DATA' && msg.name === flowName) {
114
+ // dispatch(actions.replaceData({ flowName: msg.name, value: msg.data }))
115
+ // }
116
+ // if (msg.action === 'ADD_DATA' && msg.name === flowName) {
117
+ // dispatch(actions.addData({ flowName: msg.name, value: msg.data }))
118
+ // }
138
119
  };
139
120
  /* istanbul ignore next */
140
121
  if (devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS__')) {
@@ -147,13 +128,22 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
147
128
  }
148
129
  };
149
130
  }, [dispatch, flowName]);
131
+ };
132
+
133
+ const useDestroyFlow = ({ flowName, one, persist }) => {
134
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
150
135
  useEffect(() => () => {
151
- // unmount function
152
- if (destroyOnUnmount && one.current === true) {
136
+ if (persist)
137
+ return;
138
+ if (one.current === true) {
153
139
  one.current = false;
154
- dispatch(actions.destroy({ name: flowName }));
140
+ dispatch(flowerActions.destroy({ name: flowName }));
155
141
  }
156
- }, [dispatch, flowName, destroyOnUnmount]);
142
+ }, [dispatch, flowName, persist]);
143
+ };
144
+
145
+ const useClientInitEvent = ({ flowName, isInitialized, wsDevtools }) => {
146
+ const { store } = ReduxFlowerProvider.getReduxHooks();
157
147
  useEffect(() => {
158
148
  /* istanbul ignore next */
159
149
  if (isInitialized &&
@@ -169,7 +159,11 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
169
159
  getState: store.getState
170
160
  });
171
161
  }
172
- }, [dispatch, flowName, wsDevtools, isInitialized, store]);
162
+ }, [flowName, wsDevtools, isInitialized, store]);
163
+ };
164
+ const useSetHistoryEvent = ({ flowName, isInitialized, wsDevtools }) => {
165
+ const { useSelector } = ReduxFlowerProvider.getReduxHooks();
166
+ const history = useSelector(selectFlowerHistory(flowName));
173
167
  useEffect(() => {
174
168
  /* istanbul ignore next */
175
169
  if (isInitialized &&
@@ -183,7 +177,9 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
183
177
  history
184
178
  });
185
179
  }
186
- }, [dispatch, flowName, history, wsDevtools, isInitialized]);
180
+ }, [flowName, history, wsDevtools, isInitialized]);
181
+ };
182
+ const useSetCurrentEvent = ({ current, flowName, isInitialized, wsDevtools }) => {
187
183
  useEffect(() => {
188
184
  if (!current)
189
185
  return;
@@ -203,6 +199,9 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
203
199
  });
204
200
  }
205
201
  }, [flowName, current, wsDevtools, isInitialized]);
202
+ };
203
+ const useFlowerNavigateEvent = ({ current, flowName, isInitialized, wsDevtools, isDisabled }) => {
204
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
206
205
  useEffect(() => {
207
206
  if (!current)
208
207
  return;
@@ -210,7 +209,7 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
210
209
  if (!isInitialized)
211
210
  return;
212
211
  if (isDisabled) {
213
- dispatch({ type: 'flower/next', payload: { flowName, disabled: true } });
212
+ dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/next`, payload: { flowName, disabled: true } });
214
213
  // eslint-disable-next-line no-underscore-dangle, no-undef
215
214
  /* istanbul ignore next */
216
215
  if (wsDevtools &&
@@ -242,43 +241,72 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
242
241
  time: new Date()
243
242
  });
244
243
  }
245
- }, [
246
- dispatch,
247
- flowName,
244
+ }, [dispatch, flowName, current, isDisabled, wsDevtools, isInitialized]);
245
+ };
246
+
247
+ const useSelectorsClient = (flowName) => {
248
+ const { useSelector } = 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,
248
255
  current,
249
256
  isDisabled,
250
- store,
251
- wsDevtools,
252
- isInitialized
253
- ]);
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 = useRef(false);
268
+ const [wsDevtools, setWsDevtools] = useState(devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
269
+ const nodes = useMemo(() => generateNodesForFlowerJson(Children.toArray(children)), [children]);
270
+ const nodeById = useMemo(() => _keyBy(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, 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
+ });
254
293
  const currentNodeId = prevFlowerNodeId || current;
255
294
  const contextValues = useMemo(() => ({
256
- flowName,
257
- initialData,
295
+ name: flowName,
258
296
  currentNode: current
259
- }), [flowName, initialData, current]);
297
+ }), [flowName, current]);
260
298
  const prevContextValues = useMemo(() => ({
261
- flowName,
262
- initialData,
299
+ name: flowName,
263
300
  currentNode: currentNodeId
264
- }), [flowName, initialData, currentNodeId]);
301
+ }), [flowName, currentNodeId]);
265
302
  return isInitialized ? (React.createElement(React.Fragment, null,
266
- React.createElement(Provider, { value: prevContextValues }, nodeById[currentNodeId]),
267
- React.createElement(Provider, { value: contextValues }, !isDisabled && current !== currentNodeId && nodeById[current]))) : null;
268
- };
269
- const component$c = memo(FlowerClient);
270
-
271
- const FlowerNode = ({ children, onEnter, onExit }) => {
272
- useEffect(() => {
273
- onEnter?.();
274
- return () => {
275
- onExit?.();
276
- };
277
- }, [onEnter, onExit]);
278
- return children;
303
+ React.createElement(FlowerReactProvider, { value: prevContextValues }, nodeById[currentNodeId]),
304
+ React.createElement(FlowerReactProvider, { value: contextValues }, !isDisabled && current !== currentNodeId && nodeById[current]))) : null;
279
305
  };
280
- const component$b = memo(FlowerNode);
281
- component$b.displayName = 'FlowerNode';
306
+ const component$8 = memo(FlowerClient);
307
+ component$8.displayName = 'Flower';
308
+ // workaround for let typescript read JSX component as a valid JSX element using react 19(?)
309
+ const Flower = component$8;
282
310
 
283
311
  const FlowAction = ({ children, onEnter, onExit }) => {
284
312
  useEffect(() => {
@@ -287,400 +315,39 @@ const FlowAction = ({ children, onEnter, onExit }) => {
287
315
  onExit?.();
288
316
  };
289
317
  }, [onEnter, onExit]);
290
- return children;
318
+ return React.createElement(React.Fragment, null, children);
291
319
  };
292
- const component$a = React.memo(FlowAction);
293
- component$a.displayName = 'FlowerAction';
320
+ const component$7 = React.memo(FlowAction);
321
+ component$7.displayName = 'FlowerAction';
322
+ const FlowerAction = component$7;
294
323
 
295
- const FlowerServer = ({ children }) => {
296
- return children;
297
- };
298
- const component$9 = React.memo(FlowerServer);
299
- component$9.displayName = 'FlowerServer';
324
+ const _FlowerComponent = ({ children }) => children;
325
+ memo(_FlowerComponent);
300
326
 
301
- const FlowerFlow = ({ children, onEnter, onExit }) => {
327
+ const _FlowerFlow = ({ children, onEnter, onExit }) => {
302
328
  useEffect(() => {
303
329
  onEnter?.();
304
330
  return () => {
305
331
  onExit?.();
306
332
  };
307
333
  }, [onEnter, onExit]);
308
- return children;
309
- };
310
- const component$8 = React.memo(FlowerFlow);
311
- component$8.displayName = 'FlowerFlow';
312
-
313
- function FlowerStart() {
314
- const dispatch = useDispatch();
315
- const one = useRef(false);
316
- const { flowName, autostart = true, currentNode } = useContext(context);
317
- const startNodeId = useSelector(makeSelectStartNodeId(flowName ?? ''));
318
- useEffect(() => {
319
- if (startNodeId === currentNode && autostart && one.current === false) {
320
- one.current = true;
321
- dispatch({ type: 'flower/next', payload: { flowName, isStart: true } });
322
- // if (global.window
323
- // // eslint-disable-next-line no-underscore-dangle, no-undef
324
- // && global.window.__FLOWER_DEVTOOLS__ && global.window.__FLOWER_DEVTOOLS__AUTO) {
325
- // Emitter.emit('flower-devtool-from-client', {
326
- // source: 'flower-client',
327
- // action: 'START_FLOWER',
328
- // name: flowName,
329
- // });
330
- // }
331
- }
332
- }, [dispatch, autostart, startNodeId, currentNode, flowName]);
333
- return null;
334
- }
335
- const component$7 = React.memo(FlowerStart);
336
- component$7.displayName = 'FlowerStart';
337
-
338
- const FlowerRoute = ({ autostart = true, children, onEnter, onExit }) => {
339
- const dispatch = useDispatch();
340
- const one = useRef(false);
341
- const { flowName } = useContext(context);
342
- useEffect(() => {
343
- onEnter?.();
344
- return () => {
345
- onExit?.();
346
- };
347
- }, [onEnter, onExit]);
348
- useEffect(() => {
349
- if (autostart && one.current === false) {
350
- one.current = true;
351
- dispatch({ type: 'flower/next', payload: { flowName } });
352
- }
353
- }, [dispatch, flowName, autostart]);
354
- return children;
355
- };
356
- const component$6 = React.memo(FlowerRoute);
357
- component$6.displayName = 'FlowerRoute';
358
-
359
- const FlowerRule = ({ children, rules, value, alwaysDisplay, flowName, id, onUpdate }) => {
360
- const { flowName: flowNameContext, currentNode } = useContext(context);
361
- const name = flowName || flowNameContext;
362
- const keys = MatchRules.utils.getKeys(rules, { prefix: name });
363
- const hidden = useSelector(selectorRulesDisabled(id ?? '', rules, keys ?? [], name ?? '', value, currentNode ?? ''));
364
- useEffect(() => {
365
- if (onUpdate) {
366
- onUpdate(hidden);
367
- }
368
- }, [hidden, onUpdate]);
369
- if (typeof children === 'function') {
370
- if (alwaysDisplay && hidden) {
371
- return children({ hidden });
372
- }
373
- if (hidden) {
374
- return undefined;
375
- }
376
- return children({});
377
- }
378
- if (alwaysDisplay && hidden) {
379
- return React.Children.map(children, (child, i) => {
380
- if (React.isValidElement(child)) {
381
- const { props, type } = child;
382
- const Component = type;
383
- // eslint-disable-next-line react/jsx-props-no-spreading
384
- return Component && React.createElement(Component, { key: i, hidden: true, ...props });
385
- }
386
- return child;
387
- });
388
- }
389
- return hidden
390
- ? undefined
391
- : React.Children.map(children, (child, i) => {
392
- if (React.isValidElement(child)) {
393
- const { props, type } = child;
394
- const Component = type;
395
- // eslint-disable-next-line react/jsx-props-no-spreading
396
- return Component && React.createElement(Component, { key: i, ...props });
397
- }
398
- return child;
399
- });
334
+ return React.createElement(React.Fragment, null, children);
400
335
  };
401
- const component$5 = React.memo(FlowerRule);
402
- component$5.displayName = 'FlowerRule';
403
-
404
- /* eslint-disable */
405
- function isIntrinsicElement$1(x) {
406
- return typeof x === 'string';
407
- }
408
- //TODO make types for wrapper function props
409
- function Wrapper$1({ Component, id, flowName, currentNode, validate, asyncDebounce = 0, asyncValidate, asyncInitialError, asyncWaitingError, destroyValue, destroyOnHide, onBlur, onFocus, hidden, onUpdate, defaultValue, ...props }) {
410
- const dispatch = useDispatch();
411
- const [customAsyncErrors, setCustomAsyncErrors] = useState(asyncValidate && asyncInitialError && [asyncInitialError]);
412
- const [isValidating, setIsValidating] = useState(undefined);
413
- const { flowNameFromPath = flowName, path } = useMemo(() => CoreUtils.getPath(id), [id]);
414
- const value = useSelector(getDataFromState(flowNameFromPath, path));
415
- const errors = useSelector(makeSelectFieldError(flowName, id, validate), CoreUtils.allEqual);
416
- const dirty = useSelector(makeSelectNodeFieldDirty(flowName, currentNode, id));
417
- const touched = useSelector(makeSelectNodeFieldTouched(flowName, currentNode, id));
418
- const focused = useSelector(makeSelectNodeFieldFocused(flowName, currentNode, id));
419
- const refValue = useRef();
420
- const isSubmitted = useSelector(makeSelectNodeFormSubmitted(flowName, currentNode));
421
- const allErrors = useMemo(() => hidden ? [] : [...errors, ...(customAsyncErrors || []).filter(Boolean)], [errors, hidden, customAsyncErrors]);
422
- const setTouched = useCallback((touched) => {
423
- dispatch({
424
- type: 'flower/formFieldTouch',
425
- payload: {
426
- name: flowName,
427
- id,
428
- currentNode,
429
- touched
430
- }
431
- });
432
- }, [dispatch, flowName, currentNode, id]);
433
- const setFocus = useCallback((focused) => {
434
- dispatch({
435
- type: 'flower/formFieldFocus',
436
- payload: {
437
- name: flowName,
438
- id,
439
- currentNode,
440
- focused
441
- }
442
- });
443
- }, [dispatch, flowName, currentNode, id]);
444
- const validateFn = useCallback(async (value) => {
445
- if (asyncWaitingError) {
446
- setCustomAsyncErrors([asyncWaitingError]);
447
- }
448
- setIsValidating(true);
449
- const state = FlowerStateUtils.getAllData(store);
450
- const res = await asyncValidate(value, state, errors);
451
- setIsValidating(false);
452
- setCustomAsyncErrors(res);
453
- }, [asyncWaitingError, errors]);
454
- const debouncedValidation = useCallback(debounce(validateFn, asyncDebounce), [
455
- validateFn
456
- ]);
457
- const onChange = useCallback((val) => {
458
- if (asyncValidate && asyncWaitingError) {
459
- setCustomAsyncErrors([asyncWaitingError]);
460
- }
461
- dispatch({
462
- type: `flower/addDataByPath`,
463
- payload: {
464
- flowName: flowNameFromPath,
465
- id,
466
- value: val,
467
- dirty: defaultValue ? !isEqual(val, defaultValue) : true
468
- }
469
- });
470
- }, [flowNameFromPath, id, dispatch, setCustomAsyncErrors, asyncValidate, asyncWaitingError]);
471
- const onBlurInternal = useCallback((e) => {
472
- setTouched(true);
473
- setFocus(false);
474
- onBlur && onBlur(e);
475
- }, [onBlur, setTouched, setFocus]);
476
- const onFocusInternal = useCallback((e) => {
477
- setFocus(true);
478
- onFocus && onFocus(e);
479
- }, [onFocus, setFocus]);
480
- useEffect(() => {
481
- if (hidden)
482
- return;
483
- if (asyncValidate) {
484
- if (refValue.current === value)
485
- return;
486
- refValue.current = value;
487
- const hasValue = !MatchRules.utils.isEmpty(value);
488
- if (!hasValue) {
489
- setCustomAsyncErrors(asyncInitialError && [asyncInitialError]);
490
- setIsValidating(false);
491
- return;
492
- }
493
- setTouched(true);
494
- debouncedValidation(value);
495
- }
496
- }, [asyncValidate, asyncInitialError, value, debouncedValidation, setTouched, hidden]);
497
- useEffect(() => {
498
- if (onUpdate) {
499
- onUpdate(value);
500
- }
501
- }, [value, onUpdate]);
502
- useEffect(() => {
503
- dispatch({
504
- type: 'flower/formAddErrors',
505
- payload: {
506
- name: flowName,
507
- id,
508
- currentNode,
509
- errors: allErrors
510
- }
511
- });
512
- }, [id, flowName, allErrors, currentNode, touched]);
513
- useEffect(() => {
514
- dispatch({
515
- type: 'flower/setFormIsValidating',
516
- payload: {
517
- name: flowName,
518
- currentNode,
519
- isValidating
520
- }
521
- });
522
- }, [flowName, currentNode, isValidating]);
523
- const resetField = useCallback(() => {
524
- dispatch({
525
- type: 'flower/formFieldTouch',
526
- payload: {
527
- name: flowName,
528
- id,
529
- currentNode,
530
- touched: false
531
- }
532
- });
533
- dispatch({
534
- type: 'flower/formFieldDirty',
535
- payload: {
536
- name: flowName,
537
- id,
538
- currentNode,
539
- dirty: false
540
- }
541
- });
542
- dispatch({
543
- type: 'flower/formRemoveErrors',
544
- payload: {
545
- name: flowName,
546
- id,
547
- currentNode
548
- }
549
- });
550
- }, [currentNode, id, flowName]);
551
- useEffect(() => {
552
- // destroy
553
- return () => {
554
- if (destroyValue) {
555
- dispatch({
556
- type: `flower/unsetData`,
557
- payload: { flowName: flowNameFromPath, id: path }
558
- });
559
- }
560
- resetField();
561
- };
562
- }, [destroyValue, id, flowNameFromPath, path, resetField]);
563
- useEffect(() => {
564
- if (hidden) {
565
- if (destroyOnHide) {
566
- dispatch({
567
- type: `flower/unsetData`,
568
- payload: { flowName: flowNameFromPath, id: path }
569
- });
570
- resetField();
571
- }
572
- }
573
- }, [destroyOnHide, hidden, flowNameFromPath, path, resetField]);
574
- useEffect(() => {
575
- if (defaultValue && !dirty && !isEqual(value, defaultValue)) {
576
- onChange(defaultValue);
577
- }
578
- }, [defaultValue, value, dirty, onChange]);
579
- const newProps = useMemo(() => ({
580
- ...props,
581
- id,
582
- value,
583
- errors: allErrors,
584
- hasError: !!allErrors.length,
585
- onChange,
586
- onBlur: onBlurInternal,
587
- onFocus: onFocusInternal,
588
- focused: !!focused,
589
- touched,
590
- dirty,
591
- hidden,
592
- isValidating,
593
- isSubmitted,
594
- }), [
595
- props,
596
- id,
597
- value,
598
- allErrors,
599
- touched,
600
- dirty,
601
- onChange,
602
- onBlurInternal,
603
- onFocusInternal,
604
- hidden,
605
- isValidating,
606
- isSubmitted,
607
- focused
608
- ]);
609
- if (typeof Component === 'function') {
610
- return Component(newProps);
611
- }
612
- // TODO si arriva in questa condizione quando si passa un componente primitivo es. div
613
- // in questo caso non posso props custom di flower
614
- if (isIntrinsicElement$1(Component)) {
615
- return React.createElement(Component, { id: id, ...props });
616
- }
617
- return Component && React.createElement(Component, { ...newProps });
618
- }
619
- const FlowerField = ({ id, validate, asyncValidate, asyncDebounce, asyncInitialError, asyncWaitingError, rules, alwaysDisplay, value, children, defaultValue, destroyOnHide, destroyValue, flowName, onUpdate }) => {
620
- const { flowName: flowNameContext, currentNode } = useContext(context);
621
- const name = flowName || flowNameContext;
622
- if (typeof children === 'function') {
623
- return (React.createElement(component$5, { alwaysDisplay: alwaysDisplay, rules: rules, value: value, flowName: name, id: id }, ({ hidden }) => (React.createElement(Wrapper$1, { hidden: hidden, id: id, Component: children, flowName: name, currentNode: currentNode, validate: validate, asyncValidate: asyncValidate, asyncDebounce: asyncDebounce, asyncInitialError: asyncInitialError, asyncWaitingError: asyncWaitingError, destroyValue: destroyValue, onUpdate: onUpdate, defaultValue: defaultValue, destroyOnHide: destroyOnHide }))));
624
- }
625
- return React.Children.map(children, (child, i) => {
626
- if (!React.isValidElement(child)) {
627
- return child;
628
- }
629
- const { type, props } = child;
630
- const Component = type;
631
- return (React.createElement(component$5, { key: i, alwaysDisplay: alwaysDisplay, rules: rules, value: value, flowName: name }, ({ hidden }) => (React.createElement(Wrapper$1, { ...props, hidden: hidden, id: id, Component: Component, flowName: name, currentNode: currentNode, validate: validate, asyncValidate: asyncValidate, asyncDebounce: asyncDebounce, asyncInitialError: asyncInitialError, asyncWaitingError: asyncWaitingError, destroyValue: destroyValue, onUpdate: onUpdate, defaultValue: defaultValue }))));
632
- });
633
- };
634
- const component$4 = React.memo(FlowerField);
635
- component$4.displayName = 'FlowerField';
636
-
637
- /* eslint-disable */
638
- //TODO make types for wrapper function
639
- function Wrapper({ Component, id, flowName, spreadValue, hidden, onUpdate, ...props }) {
640
- const { flowNameFromPath = flowName, path } = useMemo(() => CoreUtils.getPath(id), [id]);
641
- const value = useSelector(getDataFromState(flowNameFromPath, path));
642
- const values = spreadValue && typeof value === 'object' && !Array.isArray(value)
643
- ? value
644
- : { value };
645
- useEffect(() => {
646
- if (onUpdate) {
647
- onUpdate(value);
648
- }
649
- }, [onUpdate, value]);
650
- return (React.createElement(Component, { id: id, ...props, flowName: flowName, hidden: hidden, ...values }));
651
- }
652
- const RenderRules$1 = ({ id, alwaysDisplay, rules, value, Component, spreadValue, flowName, onUpdate, ...props }) => {
653
- return (React.createElement(component$5, { alwaysDisplay: alwaysDisplay, rules: rules, value: value, flowName: flowName, id: id }, ({ hidden }) => (React.createElement(Wrapper, { ...props, hidden: hidden, id: id, Component: Component, spreadValue: spreadValue, flowName: flowName, onUpdate: onUpdate }))));
654
- };
655
- const FlowerValue = ({ id = '*', rules, alwaysDisplay, value, children, spreadValue, flowName, onUpdate, }) => {
656
- const { flowName: flowNameContext } = useContext(context);
657
- const name = flowName || flowNameContext;
658
- if (typeof children === 'function') {
659
- return (React.createElement(RenderRules$1, { id: id, alwaysDisplay: alwaysDisplay, rules: rules, value: value, spreadValue: spreadValue, Component: children, flowName: name, onUpdate: onUpdate }));
660
- }
661
- return React.Children.map(children, (child, i) => {
662
- if (!React.isValidElement(child))
663
- return child;
664
- const { type, props } = child;
665
- const Component = type;
666
- // eslint-disable-next-line react/jsx-props-no-spreading
667
- return (React.createElement(RenderRules$1, { key: i, id: id, alwaysDisplay: alwaysDisplay, rules: rules, value: value, spreadValue: spreadValue, flowName: name, Component: Component, ...props, onUpdate: onUpdate }));
668
- });
669
- };
670
- const component$3 = React.memo(FlowerValue);
671
- component$3.displayName = 'FlowerValue';
336
+ const component$6 = React.memo(_FlowerFlow);
337
+ component$6.displayName = 'FlowerFlow';
338
+ const FlowerFlow = component$6;
672
339
 
673
340
  const ACTION_TYPES = {
674
- back: ['prev', 'prevToNode'],
341
+ back: ['back', 'prevToNode'],
675
342
  jump: ['node', 'node'],
676
343
  next: ['next', 'next'],
677
344
  restart: ['restart', 'restart'],
678
345
  reset: ['reset', 'initializeFromNode']
679
346
  };
680
- const PAYLAOAD_KEYS_NEEDED = {
347
+ const PAYLOAD_KEYS_NEEDED = {
681
348
  back: ['node'],
682
349
  jump: ['node', 'history'],
683
- next: ['node', 'route', 'data'],
350
+ next: ['node', 'route', 'data', 'dataIn'],
684
351
  restart: ['node'],
685
352
  reset: ['node', 'initialData']
686
353
  };
@@ -696,11 +363,49 @@ const makeActionPayload = (actions, keys) => (flowName, params) => {
696
363
  payload
697
364
  };
698
365
  };
699
- const makeActionPayloadOnPrev = makeActionPayload(ACTION_TYPES.back, PAYLAOAD_KEYS_NEEDED.back);
700
- const makeActionPayloadOnReset = makeActionPayload(ACTION_TYPES.reset, PAYLAOAD_KEYS_NEEDED.reset);
701
- const makeActionPayloadOnNode = makeActionPayload(ACTION_TYPES.jump, PAYLAOAD_KEYS_NEEDED.jump);
702
- const makeActionPayloadOnNext = makeActionPayload(ACTION_TYPES.next, PAYLAOAD_KEYS_NEEDED.next);
703
- const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLAOAD_KEYS_NEEDED.restart);
366
+ const makeActionPayloadOnBack = makeActionPayload(ACTION_TYPES.back, PAYLOAD_KEYS_NEEDED.back);
367
+ const makeActionPayloadOnReset = makeActionPayload(ACTION_TYPES.reset, PAYLOAD_KEYS_NEEDED.reset);
368
+ const makeActionPayloadOnNode = makeActionPayload(ACTION_TYPES.jump, PAYLOAD_KEYS_NEEDED.jump);
369
+ const makeActionPayloadOnNext = makeActionPayload(ACTION_TYPES.next, PAYLOAD_KEYS_NEEDED.next);
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 } = ReduxFlowerProvider.getReduxHooks();
387
+ const { index, isActive, setIndex, withUrl } = useHistoryContext();
388
+ 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
+ };
408
+
704
409
  /** This hook allows you to read flow informations, such as the flowName and ID of the current node.
705
410
  *
706
411
  * It also exposes all the functions to navigate within the flow:
@@ -720,12 +425,19 @@ const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLA
720
425
  * @param {string} name - optional parameter, if flowName exist, name is not used
721
426
  */
722
427
  const useFlower = ({ flowName: customFlowName, name } = {}) => {
723
- const dispatch = useDispatch();
724
- const { flowName: flowNameDefault, initialData } = useContext(context);
725
- const store = useStore();
428
+ const { name: flowNameDefault, initialData } = useContext(FlowerReactContext);
429
+ const { index, isActive, setIndex, withUrl } = useHistoryContext();
430
+ const { store, dispatch, useSelector } = ReduxFlowerProvider.getReduxHooks();
726
431
  const flowName = (customFlowName || name || flowNameDefault);
727
432
  const nodeId = useSelector(makeSelectCurrentNodeId(flowName ?? ''));
433
+ const currentNode = useSelector(makeSelectIsCurrentNode(flowName ?? ''));
728
434
  const startId = useSelector(makeSelectStartNodeId(flowName ?? ''));
435
+ 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 = useMemo(() => window.history.state?.stack?.map((path) => path.split('/')[1]), [window.history.state?.stack]);
729
441
  const emitNavigateEvent = useCallback(
730
442
  //TODO check this function is needed
731
443
  (params) => {
@@ -743,42 +455,61 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
743
455
  }
744
456
  }, [flowName, nodeId]);
745
457
  const next = useCallback((param) => {
746
- const params = typeof param === 'string' ? { route: param } : { data: param };
458
+ const params = typeof param === 'string' ? { route: param } : { dataIn: param };
747
459
  const { type, payload } = makeActionPayloadOnNext(flowName, params);
748
460
  dispatch({
749
- type: `flower/${type}`,
750
- payload
461
+ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`,
462
+ payload: { ...payload, data: store.getState() }
751
463
  });
464
+ if (isActive) {
465
+ setIndex(handleHistoryStackChange(index, currentNode, flowName, withUrl));
466
+ }
752
467
  emitNavigateEvent({ type, payload });
753
- }, [dispatch, emitNavigateEvent, flowName]);
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
+ };
754
483
  const back = useCallback((param) => {
755
- const { type, payload } = makeActionPayloadOnPrev(flowName, param);
756
- dispatch({ type: `flower/${type}`, payload });
484
+ const { type, payload } = makeActionPayloadOnBack(isActive ? (stack?.[stack?.length - 1] ?? flowName) : flowName, param);
485
+ dispatch({
486
+ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`,
487
+ payload
488
+ });
489
+ setIndex(index - 1);
757
490
  emitNavigateEvent({ type, payload });
758
- }, [dispatch, emitNavigateEvent, flowName]);
491
+ }, [dispatch, emitNavigateEvent, flowName, stack]);
492
+ useHistorySync({ backAction: back, nextAction: next });
759
493
  const restart = useCallback((param) => {
760
494
  const { type, payload } = makeActionPayloadOnRestart(flowName, param);
761
- dispatch({ type: `flower/${type}`, payload });
495
+ dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
762
496
  emitNavigateEvent({ type, payload });
763
497
  }, [dispatch, emitNavigateEvent, flowName]);
764
498
  const reset = useCallback((param) => {
765
499
  const { type, payload } = makeActionPayloadOnReset(flowName, typeof param === 'string'
766
500
  ? { node: param, initialData }
767
- : {
768
- ...param,
769
- initialData
770
- });
771
- dispatch({ type: `flower/${type}`, payload });
501
+ : { ...param, initialData });
502
+ dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
772
503
  emitNavigateEvent({ type, payload });
773
504
  }, [dispatch, emitNavigateEvent, flowName, initialData]);
774
505
  const jump = useCallback((param) => {
775
506
  const { type, payload } = makeActionPayloadOnNode(flowName, param);
776
- dispatch({ type: `flower/${type}`, payload });
507
+ dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
777
508
  emitNavigateEvent({ type, payload });
778
509
  }, [dispatch, emitNavigateEvent, flowName]);
779
510
  const getCurrentNodeId = useCallback((customFlowName) => {
780
511
  return _get(store.getState(), [
781
- 'flower',
512
+ REDUCER_NAME.FLOWER_FLOW,
782
513
  customFlowName || flowName,
783
514
  'current'
784
515
  ]);
@@ -790,7 +521,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
790
521
  startId,
791
522
  next,
792
523
  jump,
793
- back,
524
+ back: isActive ? interceptBack : back,
794
525
  reset,
795
526
  restart
796
527
  };
@@ -819,7 +550,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
819
550
  // }
820
551
  // );
821
552
  const useFlowerNavigate = ({ flowName, action, route, node }) => {
822
- const { flowName: flowNameContext } = useContext(context);
553
+ const { name: flowNameContext } = useContext(FlowerReactContext);
823
554
  const name = flowName || flowNameContext;
824
555
  const { next, jump, back, reset, restart } = useFlower({ flowName: name });
825
556
  const onNavigate = useCallback(() => {
@@ -855,7 +586,7 @@ function isIntrinsicElement(x) {
855
586
  return typeof x === 'string';
856
587
  }
857
588
  //TODO type FlowerNavigateWrapper props
858
- function FlowerNavigateWrapper({ hidden, Component, onNavigate, ...props }) {
589
+ function _FlowerNavigateWrapper({ hidden, Component, onNavigate, ...props }) {
859
590
  const newProps = useMemo(() => ({
860
591
  ...props,
861
592
  hidden,
@@ -874,136 +605,160 @@ function FlowerNavigateWrapper({ hidden, Component, onNavigate, ...props }) {
874
605
  /* istanbul ignore next */
875
606
  return Component && React.createElement(Component, { ...newProps });
876
607
  }
877
- const component$2 = React.memo(FlowerNavigateWrapper);
608
+ const component$5 = React.memo(_FlowerNavigateWrapper);
609
+ const FlowerNavigateWrapper = component$5;
878
610
 
879
611
  /* eslint-disable */
880
612
  //TODO type RenderRules props
613
+ // ! Probably in this scenario we must replace `FlowerRule` or refactor `FlowerNavigate`
881
614
  const RenderRules = ({ alwaysDisplay, rules, Component, flowName, onNavigate, ...props }) => {
882
- return (React.createElement(component$5, { alwaysDisplay: alwaysDisplay, rules: rules, flowName: flowName }, ({ hidden }) => React.createElement(component$2, { ...props, Component: Component, hidden: hidden, onNavigate: onNavigate })));
615
+ return (React.createElement(FlowerRule, { alwaysDisplay: alwaysDisplay, rules: rules, rootName: flowName }, ({ hidden }) => (React.createElement(FlowerNavigateWrapper, { ...props, Component: Component, hidden: hidden, onNavigate: onNavigate }))));
883
616
  };
884
- const FlowerNavigate = ({ children, flowName: forceFlowName, action, route, node, rules, alwaysDisplay, }) => {
885
- const { onNavigate, flowName } = useFlowerNavigate({ flowName: forceFlowName, action, route, node });
617
+ const _FlowerNavigate = ({ children, flowName: forceFlowName, action, route, node, rules, alwaysDisplay }) => {
618
+ const { onNavigate, flowName } = useFlowerNavigate({
619
+ flowName: forceFlowName,
620
+ action,
621
+ route,
622
+ node
623
+ });
886
624
  if (typeof children === 'function') {
887
- return React.createElement(RenderRules, { alwaysDisplay: alwaysDisplay, rules: rules, Component: children, flowName: flowName, onNavigate: onNavigate });
625
+ return (React.createElement(RenderRules, { alwaysDisplay: alwaysDisplay, rules: rules, Component: children, flowName: flowName, onNavigate: onNavigate }));
888
626
  }
889
- return React.Children.map(children, (child, i) => {
627
+ return (React.createElement(React.Fragment, null, React.Children.map(children, (child, i) => {
890
628
  if (!React.isValidElement(child))
891
629
  return child;
892
630
  const { type, props } = child;
893
631
  const Component = type;
894
- // eslint-disable-next-line react/jsx-props-no-spreading
895
- return React.createElement(RenderRules, { key: i, alwaysDisplay: alwaysDisplay, rules: rules, Component: Component, flowName: flowName, onNavigate: onNavigate, ...props });
896
- });
632
+ return (React.createElement(RenderRules, { key: i, alwaysDisplay: alwaysDisplay, rules: rules, Component: Component, flowName: flowName, onNavigate: onNavigate, ...props }));
633
+ })));
897
634
  };
898
- const component$1 = React.memo(FlowerNavigate);
899
- component$1.displayName = 'FlowerNavigate';
635
+ const component$4 = React.memo(_FlowerNavigate);
636
+ component$4.displayName = 'FlowerNavigate';
637
+ const FlowerNavigate = component$4;
900
638
 
901
- const FlowerComponent = ({ children }) => children;
902
- const component = memo(FlowerComponent);
639
+ const _FlowerNode = ({ children, onEnter, onExit }) => {
640
+ useEffect(() => {
641
+ onEnter?.();
642
+ return () => {
643
+ onExit?.();
644
+ };
645
+ }, [onEnter, onExit]);
646
+ return React.createElement(React.Fragment, null, children);
647
+ };
648
+ const component$3 = memo(_FlowerNode);
649
+ component$3.displayName = 'FlowerNode';
650
+ const FlowerNode = component$3;
903
651
 
904
- /** This hook allows you to manage and retrieve information about Forms.
905
- *
906
- * It exposes details regarding the form's state and a set of methods for reading and writing within it:
907
- *
908
- * - isSubmitted,
909
- * - isDirty,
910
- * - errors,
911
- * - customErrors,
912
- * - isValid,
913
- * - isValidating,
914
- * - getData,
915
- * - setData,
916
- * - unsetData,
917
- * - replaceData,
918
- * - reset,
919
- * - setCustomErrors
920
- * - getFormStatus
921
- *
922
- * @param {string} flowName - first optional parameter
923
- *
924
- * @param {string} name - alias optional parameter, if flowName exist, name is not used
652
+ const _FlowerRoute = ({ autostart = true, children, onEnter, onExit }) => {
653
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
654
+ const one = useRef(false);
655
+ const { name } = useContext(FlowerReactContext);
656
+ useEffect(() => {
657
+ onEnter?.();
658
+ return () => {
659
+ onExit?.();
660
+ };
661
+ }, [onEnter, onExit]);
662
+ useEffect(() => {
663
+ if (autostart && one.current === false) {
664
+ one.current = true;
665
+ dispatch(flowerActions.next({ flowName: name }));
666
+ }
667
+ }, [dispatch, name, autostart]);
668
+ return React.createElement(React.Fragment, null, children);
669
+ };
670
+ const component$2 = React.memo(_FlowerRoute);
671
+ component$2.displayName = 'FlowerRoute';
672
+ const FlowerRoute = component$2;
673
+
674
+ const _FlowerServer = ({ children }) => {
675
+ return React.createElement(React.Fragment, null, children);
676
+ };
677
+ const component$1 = React.memo(_FlowerServer);
678
+ component$1.displayName = 'FlowerServer';
679
+ const FlowerServer = component$1;
680
+
681
+ function _FlowerStart() {
682
+ const { dispatch, useSelector } = ReduxFlowerProvider.getReduxHooks();
683
+ const one = useRef(false);
684
+ const { name, autostart = true, currentNode } = useContext(FlowerReactContext);
685
+ const startNodeId = useSelector(makeSelectStartNodeId(name ?? ''));
686
+ useEffect(() => {
687
+ if (startNodeId === currentNode && autostart && one.current === false) {
688
+ one.current = true;
689
+ dispatch(flowerActions.next({ flowName: name, isStart: true }));
690
+ // if (global.window
691
+ // // eslint-disable-next-line no-underscore-dangle, no-undef
692
+ // && global.window.__FLOWER_DEVTOOLS__ && global.window.__FLOWER_DEVTOOLS__AUTO) {
693
+ // Emitter.emit('flower-devtool-from-client', {
694
+ // source: 'flower-client',
695
+ // action: 'START_FLOWER',
696
+ // name: flowName,
697
+ // });
698
+ // }
699
+ }
700
+ }, [dispatch, autostart, startNodeId, currentNode, name]);
701
+ return null;
702
+ }
703
+ const component = React.memo(_FlowerStart);
704
+ component.displayName = 'FlowerStart';
705
+ const FlowerStart = component;
706
+
707
+ const FlowerProvider = ({ children, enableReduxDevtool, configureStore }) => {
708
+ const { reducer, ...rest } = configureStore ?? {
709
+ devTools: enableReduxDevtool
710
+ };
711
+ return (React.createElement(ReduxFlowerProvider, { configureStore: {
712
+ reducer: { ...reducerFlower, ...(reducer ?? {}) },
713
+ ...rest,
714
+ devTools: enableReduxDevtool ?? rest?.devTools
715
+ } }, children));
716
+ };
717
+
718
+ /**
925
719
  *
720
+ * @param {ConfigureStoreOptions} configureStore object to configure store - same structure as redux configureStore options
721
+ * @param {MiddlewareList} middlewaresBlacklist list of flower middlewares to blacklist
722
+ * @returns {Store} redux store instance
926
723
  */
927
- const useFlowerForm = ({ flowName: customFlowName, name } = {}) => {
928
- const { flowName: flowNameDefault } = useContext(context);
929
- const dispatch = useDispatch();
930
- const store = useStore();
931
- const flowName = customFlowName || name || flowNameDefault || '';
932
- const currentNode = useSelector(makeSelectCurrentNodeId(flowName));
933
- const { errors, customErrors, isValid, isSubmitted, isDirty, hasFocus, isValidating } = useSelector(makeSelectNodeErrors(flowName, currentNode));
934
- const getData = useCallback((path) => {
935
- const { flowNameFromPath = flowName, path: newpath } = CoreUtils.getPath(path);
936
- return _get(store.getState(), [
937
- 'flower',
938
- flowNameFromPath,
939
- 'data',
940
- ...newpath
941
- ]);
942
- }, [store, flowName]);
943
- const getFormStatus = useCallback((path) => {
944
- const { flowNameFromPath = flowName, path: newpath } = CoreUtils.getPath(path);
945
- return _get(store.getState(), [
946
- 'flower',
947
- flowNameFromPath,
948
- 'form',
949
- ...newpath
950
- ]);
951
- }, [store, flowName]);
952
- const setDataField = useCallback((id, val, dirty = true) => {
953
- const { flowNameFromPath = flowName } = CoreUtils.getPath(id);
954
- dispatch(actions.addDataByPath({
955
- flowName: flowNameFromPath,
956
- id,
957
- value: val,
958
- dirty
959
- }));
960
- return;
961
- }, [flowName, dispatch]);
962
- const setData = useCallback((val, id) => {
963
- if (id) {
964
- setDataField(id, val);
965
- return;
966
- }
967
- dispatch(actions.addData({ flowName, value: val }));
968
- }, [flowName, setDataField, dispatch]);
969
- const unsetData = useCallback((path) => {
970
- const { flowNameFromPath = flowName, path: newpath } = CoreUtils.getPath(path);
971
- dispatch(actions.unsetData({ flowName: flowNameFromPath, id: newpath }));
972
- }, [flowName, dispatch]);
973
- const replaceData = useCallback((val) => {
974
- dispatch(actions.replaceData({ flowName, value: val }));
975
- }, [flowName, dispatch]);
976
- const reset = useCallback((nodeId) => {
977
- dispatch(actions.resetForm({ flowName, id: nodeId || currentNode }));
978
- }, [flowName, currentNode, dispatch]);
979
- const setCustomErrors = useCallback((field, errors, nodeId) => {
980
- dispatch({
981
- type: 'flower/formAddCustomErrors',
982
- payload: {
983
- name: flowName,
984
- id: field,
985
- currentNode: nodeId || currentNode,
986
- errors
987
- }
724
+ const createStoreWithFlower = (configureStore$1, middlewaresBlacklist) => {
725
+ if (!configureStore$1) {
726
+ return configureStore({
727
+ reducer: combineReducers({ ...reducerData, ...reducerFlower }),
728
+ devTools: true
988
729
  });
989
- }, [flowName, currentNode, dispatch]);
990
- return {
991
- isSubmitted,
992
- isDirty,
993
- hasFocus,
994
- errors,
995
- customErrors,
996
- isValid,
997
- isValidating,
998
- getData,
999
- setData,
1000
- setDataField,
1001
- unsetData,
1002
- replaceData,
1003
- reset,
1004
- setCustomErrors,
1005
- getFormStatus
1006
- };
730
+ }
731
+ const { reducer, ...configOptions } = configureStore$1 ?? {};
732
+ const combinedReducers = combineReducers({
733
+ ...reducerData,
734
+ ...reducerFlower,
735
+ ...(reducer || {})
736
+ });
737
+ const flowerMiddlewares = middlewaresBlacklist && !!middlewaresBlacklist.length
738
+ ? middlewares.filter((middleware) => !middlewaresBlacklist.includes(middleware.name))
739
+ : middlewares;
740
+ return configureStore({
741
+ reducer: combinedReducers,
742
+ ...configOptions,
743
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(...flowerMiddlewares, ...(configOptions.middleware ?? []))
744
+ });
745
+ };
746
+
747
+ const updateAction = (reducerName) => createAction(`${reducerName}/flowerUpdateData`);
748
+ const createSliceWithFlower = (createSliceOptions) => {
749
+ const { name, reducers } = createSliceOptions;
750
+ updateAction(name);
751
+ const slice = createSlice({
752
+ ...createSliceOptions,
753
+ reducers: {
754
+ ...reducers,
755
+ flowerUpdateData: (state, { payload }) => {
756
+ const { path, value } = payload;
757
+ set(state, path, value);
758
+ }
759
+ }
760
+ });
761
+ return slice;
1007
762
  };
1008
763
 
1009
- export { component$c as Flower, component$a as FlowerAction, component as FlowerComponent, context as FlowerContext, Consumer as FlowerContextConsumer, Provider as FlowerContextProvider, component$4 as FlowerField, component$8 as FlowerFlow, component$1 as FlowerNavigate, component$b as FlowerNode, FlowerProvider, component$6 as FlowerRoute, component$5 as FlowerRule, component$9 as FlowerServer, component$7 as FlowerStart, component$3 as FlowerValue, getDataByFlow, useFlower, useFlowerForm, useSelector };
764
+ export { Flower, FlowerAction, FlowerFlow, FlowerNavigate, FlowerNode, FlowerProvider, FlowerRoute, FlowerServer, FlowerStart, createSliceWithFlower, createStoreWithFlower, flowerReducers, makeSelectData, reducerFlower, useFlower, useFlowerNavigate };