@inseefr/lunatic 0.3.1-experimental → 0.3.5-experimental

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/index.js +218 -259
  2. package/lib/index.js.map +1 -1
  3. package/package.json +2 -2
  4. package/src/components/component-wrapper/controls/validators/datepicker.js +25 -14
  5. package/src/components/component-wrapper/missing/component.js +37 -17
  6. package/src/components/datepicker/component.js +8 -12
  7. package/src/components/declarations/wrappers/input-declarations-wrapper.js +31 -9
  8. package/src/components/dropdown/commons/components/dropdown.js +21 -0
  9. package/src/components/dropdown/dropdown-edit/dropdown-edit.js +4 -1
  10. package/src/components/dropdown/dropdown-simple/dropdown.js +3 -1
  11. package/src/components/input/input-number.js +2 -1
  12. package/src/components/loop-constructor/block/index.js +1 -1
  13. package/src/components/loop-constructor/index.js +1 -1
  14. package/src/components/loop-constructor/roster/index.js +1 -1
  15. package/src/components/loop-constructor/wrapper/body-component.js +3 -0
  16. package/src/components/loop-constructor/wrapper/build-components.js +33 -33
  17. package/src/components/loop-constructor/wrapper/index.js +1 -1
  18. package/src/components/suggester/components/panel/option-container.js +1 -1
  19. package/src/components/suggester/components/suggester-content.js +42 -42
  20. package/src/components/suggester/components/suggester.js +43 -3
  21. package/src/components/suggester/idb-suggester.js +7 -1
  22. package/src/components/suggester/lunatic-suggester.js +1 -0
  23. package/src/components/suggester/suggester-wrapper.js +9 -3
  24. package/src/components/table/table.js +3 -1
  25. package/src/stories/loop-constructor/README.md +27 -27
  26. package/src/stories/loop-constructor/data-input-forced.json +64 -64
  27. package/src/stories/loop-constructor/data-input.json +100 -100
  28. package/src/stories/loop-constructor/data-loop-forced.json +66 -66
  29. package/src/stories/loop-constructor/data-loop-static-forced.json +66 -66
  30. package/src/stories/loop-constructor/data-loop-static.json +81 -81
  31. package/src/stories/loop-constructor/data-loop.json +81 -81
  32. package/src/stories/loop-constructor/data-roster-forced.json +68 -68
  33. package/src/stories/loop-constructor/data-roster.json +83 -83
  34. package/src/stories/loop-constructor/loop-constructor.stories.js +180 -180
  35. package/src/stories/questionnaire/arithmetic-management.json +47 -0
  36. package/src/stories/questionnaire/logement-queen.json +23389 -22705
  37. package/src/stories/questionnaire/logement-s2.json +46027 -44536
  38. package/src/stories/questionnaire/questionnaire.stories.js +12 -12
  39. package/src/stories/suggester/data.json +4 -1
  40. package/src/stories/suggester/suggester-workers.stories.js +4 -1
  41. package/src/stories/utils/orchestrator-split.js +119 -0
  42. package/src/stories/utils/orchestrator.js +4 -2
  43. package/src/tests/utils/to-expose/handler/results/res-input-edited.json +158 -158
  44. package/src/tests/utils/to-expose/state/state.spec.js +59 -59
  45. package/src/utils/lib/index.js +1 -0
  46. package/src/utils/lib/pagination/navigation/shared.js +5 -5
  47. package/src/utils/lib/splitting.js +142 -0
  48. package/src/utils/suggester-workers/commons-tokenizer/create-entity-tokenizer.js +4 -2
  49. package/src/utils/suggester-workers/commons-tokenizer/filters/{filter-accents-to-lower.js → filter-accents.js} +2 -2
  50. package/src/utils/suggester-workers/commons-tokenizer/filters/{filter-accents-to-lower.spec.js → filter-accents.spec.js} +1 -1
  51. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-synonyms.js +27 -1
  52. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-to-lower.js +10 -0
  53. package/src/utils/suggester-workers/commons-tokenizer/filters/filter-to-lower.spec.js +12 -0
  54. package/src/utils/suggester-workers/commons-tokenizer/index.js +1 -1
  55. package/src/utils/to-expose/handler.js +47 -28
  56. package/src/utils/to-expose/hooks/filter-components.js +106 -106
  57. package/src/utils/to-expose/hooks/index.js +2 -1
  58. package/src/utils/to-expose/hooks/lunatic-split.js +421 -0
  59. package/src/utils/to-expose/hooks/lunatic.js +23 -5
  60. package/src/utils/to-expose/index.js +11 -11
  61. package/src/utils/to-expose/state.js +23 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inseefr/lunatic",
3
- "version": "0.3.1-experimental",
3
+ "version": "0.3.5-experimental",
4
4
  "workersVersion": "0.2.1-experimental",
5
5
  "description": "Library of questionnaire components",
6
6
  "repository": {
@@ -50,7 +50,7 @@
50
50
  "library"
51
51
  ],
52
52
  "dependencies": {
53
- "@inseefr/trevas": "^0.1.10",
53
+ "@inseefr/trevas": "^0.1.12",
54
54
  "date-fns": "^2.25.0",
55
55
  "lodash.camelcase": "^4.3.0",
56
56
  "lodash.debounce": "^4.0.8",
@@ -13,21 +13,32 @@ const getMessage = (min, max, value) => {
13
13
  if (!value) {
14
14
  return undefined;
15
15
  }
16
- const dateFormat = 'dd/MM/yyyy';
16
+ const dateFormat = 'dd-MM-yyyy';
17
17
  const date = new Date(value);
18
- if (isNaN(Date.parse(min))) return undefined;
19
- if (isNaN(Date.parse(max))) return undefined;
20
- const minDate = new Date(min);
21
- const maxDate = new Date(max);
22
- const minDateAsString = minDate ? format(minDate, dateFormat) : '';
23
- const maxDateAsString = maxDate ? format(maxDate, dateFormat) : '';
24
- if (!min && isDef(max) && compareAsc(date, maxDate) > 0)
25
- return `La date doit être inférieure au ${maxDateAsString}`;
26
- else if (isDef(min) && !max && compareAsc(date, minDate) > 0)
27
- return `La date doit être supérieure au ${minDateAsString}`;
28
- else if (isDef(min) && isDef(max) && (date < minDate || date > maxDate))
29
- return `La date doit être comprise entre le ${minDateAsString} et le ${maxDateAsString}`;
18
+ if (isDef(min) && isDef(max)) {
19
+ const minDate = new Date(min);
20
+ const maxDate = new Date(max);
21
+ if (date < minDate || date > maxDate) {
22
+ const minDateAsString = minDate ? format(minDate, dateFormat) : '';
23
+ const maxDateAsString = maxDate ? format(maxDate, dateFormat) : '';
24
+ return `La date doit être comprise entre le ${minDateAsString} et le ${maxDateAsString}`;
25
+ }
26
+ }
27
+ if (isDef(min)) {
28
+ const minDate = new Date(min);
29
+ if (compareAsc(date, minDate) < 0) {
30
+ const minDateAsString = minDate ? format(minDate, dateFormat) : '';
31
+ return `La date doit être supérieure au ${minDateAsString}`;
32
+ }
33
+ }
34
+ if (isDef(max)) {
35
+ const maxDate = new Date(max);
36
+ if (compareAsc(date, maxDate) > 0) {
37
+ const maxDateAsString = maxDate ? format(maxDate, dateFormat) : '';
38
+ return `La date doit être inférieure au ${maxDateAsString}`;
39
+ }
40
+ }
30
41
  return undefined;
31
42
  };
32
43
 
33
- const isDef = (d) => d;
44
+ const isDef = (d) => !isNaN(Date.parse(d));
@@ -1,4 +1,4 @@
1
- import React, { useEffect } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import KeyboardEventHandler from 'react-keyboard-event-handler';
3
3
  import Button from '../../button';
4
4
  import * as U from '../../../utils/lib';
@@ -21,10 +21,33 @@ const Missing = ({ Component, props }) => {
21
21
  bindings,
22
22
  shortcut,
23
23
  componentType,
24
- missingLoopIteration,
24
+ paginatedLoop,
25
25
  } = props;
26
26
 
27
+ const missingResponseName = U.getResponseName(missingResponse);
27
28
  const buttonState = U.getResponseByPreference(preferences)(missingResponse);
29
+ const [oldMissingValue] = useState(() => buttonState);
30
+
31
+ const [bindingsForMissingStrategy, setBindingsForMissingStrategy] =
32
+ useState(null);
33
+
34
+ /**
35
+ * Sources split: use MissingStragy only if missingResponse has been updated
36
+ * Ensures that missingResponse is persisted when the source has to be changed
37
+ */
38
+ useEffect(() => {
39
+ const isSameValue = buttonState === oldMissingValue;
40
+ if (bindingsForMissingStrategy && !isSameValue) {
41
+ if (U.isFunction(missingStrategy))
42
+ missingStrategy(bindingsForMissingStrategy);
43
+ setBindingsForMissingStrategy(null);
44
+ }
45
+ }, [
46
+ bindingsForMissingStrategy,
47
+ missingStrategy,
48
+ buttonState,
49
+ oldMissingValue,
50
+ ]);
28
51
 
29
52
  useEffect(() => {
30
53
  if (
@@ -36,7 +59,7 @@ const Missing = ({ Component, props }) => {
36
59
  components,
37
60
  })
38
61
  ) {
39
- handleChange({ [U.getResponseName(missingResponse)]: null });
62
+ handleChange({ [missingResponseName]: null });
40
63
  }
41
64
  }, [
42
65
  buttonState,
@@ -46,7 +69,7 @@ const Missing = ({ Component, props }) => {
46
69
  responses,
47
70
  cells,
48
71
  components,
49
- missingResponse,
72
+ missingResponseName,
50
73
  ]);
51
74
 
52
75
  const getVarsToClean = () =>
@@ -62,10 +85,9 @@ const Missing = ({ Component, props }) => {
62
85
  if (!isSameValue) {
63
86
  const toClean = getVarsToClean();
64
87
  if (Object.keys(toClean)) {
65
- const { missingLoopIteration, currentPage } = props;
88
+ const { currentPage } = props;
66
89
  const currentIterationIndex = getCurrentIterationIndex({
67
90
  currentPage,
68
- missingLoopIteration,
69
91
  });
70
92
  handleChange(toClean);
71
93
  if (U.isFunction(missingStrategy)) {
@@ -81,20 +103,17 @@ const Missing = ({ Component, props }) => {
81
103
  fullBindings,
82
104
  toHandle,
83
105
  });
84
- missingStrategy(missingBindings);
106
+ setBindingsForMissingStrategy(missingBindings);
85
107
  }
86
108
  } else {
87
- if (U.isFunction(missingStrategy)) missingStrategy(bindings);
109
+ if (U.isFunction(missingStrategy))
110
+ setBindingsForMissingStrategy(bindings);
88
111
  }
89
- handleChange({ [U.getResponseName(missingResponse)]: value });
112
+ handleChange({ [missingResponseName]: value });
90
113
  }
91
114
  };
92
115
 
93
- if (
94
- componentType === 'Loop' ||
95
- missingLoopIteration ||
96
- missingLoopIteration === 0
97
- )
116
+ if ((componentType === 'Loop' && paginatedLoop) || !missingResponse)
98
117
  return <Component {...props} />;
99
118
 
100
119
  return (
@@ -111,6 +130,7 @@ const Missing = ({ Component, props }) => {
111
130
  <Button
112
131
  label="dont-know-button"
113
132
  value={dontKnowButton}
133
+ disabled={!missingResponseName || missingResponseName?.length === 0}
114
134
  onClick={onClick(U.DK)}
115
135
  />
116
136
  </span>
@@ -122,11 +142,13 @@ const Missing = ({ Component, props }) => {
122
142
  <Button
123
143
  label="refused-button"
124
144
  value={refusedButton}
145
+ disabled={!missingResponseName || missingResponseName?.length === 0}
125
146
  onClick={onClick(U.RF)}
126
147
  />
127
148
  </span>
128
149
  </div>
129
150
  {shortcut &&
151
+ missingResponseName?.length > 0 &&
130
152
  missingShortcut &&
131
153
  missingShortcut.dontKnow &&
132
154
  missingShortcut.refused && (
@@ -147,11 +169,9 @@ const Missing = ({ Component, props }) => {
147
169
  export default Missing;
148
170
 
149
171
  // TODO: make it recursive for Loop into Loop
150
- const getCurrentIterationIndex = ({ currentPage, missingLoopIteration }) => {
172
+ const getCurrentIterationIndex = ({ currentPage }) => {
151
173
  const { currentIteration } = U.splitPage(currentPage, 1);
152
174
  if (currentIteration) return currentIteration - 1;
153
- if (missingLoopIteration || missingLoopIteration === 0)
154
- return missingLoopIteration;
155
175
  return null;
156
176
  };
157
177
 
@@ -6,18 +6,14 @@ import { areEqual } from '../../utils/lib';
6
6
  import { getTypeControls } from '../component-wrapper/controls/validators';
7
7
  import './datepicker.scss';
8
8
 
9
- const Datepicker = (props) => {
10
- const { max } = props;
11
- return (
12
- <InputDeclarationsWrapper
13
- type="date"
14
- roleType="datepicker"
15
- {...props}
16
- validators={[getTypeControls]}
17
- max={max || '1979-12-31'}
18
- />
19
- );
20
- };
9
+ const Datepicker = (props) => (
10
+ <InputDeclarationsWrapper
11
+ type="date"
12
+ roleType="datepicker"
13
+ {...props}
14
+ validators={[getTypeControls]}
15
+ />
16
+ );
21
17
 
22
18
  Datepicker.defaultProps = {
23
19
  validators: [],
@@ -172,13 +172,25 @@ const InputDeclarationsWrapper = ({
172
172
  aria-required={mandatory}
173
173
  onChange={(e) => {
174
174
  const v = e.target.value;
175
+ const valueToFire = v === '' ? null : v;
176
+ const valueToFireForArrows =
177
+ Number.parseFloat(v).toFixed(decimals);
175
178
  if (
176
- ([null, ''].includes(v) && value.length > 0) ||
177
- ([null, ''].includes(value) && v.length > 0)
179
+ decimals &&
180
+ v !== '' &&
181
+ !new RegExp(`^[0-9]+(.[0-9]{1,${decimals}})?$`).test(
182
+ valueToFireForArrows
183
+ )
178
184
  ) {
179
- setValue(v);
185
+ e.preventDefault();
186
+ } else if (
187
+ (([null, ''].includes(v) && value.length > 0) ||
188
+ ([null, ''].includes(value) && v.length > 0)) &&
189
+ componentType !== 'Datepicker'
190
+ ) {
191
+ setValue(valueToFire);
180
192
  handleChange({
181
- [U.getResponseName(response)]: v,
193
+ [U.getResponseName(response)]: valueToFire,
182
194
  });
183
195
  } else if (
184
196
  // Chrome
@@ -186,11 +198,16 @@ const InputDeclarationsWrapper = ({
186
198
  'Event' &&
187
199
  roleType !== 'datepicker') ||
188
200
  // FF hack: impossible to handle arrow events
189
- (Math.abs(v - value) === 1 && isInputNumber)
201
+ (Math.abs(v - value).toFixed(decimals) !==
202
+ Number.parseFloat(`${Math.pow(10, -decimals)}`).toFixed(
203
+ decimals
204
+ ) &&
205
+ !Number.parseInt(v, 10) &&
206
+ isInputNumber)
190
207
  ) {
191
- setValue(v);
208
+ setValue(valueToFireForArrows);
192
209
  handleChange({
193
- [U.getResponseName(response)]: v,
210
+ [U.getResponseName(response)]: valueToFireForArrows,
194
211
  });
195
212
  } else {
196
213
  if (isInputNumber) {
@@ -204,12 +221,17 @@ const InputDeclarationsWrapper = ({
204
221
  return null;
205
222
  } else validate(v);
206
223
  }
207
- if (management) setValue(v);
208
- else setValue(v === '' ? null : v);
224
+ if (management) setValue(valueToFire);
225
+ else setValue(valueToFire);
209
226
  }
210
227
  }}
211
228
  onBlur={handleChangeOnBlur}
212
229
  onFocus={handleFocusIn}
230
+ onKeyPress={(event) => {
231
+ if (decimals === 0 && !/[0-9]/.test(event.key)) {
232
+ event.preventDefault();
233
+ }
234
+ }}
213
235
  />
214
236
  {isInputNumber && unit && unitPosition === 'AFTER' && (
215
237
  <span className="unit">{unit}</span>
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import classnames from 'classnames';
4
4
  import Label from './label';
5
5
  import * as U from '../../../../utils/lib';
6
+ import * as C from '../../../../constants';
6
7
  import * as CLEAN from '../cleaner-callbacks';
7
8
  import * as actions from '../actions';
8
9
  import DropdownField from './dropdown-field';
@@ -65,9 +66,19 @@ function Dropdown({
65
66
  state,
66
67
  dispatch,
67
68
  refs,
69
+ logFunction,
68
70
  }) {
69
71
  const { visible, focused, id, disabled } = state;
70
72
 
73
+ const createEventFocus = (focusIn = true) =>
74
+ U.createObjectEvent(
75
+ id,
76
+ C.INPUT_CATEGORY,
77
+ focusIn ? C.EVENT_FOCUS_IN : C.EVENT_FOCUS_OUT,
78
+ U.getResponseName(response),
79
+ valueFromProps
80
+ );
81
+
71
82
  CLEAN.add(id, function () {
72
83
  dispatch(actions.hidePanel());
73
84
  dispatch(actions.setFocused(false));
@@ -130,6 +141,16 @@ function Dropdown({
130
141
  [state, dispatch, onSelect]
131
142
  );
132
143
 
144
+ // log info when focus change
145
+ useEffect(() => {
146
+ if (id && focused && U.isFunction(logFunction))
147
+ logFunction(createEventFocus());
148
+ if (id && !focused && U.isFunction(logFunction))
149
+ logFunction(createEventFocus(false));
150
+
151
+ // eslint-disable-next-line react-hooks/exhaustive-deps
152
+ }, [focused, id]);
153
+
133
154
  return (
134
155
  <DropdownContainer
135
156
  className={classnames(className, U.getLabelPositionClass(labelPosition), {
@@ -37,6 +37,7 @@ const createOnSelect = (_, dispatch, onSelect) => (option) => {
37
37
  * @param {props}
38
38
  */
39
39
  function Dropdown({
40
+ id: initId,
40
41
  widthAuto,
41
42
  options = [],
42
43
  onSelect,
@@ -52,10 +53,11 @@ function Dropdown({
52
53
  disabled,
53
54
  focused: initFocused,
54
55
  DeclarationAfterLabel,
56
+ logFunction,
55
57
  }) {
56
58
  const [state, dispatch] = useReducer(reducer, {
57
59
  ...initial,
58
- id: `dropdown-${new Date().getMilliseconds()}`,
60
+ id: `dropdown-${initId || new Date().getMilliseconds()}`,
59
61
  disabled,
60
62
  focused: initFocused,
61
63
  });
@@ -98,6 +100,7 @@ function Dropdown({
98
100
  value={valueFromProps}
99
101
  zIndex={zIndex}
100
102
  management={management}
103
+ logFunction={logFunction}
101
104
  >
102
105
  <DeclarationAfterLabel />
103
106
  <span
@@ -26,11 +26,12 @@ const Dropdown = ({
26
26
  className,
27
27
  zIndex,
28
28
  DeclarationAfterLabel,
29
+ logFunction,
29
30
  }) => {
30
31
  const containerEl = useRef();
31
32
  const [state, dispatch] = useReducer(reducer, {
32
33
  ...initial,
33
- id: `dropdown-${initId}-${new Date().getMilliseconds()}`,
34
+ id: `dropdown-${initId || new Date().getMilliseconds()}`,
34
35
  disabled,
35
36
  focused: initFocused,
36
37
  });
@@ -59,6 +60,7 @@ const Dropdown = ({
59
60
  onSelect={onSelect_}
60
61
  value={value}
61
62
  zIndex={zIndex}
63
+ logFunction={logFunction}
62
64
  >
63
65
  <DeclarationAfterLabel />
64
66
  <span className={classnames('lunatic-dropdown-input', { focused })}>
@@ -6,11 +6,12 @@ import { areEqual } from '../../utils/lib';
6
6
  import { getTypeControls } from '../component-wrapper/controls/validators';
7
7
  import './input.scss';
8
8
 
9
- const InputNumber = ({ numberAsTextfield, ...props }) => (
9
+ const InputNumber = ({ numberAsTextfield, decimals, ...props }) => (
10
10
  <InputDeclarationsWrapper
11
11
  type={numberAsTextfield ? 'text' : 'number'}
12
12
  roleType="input"
13
13
  {...props}
14
+ decimals={decimals || 0}
14
15
  isInputNumber
15
16
  numberAsTextfield
16
17
  validators={[getTypeControls]}
@@ -1 +1 @@
1
- export { default } from './component';
1
+ export { default } from './component';
@@ -1 +1 @@
1
- export { default as RosterForLoop } from './roster';
1
+ export { default as RosterForLoop } from './roster';
@@ -1 +1 @@
1
- export { default } from './component';
1
+ export { default } from './component';
@@ -100,11 +100,14 @@ const BodyComponent = ({
100
100
  const Component = lunatic[componentType];
101
101
  const localBindings =
102
102
  U.buildBindingsForDeeperComponents(i)(bindings);
103
+ // ensure to have only N-1 missingResponse
104
+ const { missingResponse } = componentProps;
103
105
  return (
104
106
  <div className="block-component" key={`${id}-row-${i}`}>
105
107
  <Component
106
108
  {...otherProps}
107
109
  {...componentProps}
110
+ missingResponse={missingResponse}
108
111
  id={`${id}-row-${i}`}
109
112
  label={label}
110
113
  handleChange={(up) => {
@@ -1,33 +1,33 @@
1
- import * as C from '../../../constants';
2
-
3
- export const buildContentForLoopConstructor = ({ components, headers }) => {
4
- // Start hack to find interation number
5
- // Refactor if we have to handle complex components (vector, matrix)
6
- const iterations =
7
- components.find((c) => c.response).response.values[C.COLLECTED].length || 1;
8
- // End
9
- const initialArray = [...Array(iterations).keys()];
10
- const uiComponents = components.map((comp) => {
11
- const { response, ...other } = comp;
12
- if (!response) return initialArray.map(() => comp);
13
- // Handle reponses & cells components ?
14
- const { name, values } = response;
15
- return initialArray.map((rowNumber) => {
16
- const newValues = Object.entries(values).reduce(
17
- (acc, [key, value]) => ({
18
- ...acc,
19
- [key]: value ? value[rowNumber] : null,
20
- }),
21
- {}
22
- );
23
- return {
24
- ...other,
25
- response: { name, values: newValues },
26
- rowNumber,
27
- };
28
- });
29
- }, []);
30
- const transpose = (m) => m[0].map((_, i) => m.map((x) => x[i]));
31
- const rows = transpose(uiComponents);
32
- return headers ? [headers, ...rows] : rows;
33
- };
1
+ import * as C from '../../../constants';
2
+
3
+ export const buildContentForLoopConstructor = ({ components, headers }) => {
4
+ // Start hack to find interation number
5
+ // Refactor if we have to handle complex components (vector, matrix)
6
+ const iterations =
7
+ components.find((c) => c.response).response.values[C.COLLECTED].length || 1;
8
+ // End
9
+ const initialArray = [...Array(iterations).keys()];
10
+ const uiComponents = components.map((comp) => {
11
+ const { response, ...other } = comp;
12
+ if (!response) return initialArray.map(() => comp);
13
+ // Handle reponses & cells components ?
14
+ const { name, values } = response;
15
+ return initialArray.map((rowNumber) => {
16
+ const newValues = Object.entries(values).reduce(
17
+ (acc, [key, value]) => ({
18
+ ...acc,
19
+ [key]: value ? value[rowNumber] : null,
20
+ }),
21
+ {}
22
+ );
23
+ return {
24
+ ...other,
25
+ response: { name, values: newValues },
26
+ rowNumber,
27
+ };
28
+ });
29
+ }, []);
30
+ const transpose = (m) => m[0].map((_, i) => m.map((x) => x[i]));
31
+ const rows = transpose(uiComponents);
32
+ return headers ? [headers, ...rows] : rows;
33
+ };
@@ -1 +1 @@
1
- export { default } from './component';
1
+ export { default } from './component';
@@ -50,7 +50,7 @@ function OptionContainer({ children, index, selected }) {
50
50
  className={classnames('lunatic-suggester-option', { selected })}
51
51
  role="option"
52
52
  aria-selected={selected}
53
- onClick={onClick}
53
+ onMouseDown={onClick}
54
54
  ref={ref}
55
55
  >
56
56
  {children}
@@ -1,42 +1,42 @@
1
- import React, { useRef, useCallback } from 'react';
2
- import classnames from 'classnames';
3
- import useDocumentAddEventListener from '../../../utils/to-expose/hooks/use-document-add-event-listener';
4
-
5
- function SuggesterContent({
6
- children,
7
- id,
8
- focused,
9
- onFocus,
10
- onBlur,
11
- onKeyDown,
12
- }) {
13
- const ref = useRef();
14
- const onClick = useCallback(
15
- function (e) {
16
- const { current } = ref;
17
- if (!current.contains(e.target)) {
18
- onBlur();
19
- }
20
- },
21
- [ref, onBlur]
22
- );
23
-
24
- useDocumentAddEventListener('mousedown', onClick);
25
-
26
- return (
27
- <div
28
- className={classnames('lunatic-suggester', {
29
- focused,
30
- })}
31
- onFocus={onFocus}
32
- onKeyDown={onKeyDown}
33
- ref={ref}
34
- >
35
- <div className={classnames('lunatic-suggester-content', { focused })}>
36
- {children}
37
- </div>
38
- </div>
39
- );
40
- }
41
-
42
- export default SuggesterContent;
1
+ import React, { useRef, useCallback } from 'react';
2
+ import classnames from 'classnames';
3
+ import useDocumentAddEventListener from '../../../utils/to-expose/hooks/use-document-add-event-listener';
4
+
5
+ function SuggesterContent({
6
+ children,
7
+ id,
8
+ focused,
9
+ onFocus,
10
+ onBlur,
11
+ onKeyDown,
12
+ }) {
13
+ const ref = useRef();
14
+ const onClick = useCallback(
15
+ function (e) {
16
+ const { current } = ref;
17
+ if (!current.contains(e.target) && focused) {
18
+ onBlur();
19
+ }
20
+ },
21
+ [ref, focused, onBlur]
22
+ );
23
+
24
+ useDocumentAddEventListener('mousedown', onClick);
25
+
26
+ return (
27
+ <div
28
+ className={classnames('lunatic-suggester', {
29
+ focused,
30
+ })}
31
+ onFocus={onFocus}
32
+ onKeyDown={onKeyDown}
33
+ ref={ref}
34
+ >
35
+ <div className={classnames('lunatic-suggester-content', { focused })}>
36
+ {children}
37
+ </div>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ export default SuggesterContent;
@@ -1,4 +1,11 @@
1
- import React, { useCallback, useContext, useRef, useMemo } from 'react';
1
+ import React, {
2
+ useCallback,
3
+ useContext,
4
+ useRef,
5
+ useMemo,
6
+ useEffect,
7
+ useState,
8
+ } from 'react';
2
9
  import classnames from 'classnames';
3
10
  import { actions, SuggesterContext } from '../state-management';
4
11
  import SuggesterContent from './suggester-content';
@@ -6,6 +13,8 @@ import Selection from './selection';
6
13
  import Panel from './panel';
7
14
  import createOnKeyDownCallback from './create-on-keydown-callback';
8
15
  import Delete from './selection/delete';
16
+ import * as C from '../../../constants';
17
+ import * as U from '../../../utils/lib';
9
18
  import './suggester.scss';
10
19
 
11
20
  function Suggester({
@@ -16,23 +25,54 @@ function Suggester({
16
25
  labelRenderer,
17
26
  onSelect,
18
27
  value,
28
+ focused: initFocused,
29
+ response,
30
+ logFunction,
19
31
  }) {
20
32
  const inputEl = useRef();
21
33
  const [state, dispatch] = useContext(SuggesterContext);
22
34
  const { focused, id, messageError, search, disabled } = state;
23
35
 
36
+ const [init, setInit] = useState(false);
37
+
38
+ const createEventFocus = (focusIn = true) =>
39
+ U.createObjectEvent(
40
+ `suggester-${id}`,
41
+ C.INPUT_CATEGORY,
42
+ focusIn ? C.EVENT_FOCUS_IN : C.EVENT_FOCUS_OUT,
43
+ U.getResponseName(response),
44
+ value
45
+ );
46
+
24
47
  const onFocus = useCallback(
25
48
  function () {
26
- if (!disabled) {
49
+ if (!focused && !disabled) {
27
50
  if (inputEl.current !== document.activeElement) {
28
51
  }
29
52
  inputEl.current.focus();
30
53
  dispatch(actions.onFocus());
31
54
  }
32
55
  },
33
- [dispatch, disabled]
56
+ [disabled, dispatch, focused]
34
57
  );
35
58
 
59
+ // Handle focused props of Component
60
+ useEffect(() => {
61
+ if (!init && id) {
62
+ if (initFocused && !focused) onFocus();
63
+ setInit(true);
64
+ }
65
+ }, [focused, init, initFocused, onFocus, id]);
66
+
67
+ // log info when focus change
68
+ useEffect(() => {
69
+ if (init && id && focused && U.isFunction(logFunction))
70
+ logFunction(createEventFocus());
71
+ if (init && id && !focused && U.isFunction(logFunction))
72
+ logFunction(createEventFocus(false));
73
+ // eslint-disable-next-line react-hooks/exhaustive-deps
74
+ }, [focused, id, init]);
75
+
36
76
  const onDelete = useCallback(
37
77
  function () {
38
78
  dispatch(actions.onDeleteSearch());