@inseefr/lunatic 0.2.2-experimental → 0.2.3-prisme

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 (219) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +25 -23
  3. package/lib/index.js +1191 -475
  4. package/lib/index.js.map +1 -1
  5. package/package.json +174 -172
  6. package/src/components/breadcrumb/breadcrumb.scss +22 -22
  7. package/src/components/checkbox/boolean.js +172 -172
  8. package/src/components/checkbox/checkbox.scss +73 -73
  9. package/src/components/checkbox/group.js +231 -231
  10. package/src/components/checkbox/one.js +11 -11
  11. package/src/components/component-wrapper/controls/component.js +70 -0
  12. package/src/components/component-wrapper/controls/controls.scss +6 -0
  13. package/src/components/component-wrapper/controls/index.js +1 -0
  14. package/src/components/component-wrapper/controls/validators/datepicker.js +44 -0
  15. package/src/components/component-wrapper/controls/validators/index.js +16 -0
  16. package/src/components/component-wrapper/controls/validators/input-number.js +23 -0
  17. package/src/components/{missing-wrapper → component-wrapper}/index.js +0 -0
  18. package/src/components/component-wrapper/missing/component.js +200 -0
  19. package/src/components/component-wrapper/missing/index.js +1 -0
  20. package/src/components/{missing-wrapper → component-wrapper/missing}/missing.scss +32 -32
  21. package/src/components/component-wrapper/wrapper.js +23 -0
  22. package/src/components/components.js +1 -0
  23. package/src/components/datepicker/component.js +26 -11
  24. package/src/components/declarations/component.js +46 -46
  25. package/src/components/declarations/declarations.scss +40 -40
  26. package/src/components/declarations/wrappers/input-declarations-wrapper.js +328 -274
  27. package/src/components/declarations/wrappers/simple-declarations-wrapper.js +54 -54
  28. package/src/components/dropdown/commons/actions.js +65 -56
  29. package/src/components/dropdown/commons/children-to-option.js +9 -9
  30. package/src/components/dropdown/commons/cleaner-callbacks.js +58 -58
  31. package/src/components/dropdown/commons/components/dropdown-container.js +29 -29
  32. package/src/components/dropdown/commons/components/dropdown.js +204 -183
  33. package/src/components/dropdown/commons/event-callbacks/on-mousedown-callback.js +15 -15
  34. package/src/components/dropdown/commons/reducer.js +3 -0
  35. package/src/components/dropdown/commons/tools/index.js +17 -17
  36. package/src/components/dropdown/component.js +135 -135
  37. package/src/components/dropdown/dropdown-edit/dropdown-edit.js +194 -185
  38. package/src/components/dropdown/dropdown-edit/index.js +11 -11
  39. package/src/components/dropdown/dropdown-simple/dropdown.js +173 -164
  40. package/src/components/dropdown/dropdown-simple/index.js +20 -20
  41. package/src/components/dropdown/dropdown.scss +178 -178
  42. package/src/components/icon/icon.scss +15 -15
  43. package/src/components/index.js +2 -0
  44. package/src/components/index.scss +177 -175
  45. package/src/components/input/input-number.js +30 -54
  46. package/src/components/input/input.js +11 -11
  47. package/src/components/input/input.scss +31 -37
  48. package/src/components/loop/component.js +170 -169
  49. package/src/components/loop/loop.scss +13 -13
  50. package/src/components/loop/wrapper.js +1 -1
  51. package/src/components/loop-constructor/block/block.scss +10 -10
  52. package/src/components/loop-constructor/block/component.js +9 -9
  53. package/src/components/loop-constructor/roster/component.js +8 -8
  54. package/src/components/loop-constructor/wrapper/body-component.js +146 -125
  55. package/src/components/modal/component.js +36 -0
  56. package/src/components/modal/index.js +1 -0
  57. package/src/components/modal/modal.scss +33 -0
  58. package/src/components/progress-bar/progress-bar.scss +54 -54
  59. package/src/components/radio/component.js +9 -9
  60. package/src/components/radio/radio.scss +59 -59
  61. package/src/components/sequence/component.js +50 -50
  62. package/src/components/sequence/sequence.scss +10 -10
  63. package/src/components/subsequence/component.js +49 -49
  64. package/src/components/suggester/check-store.js +2 -4
  65. package/src/components/suggester/components/panel/default-option-renderer.js +27 -27
  66. package/src/components/suggester/components/panel/option-container.js +61 -61
  67. package/src/components/suggester/components/panel/panel.js +47 -47
  68. package/src/components/suggester/components/selection/default-label-renderer.js +31 -31
  69. package/src/components/suggester/components/selection/label.js +35 -35
  70. package/src/components/suggester/components/selection/selection.js +50 -50
  71. package/src/components/suggester/components/suggester-content.js +2 -2
  72. package/src/components/suggester/components/suggester.js +128 -88
  73. package/src/components/suggester/components/suggester.scss +101 -101
  74. package/src/components/suggester/default-style.scss +125 -125
  75. package/src/components/suggester/find-best-label/find-best-label.js +51 -49
  76. package/src/components/suggester/find-best-label/index.js +1 -1
  77. package/src/components/suggester/idb-suggester.js +73 -67
  78. package/src/components/suggester/lunatic-suggester.js +140 -137
  79. package/src/components/suggester/searching/create-searching.js +49 -47
  80. package/src/components/suggester/searching/index.js +1 -1
  81. package/src/components/suggester/state-management/actions.js +38 -38
  82. package/src/components/suggester/state-management/reducer/reduce-on-delete-search.js +11 -11
  83. package/src/components/suggester/state-management/reducer/reduce-on-init.js +29 -29
  84. package/src/components/suggester/state-management/reducer/reducer.js +38 -38
  85. package/src/components/suggester/suggester-wrapper.js +127 -121
  86. package/src/components/suggester-loader-widget/loader.js +67 -67
  87. package/src/components/suggester-loader-widget/widget.js +123 -123
  88. package/src/components/table/table.js +173 -171
  89. package/src/components/table/table.scss +26 -26
  90. package/src/components/textarea/component.js +11 -11
  91. package/src/components/textarea/textarea.scss +8 -8
  92. package/src/components/tooltip/tooltip.scss +30 -30
  93. package/src/stories/checkbox-boolean/data.json +78 -78
  94. package/src/stories/datepicker/data.json +3 -1
  95. package/src/stories/dropdown/README.md +44 -44
  96. package/src/stories/dropdown/data.json +98 -98
  97. package/src/stories/dropdown/dropdown.stories.js +89 -89
  98. package/src/stories/progress-bar/progress-bar.stories.js +24 -24
  99. package/src/stories/questionnaire/arithmetic-management.json +47 -0
  100. package/src/stories/questionnaire/arithmetic.json +247 -247
  101. package/src/stories/questionnaire/kish.json +275 -0
  102. package/src/stories/questionnaire/logement-queen.json +23390 -0
  103. package/src/stories/questionnaire/logement-s2.json +46028 -0
  104. package/src/stories/questionnaire/logement.json +20347 -26087
  105. package/src/stories/questionnaire/loop-and-controls.json +481 -0
  106. package/src/stories/questionnaire/questionnaire.stories.js +236 -138
  107. package/src/stories/questionnaire/update-external/data.json +1 -0
  108. package/src/stories/questionnaire/update-external/questionnaire.json +75 -0
  109. package/src/stories/suggester/README.md +46 -46
  110. package/src/stories/suggester/bailleurs-sociaux/fetch-bailleurs.js +15 -15
  111. package/src/stories/suggester/bailleurs-sociaux/index.js +2 -2
  112. package/src/stories/suggester/bailleurs-sociaux/option-bailleur-renderer.js +58 -58
  113. package/src/stories/suggester/bailleurs-sociaux/preloader.svg +51 -51
  114. package/src/stories/suggester/bailleurs-sociaux/theme.scss +22 -22
  115. package/src/stories/suggester/bailleurs-sociaux-2021/fetch-bailleurs.js +12 -0
  116. package/src/stories/suggester/bailleurs-sociaux-2021/index.js +1 -0
  117. package/src/stories/suggester/cog-communes/fetch-cog.js +15 -15
  118. package/src/stories/suggester/data-auto.json +232 -231
  119. package/src/stories/suggester/data-vtl.json +82 -82
  120. package/src/stories/suggester/data.json +169 -136
  121. package/src/stories/suggester/naf-rev2/index.js +2 -2
  122. package/src/stories/suggester/naf-rev2/option-naf-renderer.js +17 -17
  123. package/src/stories/suggester/suggester-workers.stories.js +226 -179
  124. package/src/stories/suggester/suggester.stories.js +138 -133
  125. package/src/stories/utils/orchestrator-split.js +119 -0
  126. package/src/stories/utils/orchestrator.js +119 -108
  127. package/src/tests/components/input-number.spec.js +6 -12
  128. package/src/tests/components/missing-wrapper.spec.js +0 -1
  129. package/src/tests/utils/lib/table/roster.spec.js +25 -25
  130. package/src/tests/utils/to-expose/handler/results/res-input-edited.json +1 -1
  131. package/src/utils/components/dragger/dragger.scss +7 -7
  132. package/src/utils/idb-tools/create-db-opener.js +43 -43
  133. package/src/utils/idb-tools/create-open-db.js +25 -25
  134. package/src/utils/idb-tools/idb-bulk-insert.js +96 -96
  135. package/src/utils/idb-tools/index.js +10 -10
  136. package/src/utils/idb-tools/insert-entity.js +15 -15
  137. package/src/utils/idb-tools/open-db.js +13 -13
  138. package/src/utils/idb-tools/open-or-create-db.js +34 -34
  139. package/src/utils/lib/controls/index.js +1 -0
  140. package/src/utils/lib/controls/utils.js +152 -0
  141. package/src/utils/lib/decorator/title-decorator.js +16 -16
  142. package/src/utils/lib/env.js +2 -2
  143. package/src/utils/lib/index.js +2 -0
  144. package/src/utils/lib/input-number.js +1 -1
  145. package/src/utils/lib/options-positioning.js +9 -9
  146. package/src/utils/lib/pagination/navigation/shared.js +12 -9
  147. package/src/utils/lib/prop-types/lines.js +6 -6
  148. package/src/utils/lib/responses.js +11 -9
  149. package/src/utils/lib/splitting.js +142 -0
  150. package/src/utils/lib/style.js +10 -10
  151. package/src/utils/store-tools/auto-load.js +74 -73
  152. package/src/utils/suggester-workers/append-to-index/append.js +25 -25
  153. package/src/utils/suggester-workers/append-to-index/append.worker.js +16 -16
  154. package/src/utils/suggester-workers/append-to-index/create-append-task.js +45 -43
  155. package/src/utils/suggester-workers/append-to-index/index.js +2 -2
  156. package/src/utils/suggester-workers/append-to-index/prepare-entities.js +61 -61
  157. package/src/utils/suggester-workers/append-to-index/store-messages.js +21 -21
  158. package/src/utils/suggester-workers/commons-tokenizer/create-entity-tokenizer.js +56 -0
  159. package/src/utils/suggester-workers/commons-tokenizer/create-fields-tokenizer.js +56 -0
  160. package/src/utils/suggester-workers/commons-tokenizer/create-filter-stop-words.js +17 -17
  161. package/src/utils/suggester-workers/commons-tokenizer/filters/compose-filters.js +10 -0
  162. package/src/utils/suggester-workers/commons-tokenizer/filters/create-filter-stop-words.js +17 -0
  163. package/src/utils/suggester-workers/commons-tokenizer/filters/create-filter-stop-words.spec.js +14 -0
  164. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-accents.js +12 -0
  165. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-accents.spec.js +12 -0
  166. package/src/utils/suggester-workers/commons-tokenizer/{filter-double.js → filters/filter-double.js} +12 -12
  167. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-double.spec.js +20 -0
  168. package/src/utils/suggester-workers/commons-tokenizer/{filter-length.js → filters/filter-length.js} +7 -7
  169. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-length.spec.js +18 -0
  170. package/src/utils/suggester-workers/commons-tokenizer/{filter-stemmer.js → filters/filter-stemmer.js} +13 -13
  171. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-stemmer.spec.js +12 -0
  172. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-synonyms.js +36 -0
  173. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-synonyms.spec.js +12 -0
  174. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-to-lower.js +10 -0
  175. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-to-lower.spec.js +12 -0
  176. package/src/utils/suggester-workers/commons-tokenizer/filters/index.js +2 -0
  177. package/src/utils/suggester-workers/commons-tokenizer/{stop-words.js → filters/stop-words.js} +118 -118
  178. package/src/utils/suggester-workers/commons-tokenizer/get-regexp-from-pattern.js +8 -8
  179. package/src/utils/suggester-workers/commons-tokenizer/get-stemmer.js +18 -18
  180. package/src/utils/suggester-workers/commons-tokenizer/index.js +9 -8
  181. package/src/utils/suggester-workers/commons-tokenizer/prepare-string-indexation.js +13 -9
  182. package/src/utils/suggester-workers/commons-tokenizer/soft-tokenizer.js +7 -7
  183. package/src/utils/suggester-workers/create-worker.js +56 -0
  184. package/src/utils/suggester-workers/find-best-label/find-best-label.js +39 -39
  185. package/src/utils/suggester-workers/find-best-label/find-best-label.worker.js +40 -40
  186. package/src/utils/suggester-workers/find-best-label/tokenize.js +30 -33
  187. package/src/utils/suggester-workers/find-best-label/tokenize.spec.js +19 -19
  188. package/src/utils/suggester-workers/searching/compute-score.js +33 -33
  189. package/src/utils/suggester-workers/searching/get-db.js +18 -18
  190. package/src/utils/suggester-workers/searching/index.js +1 -1
  191. package/src/utils/suggester-workers/searching/order/create-alphanumeric-orderer.js +20 -20
  192. package/src/utils/suggester-workers/searching/order/index.js +19 -19
  193. package/src/utils/suggester-workers/{query-parser → searching/query-parser}/index.js +2 -2
  194. package/src/utils/suggester-workers/searching/query-parser/query-parser-soft.js +7 -0
  195. package/src/utils/suggester-workers/{query-parser → searching/query-parser}/query-parser-soft.spec.js +24 -24
  196. package/src/utils/suggester-workers/searching/query-parser/query-parser-tokenized.js +34 -0
  197. package/src/utils/suggester-workers/searching/resolve-query-parser.js +27 -27
  198. package/src/utils/suggester-workers/searching/search-in-index.js +17 -17
  199. package/src/utils/suggester-workers/searching/searching.js +70 -70
  200. package/src/utils/suggester-workers/searching/searching.worker.js +11 -11
  201. package/src/utils/to-expose/calculated-variables.js +113 -113
  202. package/src/utils/to-expose/handler.js +149 -112
  203. package/src/utils/to-expose/hooks/filter-components.js +27 -12
  204. package/src/utils/to-expose/hooks/index.js +2 -1
  205. package/src/utils/to-expose/hooks/lunatic-split.js +428 -0
  206. package/src/utils/to-expose/hooks/lunatic.js +284 -187
  207. package/src/utils/to-expose/index.js +1 -1
  208. package/src/utils/to-expose/init-questionnaire.js +164 -164
  209. package/src/utils/to-expose/interpret/vtl.js +18 -18
  210. package/src/utils/to-expose/state.js +66 -58
  211. package/src/components/missing-wrapper/component.js +0 -120
  212. package/src/components/missing-wrapper/wrapper.js +0 -10
  213. package/src/tests/utils/to-expose/hooks/use-lunatic.spec.js +0 -46
  214. package/src/utils/suggester-workers/commons-tokenizer/create-tokenizer.js +0 -103
  215. package/src/utils/suggester-workers/commons-tokenizer/filter-accents-to-lower.js +0 -9
  216. package/src/utils/suggester-workers/commons-tokenizer/filter-synonyms.js +0 -10
  217. package/src/utils/suggester-workers/query-parser/query-parser-soft.js +0 -7
  218. package/src/utils/suggester-workers/query-parser/query-parser-tokenized.js +0 -31
  219. package/src/utils/suggester-workers/query-parser/query-parser-tokenized.spec.js +0 -32
@@ -1,274 +1,328 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import PropTypes from 'prop-types';
3
- import Declarations from '../';
4
- import { TooltipResponse } from '../../tooltip';
5
- import * as U from '../../../utils/lib';
6
- import * as C from '../../../constants';
7
- import { interpret } from '../../../utils/to-expose';
8
-
9
- const InputDeclarationsWrapper = ({
10
- id,
11
- label,
12
- preferences,
13
- response,
14
- placeholder,
15
- handleChange,
16
- maxLength,
17
- readOnly,
18
- disabled,
19
- autoComplete,
20
- labelPosition,
21
- mandatory,
22
- declarations,
23
- features,
24
- bindings,
25
- focused,
26
- management,
27
- style,
28
- type,
29
- roleType,
30
- min,
31
- max,
32
- decimals,
33
- unit,
34
- unitPosition,
35
- validators,
36
- isInputNumber,
37
- numberAsTextfield,
38
- logFunction,
39
- }) => {
40
- const inputRef = useRef();
41
- const createEventFocus = (focusIn = true) =>
42
- U.createObjectEvent(
43
- `${roleType}-${id}`,
44
- C.INPUT_CATEGORY,
45
- focusIn ? C.EVENT_FOCUS_IN : C.EVENT_FOCUS_OUT,
46
- U.getResponseName(response),
47
- value
48
- );
49
- const [value, setValue] = useState(() =>
50
- U.getResponseByPreference(preferences)(response)
51
- );
52
-
53
- const [messagesError, setMessagesError] = useState(
54
- validators.map((v) => v(value)).filter((m) => m !== undefined)
55
- );
56
-
57
- useEffect(() => {
58
- if (focused) inputRef.current.focus();
59
- }, [focused]);
60
-
61
- // Assume we only want to handle enable external updates
62
- // Don't need to check all value changes
63
- useEffect(() => {
64
- if (U.getResponseByPreference(preferences)(response) !== value)
65
- setValue(U.getResponseByPreference(preferences)(response));
66
- // eslint-disable-next-line react-hooks/exhaustive-deps
67
- }, [response, preferences]);
68
-
69
- const validate = (v) => {
70
- setMessagesError(
71
- validators.map((f) => f(v)).filter((m) => m !== undefined)
72
- );
73
- };
74
-
75
- useEffect(() => {
76
- if (isInputNumber) {
77
- setMessagesError(
78
- validators.map((v) => v(value)).filter((m) => m !== undefined)
79
- );
80
- }
81
- }, [value, min, max, validators, isInputNumber]);
82
-
83
- const handleChangeOnBlur = () => {
84
- const initValue = U.getResponseByPreference(preferences)(response);
85
- if (value !== initValue) {
86
- const finalValue =
87
- value && value.endsWith('.') ? value.replace('.', '') : value;
88
- handleChange({
89
- [U.getResponseName(response)]: finalValue,
90
- });
91
- if (U.isFunction(logFunction)) logFunction(createEventFocus(false));
92
- if (value !== finalValue) setValue(finalValue);
93
- }
94
- };
95
-
96
- const handleFocusIn = () => {
97
- if (U.isFunction(logFunction)) logFunction(createEventFocus());
98
- };
99
-
100
- const Component = roleType === 'textarea' ? 'textarea' : 'input';
101
-
102
- return (
103
- <>
104
- <Declarations
105
- id={id}
106
- type={C.BEFORE_QUESTION_TEXT}
107
- declarations={declarations}
108
- features={features}
109
- bindings={bindings}
110
- />
111
- <div className={U.getLabelPositionClass(labelPosition)}>
112
- {label && (
113
- <label
114
- htmlFor={`${roleType}-${id}`}
115
- id={`${roleType}-label-${id}`}
116
- className={`${mandatory ? 'mandatory' : ''}`}
117
- >
118
- {interpret(features, logFunction)(bindings)(label)}
119
- {isInputNumber &&
120
- unit &&
121
- ['DEFAULT', 'BEFORE'].includes(unitPosition) && (
122
- <span className="unit">{` (${unit})`}</span>
123
- )}
124
- </label>
125
- )}
126
- <Declarations
127
- id={id}
128
- type={C.AFTER_QUESTION_TEXT}
129
- declarations={declarations}
130
- features={features}
131
- bindings={bindings}
132
- />
133
- <div className="field-container">
134
- <div className={`${management ? 'field-with-tooltip' : 'field'}`}>
135
- <Component
136
- type={type}
137
- id={`${roleType}-${id}`}
138
- ref={inputRef}
139
- value={value || ''}
140
- min={min}
141
- max={max}
142
- step={decimals ? `${Math.pow(10, -decimals)}` : '0'}
143
- placeholder={placeholder}
144
- autoComplete={autoComplete ? 'on' : 'off'}
145
- className={`${roleType}-lunatic ${
146
- isInputNumber && messagesError.length > 0 ? 'warning' : ''
147
- }`}
148
- style={U.buildStyleObject(style)}
149
- readOnly={readOnly}
150
- disabled={disabled}
151
- maxLength={maxLength || 524288}
152
- required={mandatory}
153
- aria-required={mandatory}
154
- onChange={(e) => {
155
- const v = e.target.value;
156
- if (
157
- // Chrome
158
- Object.getPrototypeOf(e.nativeEvent).constructor.name ===
159
- 'Event' ||
160
- // FF hack: impossible to handle arrow events
161
- Math.abs(v - value) === 1
162
- ) {
163
- setValue(v);
164
- handleChange({
165
- [U.getResponseName(response)]: v,
166
- });
167
- } else {
168
- if (isInputNumber) {
169
- if (
170
- numberAsTextfield &&
171
- v !== '' &&
172
- !U.isNumberValid(v, decimals)
173
- ) {
174
- e.preventDefault();
175
- e.stopPropagation();
176
- return null;
177
- } else validate(v);
178
- }
179
- if (management) setValue(v);
180
- else setValue(v === '' ? null : v);
181
- }
182
- }}
183
- onBlur={handleChangeOnBlur}
184
- onFocus={handleFocusIn}
185
- />
186
- {isInputNumber && unit && unitPosition === 'AFTER' && (
187
- <span className="unit">{unit}</span>
188
- )}
189
- </div>
190
- {management && (
191
- <div className="tooltip">
192
- <TooltipResponse
193
- id={id}
194
- response={U.buildLocalResponse(response, value)}
195
- />
196
- </div>
197
- )}
198
- </div>
199
- {isInputNumber && (
200
- <div className="lunatic-input-number-errors">
201
- {messagesError.map((m, i) => (
202
- <div key={i} className="error">
203
- {m}
204
- </div>
205
- ))}
206
- </div>
207
- )}
208
- </div>
209
- <Declarations
210
- id={id}
211
- type={C.DETACHABLE}
212
- declarations={declarations}
213
- features={features}
214
- bindings={bindings}
215
- />
216
- </>
217
- );
218
- };
219
-
220
- InputDeclarationsWrapper.defaultProps = {
221
- label: '',
222
- preferences: ['COLLECTED'],
223
- response: {},
224
- placeholder: '',
225
- readOnly: false,
226
- disabled: false,
227
- autoComplete: false,
228
- labelPosition: 'DEFAULT',
229
- mandatory: false,
230
- focused: false,
231
- declarations: [],
232
- features: [],
233
- bindings: {},
234
- management: false,
235
- style: {},
236
- validators: [],
237
- isInputNumber: false,
238
- };
239
-
240
- InputDeclarationsWrapper.propTypes = {
241
- id: PropTypes.string.isRequired,
242
- label: PropTypes.string,
243
- preferences: PropTypes.arrayOf(U.valueTypePropTypes),
244
- response: U.responsePropTypes,
245
- min: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
246
- max: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
247
- decimals: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
248
- placeholder: PropTypes.string,
249
- handleChange: PropTypes.func.isRequired,
250
- readOnly: PropTypes.bool,
251
- disabled: PropTypes.bool,
252
- maxLength: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
253
- autoComplete: PropTypes.bool,
254
- labelPosition: PropTypes.oneOf(['DEFAULT', 'TOP', 'BOTTOM', 'LEFT', 'RIGHT']),
255
- unitPosition: PropTypes.oneOf(['DEFAULT', 'BEFORE', 'AFTER']),
256
- mandatory: PropTypes.bool,
257
- focused: PropTypes.bool,
258
- declarations: U.declarationsPropTypes,
259
- features: PropTypes.arrayOf(PropTypes.string),
260
- bindings: PropTypes.object,
261
- management: PropTypes.bool,
262
- style: PropTypes.object,
263
- type: PropTypes.oneOf(['text', 'date', 'number', null]),
264
- roleType: PropTypes.oneOf([
265
- 'input',
266
- 'datepicker',
267
- 'input-number',
268
- 'textarea',
269
- ]),
270
- validators: PropTypes.arrayOf(PropTypes.func),
271
- isInputNumber: PropTypes.bool,
272
- };
273
-
274
- export default InputDeclarationsWrapper;
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Declarations from '../';
4
+ import { TooltipResponse } from '../../tooltip';
5
+ import * as U from '../../../utils/lib';
6
+ import * as C from '../../../constants';
7
+ import { interpret } from '../../../utils/to-expose';
8
+
9
+ const InputDeclarationsWrapper = ({
10
+ id,
11
+ label,
12
+ preferences,
13
+ response,
14
+ placeholder,
15
+ handleChange,
16
+ maxLength,
17
+ readOnly,
18
+ disabled,
19
+ autoComplete,
20
+ labelPosition,
21
+ mandatory,
22
+ declarations,
23
+ features,
24
+ bindings,
25
+ focused,
26
+ management,
27
+ style,
28
+ type,
29
+ roleType,
30
+ min,
31
+ max,
32
+ decimals,
33
+ unit,
34
+ unitPosition,
35
+ validators,
36
+ isInputNumber,
37
+ numberAsTextfield,
38
+ logFunction,
39
+ componentType,
40
+ inputMode,
41
+ }) => {
42
+ const inputRef = useRef();
43
+ const createEventFocus = (focusIn = true) =>
44
+ U.createObjectEvent(
45
+ `${roleType}-${id}`,
46
+ C.INPUT_CATEGORY,
47
+ focusIn ? C.EVENT_FOCUS_IN : C.EVENT_FOCUS_OUT,
48
+ U.getResponseName(response),
49
+ value
50
+ );
51
+ const [value, setValue] = useState(() =>
52
+ U.getResponseByPreference(preferences)(response)
53
+ );
54
+
55
+ const [messagesError, setMessagesError] = useState(
56
+ validators
57
+ .map((v) => v({ min, max, value, preferences, componentType, id }))
58
+ .filter((m) => m)
59
+ );
60
+
61
+ useEffect(() => {
62
+ if (focused) inputRef.current.focus();
63
+ }, [focused]);
64
+
65
+ // Assume we only want to handle enable external updates
66
+ // Don't need to check all value changes
67
+ useEffect(() => {
68
+ if (U.getResponseByPreference(preferences)(response) !== value)
69
+ setValue(U.getResponseByPreference(preferences)(response));
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ }, [response, preferences]);
72
+
73
+ const validate = (v) => {
74
+ setMessagesError(
75
+ validators
76
+ .map((v) => v({ min, max, value: v, preferences, componentType, id }))
77
+ .filter((m) => m)
78
+ );
79
+ };
80
+
81
+ useEffect(() => {
82
+ if (['InputNumber', 'Datepicker'].includes(componentType)) {
83
+ setMessagesError(
84
+ validators
85
+ .map((v) => v({ min, max, value, preferences, componentType, id }))
86
+ .filter((m) => m)
87
+ );
88
+ }
89
+ }, [
90
+ value,
91
+ min,
92
+ max,
93
+ validators,
94
+ isInputNumber,
95
+ id,
96
+ preferences,
97
+ componentType,
98
+ ]);
99
+
100
+ const handleChangeOnBlur = () => {
101
+ const initValue = U.getResponseByPreference(preferences)(response);
102
+ if (value !== initValue) {
103
+ const finalValue =
104
+ value && value.endsWith('.') ? value.replace('.', '') : value;
105
+ handleChange({
106
+ [U.getResponseName(response)]: finalValue,
107
+ });
108
+ if (U.isFunction(logFunction)) logFunction(createEventFocus(false));
109
+ if (value !== finalValue) setValue(finalValue);
110
+ }
111
+ };
112
+
113
+ const handleFocusIn = () => {
114
+ if (U.isFunction(logFunction)) logFunction(createEventFocus());
115
+ };
116
+
117
+ const Component = roleType === 'textarea' ? 'textarea' : 'input';
118
+
119
+ return (
120
+ <>
121
+ <Declarations
122
+ id={id}
123
+ type={C.BEFORE_QUESTION_TEXT}
124
+ declarations={declarations}
125
+ features={features}
126
+ bindings={bindings}
127
+ />
128
+ <div className={U.getLabelPositionClass(labelPosition)}>
129
+ {label && (
130
+ <label
131
+ htmlFor={`${roleType}-${id}`}
132
+ id={`${roleType}-label-${id}`}
133
+ className={`${mandatory ? 'mandatory' : ''}`}
134
+ >
135
+ {interpret(features, logFunction)(bindings)(label)}
136
+ {isInputNumber &&
137
+ unit &&
138
+ ['DEFAULT', 'BEFORE'].includes(unitPosition) && (
139
+ <span className="unit">{` (${unit})`}</span>
140
+ )}
141
+ </label>
142
+ )}
143
+ <Declarations
144
+ id={id}
145
+ type={C.AFTER_QUESTION_TEXT}
146
+ declarations={declarations}
147
+ features={features}
148
+ bindings={bindings}
149
+ />
150
+ <div className="field-container">
151
+ <div className={`${management ? 'field-with-tooltip' : 'field'}`}>
152
+ <Component
153
+ type={type}
154
+ id={`${roleType}-${id}`}
155
+ ref={inputRef}
156
+ value={value || ''}
157
+ min={min}
158
+ max={max}
159
+ step={decimals ? `${Math.pow(10, -decimals)}` : '0'}
160
+ placeholder={placeholder}
161
+ autoComplete={autoComplete ? 'on' : 'off'}
162
+ className={`${roleType}-lunatic ${
163
+ (isInputNumber || roleType === 'datepicker') &&
164
+ messagesError.length > 0
165
+ ? 'warning'
166
+ : ''
167
+ }`}
168
+ style={U.buildStyleObject(style)}
169
+ readOnly={readOnly}
170
+ disabled={disabled}
171
+ maxLength={maxLength || 524288}
172
+ required={mandatory}
173
+ aria-required={mandatory}
174
+ inputMode={numberAsTextfield ? inputMode : 'text'}
175
+ onChange={(e) => {
176
+ const v = e.target.value;
177
+ const vFormated =
178
+ isInputNumber && decimals ? v.replace(',', '.') : v;
179
+ const valueToFire = v === '' ? null : vFormated;
180
+ if (
181
+ decimals &&
182
+ v !== '' &&
183
+ !new RegExp(`^[0-9]+(\\.[0-9]{0,${decimals}})?$`).test(
184
+ valueToFire
185
+ )
186
+ ) {
187
+ e.preventDefault();
188
+ } else if (
189
+ (([null, ''].includes(v) && value.length > 0) ||
190
+ ([null, ''].includes(value) && v.length > 0)) &&
191
+ componentType !== 'Datepicker'
192
+ ) {
193
+ setValue(valueToFire);
194
+ handleChange({
195
+ [U.getResponseName(response)]: valueToFire,
196
+ });
197
+ } else if (
198
+ // Chrome
199
+ (Object.getPrototypeOf(e.nativeEvent).constructor.name ===
200
+ 'Event' &&
201
+ roleType !== 'datepicker') ||
202
+ // FF hack: impossible to handle arrow events
203
+ (Math.abs(v - value).toFixed(decimals) !==
204
+ Number.parseFloat(`${Math.pow(10, -decimals)}`).toFixed(
205
+ decimals
206
+ ) &&
207
+ isNaN(Number.parseInt(v, 10)) &&
208
+ isInputNumber)
209
+ ) {
210
+ setValue(valueToFire);
211
+ handleChange({
212
+ [U.getResponseName(response)]: valueToFire,
213
+ });
214
+ } else {
215
+ if (isInputNumber) {
216
+ if (
217
+ numberAsTextfield &&
218
+ v !== '' &&
219
+ !U.isNumberValid(v, decimals)
220
+ ) {
221
+ e.preventDefault();
222
+ e.stopPropagation();
223
+ return null;
224
+ } else validate(v);
225
+ }
226
+ if (management) setValue(valueToFire);
227
+ else setValue(valueToFire);
228
+ }
229
+ }}
230
+ onBlur={handleChangeOnBlur}
231
+ onFocus={handleFocusIn}
232
+ onKeyPress={(event) => {
233
+ if (decimals === 0 && !/[0-9]/.test(event.key)) {
234
+ event.preventDefault();
235
+ }
236
+ }}
237
+ />
238
+ {isInputNumber && unit && unitPosition === 'AFTER' && (
239
+ <span className="unit">{unit}</span>
240
+ )}
241
+ </div>
242
+ {management && (
243
+ <div className="tooltip">
244
+ <TooltipResponse
245
+ id={id}
246
+ response={U.buildLocalResponse(response, value)}
247
+ />
248
+ </div>
249
+ )}
250
+ </div>
251
+ {messagesError.length > 0 && (
252
+ <div className="lunatic-controls">
253
+ {messagesError.map(({ id, errorMessage }) => (
254
+ <div key={`control-${id}`} className="lunatic-control">
255
+ {errorMessage}
256
+ </div>
257
+ ))}
258
+ </div>
259
+ )}
260
+ </div>
261
+ <Declarations
262
+ id={id}
263
+ type={C.DETACHABLE}
264
+ declarations={declarations}
265
+ features={features}
266
+ bindings={bindings}
267
+ />
268
+ </>
269
+ );
270
+ };
271
+
272
+ InputDeclarationsWrapper.defaultProps = {
273
+ label: '',
274
+ preferences: ['COLLECTED'],
275
+ response: {},
276
+ placeholder: '',
277
+ readOnly: false,
278
+ disabled: false,
279
+ autoComplete: false,
280
+ labelPosition: 'DEFAULT',
281
+ mandatory: false,
282
+ focused: false,
283
+ declarations: [],
284
+ features: [],
285
+ bindings: {},
286
+ management: false,
287
+ style: {},
288
+ validators: [],
289
+ isInputNumber: false,
290
+ inputMode: 'text',
291
+ };
292
+
293
+ InputDeclarationsWrapper.propTypes = {
294
+ id: PropTypes.string.isRequired,
295
+ label: PropTypes.string,
296
+ preferences: PropTypes.arrayOf(U.valueTypePropTypes),
297
+ response: U.responsePropTypes,
298
+ min: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
299
+ max: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
300
+ decimals: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
301
+ placeholder: PropTypes.string,
302
+ handleChange: PropTypes.func.isRequired,
303
+ readOnly: PropTypes.bool,
304
+ disabled: PropTypes.bool,
305
+ maxLength: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
306
+ autoComplete: PropTypes.bool,
307
+ labelPosition: PropTypes.oneOf(['DEFAULT', 'TOP', 'BOTTOM', 'LEFT', 'RIGHT']),
308
+ unitPosition: PropTypes.oneOf(['DEFAULT', 'BEFORE', 'AFTER']),
309
+ mandatory: PropTypes.bool,
310
+ focused: PropTypes.bool,
311
+ declarations: U.declarationsPropTypes,
312
+ features: PropTypes.arrayOf(PropTypes.string),
313
+ bindings: PropTypes.object,
314
+ management: PropTypes.bool,
315
+ style: PropTypes.object,
316
+ type: PropTypes.oneOf(['text', 'date', 'number', null]),
317
+ roleType: PropTypes.oneOf([
318
+ 'input',
319
+ 'datepicker',
320
+ 'input-number',
321
+ 'textarea',
322
+ ]),
323
+ validators: PropTypes.arrayOf(PropTypes.func),
324
+ isInputNumber: PropTypes.bool,
325
+ inputMode: PropTypes.oneOf(['none', 'text', 'decimal', 'numeric']),
326
+ };
327
+
328
+ export default InputDeclarationsWrapper;