@bbl-digital/snorre 3.0.2 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.js +183 -82
- package/esm/core/Autocomplete/hooks/useAutocomplete.js +236 -0
- package/esm/core/Autocomplete/index.d.js +1 -0
- package/esm/core/Autocomplete/index.js +38 -178
- package/esm/core/Autocomplete/styles.js +30 -17
- package/esm/core/DatepickerRange/YearMonthForm.js +56 -0
- package/esm/core/Radio/styles.js +4 -4
- package/lib/core/Autocomplete/hooks/useAutocomplete.d.ts +20 -0
- package/lib/core/Autocomplete/hooks/useAutocomplete.d.ts.map +1 -0
- package/lib/core/Autocomplete/hooks/useAutocomplete.js +236 -0
- package/lib/core/Autocomplete/index.d.js +1 -0
- package/lib/core/Autocomplete/index.d.ts +2 -74
- package/lib/core/Autocomplete/index.d.ts.map +1 -1
- package/lib/core/Autocomplete/index.js +38 -178
- package/lib/core/Autocomplete/styles.d.ts +17 -0
- package/lib/core/Autocomplete/styles.d.ts.map +1 -1
- package/lib/core/Autocomplete/styles.js +30 -17
- package/lib/core/DatepickerRange/YearMonthForm.d.ts +10 -0
- package/lib/core/DatepickerRange/YearMonthForm.d.ts.map +1 -0
- package/lib/core/DatepickerRange/YearMonthForm.js +56 -0
- package/lib/core/Radio/styles.d.ts.map +1 -1
- package/lib/core/Radio/styles.js +4 -4
- package/package.json +2 -1
- package/esm/enums/ModifierKey.js +0 -13
- package/lib/enums/ModifierKey.d.ts +0 -12
- package/lib/enums/ModifierKey.d.ts.map +0 -1
- package/lib/enums/ModifierKey.js +0 -13
@@ -0,0 +1,236 @@
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
2
|
+
import { matchSorter } from 'match-sorter';
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
4
|
+
import { useDebounce } from '../../utils/debounce';
|
5
|
+
|
6
|
+
const useAutocomplete = props => {
|
7
|
+
const [showValues, setShowValues] = useState(!!props.isOpen);
|
8
|
+
const [highlightedIndex, setHighlightedIndex] = useState(null);
|
9
|
+
const [value, setValueChanged] = useState(props.value ?? '');
|
10
|
+
const [inputDirty, setInputDirty] = useState(false);
|
11
|
+
const debouncedValue = useDebounce(value, props.debounceDelay ?? 0);
|
12
|
+
const [inputValues, setInputValues] = useState(props.values);
|
13
|
+
|
14
|
+
const handleValueClick = value => {
|
15
|
+
props.onSelectItem?.(value);
|
16
|
+
setShowValues(false);
|
17
|
+
};
|
18
|
+
|
19
|
+
const clearSelectedItem = () => handleValueClick({
|
20
|
+
[props.labelFromValues ?? 'label']: '',
|
21
|
+
[props.keyFromValues ?? 'key']: ''
|
22
|
+
});
|
23
|
+
|
24
|
+
const onInputChange = e => {
|
25
|
+
setInputDirty(true);
|
26
|
+
if (inputValues) setHighlightedIndex(0);
|
27
|
+
setValueChanged(e.target.value); // Should not use onChange whenever we have fuzzy search on
|
28
|
+
|
29
|
+
if (props.fuzzy) return;
|
30
|
+
if (props.onChange) props.onChange(e);
|
31
|
+
};
|
32
|
+
|
33
|
+
const handleOnInputClick = () => {
|
34
|
+
if (inputValues?.length) setHighlightedIndex(0);
|
35
|
+
setShowValues(true);
|
36
|
+
};
|
37
|
+
|
38
|
+
const onFuzzyBlur = e => {
|
39
|
+
// If the value of the input is changed, and does not match the value of the values array,
|
40
|
+
// we should revert back the input value to the original value
|
41
|
+
//What
|
42
|
+
// if (!e.target.value.length) clearSelectedItem()
|
43
|
+
if (e.target.value === props.value) return; // If target value is the same as a value from the values array, we should set the value for the user
|
44
|
+
|
45
|
+
if (inputValues?.length) {
|
46
|
+
const valueInInputValues = inputValues.find(item => item[props.labelFromValues ?? 'label'].length && item[props.labelFromValues ?? 'label'].toLowerCase() === e.target.value.toLowerCase());
|
47
|
+
|
48
|
+
if (valueInInputValues) {
|
49
|
+
const val = valueInInputValues[props.labelFromValues ?? 'label'];
|
50
|
+
setValueChanged(val);
|
51
|
+
handleValueClick(valueInInputValues);
|
52
|
+
return;
|
53
|
+
} // Otherwise we should return to the original input value
|
54
|
+
|
55
|
+
|
56
|
+
setValueChanged(props.value ?? '');
|
57
|
+
}
|
58
|
+
};
|
59
|
+
|
60
|
+
const renderedValues = useMemo(() => {
|
61
|
+
if (!props.values?.length) return [];
|
62
|
+
|
63
|
+
if (props.fuzzy) {
|
64
|
+
const fuzzyOptions = {
|
65
|
+
keys: [props.labelFromValues ?? 'label']
|
66
|
+
};
|
67
|
+
setShowValues(Boolean(value));
|
68
|
+
return matchSorter(props.values, value ?? '', fuzzyOptions);
|
69
|
+
}
|
70
|
+
|
71
|
+
return props.values;
|
72
|
+
}, [value]);
|
73
|
+
|
74
|
+
const handleEscape = () => {
|
75
|
+
setShowValues(false);
|
76
|
+
setHighlightedIndex(null);
|
77
|
+
};
|
78
|
+
|
79
|
+
const handleClear = () => {
|
80
|
+
clearSelectedItem();
|
81
|
+
handleEscape();
|
82
|
+
};
|
83
|
+
|
84
|
+
const handleEnter = e => {
|
85
|
+
e.preventDefault();
|
86
|
+
e.stopPropagation();
|
87
|
+
if (!showValues) return;
|
88
|
+
|
89
|
+
if (!inputValues?.length) {
|
90
|
+
// If there is no autocomplete value in the list, send object with just { value }
|
91
|
+
handleValueClick({
|
92
|
+
value
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
96
|
+
if (highlightedIndex === null || !inputValues?.length) {
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
|
100
|
+
const item = inputValues[highlightedIndex];
|
101
|
+
handleValueClick(item);
|
102
|
+
setShowValues(false);
|
103
|
+
};
|
104
|
+
|
105
|
+
const handleUp = e => {
|
106
|
+
e.preventDefault();
|
107
|
+
e.stopPropagation();
|
108
|
+
if (!showValues) return;
|
109
|
+
|
110
|
+
if (highlightedIndex === null || !inputValues?.length) {
|
111
|
+
return;
|
112
|
+
}
|
113
|
+
|
114
|
+
const newHighlightIndex = highlightedIndex - 1;
|
115
|
+
|
116
|
+
if (newHighlightIndex < 0) {
|
117
|
+
setHighlightedIndex(null);
|
118
|
+
setShowValues(false);
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
|
122
|
+
setHighlightedIndex(newHighlightIndex);
|
123
|
+
};
|
124
|
+
|
125
|
+
const handleDown = e => {
|
126
|
+
if (!showValues) return;
|
127
|
+
e.preventDefault();
|
128
|
+
e.stopPropagation();
|
129
|
+
|
130
|
+
if (highlightedIndex === null || !inputValues?.length) {
|
131
|
+
return;
|
132
|
+
}
|
133
|
+
|
134
|
+
const newHighlightIndex = highlightedIndex + 1;
|
135
|
+
|
136
|
+
if (newHighlightIndex === inputValues.length) {
|
137
|
+
setHighlightedIndex(null);
|
138
|
+
setShowValues(false);
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
|
142
|
+
setHighlightedIndex(newHighlightIndex);
|
143
|
+
};
|
144
|
+
|
145
|
+
const handleCustomOnKeyDown = e => {
|
146
|
+
const {
|
147
|
+
key
|
148
|
+
} = e;
|
149
|
+
|
150
|
+
if (key === 'Escape') {
|
151
|
+
handleEscape();
|
152
|
+
return;
|
153
|
+
}
|
154
|
+
|
155
|
+
props.onKeyDown?.(e);
|
156
|
+
}; // Makes it possible to navigate using keyboard shortcuts
|
157
|
+
|
158
|
+
|
159
|
+
const handleOnKeyDown = e => {
|
160
|
+
const {
|
161
|
+
key
|
162
|
+
} = e;
|
163
|
+
|
164
|
+
if (showValues && inputValues?.length) {
|
165
|
+
setHighlightedIndex(0);
|
166
|
+
} else {
|
167
|
+
setHighlightedIndex(null);
|
168
|
+
}
|
169
|
+
|
170
|
+
switch (key) {
|
171
|
+
case 'Enter':
|
172
|
+
handleEnter(e);
|
173
|
+
break;
|
174
|
+
|
175
|
+
case 'ArrowUp':
|
176
|
+
handleUp(e);
|
177
|
+
break;
|
178
|
+
|
179
|
+
case 'ArrowDown':
|
180
|
+
case 'Tab':
|
181
|
+
handleDown(e);
|
182
|
+
break;
|
183
|
+
|
184
|
+
case 'Escape':
|
185
|
+
handleEscape();
|
186
|
+
break;
|
187
|
+
|
188
|
+
default:
|
189
|
+
break;
|
190
|
+
}
|
191
|
+
}; // Handle debounce
|
192
|
+
|
193
|
+
|
194
|
+
useEffect(() => {
|
195
|
+
const handleDebounceChange = value => {
|
196
|
+
if (props.onDebounceChange && inputDirty) {
|
197
|
+
props.onDebounceChange(value);
|
198
|
+
}
|
199
|
+
};
|
200
|
+
|
201
|
+
handleDebounceChange(debouncedValue);
|
202
|
+
}, [debouncedValue]); // Change local open state if props.isOpen changes
|
203
|
+
|
204
|
+
useEffect(() => {
|
205
|
+
setShowValues(!!props.isOpen);
|
206
|
+
}, [props.isOpen]); // Update local values state if props.values changes
|
207
|
+
|
208
|
+
useEffect(() => {
|
209
|
+
setInputValues(props.values);
|
210
|
+
}, [props.values]); // Update local value if props.value changes
|
211
|
+
|
212
|
+
useEffect(() => {
|
213
|
+
const value = props.value ?? '';
|
214
|
+
setValueChanged(value);
|
215
|
+
}, [props.value]);
|
216
|
+
useEffect(() => {
|
217
|
+
if (!props.openCustomValueInputOnKeyPress) return;
|
218
|
+
if (value.length) setShowValues(true);
|
219
|
+
}, [value]);
|
220
|
+
return {
|
221
|
+
setShowValues,
|
222
|
+
handleClear,
|
223
|
+
handleValueClick,
|
224
|
+
onInputChange,
|
225
|
+
handleOnKeyDown,
|
226
|
+
handleCustomOnKeyDown,
|
227
|
+
handleOnInputClick,
|
228
|
+
onFuzzyBlur,
|
229
|
+
value,
|
230
|
+
highlightedIndex,
|
231
|
+
showValues,
|
232
|
+
renderedValues
|
233
|
+
};
|
234
|
+
};
|
235
|
+
|
236
|
+
export default useAutocomplete;
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -1,192 +1,44 @@
|
|
1
1
|
/** @jsxImportSource @emotion/react */
|
2
|
-
import React, { createRef
|
3
|
-
import { styles, ErrorMessage } from './styles';
|
2
|
+
import React, { createRef } from 'react';
|
3
|
+
import { styles, ErrorMessage, Clear } from './styles';
|
4
4
|
import { useEffect } from 'react';
|
5
|
-
import { useDebounce } from '../utils/debounce';
|
6
5
|
import IconErrorOutline from '../../icons/General/IconErrorOutline';
|
7
6
|
import Spinner from '../Spinner';
|
8
7
|
import useHandleOptionsHeight from './utils/useHandleOptionsHeight';
|
9
8
|
import Link from '../Link';
|
9
|
+
import IconClose from '../../icons/General/IconClose';
|
10
|
+
import useAutocomplete from './hooks/useAutocomplete';
|
10
11
|
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
11
12
|
import { jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
12
13
|
import { Fragment as _Fragment } from "@emotion/react/jsx-runtime";
|
13
14
|
const Autocomplete = /*#__PURE__*/React.forwardRef(({
|
14
|
-
onDebounceChange,
|
15
|
-
debounceDelay,
|
16
15
|
height,
|
17
16
|
css,
|
18
17
|
...props
|
19
18
|
}, ref) => {
|
19
|
+
const valuesRef = /*#__PURE__*/createRef();
|
20
20
|
const {
|
21
21
|
optionsHeight,
|
22
22
|
optionsRef
|
23
23
|
} = useHandleOptionsHeight();
|
24
|
-
const
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
const handleOnInputClick = () => {
|
39
|
-
if (props.values?.length) setHighlightedIndex(0);
|
40
|
-
setShowValues(true);
|
41
|
-
};
|
42
|
-
|
43
|
-
const handleValueClick = value => {
|
44
|
-
setShowValues(false);
|
45
|
-
props.onSelectItem?.(value);
|
46
|
-
};
|
47
|
-
|
48
|
-
const handleEnter = e => {
|
49
|
-
e.preventDefault();
|
50
|
-
e.stopPropagation();
|
51
|
-
if (!showValues) return;
|
52
|
-
|
53
|
-
if (!props.values?.length) {
|
54
|
-
// If there is no autocomplete value in the list, send object with just { value }
|
55
|
-
props.onSelectItem?.({
|
56
|
-
value
|
57
|
-
});
|
58
|
-
}
|
59
|
-
|
60
|
-
if (highlightedIndex === null || !props.values?.length) {
|
61
|
-
return;
|
62
|
-
}
|
63
|
-
|
64
|
-
const item = props.values[highlightedIndex];
|
65
|
-
props.onSelectItem?.(item);
|
66
|
-
setShowValues(false);
|
67
|
-
};
|
68
|
-
|
69
|
-
const handleUp = e => {
|
70
|
-
e.preventDefault();
|
71
|
-
e.stopPropagation();
|
72
|
-
if (!showValues) return;
|
73
|
-
|
74
|
-
if (highlightedIndex === null || !props.values?.length) {
|
75
|
-
return;
|
76
|
-
}
|
77
|
-
|
78
|
-
const newHighlightIndex = highlightedIndex - 1;
|
79
|
-
|
80
|
-
if (newHighlightIndex < 0) {
|
81
|
-
setHighlightedIndex(null);
|
82
|
-
setShowValues(false);
|
83
|
-
return;
|
84
|
-
}
|
85
|
-
|
86
|
-
setHighlightedIndex(newHighlightIndex);
|
87
|
-
};
|
88
|
-
|
89
|
-
const handleDown = e => {
|
90
|
-
if (!showValues) return;
|
91
|
-
e.preventDefault();
|
92
|
-
e.stopPropagation();
|
93
|
-
|
94
|
-
if (highlightedIndex === null || !props.values?.length) {
|
95
|
-
return;
|
96
|
-
}
|
97
|
-
|
98
|
-
const newHighlightIndex = highlightedIndex + 1;
|
99
|
-
|
100
|
-
if (newHighlightIndex === props.values.length) {
|
101
|
-
setHighlightedIndex(null);
|
102
|
-
setShowValues(false);
|
103
|
-
return;
|
104
|
-
}
|
105
|
-
|
106
|
-
setHighlightedIndex(newHighlightIndex);
|
107
|
-
};
|
108
|
-
|
109
|
-
const handleEscape = () => {
|
110
|
-
setShowValues(false);
|
111
|
-
setHighlightedIndex(null);
|
112
|
-
};
|
113
|
-
|
114
|
-
const handleCustomOnKeyDown = e => {
|
115
|
-
const {
|
116
|
-
key
|
117
|
-
} = e;
|
118
|
-
|
119
|
-
if (key === 'Escape') {
|
120
|
-
handleEscape();
|
121
|
-
return;
|
122
|
-
}
|
123
|
-
|
124
|
-
props.onKeyDown?.(e);
|
125
|
-
};
|
126
|
-
|
127
|
-
const handleOnKeyDown = e => {
|
128
|
-
const {
|
129
|
-
key
|
130
|
-
} = e;
|
131
|
-
|
132
|
-
if (showValues && props.values?.length) {
|
133
|
-
setHighlightedIndex(0);
|
134
|
-
} else {
|
135
|
-
setHighlightedIndex(null);
|
136
|
-
}
|
137
|
-
|
138
|
-
switch (key) {
|
139
|
-
case 'Enter':
|
140
|
-
handleEnter(e);
|
141
|
-
break;
|
142
|
-
|
143
|
-
case 'ArrowUp':
|
144
|
-
handleUp(e);
|
145
|
-
break;
|
146
|
-
|
147
|
-
case 'ArrowDown':
|
148
|
-
case 'Tab':
|
149
|
-
handleDown(e);
|
150
|
-
break;
|
151
|
-
|
152
|
-
case 'Escape':
|
153
|
-
handleEscape();
|
154
|
-
break;
|
155
|
-
|
156
|
-
default:
|
157
|
-
break;
|
158
|
-
}
|
159
|
-
}; // Handle debounce
|
160
|
-
|
161
|
-
|
162
|
-
useEffect(() => {
|
163
|
-
const handleDebounceChange = value => {
|
164
|
-
if (onDebounceChange && inputDirty) {
|
165
|
-
onDebounceChange(value);
|
166
|
-
}
|
167
|
-
};
|
168
|
-
|
169
|
-
handleDebounceChange(debouncedValue); // eslint-disable-next-line react-hooks/exhaustive-deps
|
170
|
-
}, [debouncedValue]);
|
171
|
-
useEffect(() => {
|
172
|
-
setShowValues(!!props.isOpen);
|
173
|
-
}, [props.isOpen]);
|
174
|
-
useEffect(() => {
|
175
|
-
const value = props.value ? props.value : '';
|
176
|
-
setValueChanged(value);
|
177
|
-
}, [props.value]);
|
178
|
-
useEffect(() => {
|
179
|
-
if (!props.openCustomValueInputOnKeyPress) return;
|
180
|
-
if (value.length) setShowValues(true); // eslint-disable-next-line react-hooks/exhaustive-deps
|
181
|
-
}, [value]);
|
24
|
+
const {
|
25
|
+
value,
|
26
|
+
highlightedIndex,
|
27
|
+
showValues,
|
28
|
+
renderedValues,
|
29
|
+
setShowValues,
|
30
|
+
handleClear,
|
31
|
+
handleOnInputClick,
|
32
|
+
handleCustomOnKeyDown,
|
33
|
+
handleOnKeyDown,
|
34
|
+
handleValueClick,
|
35
|
+
onInputChange,
|
36
|
+
onFuzzyBlur
|
37
|
+
} = useAutocomplete(props);
|
182
38
|
useEffect(() => {
|
183
39
|
const handleClickOutside = e => {
|
184
40
|
const node = valuesRef.current;
|
185
|
-
|
186
|
-
if (node && node.contains(e.target)) {
|
187
|
-
return;
|
188
|
-
}
|
189
|
-
|
41
|
+
if (node && node.contains(e.target)) return;
|
190
42
|
setShowValues(false);
|
191
43
|
};
|
192
44
|
|
@@ -198,7 +50,7 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(({
|
|
198
50
|
|
199
51
|
return () => {
|
200
52
|
document.removeEventListener('mousedown', handleClickOutside);
|
201
|
-
};
|
53
|
+
}; // eslint-disable-next-line react-hooks/exhaustive-deps
|
202
54
|
}, [showValues, valuesRef]);
|
203
55
|
return _jsxs(_Fragment, {
|
204
56
|
children: [_jsxs("label", {
|
@@ -224,7 +76,7 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(({
|
|
224
76
|
value: value,
|
225
77
|
disabled: props.disabled,
|
226
78
|
autoFocus: props.focus,
|
227
|
-
onBlur: props.onBlur,
|
79
|
+
onBlur: props.fuzzy ? onFuzzyBlur : props.onBlur,
|
228
80
|
onFocus: props.onFocus,
|
229
81
|
onChange: onInputChange,
|
230
82
|
onKeyDown: props.onKeyDown ? handleCustomOnKeyDown : handleOnKeyDown,
|
@@ -234,6 +86,12 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(({
|
|
234
86
|
autoComplete: "off",
|
235
87
|
css: theme => [props.disabled && styles.disabled(theme)],
|
236
88
|
children: React.Children.map(props.children, child => child || null)
|
89
|
+
}), props.clear && _jsx(Clear, {
|
90
|
+
nostyle: true,
|
91
|
+
onClick: handleClear,
|
92
|
+
children: _jsx(IconClose, {
|
93
|
+
size: 14
|
94
|
+
})
|
237
95
|
}), props.loading && _jsx(Spinner, {
|
238
96
|
size: 14
|
239
97
|
}), props.actions && _jsx("div", {
|
@@ -253,17 +111,19 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(({
|
|
253
111
|
children: props.invalidMessage
|
254
112
|
})]
|
255
113
|
})]
|
256
|
-
}), (props.values?.length || props.renderCustomValueInput) && !props.loading && showValues && _jsxs("div", {
|
114
|
+
}), (Boolean(props.values?.length) || props.renderCustomValueInput) && !props.loading && showValues && _jsxs("div", {
|
257
115
|
ref: valuesRef,
|
258
116
|
css: () => [!props.renderCustomValueInput && styles.listWrapper, props.dynamicallyPlaceInput && Boolean(optionsHeight) && styles.listWrapperTopPosition(optionsHeight), props.dynamicallyPlaceInput && Boolean(optionsHeight) && props.invalidMessage && styles.listWrapperTopPosition(optionsHeight + 30), props.inputValuesMaxWidth && styles.listWrapperMaxWidth(props.inputValuesMaxWidth)],
|
259
|
-
children: [
|
117
|
+
children: [Boolean(renderedValues?.length) && _jsx("ul", {
|
260
118
|
css: theme => [styles.list(theme)],
|
261
|
-
children:
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
119
|
+
children: renderedValues.map((item, index) => {
|
120
|
+
return _jsx("li", {
|
121
|
+
tabIndex: 0,
|
122
|
+
css: index === highlightedIndex ? styles.highlightedItem : null,
|
123
|
+
onClick: () => handleValueClick(item),
|
124
|
+
children: props.labelFromValues ? item[props.labelFromValues] : item.label
|
125
|
+
}, props.keyFromValues ? item[props.keyFromValues] : item.key);
|
126
|
+
})
|
267
127
|
}), props.renderCustomValueInput && _jsx("div", {
|
268
128
|
children: props.renderCustomValueInput
|
269
129
|
})]
|