@shopgate/pwa-ui-shared 7.30.0-alpha.10 → 7.30.0-alpha.12

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 (188) hide show
  1. package/AccordionContainer/index.js +17 -11
  2. package/AccordionContainer/spec.js +12 -3
  3. package/ActionButton/index.js +47 -33
  4. package/ActionButton/spec.js +5 -1
  5. package/AddToCartButton/index.js +44 -33
  6. package/AddToCartButton/mock.js +20 -8
  7. package/AddToCartButton/spec.js +4 -3
  8. package/Availability/index.js +5 -3
  9. package/Availability/spec.js +6 -5
  10. package/Button/index.js +73 -58
  11. package/Button/spec.js +16 -10
  12. package/ButtonLink/index.js +20 -12
  13. package/ButtonLink/spec.js +5 -3
  14. package/Card/index.js +5 -3
  15. package/CardList/components/Item/index.js +5 -3
  16. package/CardList/index.js +21 -12
  17. package/CartTotalLine/components/Amount/index.js +10 -8
  18. package/CartTotalLine/components/Hint/index.js +7 -5
  19. package/CartTotalLine/components/Label/index.js +12 -10
  20. package/CartTotalLine/components/Spacer/index.js +5 -3
  21. package/CartTotalLine/index.js +5 -3
  22. package/Checkbox/index.js +6 -5
  23. package/Chip/index.js +28 -22
  24. package/Chip/spec.js +9 -6
  25. package/ContextMenu/ContextMenuProvider.js +5 -3
  26. package/ContextMenu/components/Item/index.js +14 -11
  27. package/ContextMenu/components/Position/index.js +20 -12
  28. package/ContextMenu/index.js +54 -44
  29. package/ContextMenu/spec.js +31 -20
  30. package/Dialog/components/HtmlContentDialog/index.js +9 -7
  31. package/Dialog/components/HtmlContentDialog/spec.js +9 -6
  32. package/Dialog/components/PipelineErrorDialog/index.js +106 -47
  33. package/Dialog/components/PipelineErrorDialog/spec.js +5 -4
  34. package/Dialog/components/TextMessageDialog/index.js +9 -7
  35. package/Dialog/components/TextMessageDialog/spec.js +9 -6
  36. package/Dialog/components/VariantSelectModal/index.js +7 -5
  37. package/Dialog/components/VariantSelectModal/spec.js +6 -3
  38. package/Dialog/index.js +12 -8
  39. package/Dialog/spec.js +7 -6
  40. package/DiscountBadge/index.js +10 -8
  41. package/DiscountBadge/spec.js +3 -2
  42. package/FavoritesButton/index.js +40 -31
  43. package/FavoritesButton/spec.js +7 -3
  44. package/Form/Builder/classes/ActionListener/index.js +400 -401
  45. package/Form/Builder/components/CheckboxElement.js +13 -5
  46. package/Form/Builder/components/CountryElement.js +13 -5
  47. package/Form/Builder/components/ProvinceElement.js +13 -5
  48. package/Form/Builder/components/RadioElement.js +19 -11
  49. package/Form/Builder/components/SelectElement.js +26 -16
  50. package/Form/Builder/components/TextElement.js +13 -5
  51. package/Form/Builder/index.js +70 -60
  52. package/Form/Builder/spec.js +10 -9
  53. package/Form/Checkbox/index.js +31 -21
  54. package/Form/InfoField/index.js +19 -12
  55. package/Form/InfoField/spec.js +4 -1
  56. package/Form/Password/index.js +22 -15
  57. package/Form/Password/spec.js +10 -5
  58. package/Form/RadioGroup/components/Item/index.js +34 -25
  59. package/Form/RadioGroup/index.js +37 -28
  60. package/Form/RadioGroup/spec.js +45 -31
  61. package/Form/Select/index.js +52 -43
  62. package/Form/Select/spec.js +5 -2
  63. package/Form/SelectContextChoices/index.js +26 -22
  64. package/Form/SelectContextChoices/spec.js +4 -1
  65. package/Form/TextField/index.js +40 -27
  66. package/Form/TextField/spec.js +34 -21
  67. package/Form/index.js +23 -15
  68. package/FormElement/components/ErrorText/index.js +7 -5
  69. package/FormElement/components/Label/index.js +6 -4
  70. package/FormElement/components/Placeholder/index.js +7 -5
  71. package/FormElement/components/Underline/index.js +8 -6
  72. package/FormElement/index.js +62 -46
  73. package/FormElement/spec.js +10 -9
  74. package/Glow/index.js +30 -22
  75. package/Glow/spec.js +6 -1
  76. package/IndicatorCircle/index.js +10 -8
  77. package/IndicatorCircle/spec.js +3 -2
  78. package/LoadingIndicator/index.js +8 -6
  79. package/Manufacturer/index.js +9 -7
  80. package/MessageBar/index.js +20 -17
  81. package/MessageBar/spec.js +6 -5
  82. package/NoResults/components/Icon/index.js +134 -110
  83. package/NoResults/index.js +27 -21
  84. package/Placeholder/index.js +13 -5
  85. package/PlaceholderLabel/index.js +2 -1
  86. package/PlaceholderLabel/spec.js +13 -6
  87. package/PlaceholderParagraph/index.js +13 -12
  88. package/PlaceholderParagraph/spec.js +13 -6
  89. package/Price/index.js +46 -28
  90. package/PriceInfo/index.js +2 -1
  91. package/PriceStriked/index.js +41 -29
  92. package/ProductProperties/index.js +13 -10
  93. package/ProgressBar/index.js +28 -19
  94. package/ProgressBar/spec.js +2 -1
  95. package/RadioButton/index.js +6 -5
  96. package/RadioButton/spec.js +3 -2
  97. package/RatingNumber/index.js +2 -1
  98. package/RatingStars/index.js +50 -34
  99. package/RatingStars/spec.js +7 -6
  100. package/Ripple/components/RippleAnimation/index.js +12 -10
  101. package/Ripple/index.js +72 -62
  102. package/RippleButton/index.js +44 -27
  103. package/RippleButton/spec.js +20 -13
  104. package/ScannerOverlay/components/CameraOverlay/index.js +8 -5
  105. package/ScannerOverlay/components/ScannerBar/components/FlashlightButton/index.js +25 -21
  106. package/ScannerOverlay/components/ScannerBar/components/ScannerInstructions/index.js +7 -5
  107. package/ScannerOverlay/components/ScannerBar/index.js +17 -12
  108. package/ScannerOverlay/index.js +24 -15
  109. package/Sheet/components/Header/components/SearchBar/index.js +35 -28
  110. package/Sheet/components/Header/components/SearchBar/spec.js +2 -1
  111. package/Sheet/components/Header/index.js +47 -34
  112. package/Sheet/components/Header/spec.js +2 -1
  113. package/Sheet/index.js +80 -65
  114. package/Sheet/spec.js +27 -14
  115. package/TaxDisclaimer/index.js +13 -10
  116. package/TaxDisclaimer/spec.js +3 -2
  117. package/TextField/components/ErrorText/index.js +7 -5
  118. package/TextField/components/FormElement/index.js +4 -3
  119. package/TextField/components/Hint/index.js +7 -5
  120. package/TextField/components/Label/index.js +6 -4
  121. package/TextField/components/Underline/index.js +8 -6
  122. package/TextField/index.js +102 -85
  123. package/TextField/spec.js +37 -23
  124. package/ToggleIcon/index.js +23 -15
  125. package/ToggleIcon/spec.js +12 -8
  126. package/icons/AccountBoxIcon.js +5 -4
  127. package/icons/AddMoreIcon.js +5 -4
  128. package/icons/ArrowDropIcon.js +5 -4
  129. package/icons/ArrowIcon.js +5 -4
  130. package/icons/BarcodeScannerIcon.js +5 -4
  131. package/icons/BoxIcon.js +5 -4
  132. package/icons/BrowseIcon.js +5 -4
  133. package/icons/BurgerIcon.js +5 -4
  134. package/icons/CalendarIcon.js +5 -5
  135. package/icons/CartCouponIcon.js +5 -4
  136. package/icons/CartIcon.js +5 -4
  137. package/icons/CartPlusIcon.js +5 -4
  138. package/icons/CheckIcon.js +5 -4
  139. package/icons/CheckedIcon.js +5 -4
  140. package/icons/ChevronIcon.js +5 -4
  141. package/icons/CreditCardIcon.js +5 -4
  142. package/icons/CrossIcon.js +5 -4
  143. package/icons/DescriptionIcon.js +5 -4
  144. package/icons/FilterIcon.js +5 -4
  145. package/icons/FlashDisabledIcon.js +5 -4
  146. package/icons/FlashEnabledIcon.js +5 -4
  147. package/icons/GridIcon.js +5 -4
  148. package/icons/HeartIcon.js +5 -4
  149. package/icons/HeartOutlineIcon.js +5 -4
  150. package/icons/HeartPlusIcon.js +5 -4
  151. package/icons/HeartPlusOutlineIcon.js +5 -4
  152. package/icons/HomeIcon.js +5 -4
  153. package/icons/InfoIcon.js +5 -4
  154. package/icons/InfoOutlineIcon.js +5 -4
  155. package/icons/ListIcon.js +5 -4
  156. package/icons/LocalShippingIcon.js +5 -4
  157. package/icons/LocationIcon.js +5 -4
  158. package/icons/LocatorIcon.js +5 -4
  159. package/icons/LockIcon.js +5 -4
  160. package/icons/LogoutIcon.js +5 -4
  161. package/icons/MagnifierIcon.js +5 -4
  162. package/icons/MapMarkerIcon.js +5 -4
  163. package/icons/MoreIcon.js +5 -4
  164. package/icons/MoreVertIcon.js +5 -4
  165. package/icons/NotificationIcon.js +5 -4
  166. package/icons/PersonIcon.js +5 -4
  167. package/icons/PhoneIcon.js +5 -4
  168. package/icons/PlaceholderIcon.js +5 -4
  169. package/icons/RadioCheckedIcon.js +5 -4
  170. package/icons/RadioUncheckedIcon.js +5 -4
  171. package/icons/SecurityIcon.js +5 -4
  172. package/icons/ShippingMethodIcon.js +5 -4
  173. package/icons/ShoppingCartIcon.js +5 -4
  174. package/icons/SortIcon.js +5 -4
  175. package/icons/StarHalfIcon.js +5 -4
  176. package/icons/StarIcon.js +5 -4
  177. package/icons/StarOutlineIcon.js +5 -4
  178. package/icons/StopIcon.js +5 -4
  179. package/icons/TickIcon.js +5 -4
  180. package/icons/TimeIcon.js +5 -4
  181. package/icons/TrashIcon.js +5 -4
  182. package/icons/TrashOutlineIcon.js +5 -4
  183. package/icons/UncheckedIcon.js +5 -4
  184. package/icons/ViewListIcon.js +5 -4
  185. package/icons/VisibilityIcon.js +5 -4
  186. package/icons/VisibilityOffIcon.js +5 -4
  187. package/icons/WarningIcon.js +5 -4
  188. package/package.json +7 -8
@@ -5,441 +5,440 @@ import { ACTION_TYPE_UPDATE_PROVINCE_ELEMENT, ACTION_TYPE_SET_VISIBILITY, ACTION
5
5
  /**
6
6
  * ActionListener and handler for the FormBuilder component
7
7
  */
8
- class ActionListener {
8
+ let ActionListener =
9
+ /**
10
+ * Constructor
11
+ * @param {function(string)} getProvincesList Takes a country code and returns a list of provinces
12
+ * @param {Object} defaults Form defaults
13
+ */
14
+ function ActionListener(getProvincesList, defaults) {
9
15
  /**
10
- * Constructor
11
- * @param {function(string)} getProvincesList Takes a country code and returns a list of provinces
12
- * @param {Object} defaults Form defaults
16
+ * Takes the elements to be rendered by the FormBuilder and attaches available action listeners
17
+ * to the component.
18
+ * @param {FormElement[]} elementList List of all elements
13
19
  */
14
- constructor(getProvincesList, defaults) {
15
- /**
16
- * Takes the elements to be rendered by the FormBuilder and attaches available action listeners
17
- * to the component.
18
- * @param {FormElement[]} elementList List of all elements
19
- */
20
- this.attachAll = elementList => {
21
- // Attach action listeners for element (context) actions
22
- elementList.forEach(element => {
23
- let elementActions = element.actions;
24
- if (element.type === ELEMENT_TYPE_PROVINCE) {
25
- elementActions = elementActions || [];
26
-
27
- // Requires a country element to create a "update provinces" action
28
- const countryElement = elementList.find(el => el.type === ELEMENT_TYPE_COUNTRY);
29
- if (countryElement) {
30
- // Attach new action, which is always triggered, when the (only) country element changes
31
- elementActions.push({
32
- type: ACTION_TYPE_UPDATE_PROVINCE_ELEMENT,
33
- rules: [{
34
- context: countryElement.id
35
- }]
36
- });
37
- }
38
- }
39
- if (elementActions === undefined) {
40
- return;
41
- }
42
-
43
- // Create listeners for all supported actions
44
- elementActions.forEach(action => {
45
- const actionRules = action.rules || [];
46
- // Always apply action to itself if no rules given
47
- if (actionRules.length === 0) {
48
- // Define a basic rule if no rules given
49
- actionRules.push({
50
- context: element.id
51
- });
52
- }
53
-
54
- // Actions do have a fixed structure which needs to be fulfilled
55
- const normalizedAction = {
56
- ...action,
57
- rules: actionRules
58
- };
20
+ this.attachAll = elementList => {
21
+ // Attach action listeners for element (context) actions
22
+ elementList.forEach(element => {
23
+ let elementActions = element.actions;
24
+ if (element.type === ELEMENT_TYPE_PROVINCE) {
25
+ elementActions = elementActions || [];
59
26
 
60
- // Assign the action listeners to all contexts of the current element
61
- actionRules.forEach(rule => {
62
- this.attach(element, normalizedAction, rule);
27
+ // Requires a country element to create a "update provinces" action
28
+ const countryElement = elementList.find(el => el.type === ELEMENT_TYPE_COUNTRY);
29
+ if (countryElement) {
30
+ // Attach new action, which is always triggered, when the (only) country element changes
31
+ elementActions.push({
32
+ type: ACTION_TYPE_UPDATE_PROVINCE_ELEMENT,
33
+ rules: [{
34
+ context: countryElement.id
35
+ }]
63
36
  });
64
- });
65
- });
66
- };
67
- /**
68
- * Attaches one or possibly multiple action listeners for the given rule
69
- * @param {FormElement} element The current element that is modified by the action
70
- * @param {FormFieldAction} action The action and it's params
71
- * @param {FormFieldActionRule} rule The rule to check in case the action listener is triggered
72
- */
73
- this.attach = (element, action, rule) => {
74
- let actionListener;
75
- switch (action.type) {
76
- case ACTION_TYPE_UPDATE_PROVINCE_ELEMENT:
77
- {
78
- actionListener = this.createUpdateProvinceElementHandler(element, action);
79
- break;
80
- }
81
- case ACTION_TYPE_SET_VISIBILITY:
82
- {
83
- // Visibility is special and uses the result of the evaluation itself
84
- actionListener = this.createSetVisibilityHandler(element, action);
85
- break;
86
- }
87
- case ACTION_TYPE_SET_VALUE:
88
- {
89
- actionListener = this.createEvaluatedHandler(element, action, this.createSetValueHandler(element, action));
90
- break;
91
- }
92
- case ACTION_TYPE_TRANSFORM:
93
- {
94
- actionListener = this.createEvaluatedHandler(element, action, this.createTransformHandler(element, action));
95
- break;
96
- }
97
- default:
98
- return;
37
+ }
99
38
  }
100
- this.register(rule.context, actionListener);
101
- };
102
- /**
103
- * Action listener creator to check all related rules before calling any further action listeners
104
- * @param {FormElement} element The element for which the listener should be created for
105
- * @param {FormFieldAction} action The action to be create a listener for
106
- * @param {Function} actionListener The action listener to call if the rule applies
107
- * @returns {Function} Returns a function to modify and return the modified state.
108
- */
109
- this.createEvaluatedHandler = (element, action, actionListener) => (prevState, nextState) => {
110
- // Apply rules before accepting any changes
111
- if (!this.evaluateRules(element, action, nextState)) {
112
- return nextState;
39
+ if (elementActions === undefined) {
40
+ return;
113
41
  }
114
- return actionListener(prevState, nextState);
115
- };
116
- /**
117
- * Action listener creator to handle "updateCountryChange" action
118
- * @param {FormElement} provinceEl The element for which the listener should be created for
119
- * @param {FormFieldAction} action The action to be create a listener for
120
- * @returns {Function} Returns a function to modify and return the modified state.
121
- */
122
- this.createUpdateProvinceElementHandler = (provinceEl, action) => (prevState, nextState) => {
123
- const countryElementId = action.rules[0].context;
124
- const countryValue = nextState.formData[countryElementId];
125
- const countryDefault = this.defaults[countryElementId];
126
- const newState = {
127
- ...nextState
128
- };
129
42
 
130
- // Overwrite province with the form's default, if country matches the default as well
131
- if (countryValue === countryDefault) {
132
- newState.formData[provinceEl.id] = this.defaults[provinceEl.id];
133
- } else {
134
- // Update province to first or no selection, based on "required" attribute
135
- newState.formData[provinceEl.id] = !provinceEl.required ? '' : Object.keys(this.getProvincesList(countryValue))[0];
136
- }
137
- return newState;
138
- };
139
- /**
140
- * Action listener creator to handle "setVisibility" actions
141
- * @param {FormElement} element The element for which the listener should be created for
142
- * @param {FormFieldAction} action The action to be create a listener for
143
- * @returns {Function} Returns a function to modify and return the modified state.
144
- */
145
- this.createSetVisibilityHandler = (element, action) => (prevState, nextState) => {
146
- let newState = {
147
- ...nextState,
148
- elementVisibility: {
149
- ...nextState.elementVisibility,
150
- [element.id]: this.evaluateRules(element, action, nextState)
151
- },
152
- // Copy form data to be able to check changes and all follow up actions
153
- formData: {
154
- ...nextState.formData
43
+ // Create listeners for all supported actions
44
+ elementActions.forEach(action => {
45
+ const actionRules = action.rules || [];
46
+ // Always apply action to itself if no rules given
47
+ if (actionRules.length === 0) {
48
+ // Define a basic rule if no rules given
49
+ actionRules.push({
50
+ context: element.id
51
+ });
155
52
  }
156
- };
157
- if (newState.formData[element.id] === undefined && newState.elementVisibility[element.id]) {
158
- newState.formData[element.id] = this.defaults[element.id];
159
- } else if (!newState.elementVisibility[element.id] && newState.formData[element.id] !== undefined) {
160
- delete newState.formData[element.id];
161
- }
162
53
 
163
- // Notify follow up listeners about the current change
164
- if (nextState.formData[element.id] !== newState.formData[element.id]) {
165
- newState = this.notify(element.id, prevState, newState);
166
- }
167
- return newState;
168
- };
169
- /**
170
- * Action listener creator to handle "setValue" actions
171
- * @param {FormElement} element The element for which the listener should be created for
172
- * @param {FormFieldAction} action The action to be create a listener for
173
- * @returns {Function} Returns the modified state.
174
- */
175
- this.createSetValueHandler = (element, action) => (prevState, nextState) => {
176
- if (typeof action.params !== 'object' || Array.isArray(action.params)) {
177
- logger.error(`Error: Invalid or missing form action in element '${element.id}'. ` + 'Params must be in the format: { "type": string, "value": string }');
178
- return nextState;
179
- }
180
- let {
181
- value
182
- } = action.params;
54
+ // Actions do have a fixed structure which needs to be fulfilled
55
+ const normalizedAction = {
56
+ ...action,
57
+ rules: actionRules
58
+ };
183
59
 
184
- // Check correctness of value data type
185
- switch (typeof value) {
186
- case 'boolean':
187
- if (element.type !== ELEMENT_TYPE_CHECKBOX) {
188
- logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Allowed '${ELEMENT_TYPE_CHECKBOX}' data type for 'params.value' is: 'boolean'`);
189
- return nextState;
190
- }
191
- break;
192
- case 'number':
193
- if (element.type !== ELEMENT_TYPE_NUMBER) {
194
- logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Allowed '${ELEMENT_TYPE_NUMBER}' data types for 'params.value' are: ` + "'number' and 'string'");
195
- return nextState;
196
- }
197
- break;
198
- case 'string':
199
- if (element.type === ELEMENT_TYPE_CHECKBOX) {
200
- logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Allowed '${ELEMENT_TYPE_CHECKBOX}' data type for 'params.value' is: 'boolean'`);
201
- return nextState;
202
- }
60
+ // Assign the action listeners to all contexts of the current element
61
+ actionRules.forEach(rule => {
62
+ this.attach(element, normalizedAction, rule);
63
+ });
64
+ });
65
+ });
66
+ };
67
+ /**
68
+ * Attaches one or possibly multiple action listeners for the given rule
69
+ * @param {FormElement} element The current element that is modified by the action
70
+ * @param {FormFieldAction} action The action and it's params
71
+ * @param {FormFieldActionRule} rule The rule to check in case the action listener is triggered
72
+ */
73
+ this.attach = (element, action, rule) => {
74
+ let actionListener;
75
+ switch (action.type) {
76
+ case ACTION_TYPE_UPDATE_PROVINCE_ELEMENT:
77
+ {
78
+ actionListener = this.createUpdateProvinceElementHandler(element, action);
203
79
  break;
204
- default:
205
- logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Can not use '${typeof value}' data for elements of type '${element.type}'`);
206
- return nextState;
207
- }
208
-
209
- // Perform action based on "setValue" type, defined in params
210
- switch (action.params.type) {
211
- case ACTION_SET_VALUE_LENGTH_OF:
212
- value = `${nextState.formData[action.params.value].length}`;
80
+ }
81
+ case ACTION_TYPE_SET_VISIBILITY:
82
+ {
83
+ // Visibility is special and uses the result of the evaluation itself
84
+ actionListener = this.createSetVisibilityHandler(element, action);
213
85
  break;
214
- case ACTION_SET_VALUE_COPY_FROM:
215
- value = nextState.formData[action.params.value];
86
+ }
87
+ case ACTION_TYPE_SET_VALUE:
88
+ {
89
+ actionListener = this.createEvaluatedHandler(element, action, this.createSetValueHandler(element, action));
216
90
  break;
217
- case undefined:
218
- case ACTION_SET_VALUE_FIXED:
91
+ }
92
+ case ACTION_TYPE_TRANSFORM:
93
+ {
94
+ actionListener = this.createEvaluatedHandler(element, action, this.createTransformHandler(element, action));
219
95
  break;
220
- default:
221
- logger.error(`Error: Invalid form action param 'type' in element '${element.id}'. ` + `Allowed param types are: '${ACTION_SET_VALUE_LENGTH_OF}', ` + `'${ACTION_SET_VALUE_COPY_FROM}', '${ACTION_SET_VALUE_FIXED}'`);
222
- return nextState;
223
- }
224
- let newState = {
225
- ...nextState,
226
- formData: {
227
- ...nextState.formData,
228
- [element.id]: value
229
96
  }
230
- };
97
+ default:
98
+ return;
99
+ }
100
+ this.register(rule.context, actionListener);
101
+ };
102
+ /**
103
+ * Action listener creator to check all related rules before calling any further action listeners
104
+ * @param {FormElement} element The element for which the listener should be created for
105
+ * @param {FormFieldAction} action The action to be create a listener for
106
+ * @param {Function} actionListener The action listener to call if the rule applies
107
+ * @returns {Function} Returns a function to modify and return the modified state.
108
+ */
109
+ this.createEvaluatedHandler = (element, action, actionListener) => (prevState, nextState) => {
110
+ // Apply rules before accepting any changes
111
+ if (!this.evaluateRules(element, action, nextState)) {
112
+ return nextState;
113
+ }
114
+ return actionListener(prevState, nextState);
115
+ };
116
+ /**
117
+ * Action listener creator to handle "updateCountryChange" action
118
+ * @param {FormElement} provinceEl The element for which the listener should be created for
119
+ * @param {FormFieldAction} action The action to be create a listener for
120
+ * @returns {Function} Returns a function to modify and return the modified state.
121
+ */
122
+ this.createUpdateProvinceElementHandler = (provinceEl, action) => (prevState, nextState) => {
123
+ const countryElementId = action.rules[0].context;
124
+ const countryValue = nextState.formData[countryElementId];
125
+ const countryDefault = this.defaults[countryElementId];
126
+ const newState = {
127
+ ...nextState
128
+ };
231
129
 
232
- // Notify follow up listeners about the current change, if there are any changes
233
- if (nextState.formData[element.id] !== value) {
234
- newState = this.notify(element.id, prevState, newState);
130
+ // Overwrite province with the form's default, if country matches the default as well
131
+ if (countryValue === countryDefault) {
132
+ newState.formData[provinceEl.id] = this.defaults[provinceEl.id];
133
+ } else {
134
+ // Update province to first or no selection, based on "required" attribute
135
+ newState.formData[provinceEl.id] = !provinceEl.required ? '' : Object.keys(this.getProvincesList(countryValue))[0];
136
+ }
137
+ return newState;
138
+ };
139
+ /**
140
+ * Action listener creator to handle "setVisibility" actions
141
+ * @param {FormElement} element The element for which the listener should be created for
142
+ * @param {FormFieldAction} action The action to be create a listener for
143
+ * @returns {Function} Returns a function to modify and return the modified state.
144
+ */
145
+ this.createSetVisibilityHandler = (element, action) => (prevState, nextState) => {
146
+ let newState = {
147
+ ...nextState,
148
+ elementVisibility: {
149
+ ...nextState.elementVisibility,
150
+ [element.id]: this.evaluateRules(element, action, nextState)
151
+ },
152
+ // Copy form data to be able to check changes and all follow up actions
153
+ formData: {
154
+ ...nextState.formData
235
155
  }
236
- return newState;
237
156
  };
238
- /**
239
- * Action listener creator to handle "transform" actions
240
- * @param {FormElement} element The element for which the listener should be created for
241
- * @param {FormFieldAction} action The action to be create a listener for
242
- * @returns {Function} Returns a function to modify and return the modified state.
243
- */
244
- this.createTransformHandler = (element, action) => (prevState, nextState) => {
245
- /**
246
- * Takes a string and applies a case function on it
247
- * @param {string|boolean|number} subject The subject to be transformed
248
- * @returns {string|boolean|number}
249
- */
250
- const transform = subject => {
251
- // Get optional params to be applied in the transformation process
252
- let args = action.params.value || [];
253
- if (Array.isArray(action.params.value)) {
254
- args = action.params.value;
157
+ if (newState.formData[element.id] === undefined && newState.elementVisibility[element.id]) {
158
+ newState.formData[element.id] = this.defaults[element.id];
159
+ } else if (!newState.elementVisibility[element.id] && newState.formData[element.id] !== undefined) {
160
+ delete newState.formData[element.id];
161
+ }
162
+
163
+ // Notify follow up listeners about the current change
164
+ if (nextState.formData[element.id] !== newState.formData[element.id]) {
165
+ newState = this.notify(element.id, prevState, newState);
166
+ }
167
+ return newState;
168
+ };
169
+ /**
170
+ * Action listener creator to handle "setValue" actions
171
+ * @param {FormElement} element The element for which the listener should be created for
172
+ * @param {FormFieldAction} action The action to be create a listener for
173
+ * @returns {Function} Returns the modified state.
174
+ */
175
+ this.createSetValueHandler = (element, action) => (prevState, nextState) => {
176
+ if (typeof action.params !== 'object' || Array.isArray(action.params)) {
177
+ logger.error(`Error: Invalid or missing form action in element '${element.id}'. ` + 'Params must be in the format: { "type": string, "value": string }');
178
+ return nextState;
179
+ }
180
+ let {
181
+ value
182
+ } = action.params;
183
+
184
+ // Check correctness of value data type
185
+ switch (typeof value) {
186
+ case 'boolean':
187
+ if (element.type !== ELEMENT_TYPE_CHECKBOX) {
188
+ logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Allowed '${ELEMENT_TYPE_CHECKBOX}' data type for 'params.value' is: 'boolean'`);
189
+ return nextState;
255
190
  }
256
- switch (typeof subject) {
257
- case 'string':
258
- {
259
- if (typeof String.prototype[action.params.type] !== 'function' && typeof String[action.params.type] !== 'function') {
260
- logger.error("Error: Invalid transform function passed to actions 'params.type' " + `attribute in element '${element.id}'. Must be withing 'String.prototype'!`);
261
- return subject;
262
- }
263
- if (typeof String.prototype[action.params.type] === 'function') {
264
- return String.prototype[action.params.type].apply(subject, args);
265
- }
266
- return String[action.params.type](subject);
267
- }
268
- case 'boolean':
269
- {
270
- if (typeof Boolean.prototype[action.params.type] !== 'function' && typeof Boolean[action.params.type] !== 'function') {
271
- logger.error("Error: Invalid transform function passed to actions 'params.type' " + `attribute in element '${element.id}'. Must be withing 'String.prototype'!`);
272
- return subject;
273
- }
274
- if (typeof Boolean.prototype[action.params.type] === 'function') {
275
- return Boolean.prototype[action.params.type].apply(subject, args);
276
- }
277
- return Boolean[action.params.type](subject);
278
- }
279
- case 'number':
280
- {
281
- if (typeof Number.prototype[action.params.type] !== 'function' && typeof Number[action.params.type] !== 'function') {
282
- logger.error("Error: Invalid transform function passed to actions 'params.type' " + `attribute in element '${element.id}'. Must be withing 'String.prototype'!`);
283
- return subject;
284
- }
285
- if (typeof Number.prototype[action.params.type] === 'function') {
286
- return Number.prototype[action.params.type].apply(subject, args);
287
- }
288
- return Number[action.params.type](subject);
289
- }
290
- default:
291
- logger.error("Error: The given data can not be transformed. Must be of type 'string', " + "'boolean' or 'number'");
292
- return subject;
191
+ break;
192
+ case 'number':
193
+ if (element.type !== ELEMENT_TYPE_NUMBER) {
194
+ logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Allowed '${ELEMENT_TYPE_NUMBER}' data types for 'params.value' are: ` + "'number' and 'string'");
195
+ return nextState;
293
196
  }
294
- };
295
- let newState = {
296
- ...nextState,
297
- formData: {
298
- ...nextState.formData,
299
- [element.id]: transform(nextState.formData[element.id])
197
+ break;
198
+ case 'string':
199
+ if (element.type === ELEMENT_TYPE_CHECKBOX) {
200
+ logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Allowed '${ELEMENT_TYPE_CHECKBOX}' data type for 'params.value' is: 'boolean'`);
201
+ return nextState;
300
202
  }
301
- };
203
+ break;
204
+ default:
205
+ logger.error(`Error: Invalid form action param in element '${element.id}'. ` + `Can not use '${typeof value}' data for elements of type '${element.type}'`);
206
+ return nextState;
207
+ }
302
208
 
303
- // Notify follow up listeners about the current change
304
- if (nextState.formData[element.id] !== newState.formData[element.id]) {
305
- newState = this.notify(element.id, prevState, newState);
209
+ // Perform action based on "setValue" type, defined in params
210
+ switch (action.params.type) {
211
+ case ACTION_SET_VALUE_LENGTH_OF:
212
+ value = `${nextState.formData[action.params.value].length}`;
213
+ break;
214
+ case ACTION_SET_VALUE_COPY_FROM:
215
+ value = nextState.formData[action.params.value];
216
+ break;
217
+ case undefined:
218
+ case ACTION_SET_VALUE_FIXED:
219
+ break;
220
+ default:
221
+ logger.error(`Error: Invalid form action param 'type' in element '${element.id}'. ` + `Allowed param types are: '${ACTION_SET_VALUE_LENGTH_OF}', ` + `'${ACTION_SET_VALUE_COPY_FROM}', '${ACTION_SET_VALUE_FIXED}'`);
222
+ return nextState;
223
+ }
224
+ let newState = {
225
+ ...nextState,
226
+ formData: {
227
+ ...nextState.formData,
228
+ [element.id]: value
306
229
  }
307
- return newState;
308
230
  };
231
+
232
+ // Notify follow up listeners about the current change, if there are any changes
233
+ if (nextState.formData[element.id] !== value) {
234
+ newState = this.notify(element.id, prevState, newState);
235
+ }
236
+ return newState;
237
+ };
238
+ /**
239
+ * Action listener creator to handle "transform" actions
240
+ * @param {FormElement} element The element for which the listener should be created for
241
+ * @param {FormFieldAction} action The action to be create a listener for
242
+ * @returns {Function} Returns a function to modify and return the modified state.
243
+ */
244
+ this.createTransformHandler = (element, action) => (prevState, nextState) => {
309
245
  /**
310
- * Evaluates all action rules of a given element action
311
- *
312
- * @param {FormElement} element The element of which the action rules should be evaluated
313
- * @param {FormFieldAction} action The current action to be evaluate rules for
314
- * @param {Object} nextState The state at the time before the "action" event finished
315
- * @returns {boolean}
246
+ * Takes a string and applies a case function on it
247
+ * @param {string|boolean|number} subject The subject to be transformed
248
+ * @returns {string|boolean|number}
316
249
  */
317
- this.evaluateRules = (element, action, nextState) => {
318
- const concatRules = this.createConcatMethod(action.ruleConcatMethod);
319
- const resultInitValue = action.ruleConcatMethod !== ACTION_RULES_CONCAT_METHOD_ANY;
320
- let result = resultInitValue;
321
- action.rules.forEach(rule => {
322
- let tmpResult = resultInitValue;
323
- let ruleType = rule.type;
324
- let ruleData = rule.data;
325
-
326
- // Default to rule type "boolean" and data true when type not given
327
- if (ruleType === undefined) {
328
- ruleType = ACTION_RULE_TYPE_BOOLEAN;
329
- ruleData = true;
330
- }
331
-
332
- // Check rule validity
333
- if (!ACTION_RULE_DATA_TYPES[ruleType]) {
334
- logger.error(`Error: Unknown action rule type '${ruleType}'in element '${element.id}'`);
335
- return;
336
- }
337
- // Check type of ruleData
338
- const ruleDataType = ACTION_RULE_DATA_TYPES[ruleType];
339
- if (ruleDataType === 'array' && !Array.isArray(ruleData)) {
340
- logger.error(`Error: Invalid FormBuilder action rule in element '${element.id}': ` + `data must be an 'array' for rule type '${ruleType}'`);
341
- return;
342
- }
343
- // eslint-disable-next-line valid-typeof
344
- if (ruleDataType !== 'array' && typeof ruleData !== ruleDataType) {
345
- logger.error(`Error: Invalid FormBuilder action rule in element '${element.id}': ` + `data must be '${ruleDataType}' for rule type '${ruleType}'`);
346
- return;
347
- }
348
- switch (ruleType) {
349
- case ACTION_RULE_TYPE_ONE_OF:
350
- {
351
- tmpResult = ruleData.includes(nextState.formData[rule.context]);
352
- break;
250
+ const transform = subject => {
251
+ // Get optional params to be applied in the transformation process
252
+ let args = action.params.value || [];
253
+ if (Array.isArray(action.params.value)) {
254
+ args = action.params.value;
255
+ }
256
+ switch (typeof subject) {
257
+ case 'string':
258
+ {
259
+ if (typeof String.prototype[action.params.type] !== 'function' && typeof String[action.params.type] !== 'function') {
260
+ logger.error("Error: Invalid transform function passed to actions 'params.type' " + `attribute in element '${element.id}'. Must be withing 'String.prototype'!`);
261
+ return subject;
353
262
  }
354
- case ACTION_RULE_TYPE_NOT_IN:
355
- {
356
- tmpResult = !ruleData.includes(nextState.formData[rule.context]);
357
- break;
263
+ if (typeof String.prototype[action.params.type] === 'function') {
264
+ return String.prototype[action.params.type].apply(subject, args);
358
265
  }
359
- case ACTION_RULE_TYPE_BOOLEAN:
360
- {
361
- tmpResult = ruleData;
362
- break;
266
+ return String[action.params.type](subject);
267
+ }
268
+ case 'boolean':
269
+ {
270
+ if (typeof Boolean.prototype[action.params.type] !== 'function' && typeof Boolean[action.params.type] !== 'function') {
271
+ logger.error("Error: Invalid transform function passed to actions 'params.type' " + `attribute in element '${element.id}'. Must be withing 'String.prototype'!`);
272
+ return subject;
363
273
  }
364
- case ACTION_RULE_TYPE_REGEX:
365
- {
366
- const regexParts = ruleData.split('/');
367
- let regexPattern = '';
368
- let regexParam = '';
369
- if (regexParts.length === 1) {
370
- [regexPattern] = regexParts;
371
- } else if (regexParts.length === 3) {
372
- regexParts.shift();
373
- [regexPattern, regexParam = ''] = regexParts;
374
- } else {
375
- logger.error(`Error: Invalid regex string in action rule in element ${element.id}`);
376
- break;
377
- }
378
- const regex = new RegExp(regexPattern, regexParam);
379
- tmpResult = regex.test(nextState.formData[rule.context]);
380
- break;
274
+ if (typeof Boolean.prototype[action.params.type] === 'function') {
275
+ return Boolean.prototype[action.params.type].apply(subject, args);
381
276
  }
382
- default:
383
- break;
384
- }
385
-
386
- // Concat rules based on the rule concat method of the action
387
- result = concatRules(result, tmpResult);
388
- });
389
- return result;
390
- };
391
- /**
392
- * Creates a concat function that defines how to concatenate action rule results
393
- *
394
- * @param {string} method The method defined by the action
395
- * @returns {Function}
396
- */
397
- this.createConcatMethod = method => (prev, next) => {
398
- switch (method) {
399
- case ACTION_RULES_CONCAT_METHOD_NONE:
400
- return prev && !next;
401
- case ACTION_RULES_CONCAT_METHOD_ANY:
402
- return prev || next;
403
- case ACTION_RULES_CONCAT_METHOD_ALL:
277
+ return Boolean[action.params.type](subject);
278
+ }
279
+ case 'number':
280
+ {
281
+ if (typeof Number.prototype[action.params.type] !== 'function' && typeof Number[action.params.type] !== 'function') {
282
+ logger.error("Error: Invalid transform function passed to actions 'params.type' " + `attribute in element '${element.id}'. Must be withing 'String.prototype'!`);
283
+ return subject;
284
+ }
285
+ if (typeof Number.prototype[action.params.type] === 'function') {
286
+ return Number.prototype[action.params.type].apply(subject, args);
287
+ }
288
+ return Number[action.params.type](subject);
289
+ }
404
290
  default:
405
- return prev && next;
291
+ logger.error("Error: The given data can not be transformed. Must be of type 'string', " + "'boolean' or 'number'");
292
+ return subject;
406
293
  }
407
294
  };
408
- /**
409
- * Adds a "action" listener to a given context element
410
- *
411
- * @param {string} elementId the element to listen for
412
- * @param {Function} handler The listener to call when something has changed
413
- */
414
- this.register = (elementId, handler) => {
415
- if (!this.actionListeners[elementId]) {
416
- this.actionListeners[elementId] = [];
295
+ let newState = {
296
+ ...nextState,
297
+ formData: {
298
+ ...nextState.formData,
299
+ [element.id]: transform(nextState.formData[element.id])
417
300
  }
418
- this.actionListeners[elementId].push(handler);
419
301
  };
420
- /**
421
- * Takes an element id, the state to work with and optional data and notifies all "action"
422
- * listeners about the change. Every listener can manipulate the state.
423
- * Returns the new state.
424
- *
425
- * @param {string} elementId The id of the element that was changed
426
- * @param {Object} prevState The state before any changes took place
427
- * @param {Object} nextState The state containing all updates before the listeners are executed
428
- * @returns {Object} The new state after all handlers have been executed.
429
- */
430
- this.notify = (elementId, prevState, nextState) => {
431
- let newState = nextState;
432
- if (this.actionListeners[elementId]) {
433
- this.actionListeners[elementId].forEach(notifyListener => {
434
- // Note: The order of state changes is applied in the same order of listener registration
435
- newState = notifyListener(prevState, newState);
436
- });
302
+
303
+ // Notify follow up listeners about the current change
304
+ if (nextState.formData[element.id] !== newState.formData[element.id]) {
305
+ newState = this.notify(element.id, prevState, newState);
306
+ }
307
+ return newState;
308
+ };
309
+ /**
310
+ * Evaluates all action rules of a given element action
311
+ *
312
+ * @param {FormElement} element The element of which the action rules should be evaluated
313
+ * @param {FormFieldAction} action The current action to be evaluate rules for
314
+ * @param {Object} nextState The state at the time before the "action" event finished
315
+ * @returns {boolean}
316
+ */
317
+ this.evaluateRules = (element, action, nextState) => {
318
+ const concatRules = this.createConcatMethod(action.ruleConcatMethod);
319
+ const resultInitValue = action.ruleConcatMethod !== ACTION_RULES_CONCAT_METHOD_ANY;
320
+ let result = resultInitValue;
321
+ action.rules.forEach(rule => {
322
+ let tmpResult = resultInitValue;
323
+ let ruleType = rule.type;
324
+ let ruleData = rule.data;
325
+
326
+ // Default to rule type "boolean" and data true when type not given
327
+ if (ruleType === undefined) {
328
+ ruleType = ACTION_RULE_TYPE_BOOLEAN;
329
+ ruleData = true;
437
330
  }
438
- return newState;
439
- };
440
- this.defaults = defaults;
441
- this.getProvincesList = getProvincesList;
442
- this.actionListeners = {};
443
- }
444
- }
331
+
332
+ // Check rule validity
333
+ if (!ACTION_RULE_DATA_TYPES[ruleType]) {
334
+ logger.error(`Error: Unknown action rule type '${ruleType}'in element '${element.id}'`);
335
+ return;
336
+ }
337
+ // Check type of ruleData
338
+ const ruleDataType = ACTION_RULE_DATA_TYPES[ruleType];
339
+ if (ruleDataType === 'array' && !Array.isArray(ruleData)) {
340
+ logger.error(`Error: Invalid FormBuilder action rule in element '${element.id}': ` + `data must be an 'array' for rule type '${ruleType}'`);
341
+ return;
342
+ }
343
+ // eslint-disable-next-line valid-typeof
344
+ if (ruleDataType !== 'array' && typeof ruleData !== ruleDataType) {
345
+ logger.error(`Error: Invalid FormBuilder action rule in element '${element.id}': ` + `data must be '${ruleDataType}' for rule type '${ruleType}'`);
346
+ return;
347
+ }
348
+ switch (ruleType) {
349
+ case ACTION_RULE_TYPE_ONE_OF:
350
+ {
351
+ tmpResult = ruleData.includes(nextState.formData[rule.context]);
352
+ break;
353
+ }
354
+ case ACTION_RULE_TYPE_NOT_IN:
355
+ {
356
+ tmpResult = !ruleData.includes(nextState.formData[rule.context]);
357
+ break;
358
+ }
359
+ case ACTION_RULE_TYPE_BOOLEAN:
360
+ {
361
+ tmpResult = ruleData;
362
+ break;
363
+ }
364
+ case ACTION_RULE_TYPE_REGEX:
365
+ {
366
+ const regexParts = ruleData.split('/');
367
+ let regexPattern = '';
368
+ let regexParam = '';
369
+ if (regexParts.length === 1) {
370
+ [regexPattern] = regexParts;
371
+ } else if (regexParts.length === 3) {
372
+ regexParts.shift();
373
+ [regexPattern, regexParam = ''] = regexParts;
374
+ } else {
375
+ logger.error(`Error: Invalid regex string in action rule in element ${element.id}`);
376
+ break;
377
+ }
378
+ const regex = new RegExp(regexPattern, regexParam);
379
+ tmpResult = regex.test(nextState.formData[rule.context]);
380
+ break;
381
+ }
382
+ default:
383
+ break;
384
+ }
385
+
386
+ // Concat rules based on the rule concat method of the action
387
+ result = concatRules(result, tmpResult);
388
+ });
389
+ return result;
390
+ };
391
+ /**
392
+ * Creates a concat function that defines how to concatenate action rule results
393
+ *
394
+ * @param {string} method The method defined by the action
395
+ * @returns {Function}
396
+ */
397
+ this.createConcatMethod = method => (prev, next) => {
398
+ switch (method) {
399
+ case ACTION_RULES_CONCAT_METHOD_NONE:
400
+ return prev && !next;
401
+ case ACTION_RULES_CONCAT_METHOD_ANY:
402
+ return prev || next;
403
+ case ACTION_RULES_CONCAT_METHOD_ALL:
404
+ default:
405
+ return prev && next;
406
+ }
407
+ };
408
+ /**
409
+ * Adds a "action" listener to a given context element
410
+ *
411
+ * @param {string} elementId the element to listen for
412
+ * @param {Function} handler The listener to call when something has changed
413
+ */
414
+ this.register = (elementId, handler) => {
415
+ if (!this.actionListeners[elementId]) {
416
+ this.actionListeners[elementId] = [];
417
+ }
418
+ this.actionListeners[elementId].push(handler);
419
+ };
420
+ /**
421
+ * Takes an element id, the state to work with and optional data and notifies all "action"
422
+ * listeners about the change. Every listener can manipulate the state.
423
+ * Returns the new state.
424
+ *
425
+ * @param {string} elementId The id of the element that was changed
426
+ * @param {Object} prevState The state before any changes took place
427
+ * @param {Object} nextState The state containing all updates before the listeners are executed
428
+ * @returns {Object} The new state after all handlers have been executed.
429
+ */
430
+ this.notify = (elementId, prevState, nextState) => {
431
+ let newState = nextState;
432
+ if (this.actionListeners[elementId]) {
433
+ this.actionListeners[elementId].forEach(notifyListener => {
434
+ // Note: The order of state changes is applied in the same order of listener registration
435
+ newState = notifyListener(prevState, newState);
436
+ });
437
+ }
438
+ return newState;
439
+ };
440
+ this.defaults = defaults;
441
+ this.getProvincesList = getProvincesList;
442
+ this.actionListeners = {};
443
+ };
445
444
  export default ActionListener;