@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/dist/index.esm.js CHANGED
@@ -1,20 +1,46 @@
1
- import React, { memo, useRef, useState, useMemo, Children, useEffect, useContext, useCallback } from 'react';
1
+ import React, { useEffect, memo, useRef, useState, useMemo, Children, useContext, useCallback, createContext } from 'react';
2
2
  import _keyBy from 'lodash/keyBy';
3
- import { CoreUtils, FlowerCoreBaseReducers, REDUCER_NAME, FlowerCoreStateSelectors, FlowerStateUtils, devtoolState, Emitter } from '@flowerforce/flower-core';
3
+ import { FlowUtils, FlowerCoreBaseReducers, REDUCER_NAME, FlowerCoreStateSelectors, FlowerStateUtils, Emitter, devtoolState, isAbacInitialized, getRawRules, initAbac, getSubject, getAbacEngine } from '@flowerforce/flower-core';
4
4
  import { FlowerReactProvider, FlowerReactContext } from '@flowerforce/flower-react-context';
5
5
  import _get from 'lodash/get';
6
- import { createSlice, configureStore, combineReducers, createAction } from '@reduxjs/toolkit';
7
6
  import { reducerData, ReduxFlowerProvider, flowerDataActions, middlewares } from '@flowerforce/flower-react-store';
8
7
  export { createApi } from '@flowerforce/flower-react-store';
8
+ import { createSlice, configureStore, combineReducers, createAction } from '@reduxjs/toolkit';
9
9
  import { createSelector } from 'reselect';
10
+ import { useHistoryContext } from '@flowerforce/flower-react-history-context';
11
+ export { HistoryContextProvider } from '@flowerforce/flower-react-history-context';
10
12
  import { FlowerRule } from '@flowerforce/flower-react-shared';
11
13
  import set from 'lodash/set';
12
14
 
13
- // eslint-disable-next-line import/prefer-default-export
14
- const convertElements = (nodes) => {
15
- const res = CoreUtils.generateNodesForFlowerJson(nodes);
16
- return res;
15
+ const getRulesExists = (rules) => {
16
+ return Object.keys(rules).length ? FlowUtils.mapEdge(rules) : undefined;
17
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
+ });
18
44
 
19
45
  const flowerReducer = createSlice({
20
46
  name: REDUCER_NAME.FLOWER_FLOW,
@@ -41,60 +67,37 @@ const selectFlowerHistory = (name) => createSelector(selectFlower(name), FlowerC
41
67
  const makeSelectNodesIds = (name) => createSelector(selectFlower(name), FlowerCoreStateSelectors.makeSelectNodesIds);
42
68
  const makeSelectStartNodeId = (name) => createSelector(selectFlower(name), FlowerCoreStateSelectors.makeSelectStartNodeId);
43
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]);
44
71
  const makeSelectPrevNodeRetain = (name) => createSelector(makeSelectNodesIds(name), selectFlowerHistory(name), makeSelectCurrentNodeId(name), FlowerCoreStateSelectors.makeSelectPrevNodeRetain);
45
72
  const makeSelectCurrentNodeDisabled = (name) => createSelector(makeSelectNodesIds(name), makeSelectCurrentNodeId(name), FlowerCoreStateSelectors.makeSelectCurrentNodeDisabled);
46
73
  // dati nel flow selezionato
47
74
  const makeSelectData = (name) => createSelector(selectFlowerDataNode(name), (data) => data?.data ?? {});
48
75
  createSelector(selectGlobalData, FlowerStateUtils.getAllData);
49
76
 
50
- /* eslint-disable no-undef */
51
- /* eslint-disable no-underscore-dangle */
52
- /*
53
- * FlowerClient
54
- */
55
- const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null, initialState = {}, initialData }) => {
56
- const flowName = name;
57
- const { dispatch, store, useSelector } = ReduxFlowerProvider.getReduxHooks();
58
- const one = useRef(false);
59
- const [wsDevtools, setWsDevtools] = useState(devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
60
- // TODO could make that transformation in CoreUtils.generateNodesForFlowerJson
61
- // eslint-disable-next-line react-hooks/exhaustive-deps, max-len
62
- const nodes = useMemo(
63
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
- () => convertElements(Children.toArray(children)), [children]);
65
- const nodeById = useMemo(() => _keyBy(Children.toArray(children), 'props.id'), [children]);
66
- const isInitialized = useSelector(makeSelectStartNodeId(name));
67
- const history = useSelector(selectFlowerHistory(name));
68
- const current = useSelector(makeSelectCurrentNodeId(flowName));
69
- const isDisabled = useSelector(makeSelectCurrentNodeDisabled(flowName));
70
- const prevFlowerNodeId = useSelector(makeSelectPrevNodeRetain(flowName));
77
+ const useInitNodes = ({ one, nodes, name, startId, persist = false, initialData, initialState = {} }) => {
78
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
71
79
  useEffect(() => {
72
80
  if (nodes.length > 0 && one.current === false) {
73
81
  one.current = true;
74
82
  dispatch(flowerActions.initNodes({
75
- name: flowName,
76
- // @ts-expect-error FIX ME
83
+ name,
77
84
  nodes,
78
85
  startId: startId ?? '',
79
- persist: destroyOnUnmount === false,
86
+ persist,
80
87
  initialState
81
88
  }));
82
- if (initialData) {
83
- dispatch(flowerDataActions.initData({
84
- rootName: flowName,
85
- initialData: initialData ?? {}
86
- }));
87
- }
88
89
  }
89
- }, [
90
- dispatch,
91
- flowName,
92
- nodes,
93
- startId,
94
- destroyOnUnmount,
95
- initialState,
96
- initialData
97
- ]);
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();
98
101
  useEffect(() => {
99
102
  /* istanbul ignore next */
100
103
  const eventCb = (msg) => {
@@ -108,12 +111,10 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
108
111
  dispatch(flowerActions.setCurrentNode({ name: msg.name, node: msg.id }));
109
112
  }
110
113
  // if (msg.action === 'REPLACE_DATA' && msg.name === flowName) {
111
- // dispatch(
112
- // formActions.replaceData({ flowName: msg.name, value: msg.data })
113
- // )
114
+ // dispatch(actions.replaceData({ flowName: msg.name, value: msg.data }))
114
115
  // }
115
116
  // if (msg.action === 'ADD_DATA' && msg.name === flowName) {
116
- // dispatch(formActions.addData({ flowName: msg.name, value: msg.data }))
117
+ // dispatch(actions.addData({ flowName: msg.name, value: msg.data }))
117
118
  // }
118
119
  };
119
120
  /* istanbul ignore next */
@@ -127,13 +128,22 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
127
128
  }
128
129
  };
129
130
  }, [dispatch, flowName]);
131
+ };
132
+
133
+ const useDestroyFlow = ({ flowName, one, persist }) => {
134
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
130
135
  useEffect(() => () => {
131
- // unmount function
132
- if (destroyOnUnmount && one.current === true) {
136
+ if (persist)
137
+ return;
138
+ if (one.current === true) {
133
139
  one.current = false;
134
140
  dispatch(flowerActions.destroy({ name: flowName }));
135
141
  }
136
- }, [dispatch, flowName, destroyOnUnmount]);
142
+ }, [dispatch, flowName, persist]);
143
+ };
144
+
145
+ const useClientInitEvent = ({ flowName, isInitialized, wsDevtools }) => {
146
+ const { store } = ReduxFlowerProvider.getReduxHooks();
137
147
  useEffect(() => {
138
148
  /* istanbul ignore next */
139
149
  if (isInitialized &&
@@ -149,7 +159,11 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
149
159
  getState: store.getState
150
160
  });
151
161
  }
152
- }, [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));
153
167
  useEffect(() => {
154
168
  /* istanbul ignore next */
155
169
  if (isInitialized &&
@@ -163,7 +177,9 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
163
177
  history
164
178
  });
165
179
  }
166
- }, [dispatch, flowName, history, wsDevtools, isInitialized]);
180
+ }, [flowName, history, wsDevtools, isInitialized]);
181
+ };
182
+ const useSetCurrentEvent = ({ current, flowName, isInitialized, wsDevtools }) => {
167
183
  useEffect(() => {
168
184
  if (!current)
169
185
  return;
@@ -183,6 +199,9 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
183
199
  });
184
200
  }
185
201
  }, [flowName, current, wsDevtools, isInitialized]);
202
+ };
203
+ const useFlowerNavigateEvent = ({ current, flowName, isInitialized, wsDevtools, isDisabled }) => {
204
+ const { dispatch } = ReduxFlowerProvider.getReduxHooks();
186
205
  useEffect(() => {
187
206
  if (!current)
188
207
  return;
@@ -190,7 +209,10 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
190
209
  if (!isInitialized)
191
210
  return;
192
211
  if (isDisabled) {
193
- dispatch(flowerActions.next({ flowName }));
212
+ dispatch({
213
+ type: `${REDUCER_NAME.FLOWER_FLOW}/next`,
214
+ payload: { flowName, disabled: true }
215
+ });
194
216
  // eslint-disable-next-line no-underscore-dangle, no-undef
195
217
  /* istanbul ignore next */
196
218
  if (wsDevtools &&
@@ -213,7 +235,7 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
213
235
  devtoolState &&
214
236
  _get(devtoolState, '__FLOWER_DEVTOOLS__')) {
215
237
  if (isInitialized === current)
216
- return; // salto il primo event
238
+ return; // salto il primo evento
217
239
  Emitter.emit('flower-devtool-from-client', {
218
240
  source: 'flower-client',
219
241
  action: 'SET_SELECTED',
@@ -222,15 +244,55 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
222
244
  time: new Date()
223
245
  });
224
246
  }
225
- }, [
226
- dispatch,
227
- flowName,
247
+ }, [dispatch, flowName, current, isDisabled, wsDevtools, isInitialized]);
248
+ };
249
+
250
+ const useSelectorsClient = (flowName) => {
251
+ const { useSelector } = 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 = useRef(false);
271
+ const [wsDevtools, setWsDevtools] = useState(devtoolState && _get(devtoolState, '__FLOWER_DEVTOOLS_INITIALIZED__', false));
272
+ const nodes = useMemo(() => generateNodesForFlowerJson(Children.toArray(children)), [children]);
273
+ const nodeById = useMemo(() => _keyBy(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, setWsDevtools, flowName });
286
+ useClientInitEvent({ flowName, isInitialized, wsDevtools });
287
+ useSetHistoryEvent({ flowName, isInitialized, wsDevtools });
288
+ useSetCurrentEvent({ current, flowName, isInitialized, wsDevtools });
289
+ useFlowerNavigateEvent({
228
290
  current,
291
+ flowName,
229
292
  isDisabled,
230
- store,
231
- wsDevtools,
232
- isInitialized
233
- ]);
293
+ isInitialized,
294
+ wsDevtools
295
+ });
234
296
  const currentNodeId = prevFlowerNodeId || current;
235
297
  const contextValues = useMemo(() => ({
236
298
  name: flowName,
@@ -244,10 +306,10 @@ const FlowerClient = ({ children, name, destroyOnUnmount = true, startId = null,
244
306
  React.createElement(FlowerReactProvider, { value: prevContextValues }, nodeById[currentNodeId]),
245
307
  React.createElement(FlowerReactProvider, { value: contextValues }, !isDisabled && current !== currentNodeId && nodeById[current]))) : null;
246
308
  };
247
- const component$9 = memo(FlowerClient);
248
- component$9.displayName = 'Flower';
309
+ const component$8 = memo(FlowerClient);
310
+ component$8.displayName = 'Flower';
249
311
  // workaround for let typescript read JSX component as a valid JSX element using react 19(?)
250
- const Flower = component$9;
312
+ const Flower = component$8;
251
313
 
252
314
  const FlowAction = ({ children, onEnter, onExit }) => {
253
315
  useEffect(() => {
@@ -258,13 +320,12 @@ const FlowAction = ({ children, onEnter, onExit }) => {
258
320
  }, [onEnter, onExit]);
259
321
  return React.createElement(React.Fragment, null, children);
260
322
  };
261
- const component$8 = React.memo(FlowAction);
262
- component$8.displayName = 'FlowerAction';
263
- const FlowerAction = component$8;
323
+ const component$7 = React.memo(FlowAction);
324
+ component$7.displayName = 'FlowerAction';
325
+ const FlowerAction = component$7;
264
326
 
265
327
  const _FlowerComponent = ({ children }) => children;
266
- const component$7 = memo(_FlowerComponent);
267
- const FlowerComponent = component$7;
328
+ memo(_FlowerComponent);
268
329
 
269
330
  const _FlowerFlow = ({ children, onEnter, onExit }) => {
270
331
  useEffect(() => {
@@ -310,6 +371,43 @@ const makeActionPayloadOnReset = makeActionPayload(ACTION_TYPES.reset, PAYLOAD_K
310
371
  const makeActionPayloadOnNode = makeActionPayload(ACTION_TYPES.jump, PAYLOAD_KEYS_NEEDED.jump);
311
372
  const makeActionPayloadOnNext = makeActionPayload(ACTION_TYPES.next, PAYLOAD_KEYS_NEEDED.next);
312
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 } = ReduxFlowerProvider.getReduxHooks();
390
+ const { index, isActive, setIndex, withUrl } = useHistoryContext();
391
+ 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
+ };
313
411
 
314
412
  /** This hook allows you to read flow informations, such as the flowName and ID of the current node.
315
413
  *
@@ -331,10 +429,18 @@ const makeActionPayloadOnRestart = makeActionPayload(ACTION_TYPES.restart, PAYLO
331
429
  */
332
430
  const useFlower = ({ flowName: customFlowName, name } = {}) => {
333
431
  const { name: flowNameDefault, initialData } = useContext(FlowerReactContext);
432
+ const { index, isActive, setIndex, withUrl } = useHistoryContext();
334
433
  const { store, dispatch, useSelector } = ReduxFlowerProvider.getReduxHooks();
335
434
  const flowName = (customFlowName || name || flowNameDefault);
336
435
  const nodeId = useSelector(makeSelectCurrentNodeId(flowName ?? ''));
436
+ const currentNode = useSelector(makeSelectIsCurrentNode(flowName ?? ''));
337
437
  const startId = useSelector(makeSelectStartNodeId(flowName ?? ''));
438
+ 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 = useMemo(() => window.history.state?.stack?.map((path) => path.split('/')[1]), [window.history.state?.stack]);
338
444
  const emitNavigateEvent = useCallback(
339
445
  //TODO check this function is needed
340
446
  (params) => {
@@ -356,18 +462,37 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
356
462
  const { type, payload } = makeActionPayloadOnNext(flowName, params);
357
463
  dispatch({
358
464
  type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`,
359
- payload: {
360
- ...payload,
361
- data: store.getState()
362
- }
465
+ payload: { ...payload, data: store.getState() }
363
466
  });
467
+ if (isActive) {
468
+ setIndex(handleHistoryStackChange(index, currentNode, flowName, withUrl));
469
+ }
364
470
  emitNavigateEvent({ type, payload });
365
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
+ };
366
486
  const back = useCallback((param) => {
367
- const { type, payload } = makeActionPayloadOnBack(flowName, param);
368
- dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
487
+ const { type, payload } = makeActionPayloadOnBack(isActive ? (stack?.[stack?.length - 1] ?? flowName) : flowName, param);
488
+ dispatch({
489
+ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`,
490
+ payload
491
+ });
492
+ setIndex(index - 1);
369
493
  emitNavigateEvent({ type, payload });
370
- }, [dispatch, emitNavigateEvent, flowName]);
494
+ }, [dispatch, emitNavigateEvent, flowName, stack]);
495
+ useHistorySync({ backAction: back, nextAction: next });
371
496
  const restart = useCallback((param) => {
372
497
  const { type, payload } = makeActionPayloadOnRestart(flowName, param);
373
498
  dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
@@ -376,10 +501,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
376
501
  const reset = useCallback((param) => {
377
502
  const { type, payload } = makeActionPayloadOnReset(flowName, typeof param === 'string'
378
503
  ? { node: param, initialData }
379
- : {
380
- ...param,
381
- initialData
382
- });
504
+ : { ...param, initialData });
383
505
  dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/${type}`, payload });
384
506
  emitNavigateEvent({ type, payload });
385
507
  }, [dispatch, emitNavigateEvent, flowName, initialData]);
@@ -402,7 +524,7 @@ const useFlower = ({ flowName: customFlowName, name } = {}) => {
402
524
  startId,
403
525
  next,
404
526
  jump,
405
- back,
527
+ back: isActive ? interceptBack : back,
406
528
  reset,
407
529
  restart
408
530
  };
@@ -642,4 +764,73 @@ const createSliceWithFlower = (createSliceOptions) => {
642
764
  return slice;
643
765
  };
644
766
 
645
- export { Flower, FlowerAction, FlowerComponent, FlowerFlow, FlowerNavigate, FlowerNode, FlowerProvider, FlowerRoute, FlowerServer, FlowerStart, createSliceWithFlower, createStoreWithFlower, flowerReducers, makeSelectData, reducerFlower, useFlower, useFlowerNavigate };
767
+ const AbacContext = 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] = useState(isAbacInitialized());
775
+ const [localRawRules, setLocalRawRules] = useState(getRawRules());
776
+ useEffect(() => {
777
+ if (rules) {
778
+ 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
+ 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 = useCallback((ctx) => {
813
+ if (!isAbacInitialized())
814
+ return !denyByDefault;
815
+ const subject = getSubject();
816
+ return (getAbacEngine()?.decide({ subject: subject ?? {}, ...ctx }) === 'Permit');
817
+ }, []);
818
+ const allowedActions = useCallback((resource, actions = ['read', 'create', 'update', 'delete']) => {
819
+ if (!isAbacInitialized())
820
+ return denyByDefault ? [] : actions.slice();
821
+ const subject = getSubject();
822
+ return getAbacEngine()?.allowedActions({ subject: subject ?? {}, resource, environment: undefined }, actions);
823
+ }, []);
824
+ const value = 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 useContext(AbacContext);
834
+ }
835
+
836
+ export { AbacProvider, Flower, FlowerAction, FlowerFlow, FlowerNavigate, FlowerNode, FlowerProvider, FlowerRoute, FlowerServer, FlowerStart, createSliceWithFlower, createStoreWithFlower, flowerReducers, makeSelectData, reducerFlower, useAbac, useFlower, useFlowerNavigate };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { RuleInput, AbacCtx, Resource, Action } from '@flowerforce/flower-core';
3
+ type AbacProviderProps = {
4
+ children: React.ReactNode;
5
+ rules?: RuleInput[];
6
+ rulesPath?: string;
7
+ fetchOptions?: RequestInit;
8
+ denyByDefault?: boolean;
9
+ };
10
+ type AbacContextValue = {
11
+ can: (args: Omit<AbacCtx, 'subject'>) => boolean;
12
+ allowedActions: (resource: Resource | undefined, actions?: Action[]) => Action[] | undefined;
13
+ loaded: boolean;
14
+ rawRules?: RuleInput[] | null;
15
+ };
16
+ export declare const AbacProvider: React.FC<AbacProviderProps>;
17
+ export declare function useAbac(): AbacContextValue;
18
+ export {};
@@ -0,0 +1 @@
1
+ export * from './abacProvider';
@@ -1,15 +1,3 @@
1
- import React, { PropsWithChildren } from 'react';
2
- type FlowerInitialState = {
3
- startId?: string;
4
- current?: string;
5
- history?: string[];
6
- };
7
- type FlowerClientProps = PropsWithChildren<{
8
- name: string;
9
- destroyOnUnmount?: boolean;
10
- startId?: string | null;
11
- initialState?: FlowerInitialState;
12
- initialData?: Record<string, unknown>;
13
- }>;
14
- export declare const Flower: ({ children, name, destroyOnUnmount, startId, initialState, initialData }: FlowerClientProps) => React.JSX.Element | null;
15
- export {};
1
+ import React from 'react';
2
+ import { FlowerClientProps } from '../types/Flower';
3
+ export declare const Flower: ({ children, name, destroyOnUnmount, persist, startId, initialState, initialData }: FlowerClientProps) => React.JSX.Element | null;
@@ -0,0 +1,5 @@
1
+ import { UseClientInitEventProps, UseFlowerNavigateEventProps, UseSetCurrentEventProps, UseSetHistoryEventProps } from './types';
2
+ export declare const useClientInitEvent: ({ flowName, isInitialized, wsDevtools }: UseClientInitEventProps) => void;
3
+ export declare const useSetHistoryEvent: ({ flowName, isInitialized, wsDevtools }: UseSetHistoryEventProps) => void;
4
+ export declare const useSetCurrentEvent: ({ current, flowName, isInitialized, wsDevtools }: UseSetCurrentEventProps) => void;
5
+ export declare const useFlowerNavigateEvent: ({ current, flowName, isInitialized, wsDevtools, isDisabled }: UseFlowerNavigateEventProps) => void;
@@ -0,0 +1,5 @@
1
+ export * from './useInitNodes';
2
+ export * from './useInitDevtools';
3
+ export * from './useDestroyFlow';
4
+ export * from './eventsHandlers';
5
+ export * from './useSelectorsClient';
@@ -0,0 +1,29 @@
1
+ import { NodeConfig } from '@flowerforce/flower-core';
2
+ import { FlowerClientProps } from '../../../types/Flower';
3
+ import { MutableRefObject } from 'react';
4
+ export type UseClientInitEventProps = {
5
+ isInitialized: string;
6
+ wsDevtools: boolean;
7
+ flowName: string;
8
+ };
9
+ export type UseSetHistoryEventProps = UseClientInitEventProps;
10
+ export type UseSetCurrentEventProps = UseClientInitEventProps & {
11
+ current: string;
12
+ };
13
+ export type UseFlowerNavigateEventProps = UseSetCurrentEventProps & {
14
+ isDisabled: boolean;
15
+ };
16
+ export type UseDestroyFlowProps = {
17
+ one: MutableRefObject<boolean>;
18
+ persist: boolean;
19
+ flowName: string;
20
+ };
21
+ export type UseInitDevtoolsProps = {
22
+ devtoolState: Object;
23
+ flowName: string;
24
+ setWsDevtools: (value: boolean) => void;
25
+ };
26
+ export type UseInitNodesProps = {
27
+ one: MutableRefObject<boolean>;
28
+ nodes: NodeConfig[];
29
+ } & Omit<FlowerClientProps, 'destroyOnUnmount'>;
@@ -0,0 +1,12 @@
1
+ import { useFlowerActions } from '../../types';
2
+ /**
3
+ * Hook centralizzato per sincronizzare la navigazione browser con Redux
4
+ * @param {function} backAction - Azione Redux da chiamare quando si fa "indietro"
5
+ * @param {function} nextAction - Azione Redux da chiamare quando si fa "avanti"
6
+ */
7
+ type UseHistorySyncProps = {
8
+ backAction: () => ReturnType<useFlowerActions['back']>;
9
+ nextAction: () => ReturnType<useFlowerActions['next']>;
10
+ };
11
+ export declare const useHistorySync: ({ backAction, nextAction }: UseHistorySyncProps) => void;
12
+ export {};
@@ -0,0 +1,2 @@
1
+ import { UseDestroyFlowProps } from './types';
2
+ export declare const useDestroyFlow: ({ flowName, one, persist }: UseDestroyFlowProps) => void;
@@ -0,0 +1,2 @@
1
+ import { UseInitDevtoolsProps } from './types';
2
+ export declare const useInitDevtools: ({ devtoolState, setWsDevtools, flowName }: UseInitDevtoolsProps) => void;
@@ -0,0 +1,2 @@
1
+ import { UseInitNodesProps } from './types';
2
+ export declare const useInitNodes: ({ one, nodes, name, startId, persist, initialData, initialState }: UseInitNodesProps) => void;
@@ -0,0 +1,6 @@
1
+ export declare const useSelectorsClient: (flowName: string) => {
2
+ isInitialized: string;
3
+ current: string;
4
+ isDisabled: boolean;
5
+ prevFlowerNodeId: string | undefined;
6
+ };
@@ -1,3 +1,4 @@
1
+ import { INode } from '@flowerforce/flower-core';
1
2
  export declare const makeActionPayloadOnBack: (flowName: string | undefined, params: any) => {
2
3
  type: string;
3
4
  payload: Record<string, any>;
@@ -18,3 +19,4 @@ export declare const makeActionPayloadOnRestart: (flowName: string | undefined,
18
19
  type: string;
19
20
  payload: Record<string, any>;
20
21
  };
22
+ export declare const handleHistoryStackChange: (currentIndex: number, currentNode: INode, flowName: string, withUrl?: boolean) => number;