@reykjavik/hanna-react 0.10.157 → 0.10.159

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/CHANGELOG.md CHANGED
@@ -4,12 +4,21 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
- ## 0.10.157
7
+ ## 0.10.159
8
+
9
+ _2025-10-09_
10
+
11
+ - `FileUpload`:
12
+ - feat: Add prop `unstable_confirmReplace` for multi-upload name-conflicts
13
+
14
+ ## 0.10.157 – 0.10.158
8
15
 
9
16
  _2025-09-16_
10
17
 
11
18
  - `Multiselect`
19
+ - feat: Add prop `onDropdown` prop
12
20
  - fix: Sync `.checked` prop of keyboard-toggled `input`s with visual state
21
+ - fix: Scope "global" keyboard event listener to the component's element
13
22
  - `Selectbox`:
14
23
  - fix: Remove stray `modifier` prop from `SelectboxProps`
15
24
  - docs: Add JSDoc comments for `FormfieldProps.renderInput` parameters
@@ -21,8 +21,6 @@ export declare const formatBytes: (bytes: number, lang?: string, decimals?: numb
21
21
  * Figures out how to handle adding files to a FileInput
22
22
  * Which files to retaine, which too delete, and
23
23
  * what the updated fileList should look like.
24
- *
25
- *
26
24
  */
27
25
  export declare const getFileListUpdate: (oldFileList: Array<File>, added: Array<File>, replaceMode: boolean) => {
28
26
  fileList: File[];
@@ -46,8 +46,6 @@ exports.formatBytes = formatBytes;
46
46
  * Figures out how to handle adding files to a FileInput
47
47
  * Which files to retaine, which too delete, and
48
48
  * what the updated fileList should look like.
49
- *
50
- *
51
49
  */
52
50
  const getFileListUpdate = (oldFileList, added,
53
51
  /**
package/FileInput.d.ts CHANGED
@@ -23,7 +23,8 @@ export type FileInputProps = FormFieldWrappingProps & {
23
23
  onFilesUpdated?: (
24
24
  /** Updated, full list of Files. */
25
25
  files: Array<File>,
26
- /** Information about which Files were added or removed during with this update.
26
+ /**
27
+ * Information about which Files were added or removed during this update.
27
28
  *
28
29
  * NOTE: When a diff contains both added and deleted files, this indicates a
29
30
  * name-conflict occurred — i.e. one of the added files has a name that
@@ -36,6 +37,12 @@ export type FileInputProps = FormFieldWrappingProps & {
36
37
  deleted?: Array<File>;
37
38
  added?: Array<File>;
38
39
  }) => void;
40
+ /**
41
+ * Confirm replacing existing (name-conflicting) files in multi-upload mode
42
+ *
43
+ * This feature is "unstable" because its behavior might change in the future.
44
+ */
45
+ unstable_confirmReplace?: boolean;
39
46
  onFilesRejected?: (rejectedFiles: Array<File>) => void;
40
47
  name?: string;
41
48
  value?: ReadonlyArray<File>;
package/FileInput.js CHANGED
@@ -29,6 +29,11 @@ const defaultDropzoneText = {
29
29
  react_1.default.createElement("strong", null, "kliknij"),
30
30
  " by wybra\u0107.")),
31
31
  };
32
+ const replaceFileTest = {
33
+ is: 'Yfirskrifa núverandi skrá',
34
+ en: 'Replace existing file',
35
+ pl: 'Zastąp istniejący plik',
36
+ };
32
37
  const defaultOnFilesRejected = (rejectedFiles) => {
33
38
  window.alert(`Error:\n${rejectedFiles
34
39
  .map((elm) => {
@@ -49,7 +54,7 @@ const FileInput = (props) => {
49
54
  const _d = (0, FormField_js_1.groupFormFieldWrapperProps)(props), { dropzoneProps, // eslint-disable-line deprecation/deprecation
50
55
  multiple = (_b = (_a = props.dropzoneProps) === null || _a === void 0 ? void 0 : _a.multiple) !== null && _b !== void 0 ? _b : true, // eslint-disable-line deprecation/deprecation
51
56
  accept = (_c = props.dropzoneProps) === null || _c === void 0 ? void 0 : _c.accept, // eslint-disable-line deprecation/deprecation
52
- dropzoneText = defaultDropzoneText[lang](), removeFileText = defaultRemoveFileText[lang], FileList = _FileInputFileList_js_1.DefaultFileList, onFilesUpdated = () => undefined, onFilesRejected, showFileSize, showImagePreviews, value = [], fieldWrapperProps } = _d, inputElementProps = tslib_1.__rest(_d, ["dropzoneProps", "multiple", "accept", "dropzoneText", "removeFileText", "FileList", "onFilesUpdated", "onFilesRejected", "showFileSize", "showImagePreviews", "value", "fieldWrapperProps"]);
57
+ dropzoneText = defaultDropzoneText[lang](), unstable_confirmReplace, removeFileText = defaultRemoveFileText[lang], FileList = _FileInputFileList_js_1.DefaultFileList, onFilesUpdated = () => undefined, onFilesRejected, showFileSize, showImagePreviews, value = [], fieldWrapperProps } = _d, inputElementProps = tslib_1.__rest(_d, ["dropzoneProps", "multiple", "accept", "dropzoneText", "unstable_confirmReplace", "removeFileText", "FileList", "onFilesUpdated", "onFilesRejected", "showFileSize", "showImagePreviews", "value", "fieldWrapperProps"]);
53
58
  const domid = (0, useDomid_js_1.useDomid)(props.id);
54
59
  const fileInputWrapper = (0, react_1.useRef)(null);
55
60
  const fileInput = (0, react_1.useRef)(null);
@@ -113,14 +118,24 @@ const FileInput = (props) => {
113
118
  };
114
119
  const addFiles = (added) => {
115
120
  const { fileList, diff } = (0, _FileInput_utils_js_1.getFileListUpdate)(files, added, !multiple);
121
+ let updatedFileList = fileList;
122
+ if (diff.deleted && multiple && props.unstable_confirmReplace) {
123
+ diff.deleted = diff.deleted.filter((file) => window.confirm(`${replaceFileTest} "${file.name}"?`));
124
+ // When unstable_confirmReplace becomes stable, we should stop returning
125
+ // `fileList` from getFileListUpdate and just always do this thing.
126
+ // That change will require changing the tests too, so let's wait.
127
+ updatedFileList = files
128
+ .filter((file) => !diff.deleted.includes(file))
129
+ .concat(added);
130
+ }
116
131
  if (fileInput.current) {
117
- fileInput.current.files = arrayToFileList(fileList);
132
+ fileInput.current.files = arrayToFileList(updatedFileList);
118
133
  }
119
134
  if (inputRef.current) {
120
135
  // Empty on every add
121
136
  inputRef.current.files = arrayToFileList([]);
122
137
  }
123
- onFilesUpdated(fileList, diff);
138
+ onFilesUpdated(updatedFileList, diff);
124
139
  };
125
140
  return (react_1.default.createElement(FormField_js_1.default, Object.assign({ extraClassName: (0, hanna_utils_1.modifiedClass)('FileInput', [multiple && 'multi']) }, fieldWrapperProps, { id: `${domid}-fake`, LabelTag: "h4", renderInput: (className, inputProps /* , addFocusProps */) => {
126
141
  return (react_1.default.createElement("div", { className: className.control, ref: fileInputWrapper },
package/Multiselect.d.ts CHANGED
@@ -55,6 +55,8 @@ export type MultiselectProps = TogglerGroupFieldProps<string, {
55
55
  forceSearchable?: boolean;
56
56
  texts?: MultiselectI18n;
57
57
  lang?: HannaLang;
58
+ /** Fires whenever the dropdown menu is opened or closed */
59
+ onDropdown?: (isOpen: boolean) => void;
58
60
  };
59
61
  export declare const Multiselect: {
60
62
  (props: MultiselectProps): JSX.Element;
package/Multiselect.js CHANGED
@@ -58,7 +58,7 @@ const defaultTexts = {
58
58
  },
59
59
  };
60
60
  const Multiselect = (props) => {
61
- const { onSelected, options: _options, disabled: _disabled, readOnly } = props;
61
+ const { onSelected, onDropdown, options: _options, disabled: _disabled, readOnly, } = props;
62
62
  const disabled = _disabled === true;
63
63
  const disableds = !disabled && _disabled;
64
64
  const name = (0, useDomid_js_1.useDomid)(props.name);
@@ -80,6 +80,9 @@ const Multiselect = (props) => {
80
80
  setSearchQuery('');
81
81
  setActiveItemIndex(-1);
82
82
  }
83
+ if (onDropdown && newIsOpen !== isOpen) {
84
+ onDropdown(newIsOpen);
85
+ }
83
86
  return newIsOpen;
84
87
  });
85
88
  };
@@ -131,59 +134,53 @@ const Multiselect = (props) => {
131
134
  toggleOpen(activeItemIndex > -1 ? true : !isOpen);
132
135
  }
133
136
  };
134
- // When the dropdown is open, add keydown handlers
135
- (0, react_1.useEffect)(() => {
136
- if (!isOpen) {
137
+ const handleWrapperKeyDown = (e) => {
138
+ var _a;
139
+ if (!isOpen || !((_a = inputWrapperRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
137
140
  return;
138
141
  }
139
- const handleKeyDown = (e) => {
140
- const inputElm = inputRef.current;
141
- if (e.key === 'ArrowUp') {
142
- e.preventDefault();
143
- inputElm.focus();
144
- setActiveItemIndex((prevIndex) => prevIndex === 0 ? filteredOptions.length - 1 : prevIndex - 1);
145
- }
146
- else if (e.key === 'ArrowDown') {
147
- e.preventDefault();
148
- inputElm.focus();
149
- setActiveItemIndex((prevIndex) => prevIndex === filteredOptions.length - 1 ? 0 : prevIndex + 1);
142
+ const inputElm = inputRef.current;
143
+ if (e.key === 'ArrowUp') {
144
+ e.preventDefault();
145
+ inputElm.focus();
146
+ setActiveItemIndex((prevIndex) => prevIndex === 0 ? filteredOptions.length - 1 : prevIndex - 1);
147
+ }
148
+ else if (e.key === 'ArrowDown') {
149
+ e.preventDefault();
150
+ inputElm.focus();
151
+ setActiveItemIndex((prevIndex) => prevIndex === filteredOptions.length - 1 ? 0 : prevIndex + 1);
152
+ }
153
+ else if (e.key === 'Escape') {
154
+ e.preventDefault();
155
+ inputElm.blur();
156
+ inputElm.focus();
157
+ toggleOpen(false);
158
+ }
159
+ else if (e.key === 'Enter' || e.key === ' ') {
160
+ if (e.target.closest('.Multiselect__currentvalues')) {
161
+ return;
150
162
  }
151
- else if (e.key === 'Escape') {
163
+ const focusInRange = activeItemIndex >= 0 && activeItemIndex < filteredOptions.length;
164
+ if (focusInRange) {
152
165
  e.preventDefault();
153
- inputElm.blur();
154
- inputElm.focus();
155
- toggleOpen(false);
156
- }
157
- else if (e.key === 'Enter' || e.key === ' ') {
158
- if (e.target.closest('.Multiselect__currentvalues')) {
159
- return;
160
- }
161
- const focusInRange = activeItemIndex >= 0 && activeItemIndex < filteredOptions.length;
162
- if (focusInRange) {
163
- e.preventDefault();
164
- const selItem = filteredOptions[activeItemIndex];
165
- if (selItem) {
166
- // Manually toggle the checkbox, to ensure that uncontrolled
167
- // components (e.g. screen readers) are in sync with the visual state.
168
- let input;
169
- inputWrapperRef
170
- .current.querySelectorAll(`input[type="checkbox"]`)
171
- .forEach((elm) => {
172
- if (elm.value === selItem.value) {
173
- input = elm;
174
- }
175
- });
176
- input.checked = !input.checked;
177
- handleCheckboxSelection(selItem);
178
- }
166
+ const selItem = filteredOptions[activeItemIndex];
167
+ if (selItem) {
168
+ // Manually toggle the checkbox, to ensure that uncontrolled
169
+ // components (e.g. screen readers) are in sync with the visual state.
170
+ let input;
171
+ e.currentTarget
172
+ .querySelectorAll(`input[type="checkbox"]`)
173
+ .forEach((elm) => {
174
+ if (elm.value === selItem.value) {
175
+ input = elm;
176
+ }
177
+ });
178
+ input.checked = !input.checked;
179
+ handleCheckboxSelection(selItem);
179
180
  }
180
181
  }
181
- };
182
- document.addEventListener('keydown', handleKeyDown);
183
- return () => {
184
- document.removeEventListener('keydown', handleKeyDown);
185
- };
186
- }, [activeItemIndex, filteredOptions, isOpen, handleCheckboxSelection, inputRef]);
182
+ }
183
+ };
187
184
  // Auto-close the dropdown when focus has left the building
188
185
  (0, react_1.useEffect)(() => {
189
186
  const wrapperDiv = inputWrapperRef.current;
@@ -211,7 +208,7 @@ const Multiselect = (props) => {
211
208
  }, [activeItemIndex]);
212
209
  return (react_1.default.createElement(FormField_js_1.default, Object.assign({ extraClassName: (0, hanna_utils_1.modifiedClass)('Multiselect', props.nowrap && 'nowrap'), group: "inputlike", filled: filled, empty: empty }, (0, FormField_js_1.getFormFieldWrapperProps)(props), { renderInput: (className, inputProps, addFocusProps, isBrowser) => {
213
210
  const { id } = inputProps;
214
- return (react_1.default.createElement("div", Object.assign({ className: (0, hanna_utils_1.modifiedClass)('Multiselect__input', [isOpen && 'open'], className.input) }, addFocusProps(), { "data-sprinkled": isBrowser, ref: inputWrapperRef }),
211
+ return (react_1.default.createElement("div", Object.assign({ className: (0, hanna_utils_1.modifiedClass)('Multiselect__input', [isOpen && 'open'], className.input) }, addFocusProps(), { "data-sprinkled": isBrowser, ref: inputWrapperRef, onKeyDown: handleWrapperKeyDown }),
215
212
  !isBrowser ? null : isSearchable ? (react_1.default.createElement("input", { className: "Multiselect__search", id: `toggler:${id}`, "aria-label": texts.search, "aria-controls": id, "data-expanded": isOpen || undefined, onChange: handleInputChange, onKeyDown: handleInputKeyDown, onClick: () => toggleOpen(), value: searchQuery,
216
213
  // onFocus={handleInputFocus}
217
214
  placeholder: placeholderText, disabled: disabled, ref: inputRef })) : (react_1.default.createElement("button", { className: "Multiselect__toggler", id: `toggler:${id}`, type: "button", "aria-label": texts.buttonShow, "aria-controls": id, "aria-expanded": isOpen, onClick: () => toggleOpen(), disabled: disabled,
@@ -21,8 +21,6 @@ export declare const formatBytes: (bytes: number, lang?: string, decimals?: numb
21
21
  * Figures out how to handle adding files to a FileInput
22
22
  * Which files to retaine, which too delete, and
23
23
  * what the updated fileList should look like.
24
- *
25
- *
26
24
  */
27
25
  export declare const getFileListUpdate: (oldFileList: Array<File>, added: Array<File>, replaceMode: boolean) => {
28
26
  fileList: File[];
@@ -40,8 +40,6 @@ export const formatBytes = (bytes, lang = 'is', decimals = 2) => {
40
40
  * Figures out how to handle adding files to a FileInput
41
41
  * Which files to retaine, which too delete, and
42
42
  * what the updated fileList should look like.
43
- *
44
- *
45
43
  */
46
44
  export const getFileListUpdate = (oldFileList, added,
47
45
  /**
@@ -23,7 +23,8 @@ export type FileInputProps = FormFieldWrappingProps & {
23
23
  onFilesUpdated?: (
24
24
  /** Updated, full list of Files. */
25
25
  files: Array<File>,
26
- /** Information about which Files were added or removed during with this update.
26
+ /**
27
+ * Information about which Files were added or removed during this update.
27
28
  *
28
29
  * NOTE: When a diff contains both added and deleted files, this indicates a
29
30
  * name-conflict occurred — i.e. one of the added files has a name that
@@ -36,6 +37,12 @@ export type FileInputProps = FormFieldWrappingProps & {
36
37
  deleted?: Array<File>;
37
38
  added?: Array<File>;
38
39
  }) => void;
40
+ /**
41
+ * Confirm replacing existing (name-conflicting) files in multi-upload mode
42
+ *
43
+ * This feature is "unstable" because its behavior might change in the future.
44
+ */
45
+ unstable_confirmReplace?: boolean;
39
46
  onFilesRejected?: (rejectedFiles: Array<File>) => void;
40
47
  name?: string;
41
48
  value?: ReadonlyArray<File>;
package/esm/FileInput.js CHANGED
@@ -26,6 +26,11 @@ const defaultDropzoneText = {
26
26
  React.createElement("strong", null, "kliknij"),
27
27
  " by wybra\u0107.")),
28
28
  };
29
+ const replaceFileTest = {
30
+ is: 'Yfirskrifa núverandi skrá',
31
+ en: 'Replace existing file',
32
+ pl: 'Zastąp istniejący plik',
33
+ };
29
34
  const defaultOnFilesRejected = (rejectedFiles) => {
30
35
  window.alert(`Error:\n${rejectedFiles
31
36
  .map((elm) => {
@@ -46,7 +51,7 @@ export const FileInput = (props) => {
46
51
  const _d = groupFormFieldWrapperProps(props), { dropzoneProps, // eslint-disable-line deprecation/deprecation
47
52
  multiple = (_b = (_a = props.dropzoneProps) === null || _a === void 0 ? void 0 : _a.multiple) !== null && _b !== void 0 ? _b : true, // eslint-disable-line deprecation/deprecation
48
53
  accept = (_c = props.dropzoneProps) === null || _c === void 0 ? void 0 : _c.accept, // eslint-disable-line deprecation/deprecation
49
- dropzoneText = defaultDropzoneText[lang](), removeFileText = defaultRemoveFileText[lang], FileList = DefaultFileList, onFilesUpdated = () => undefined, onFilesRejected, showFileSize, showImagePreviews, value = [], fieldWrapperProps } = _d, inputElementProps = __rest(_d, ["dropzoneProps", "multiple", "accept", "dropzoneText", "removeFileText", "FileList", "onFilesUpdated", "onFilesRejected", "showFileSize", "showImagePreviews", "value", "fieldWrapperProps"]);
54
+ dropzoneText = defaultDropzoneText[lang](), unstable_confirmReplace, removeFileText = defaultRemoveFileText[lang], FileList = DefaultFileList, onFilesUpdated = () => undefined, onFilesRejected, showFileSize, showImagePreviews, value = [], fieldWrapperProps } = _d, inputElementProps = __rest(_d, ["dropzoneProps", "multiple", "accept", "dropzoneText", "unstable_confirmReplace", "removeFileText", "FileList", "onFilesUpdated", "onFilesRejected", "showFileSize", "showImagePreviews", "value", "fieldWrapperProps"]);
50
55
  const domid = useDomid(props.id);
51
56
  const fileInputWrapper = useRef(null);
52
57
  const fileInput = useRef(null);
@@ -110,14 +115,24 @@ export const FileInput = (props) => {
110
115
  };
111
116
  const addFiles = (added) => {
112
117
  const { fileList, diff } = getFileListUpdate(files, added, !multiple);
118
+ let updatedFileList = fileList;
119
+ if (diff.deleted && multiple && props.unstable_confirmReplace) {
120
+ diff.deleted = diff.deleted.filter((file) => window.confirm(`${replaceFileTest} "${file.name}"?`));
121
+ // When unstable_confirmReplace becomes stable, we should stop returning
122
+ // `fileList` from getFileListUpdate and just always do this thing.
123
+ // That change will require changing the tests too, so let's wait.
124
+ updatedFileList = files
125
+ .filter((file) => !diff.deleted.includes(file))
126
+ .concat(added);
127
+ }
113
128
  if (fileInput.current) {
114
- fileInput.current.files = arrayToFileList(fileList);
129
+ fileInput.current.files = arrayToFileList(updatedFileList);
115
130
  }
116
131
  if (inputRef.current) {
117
132
  // Empty on every add
118
133
  inputRef.current.files = arrayToFileList([]);
119
134
  }
120
- onFilesUpdated(fileList, diff);
135
+ onFilesUpdated(updatedFileList, diff);
121
136
  };
122
137
  return (React.createElement(FormField, Object.assign({ extraClassName: modifiedClass('FileInput', [multiple && 'multi']) }, fieldWrapperProps, { id: `${domid}-fake`, LabelTag: "h4", renderInput: (className, inputProps /* , addFocusProps */) => {
123
138
  return (React.createElement("div", { className: className.control, ref: fileInputWrapper },
@@ -55,6 +55,8 @@ export type MultiselectProps = TogglerGroupFieldProps<string, {
55
55
  forceSearchable?: boolean;
56
56
  texts?: MultiselectI18n;
57
57
  lang?: HannaLang;
58
+ /** Fires whenever the dropdown menu is opened or closed */
59
+ onDropdown?: (isOpen: boolean) => void;
58
60
  };
59
61
  export declare const Multiselect: {
60
62
  (props: MultiselectProps): JSX.Element;
@@ -54,7 +54,7 @@ const defaultTexts = {
54
54
  },
55
55
  };
56
56
  export const Multiselect = (props) => {
57
- const { onSelected, options: _options, disabled: _disabled, readOnly } = props;
57
+ const { onSelected, onDropdown, options: _options, disabled: _disabled, readOnly, } = props;
58
58
  const disabled = _disabled === true;
59
59
  const disableds = !disabled && _disabled;
60
60
  const name = useDomid(props.name);
@@ -76,6 +76,9 @@ export const Multiselect = (props) => {
76
76
  setSearchQuery('');
77
77
  setActiveItemIndex(-1);
78
78
  }
79
+ if (onDropdown && newIsOpen !== isOpen) {
80
+ onDropdown(newIsOpen);
81
+ }
79
82
  return newIsOpen;
80
83
  });
81
84
  };
@@ -127,59 +130,53 @@ export const Multiselect = (props) => {
127
130
  toggleOpen(activeItemIndex > -1 ? true : !isOpen);
128
131
  }
129
132
  };
130
- // When the dropdown is open, add keydown handlers
131
- useEffect(() => {
132
- if (!isOpen) {
133
+ const handleWrapperKeyDown = (e) => {
134
+ var _a;
135
+ if (!isOpen || !((_a = inputWrapperRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
133
136
  return;
134
137
  }
135
- const handleKeyDown = (e) => {
136
- const inputElm = inputRef.current;
137
- if (e.key === 'ArrowUp') {
138
- e.preventDefault();
139
- inputElm.focus();
140
- setActiveItemIndex((prevIndex) => prevIndex === 0 ? filteredOptions.length - 1 : prevIndex - 1);
141
- }
142
- else if (e.key === 'ArrowDown') {
143
- e.preventDefault();
144
- inputElm.focus();
145
- setActiveItemIndex((prevIndex) => prevIndex === filteredOptions.length - 1 ? 0 : prevIndex + 1);
138
+ const inputElm = inputRef.current;
139
+ if (e.key === 'ArrowUp') {
140
+ e.preventDefault();
141
+ inputElm.focus();
142
+ setActiveItemIndex((prevIndex) => prevIndex === 0 ? filteredOptions.length - 1 : prevIndex - 1);
143
+ }
144
+ else if (e.key === 'ArrowDown') {
145
+ e.preventDefault();
146
+ inputElm.focus();
147
+ setActiveItemIndex((prevIndex) => prevIndex === filteredOptions.length - 1 ? 0 : prevIndex + 1);
148
+ }
149
+ else if (e.key === 'Escape') {
150
+ e.preventDefault();
151
+ inputElm.blur();
152
+ inputElm.focus();
153
+ toggleOpen(false);
154
+ }
155
+ else if (e.key === 'Enter' || e.key === ' ') {
156
+ if (e.target.closest('.Multiselect__currentvalues')) {
157
+ return;
146
158
  }
147
- else if (e.key === 'Escape') {
159
+ const focusInRange = activeItemIndex >= 0 && activeItemIndex < filteredOptions.length;
160
+ if (focusInRange) {
148
161
  e.preventDefault();
149
- inputElm.blur();
150
- inputElm.focus();
151
- toggleOpen(false);
152
- }
153
- else if (e.key === 'Enter' || e.key === ' ') {
154
- if (e.target.closest('.Multiselect__currentvalues')) {
155
- return;
156
- }
157
- const focusInRange = activeItemIndex >= 0 && activeItemIndex < filteredOptions.length;
158
- if (focusInRange) {
159
- e.preventDefault();
160
- const selItem = filteredOptions[activeItemIndex];
161
- if (selItem) {
162
- // Manually toggle the checkbox, to ensure that uncontrolled
163
- // components (e.g. screen readers) are in sync with the visual state.
164
- let input;
165
- inputWrapperRef
166
- .current.querySelectorAll(`input[type="checkbox"]`)
167
- .forEach((elm) => {
168
- if (elm.value === selItem.value) {
169
- input = elm;
170
- }
171
- });
172
- input.checked = !input.checked;
173
- handleCheckboxSelection(selItem);
174
- }
162
+ const selItem = filteredOptions[activeItemIndex];
163
+ if (selItem) {
164
+ // Manually toggle the checkbox, to ensure that uncontrolled
165
+ // components (e.g. screen readers) are in sync with the visual state.
166
+ let input;
167
+ e.currentTarget
168
+ .querySelectorAll(`input[type="checkbox"]`)
169
+ .forEach((elm) => {
170
+ if (elm.value === selItem.value) {
171
+ input = elm;
172
+ }
173
+ });
174
+ input.checked = !input.checked;
175
+ handleCheckboxSelection(selItem);
175
176
  }
176
177
  }
177
- };
178
- document.addEventListener('keydown', handleKeyDown);
179
- return () => {
180
- document.removeEventListener('keydown', handleKeyDown);
181
- };
182
- }, [activeItemIndex, filteredOptions, isOpen, handleCheckboxSelection, inputRef]);
178
+ }
179
+ };
183
180
  // Auto-close the dropdown when focus has left the building
184
181
  useEffect(() => {
185
182
  const wrapperDiv = inputWrapperRef.current;
@@ -207,7 +204,7 @@ export const Multiselect = (props) => {
207
204
  }, [activeItemIndex]);
208
205
  return (React.createElement(FormField, Object.assign({ extraClassName: modifiedClass('Multiselect', props.nowrap && 'nowrap'), group: "inputlike", filled: filled, empty: empty }, getFormFieldWrapperProps(props), { renderInput: (className, inputProps, addFocusProps, isBrowser) => {
209
206
  const { id } = inputProps;
210
- return (React.createElement("div", Object.assign({ className: modifiedClass('Multiselect__input', [isOpen && 'open'], className.input) }, addFocusProps(), { "data-sprinkled": isBrowser, ref: inputWrapperRef }),
207
+ return (React.createElement("div", Object.assign({ className: modifiedClass('Multiselect__input', [isOpen && 'open'], className.input) }, addFocusProps(), { "data-sprinkled": isBrowser, ref: inputWrapperRef, onKeyDown: handleWrapperKeyDown }),
211
208
  !isBrowser ? null : isSearchable ? (React.createElement("input", { className: "Multiselect__search", id: `toggler:${id}`, "aria-label": texts.search, "aria-controls": id, "data-expanded": isOpen || undefined, onChange: handleInputChange, onKeyDown: handleInputKeyDown, onClick: () => toggleOpen(), value: searchQuery,
212
209
  // onFocus={handleInputFocus}
213
210
  placeholder: placeholderText, disabled: disabled, ref: inputRef })) : (React.createElement("button", { className: "Multiselect__toggler", id: `toggler:${id}`, type: "button", "aria-label": texts.buttonShow, "aria-controls": id, "aria-expanded": isOpen, onClick: () => toggleOpen(), disabled: disabled,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/hanna-react",
3
- "version": "0.10.157",
3
+ "version": "0.10.159",
4
4
  "author": "Reykjavík (http://www.reykjavik.is)",
5
5
  "contributors": [
6
6
  "Hugsmiðjan ehf (http://www.hugsmidjan.is)",