@flowerforce/flower-core 3.0.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.
@@ -0,0 +1,937 @@
1
+ import { get, isBuffer, intersection, trimStart, isPlainObject, mapKeys, mapValues, keyBy, omit, has, isEmpty, find, set, lastIndexOf, slice, last, unset, cloneDeep } from 'lodash';
2
+ import { TinyEmitter } from 'tiny-emitter';
3
+
4
+ const keyIdentity = key => key;
5
+ const flatten = (target, opts) => {
6
+ const options = opts ?? {};
7
+ const safe = get(options, 'safe', false);
8
+ const maxDepth = get(options, 'maxDepth', 0);
9
+ const delimiter = get(options, 'delimiter', '.');
10
+ const transformKey = get(options, 'transformKey', keyIdentity);
11
+ const output = {};
12
+ const step = (object, prev, currentDepth) => {
13
+ const depth = currentDepth || 1;
14
+ Object.keys(object).forEach(key => {
15
+ const value = object[key];
16
+ const isarray = safe && Array.isArray(value);
17
+ const type = Object.prototype.toString.call(value);
18
+ const isbuffer = isBuffer(value);
19
+ const isobject = type === '[object Object]' || type === '[object Array]';
20
+ const newKey = prev ? prev + delimiter + transformKey(key) : transformKey(key);
21
+ if (!isarray && !isbuffer && isobject && Object.keys(value).length && (!maxDepth || depth < maxDepth)) {
22
+ return step(value, newKey, depth + 1);
23
+ }
24
+ output[newKey] = value;
25
+ });
26
+ };
27
+ step(target);
28
+ return output;
29
+ };
30
+ const unflatten = (target, opts) => {
31
+ const options = opts ?? {};
32
+ const object = get(options, 'object', false);
33
+ const overwrite = get(options, 'overwrite', false);
34
+ const delimiter = get(options, 'delimiter', '.');
35
+ const transformKey = get(options, 'transformKey', keyIdentity);
36
+ const result = {};
37
+ const isbuffer = isBuffer(target);
38
+ if (isbuffer || Object.prototype.toString.call(target) !== '[object Object]') {
39
+ return target;
40
+ }
41
+ const getkey = key => {
42
+ const parsedKey = Number(key);
43
+ return isNaN(parsedKey) || key?.indexOf('.') !== -1 || object ? key : parsedKey;
44
+ };
45
+ function addKeys(keyPrefix, recipient, target) {
46
+ return Object.keys(target).reduce((result, key) => {
47
+ result[keyPrefix + delimiter + key] = target[key];
48
+ return result;
49
+ }, recipient);
50
+ }
51
+ function isEmpty(val) {
52
+ const type = Object.prototype.toString.call(val);
53
+ const isArray = type === '[object Array]';
54
+ const isObject = type === '[object Object]';
55
+ if (!val) {
56
+ return true;
57
+ }
58
+ if (isArray) {
59
+ return !val.length;
60
+ }
61
+ if (isObject) {
62
+ return !Object.keys(val).length;
63
+ }
64
+ }
65
+ target = Object.keys(target).reduce((result, key) => {
66
+ const type = Object.prototype.toString.call(target[key]);
67
+ const isObject = type === '[object Object]' || type === '[object Array]';
68
+ if (!isObject || isEmpty(target[key])) {
69
+ result[key] = target[key];
70
+ return result;
71
+ }
72
+ return addKeys(key, result, flatten(target[key], opts));
73
+ }, {});
74
+ Object.keys(target).forEach(key => {
75
+ const split = key.split(delimiter).map(transformKey);
76
+ let key1 = getkey(split.shift());
77
+ let key2 = getkey(split[0]);
78
+ let recipient = result;
79
+ while (key2 !== undefined) {
80
+ const recipient_key_1 = key1 && get(recipient, key1);
81
+ const type = Object.prototype.toString.call(recipient_key_1);
82
+ const isobject = type === '[object Object]' || type === '[object Array]';
83
+ if (!overwrite && !isobject && typeof recipient_key_1 !== 'undefined') {
84
+ return;
85
+ }
86
+ if (overwrite && !isobject || !overwrite && recipient_key_1 == null) {
87
+ recipient[key1] = typeof key2 === 'number' && !object ? [] : {};
88
+ }
89
+ recipient = key1 && get(recipient, key1);
90
+ if (split.length > 0) {
91
+ key1 = getkey(split.shift());
92
+ key2 = getkey(split[0]);
93
+ }
94
+ }
95
+ recipient[key1] = unflatten(target[key], opts);
96
+ });
97
+ return result;
98
+ };
99
+ const flat = {
100
+ flatten,
101
+ unflatten
102
+ };
103
+
104
+ const Emitter = new TinyEmitter();
105
+
106
+ const operators = {
107
+ $exists: (a, b) => !rulesMatcherUtils.isEmpty(a) === b,
108
+ $eq: (a, b) => a === b,
109
+ $ne: (a, b) => a !== b,
110
+ $gt: (a, b) => rulesMatcherUtils.forceNumber(a) > parseFloat(b),
111
+ $gte: (a, b) => rulesMatcherUtils.forceNumber(a) >= parseFloat(b),
112
+ $lt: (a, b) => rulesMatcherUtils.forceNumber(a) < parseFloat(b),
113
+ $lte: (a, b) => rulesMatcherUtils.forceNumber(a) <= parseFloat(b),
114
+ $strGt: (a, b) => String(a || '').length > parseFloat(b),
115
+ $strGte: (a, b) => String(a || '').length >= parseFloat(b),
116
+ $strLt: (a, b) => String(a || '').length < parseFloat(b),
117
+ $strLte: (a, b) => String(a || '').length <= parseFloat(b),
118
+ $in: (a, b) => rulesMatcherUtils.forceArray(b).some(c => intersection(rulesMatcherUtils.forceArray(a), rulesMatcherUtils.forceArray(c)).length),
119
+ $nin: (a, b) => !rulesMatcherUtils.forceArray(b).some(c => intersection(rulesMatcherUtils.forceArray(a), rulesMatcherUtils.forceArray(c)).length),
120
+ $all: (a, b) => rulesMatcherUtils.forceArray(b).every(c => intersection(rulesMatcherUtils.forceArray(a), rulesMatcherUtils.forceArray(c)).length),
121
+ $regex: (a, b, opt) => rulesMatcherUtils.forceArray(b).some(c => c instanceof RegExp ? c.test(a) : new RegExp(c, opt).test(a))
122
+ };
123
+
124
+ const EMPTY_STRING_REGEXP = /^\s*$/;
125
+ const rulesMatcherUtils = {
126
+ isNumber: el => {
127
+ const num = String(el);
128
+ return !!num.match(/(^-?|^\d+\.)\d+$/);
129
+ },
130
+ rule: (val, data, options) => {
131
+ const {
132
+ prefix
133
+ } = options || {};
134
+ const path = Object.keys(val)[0];
135
+ const valueBlock = val;
136
+ const pathWithPrefix = rulesMatcherUtils.getPath(path, prefix);
137
+ const valueForKey = get(data, pathWithPrefix, undefined);
138
+ const {
139
+ name
140
+ } = get(valueBlock, [path], {}) || {};
141
+ const {
142
+ op,
143
+ value,
144
+ opt
145
+ } = rulesMatcherUtils.getDefaultRule(valueBlock[path]);
146
+ const valueRef = value && String(value).indexOf('$ref:') === 0 ? get(data, rulesMatcherUtils.getPath(value.replace('$ref:', ''), prefix), undefined) : value;
147
+ if (!operators[op]) {
148
+ throw new Error(`Error missing operator:${op}`);
149
+ }
150
+ const valid = operators[op] && operators[op](valueForKey, valueRef, opt, data);
151
+ return {
152
+ valid,
153
+ name: `${pathWithPrefix}___${name || op}`
154
+ };
155
+ },
156
+ getKey: (block, keys, options) => {
157
+ if (Object.prototype.hasOwnProperty.call(block, '$and')) {
158
+ return block.$and.map(item => rulesMatcherUtils.getKey(item, keys, options));
159
+ }
160
+ if (Object.prototype.hasOwnProperty.call(block, '$or')) {
161
+ return block.$or.map(item => rulesMatcherUtils.getKey(item, keys, options));
162
+ }
163
+ const {
164
+ prefix
165
+ } = options || {};
166
+ const path = Object.keys(block)[0];
167
+ const valueBlock = block;
168
+ const res = rulesMatcherUtils.getPath(path, prefix);
169
+ const {
170
+ value
171
+ } = rulesMatcherUtils.getDefaultRule(valueBlock[path]);
172
+ if (value && String(value).indexOf('$ref:') === 0) {
173
+ keys[rulesMatcherUtils.getPath(value.replace('$ref:', ''), prefix)] = true;
174
+ }
175
+ return keys[res] = true;
176
+ },
177
+ isDate: v => v instanceof Date,
178
+ isDefined: v => v !== null && v !== undefined,
179
+ isObject: v => v === Object(v),
180
+ isFunction: v => typeof v === 'function',
181
+ isString: v => typeof v === 'string',
182
+ getDefaultStringValue: value => {
183
+ switch (value) {
184
+ case '$required':
185
+ return {
186
+ op: '$exists',
187
+ value: true
188
+ };
189
+ case '$exists':
190
+ return {
191
+ op: '$exists',
192
+ value: true
193
+ };
194
+ default:
195
+ return {
196
+ op: '$eq',
197
+ value
198
+ };
199
+ }
200
+ },
201
+ getTypeOf: value => Array.isArray(value) ? 'array' : rulesMatcherUtils.isNumber(value) ? 'number' : value === null ? null : typeof value,
202
+ getDefaultRule: value => {
203
+ const valueType = rulesMatcherUtils.getTypeOf(value);
204
+ switch (valueType) {
205
+ case 'number':
206
+ return {
207
+ op: '$eq',
208
+ value
209
+ };
210
+ case 'string':
211
+ return rulesMatcherUtils.getDefaultStringValue(value);
212
+ case 'boolean':
213
+ return {
214
+ op: '$exists',
215
+ value
216
+ };
217
+ case 'array':
218
+ return {
219
+ op: '$in',
220
+ value
221
+ };
222
+ case 'object':
223
+ return {
224
+ ...value,
225
+ op: value.op || Object.keys(value)[0],
226
+ value: value.value || value[Object.keys(value)[0]]
227
+ };
228
+ default:
229
+ return {
230
+ op: '$eq',
231
+ value
232
+ };
233
+ }
234
+ },
235
+ isEmpty: value => {
236
+ if (!rulesMatcherUtils.isDefined(value)) {
237
+ return true;
238
+ }
239
+ if (rulesMatcherUtils.isFunction(value)) {
240
+ return false;
241
+ }
242
+ if (rulesMatcherUtils.isString(value)) {
243
+ return EMPTY_STRING_REGEXP.test(value);
244
+ }
245
+ if (Array.isArray(value)) {
246
+ return value.length === 0;
247
+ }
248
+ if (rulesMatcherUtils.isDate(value)) {
249
+ return false;
250
+ }
251
+ let attr;
252
+ if (rulesMatcherUtils.isObject(value)) {
253
+ for (attr in value) {
254
+ return false;
255
+ }
256
+ return true;
257
+ }
258
+ return false;
259
+ },
260
+ forceArray: a => Array.isArray(a) ? a : [a],
261
+ getPath: (path, prefix) => {
262
+ if (path.indexOf('^') === 0) {
263
+ return trimStart(path, '^');
264
+ }
265
+ if (path.indexOf('$') === 0) {
266
+ return path;
267
+ }
268
+ return prefix ? `${prefix}.${path}` : path;
269
+ },
270
+ forceNumber: el => {
271
+ if (Array.isArray(el)) {
272
+ return el.length;
273
+ }
274
+ if (rulesMatcherUtils.isNumber(String(el))) {
275
+ return parseFloat(String(el));
276
+ }
277
+ return 0;
278
+ },
279
+ checkRule: (block, data, options) => {
280
+ if (!Array.isArray(block) && block && Object.prototype.hasOwnProperty.call(block, '$and')) {
281
+ if (block && block['$and'] && !block['$and'].length) return true;
282
+ return block['$and'].every(item => rulesMatcherUtils.checkRule(item, data, options));
283
+ }
284
+ if (!Array.isArray(block) && block && Object.prototype.hasOwnProperty.call(block, '$or')) {
285
+ if (block && block['$or'] && !block['$or'].length) return true;
286
+ return block['$or'].some(item => rulesMatcherUtils.checkRule(item, data, options));
287
+ }
288
+ const res = rulesMatcherUtils.rule(block, data, options);
289
+ return res.valid;
290
+ },
291
+ getKeys: (rules, options) => {
292
+ if (!rules) return null;
293
+ if (!rulesMatcherUtils.forceArray(rules).every(r => rulesMatcherUtils.isObject(r))) return null;
294
+ const keys = {};
295
+ const conditions = Array.isArray(rules) ? {
296
+ $and: rules
297
+ } : rules;
298
+ rulesMatcherUtils.getKey(conditions, keys, options ?? {});
299
+ return Object.keys(keys);
300
+ }
301
+ };
302
+
303
+ const rulesMatcher = (rules, formValue = {}, apply = true, options) => {
304
+ if (!rules) return [apply];
305
+ const conditions = Array.isArray(rules) ? {
306
+ $and: rules
307
+ } : rules;
308
+ const valid = rulesMatcherUtils.checkRule(conditions, formValue, options ?? {});
309
+ return [valid === apply];
310
+ };
311
+ const MatchRules = {
312
+ rulesMatcher,
313
+ operators,
314
+ utils: rulesMatcherUtils
315
+ };
316
+
317
+ const flattenRules = ob => {
318
+ const result = {};
319
+ for (const i in ob) {
320
+ if (ob[i] === null) {
321
+ result[i] = null;
322
+ }
323
+ if (typeof ob[i] === 'object' && !Array.isArray(ob[i]) || Array.isArray(ob[i]) && typeof ob[i][0] === 'object') {
324
+ const temp = flattenRules(ob[i]);
325
+ for (const j in temp) {
326
+ result[i + '*' + j] = temp[j];
327
+ }
328
+ } else {
329
+ result[i] = ob[i];
330
+ }
331
+ }
332
+ return result;
333
+ };
334
+ const getRulesExists = rules => {
335
+ return Object.keys(rules).length ? CoreUtils.mapEdge(rules) : undefined;
336
+ };
337
+ const CoreUtils = {
338
+ generateRulesName: nextRules => {
339
+ const a = nextRules.reduce((acc, inc) => {
340
+ const n = typeof inc.rules === 'string' ? inc.rules || '__ERROR_NAME__' : inc.rules?.name || '__ERROR_NAME__';
341
+ return {
342
+ ...acc,
343
+ [n]: inc.nodeId
344
+ };
345
+ }, {});
346
+ return a;
347
+ },
348
+ mapKeysDeepLodash: (obj, cb, isRecursive) => {
349
+ if (!obj && !isRecursive) {
350
+ return {};
351
+ }
352
+ if (!isRecursive) {
353
+ if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
354
+ return {};
355
+ }
356
+ }
357
+ if (Array.isArray(obj)) {
358
+ return obj.map(item => CoreUtils.mapKeysDeepLodash(item, cb, true));
359
+ }
360
+ if (!isPlainObject(obj)) {
361
+ return obj;
362
+ }
363
+ const result = mapKeys(obj, cb);
364
+ return mapValues(result, value => CoreUtils.mapKeysDeepLodash(value, cb, true));
365
+ },
366
+ generateNodes: nodes => keyBy(nodes.map(s => omit(s, 'nextRules')), 'nodeId'),
367
+ makeObjectRules: nodes => nodes.reduce((acc, inc) => ({
368
+ ...acc,
369
+ [inc.nodeId]: inc.nextRules
370
+ }), {}),
371
+ hasNode: (state, name, node) => has(state, [name, 'nodes', node]),
372
+ isEmptyRules: rules => {
373
+ if (isEmpty(rules)) return true;
374
+ if (isEmpty(get(rules, 'rules'))) return true;
375
+ if (Object.keys(flattenRules(rules)).every(key => key.endsWith('$and') || key.endsWith('$or'))) {
376
+ return true;
377
+ }
378
+ return false;
379
+ },
380
+ mapEdge: nextNode => {
381
+ const res = nextNode.sort((a, b) => {
382
+ const x = CoreUtils.isEmptyRules(a.rules);
383
+ const y = CoreUtils.isEmptyRules(b.rules);
384
+ return Number(x) - Number(y);
385
+ });
386
+ return res;
387
+ },
388
+ makeRules: rules => Object.entries(rules).reduce((acc2, [k, v]) => [...acc2, {
389
+ nodeId: k,
390
+ rules: v
391
+ }], []),
392
+ generateNodesForFlowerJson: nodes => nodes.map(e => {
393
+ const rules = CoreUtils.makeRules(e.props.to ?? {});
394
+ const nextRules = getRulesExists(rules);
395
+ const children = e.props.data?.children;
396
+ return {
397
+ nodeId: e.props.id,
398
+ nodeType: e.type.displayName || e.props.as || 'FlowerNode',
399
+ nodeTitle: get(e.props, 'data.title'),
400
+ children,
401
+ nextRules,
402
+ retain: e.props.retain,
403
+ disabled: e.props.disabled
404
+ };
405
+ }),
406
+ cleanPath: (name, char = '^') => trimStart(name, char),
407
+ getPath: idValue => {
408
+ if (!idValue) {
409
+ return {
410
+ path: []
411
+ };
412
+ }
413
+ if (idValue === '*') {
414
+ return {
415
+ path: '*'
416
+ };
417
+ }
418
+ if (idValue.indexOf('^') === 0) {
419
+ const [flowNameFromPath, ...rest] = CoreUtils.cleanPath(idValue).split('.');
420
+ return {
421
+ flowNameFromPath,
422
+ path: rest
423
+ };
424
+ }
425
+ return {
426
+ path: idValue.split('.')
427
+ };
428
+ },
429
+ allEqual: (arr, arr2) => arr.length === arr2.length && arr.every(v => arr2.includes(v)),
430
+ findValidRule: (nextRules, value, prefix) => find(nextRules, rule => {
431
+ if (typeof rule.rules === 'string') {
432
+ return false;
433
+ }
434
+ if (rule.rules === null) {
435
+ return true;
436
+ }
437
+ if (typeof rule.rules?.rules === 'undefined') {
438
+ return false;
439
+ }
440
+ if (typeof rule.rules.rules === 'string') {
441
+ return false;
442
+ }
443
+ const [valid] = MatchRules.rulesMatcher(rule.rules.rules, value, true, {
444
+ prefix
445
+ });
446
+ return valid;
447
+ })
448
+ };
449
+
450
+ const FlowerStateUtils = {
451
+ getAllData: state => state && Object.entries(state ?? {}).reduce((acc, [k, v]) => ({
452
+ ...acc,
453
+ [k]: v.data
454
+ }), {}),
455
+ selectFlowerFormNode: (name, id) => state => get(state, [name, 'form', id]),
456
+ makeSelectCurrentNextRules: name => state => {
457
+ const nextRules = get(state, [name, 'nextRules']);
458
+ const currentNodeId = FlowerStateUtils.makeSelectCurrentNodeId(name)(state);
459
+ return get(nextRules, [currentNodeId]);
460
+ },
461
+ makeSelectCurrentNodeId: name => state => {
462
+ const subState = get(state, [name]);
463
+ const startId = get(state, ['startId']);
464
+ return get(subState, ['current']) || startId;
465
+ },
466
+ makeSelectNodeErrors: (name, currentNodeId) => state => {
467
+ const form = FlowerStateUtils.selectFlowerFormNode(name, currentNodeId)(state);
468
+ return {
469
+ touched: form?.touched || false,
470
+ errors: form?.errors,
471
+ isValidating: form?.isValidating,
472
+ isValid: form && form.errors ? Object.values(form.errors).flat().length === 0 : true
473
+ };
474
+ }
475
+ };
476
+
477
+ const {
478
+ generateNodes,
479
+ hasNode,
480
+ makeObjectRules,
481
+ generateRulesName,
482
+ findValidRule
483
+ } = CoreUtils;
484
+ const FlowerCoreReducers = {
485
+ historyAdd: (state, {
486
+ payload
487
+ }) => {
488
+ if (hasNode(state, payload.name, payload.node)) {
489
+ state[payload.name].history.push(payload.node);
490
+ set(state, [payload.name, 'current'], payload.node);
491
+ }
492
+ return state;
493
+ },
494
+ historyPrevToNode: (state, {
495
+ payload
496
+ }) => {
497
+ const history = get(state[typeof payload === 'string' ? payload : payload.name], ['history'], []);
498
+ const lastIndex = lastIndexOf(history, typeof payload === 'string' ? payload : payload.node);
499
+ if (lastIndex >= 0) {
500
+ const newHistory = slice(history, 0, lastIndex + 1);
501
+ set(state, [typeof payload === 'string' ? payload : payload.name, 'history'], newHistory);
502
+ set(state, [typeof payload === 'string' ? payload : payload.name, 'current'], typeof payload === 'string' ? payload : payload.node);
503
+ }
504
+ return state;
505
+ },
506
+ setFormTouched: (state, {
507
+ payload
508
+ }) => {
509
+ if (!get(state, [typeof payload === 'string' ? payload : payload.flowName, 'nodes', typeof payload === 'string' ? payload : payload.currentNode])) {
510
+ return state;
511
+ }
512
+ set(state, [typeof payload === 'string' ? payload : payload.flowName, 'form', typeof payload === 'string' ? payload : payload.currentNode, 'touched'], true);
513
+ return state;
514
+ },
515
+ forceAddHistory: (state, {
516
+ payload
517
+ }) => {
518
+ const {
519
+ name,
520
+ flowName,
521
+ history
522
+ } = payload;
523
+ const key = name || flowName || '' || '';
524
+ const resultHistory = history.slice(1, -1);
525
+ state[key].history.push(...resultHistory);
526
+ return state;
527
+ },
528
+ historyPop: (state, {
529
+ payload
530
+ }) => {
531
+ const container = get(state, [payload.name]);
532
+ const startId = get(container, ['startId']);
533
+ const history = get(container, ['history']);
534
+ const originHistory = [...get(container, ['history'], [])];
535
+ if (originHistory.length < 2) {
536
+ set(state, [payload.name, 'current'], startId);
537
+ set(state, [payload.name, 'history'], [startId]);
538
+ return state;
539
+ }
540
+ history.pop();
541
+ const total = originHistory.length - 2;
542
+ for (let index = total; index > 0; index--) {
543
+ const curr = originHistory[index];
544
+ const nodeType = get(container, ['nodes', curr, 'nodeType']);
545
+ const nodeDisabled = get(container, ['nodes', curr, 'disabled']);
546
+ if (nodeDisabled || nodeType === 'FlowerAction' || nodeType === 'FlowerServer' || !nodeType) {
547
+ history.pop();
548
+ } else {
549
+ break;
550
+ }
551
+ }
552
+ const lastId = last(history);
553
+ set(state, [payload.name, 'current'], lastId);
554
+ return state;
555
+ },
556
+ restoreHistory: (state, {
557
+ payload
558
+ }) => {
559
+ const startId = get(state, [payload.name, 'startId']);
560
+ set(state, [payload.name, 'current'], startId);
561
+ set(state, [payload.name, 'history'], [startId]);
562
+ return state;
563
+ },
564
+ replaceNode: (state, {
565
+ payload
566
+ }) => {
567
+ const {
568
+ name,
569
+ flowName,
570
+ node
571
+ } = payload;
572
+ if (hasNode(state, name || flowName || '', node)) {
573
+ set(state, [name || flowName || '', 'current'], node);
574
+ set(state, [name || flowName || '', 'history'], [node]);
575
+ }
576
+ return state;
577
+ },
578
+ initializeFromNode: (state, {
579
+ payload
580
+ }) => {
581
+ const {
582
+ name,
583
+ flowName,
584
+ node
585
+ } = payload;
586
+ if (hasNode(state, name || flowName || '', node)) {
587
+ set(state, [name || flowName || '', 'startId'], node);
588
+ set(state, [name || flowName || '', 'current'], node);
589
+ set(state, [name || flowName || '', 'history'], [node]);
590
+ }
591
+ return state;
592
+ },
593
+ forceResetHistory: (state, {
594
+ payload
595
+ }) => {
596
+ const {
597
+ name,
598
+ flowName
599
+ } = payload;
600
+ if (!name && !flowName) return state;
601
+ set(state, [name || flowName || '', 'history'], []);
602
+ return state;
603
+ },
604
+ destroy: (state, {
605
+ payload
606
+ }) => {
607
+ set(state, [payload.name], {});
608
+ },
609
+ initNodes: (state, {
610
+ payload
611
+ }) => {
612
+ if (payload.persist && get(state, [payload.name, 'persist'])) return;
613
+ const startId = payload.startId || get(payload, 'nodes.0.nodeId');
614
+ if (!startId) {
615
+ console.warn('Flower is empty');
616
+ return;
617
+ }
618
+ set(state, payload.name, {
619
+ persist: payload.persist,
620
+ startId,
621
+ current: startId,
622
+ history: [startId],
623
+ nodes: generateNodes(payload.nodes),
624
+ nextRules: makeObjectRules(payload.nodes),
625
+ data: payload.initialData
626
+ });
627
+ },
628
+ setCurrentNode: (state, {
629
+ payload
630
+ }) => {
631
+ if (hasNode(state, payload.name, payload.node)) {
632
+ const startId = get(state, [payload.name, 'startId']);
633
+ if (payload.node === startId) {
634
+ set(state, [payload.name, 'current'], startId);
635
+ set(state, [payload.name, 'history'], [startId]);
636
+ } else {
637
+ set(state, [payload.name, 'current'], payload.node);
638
+ }
639
+ }
640
+ },
641
+ formAddErrors: (state, {
642
+ payload
643
+ }) => {
644
+ set(state, [payload.name, 'form', payload.currentNode, 'errors', payload.id], payload.errors);
645
+ },
646
+ formRemoveErrors: (state, {
647
+ payload
648
+ }) => {
649
+ unset(state, [payload.name, 'form', payload.currentNode, 'errors', payload.id]);
650
+ unset(state, [payload.name, 'form', payload.currentNode, 'isValidating']);
651
+ },
652
+ addData: (state, {
653
+ payload
654
+ }) => {
655
+ const prevData = get(state, [payload.flowName, 'data']);
656
+ set(state, [payload.flowName, 'data'], {
657
+ ...prevData,
658
+ ...payload.value
659
+ });
660
+ },
661
+ addDataByPath: (state, {
662
+ payload
663
+ }) => {
664
+ if (payload.id && payload.id.length) {
665
+ set(state, [payload.flowName, 'data', ...payload.id], payload.value);
666
+ }
667
+ },
668
+ replaceData: (state, {
669
+ payload
670
+ }) => {
671
+ set(state, [payload.flowName, 'data'], payload.value);
672
+ },
673
+ unsetData: (state, {
674
+ payload
675
+ }) => {
676
+ unset(state, [payload.flowName, 'data', ...payload.id]);
677
+ },
678
+ setFormIsValidating: (state, {
679
+ payload
680
+ }) => {
681
+ set(state, [payload.name, 'form', payload.currentNode, 'isValidating'], payload.isValidating);
682
+ },
683
+ node: (state, {
684
+ payload
685
+ }) => {
686
+ const {
687
+ name,
688
+ history
689
+ } = payload;
690
+ const node = payload.nodeId || payload.node || '';
691
+ const flowName = name || payload.flowName || '';
692
+ const startNode = get(state, [payload.name, 'startId']);
693
+ const currentNodeId = get(state, [payload.name, 'current'], startNode);
694
+ FlowerCoreReducers.setFormTouched(state, {
695
+ type: 'setFormTouched',
696
+ payload: {
697
+ flowName,
698
+ currentNode: currentNodeId
699
+ }
700
+ });
701
+ if (global.window && get(global.window, '__FLOWER_DEVTOOLS__') && history) {
702
+ FlowerCoreReducers.forceAddHistory(state, {
703
+ type: 'forceAddHistory',
704
+ payload: {
705
+ name,
706
+ flowName,
707
+ history
708
+ }
709
+ });
710
+ }
711
+ FlowerCoreReducers.historyAdd(state, {
712
+ type: 'historyAdd',
713
+ payload: {
714
+ name: name || flowName || '',
715
+ node
716
+ }
717
+ });
718
+ },
719
+ prevToNode: (state, {
720
+ payload
721
+ }) => {
722
+ const {
723
+ node,
724
+ name,
725
+ flowName
726
+ } = payload;
727
+ FlowerCoreReducers.historyPrevToNode(state, {
728
+ type: 'historyPrevToNode',
729
+ payload: {
730
+ name: name || flowName || '',
731
+ node
732
+ }
733
+ });
734
+ },
735
+ next: (state, {
736
+ payload
737
+ }) => {
738
+ const {
739
+ name,
740
+ data = {},
741
+ route
742
+ } = payload;
743
+ const flowName = name || payload.flowName || '';
744
+ const currentNodeId = FlowerStateUtils.makeSelectCurrentNodeId(flowName)(state);
745
+ const currentNextRules = FlowerStateUtils.makeSelectCurrentNextRules(flowName)(state);
746
+ const form = FlowerStateUtils.makeSelectNodeErrors(flowName, currentNodeId)(state);
747
+ const clonedData = cloneDeep(FlowerStateUtils.getAllData(state));
748
+ const stateWithNodeData = {
749
+ $in: data,
750
+ $form: form,
751
+ ...clonedData
752
+ };
753
+ FlowerCoreReducers.setFormTouched(state, {
754
+ type: 'setFormTouched',
755
+ payload: {
756
+ flowName,
757
+ currentNode: currentNodeId
758
+ }
759
+ });
760
+ if (!currentNextRules) {
761
+ return;
762
+ }
763
+ if (route) {
764
+ const rulesByName = generateRulesName(currentNextRules);
765
+ if (!rulesByName[route]) {
766
+ return;
767
+ }
768
+ FlowerCoreReducers.historyAdd(state, {
769
+ type: 'historyAdd',
770
+ payload: {
771
+ name: flowName,
772
+ node: rulesByName[route]
773
+ }
774
+ });
775
+ return;
776
+ }
777
+ const validRule = findValidRule(currentNextRules, stateWithNodeData, flowName);
778
+ const nextNumberNode = get(validRule, 'nodeId');
779
+ if (!nextNumberNode) {
780
+ return;
781
+ }
782
+ FlowerCoreReducers.historyAdd(state, {
783
+ type: 'historyAdd',
784
+ payload: {
785
+ name: flowName,
786
+ node: nextNumberNode
787
+ }
788
+ });
789
+ },
790
+ prev: (state, {
791
+ payload
792
+ }) => {
793
+ const {
794
+ name,
795
+ flowName
796
+ } = payload;
797
+ FlowerCoreReducers.historyPop(state, {
798
+ type: 'historyPop',
799
+ payload: {
800
+ name: name || flowName || ''
801
+ }
802
+ });
803
+ },
804
+ reset: (state, {
805
+ payload
806
+ }) => {
807
+ const {
808
+ name,
809
+ flowName
810
+ } = payload;
811
+ FlowerCoreReducers.restoreHistory(state, {
812
+ type: 'restoreHistory',
813
+ payload: {
814
+ name: name || flowName || ''
815
+ }
816
+ });
817
+ }
818
+ };
819
+
820
+ const FlowerCoreStateSelectors = {
821
+ selectGlobal: state => state && state.flower,
822
+ selectFlower: name => state => get(state, [name]),
823
+ selectFlowerFormNode: id => state => get(state, ['form', id]),
824
+ selectFlowerHistory: flower => get(flower, ['history'], []),
825
+ makeSelectNodesIds: flower => get(flower, ['nodes']),
826
+ makeSelectStartNodeId: flower => get(flower, ['startId']),
827
+ getDataByFlow: flower => get(flower, ['data']) ?? {},
828
+ getDataFromState: id => data => id === '*' ? data : get(data, id),
829
+ makeSelectNodeFormTouched: form => form && form.touched,
830
+ makeSelectCurrentNodeId: (flower, startNodeId) => get(flower, ['current']) || startNodeId,
831
+ makeSelectCurrentNodeDisabled: (nodes, current) => !!get(nodes, [current, 'disabled']),
832
+ makeSelectPrevNodeRetain: (nodes, history, current) => {
833
+ if (!nodes) return;
834
+ const prevFlowerNode = [...history].reverse().find(el => {
835
+ const {
836
+ nodeType,
837
+ retain
838
+ } = nodes[el];
839
+ return nodeType === 'FlowerNode' || retain;
840
+ });
841
+ if (nodes[current].nodeType === 'FlowerNode' || nodes[current].retain) return;
842
+ if (!prevFlowerNode) return;
843
+ if (nodes[prevFlowerNode] && nodes[prevFlowerNode].disabled) return;
844
+ return nodes[prevFlowerNode] && nodes[prevFlowerNode].retain && prevFlowerNode;
845
+ },
846
+ makeSelectNodeErrors: form => {
847
+ return {
848
+ touched: form?.touched || false,
849
+ errors: form?.errors,
850
+ isValidating: form?.isValidating,
851
+ isValid: form && form.errors ? Object.values(form.errors).flat().length === 0 : true
852
+ };
853
+ },
854
+ makeSelectFieldError: (name, id, validate) => data => {
855
+ if (!validate || !data) return [];
856
+ const errors = validate.filter(rule => {
857
+ if (!rule) return true;
858
+ if (!rule.rules) return true;
859
+ const transformSelf = CoreUtils.mapKeysDeepLodash(rule.rules, (v, key) => key === '$self' ? id : key);
860
+ const [hasError] = MatchRules.rulesMatcher(transformSelf, data, false, {
861
+ prefix: name
862
+ });
863
+ return hasError;
864
+ });
865
+ const result = errors.map(r => r && r.message || 'error');
866
+ return result.length === 0 ? [] : result;
867
+ },
868
+ selectorRulesDisabled: (id, rules, keys, flowName, value) => (data, form) => {
869
+ const newState = {
870
+ ...data,
871
+ ...value,
872
+ $form: form
873
+ };
874
+ const state = Object.assign(newState, id ? {
875
+ $self: get(newState, [flowName, ...id.split('.')])
876
+ } : {});
877
+ if (!rules) return false;
878
+ if (!keys) return false;
879
+ const res = keys.reduce((acc, inc) => {
880
+ const k = inc;
881
+ return Object.assign(acc, {
882
+ [k]: get(state, k)
883
+ });
884
+ }, {});
885
+ const [disabled] = MatchRules.rulesMatcher(rules, {
886
+ ...flat.unflatten(res)
887
+ }, false, {
888
+ prefix: flowName
889
+ });
890
+ return disabled;
891
+ }
892
+ };
893
+
894
+ var RulesModes;
895
+ (function (RulesModes) {
896
+ RulesModes["$and"] = "$and";
897
+ RulesModes["$or"] = "$or";
898
+ })(RulesModes || (RulesModes = {}));
899
+ var RulesOperators$1;
900
+ (function (RulesOperators) {
901
+ RulesOperators["$exist"] = "$exist";
902
+ RulesOperators["$eq"] = "$eq";
903
+ RulesOperators["$ne"] = "$ne";
904
+ RulesOperators["$gt"] = "$gt";
905
+ RulesOperators["$gte"] = "$gte";
906
+ RulesOperators["$lt"] = "$lt";
907
+ RulesOperators["$lte"] = "$lte";
908
+ RulesOperators["$strGt"] = "$strGt";
909
+ RulesOperators["$strGte"] = "$strGte";
910
+ RulesOperators["$strLt"] = "$strLt";
911
+ RulesOperators["$strLte"] = "$strLte";
912
+ RulesOperators["$in"] = "$in";
913
+ RulesOperators["$nin"] = "$nin";
914
+ RulesOperators["$all"] = "$all";
915
+ RulesOperators["$regex"] = "$regex";
916
+ })(RulesOperators$1 || (RulesOperators$1 = {}));
917
+
918
+ var RulesOperators;
919
+ (function (RulesOperators) {
920
+ RulesOperators["$exist"] = "$exist";
921
+ RulesOperators["$eq"] = "$eq";
922
+ RulesOperators["$ne"] = "$ne";
923
+ RulesOperators["$gt"] = "$gt";
924
+ RulesOperators["$gte"] = "$gte";
925
+ RulesOperators["$lt"] = "$lt";
926
+ RulesOperators["$lte"] = "$lte";
927
+ RulesOperators["$strGt"] = "$strGt";
928
+ RulesOperators["$strGte"] = "$strGte";
929
+ RulesOperators["$strLt"] = "$strLt";
930
+ RulesOperators["$strLte"] = "$strLte";
931
+ RulesOperators["$in"] = "$in";
932
+ RulesOperators["$nin"] = "$nin";
933
+ RulesOperators["$all"] = "$all";
934
+ RulesOperators["$regex"] = "$regex";
935
+ })(RulesOperators || (RulesOperators = {}));
936
+
937
+ export { CoreUtils, Emitter, flat as Flat, FlowerCoreReducers, FlowerStateUtils, MatchRules, RulesModes, RulesOperators, FlowerCoreStateSelectors as Selectors };