smarthr-ui 99.2.0 → 99.3.0

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 (91) hide show
  1. package/lib/components/AppHeader/AppHeader.cjs +2 -2
  2. package/lib/components/AppHeader/AppHeader.cjs.map +1 -1
  3. package/lib/components/AppHeader/AppHeader.js +2 -2
  4. package/lib/components/AppHeader/AppHeader.js.map +1 -1
  5. package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs +31 -37
  6. package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs.map +1 -1
  7. package/lib/components/Combobox/MultiCombobox/MultiCombobox.js +33 -39
  8. package/lib/components/Combobox/MultiCombobox/MultiCombobox.js.map +1 -1
  9. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.cjs +8 -6
  10. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.cjs.map +1 -1
  11. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.d.ts +2 -3
  12. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.js +8 -7
  13. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.js.map +1 -1
  14. package/lib/components/Combobox/useListbox.cjs +24 -3
  15. package/lib/components/Combobox/useListbox.cjs.map +1 -1
  16. package/lib/components/Combobox/useListbox.js +24 -3
  17. package/lib/components/Combobox/useListbox.js.map +1 -1
  18. package/lib/components/Disclosure/useDisclosure.cjs +24 -19
  19. package/lib/components/Disclosure/useDisclosure.cjs.map +1 -1
  20. package/lib/components/Disclosure/useDisclosure.js +25 -20
  21. package/lib/components/Disclosure/useDisclosure.js.map +1 -1
  22. package/lib/components/FileViewer/FileViewer.cjs +35 -12
  23. package/lib/components/FileViewer/FileViewer.cjs.map +1 -1
  24. package/lib/components/FileViewer/FileViewer.d.ts +3 -1
  25. package/lib/components/FileViewer/FileViewer.js +36 -13
  26. package/lib/components/FileViewer/FileViewer.js.map +1 -1
  27. package/lib/components/FileViewer/ImageViewer.cjs +2 -2
  28. package/lib/components/FileViewer/ImageViewer.cjs.map +1 -1
  29. package/lib/components/FileViewer/ImageViewer.js +2 -2
  30. package/lib/components/FileViewer/ImageViewer.js.map +1 -1
  31. package/lib/components/FileViewer/PDFViewer.cjs +23 -15
  32. package/lib/components/FileViewer/PDFViewer.cjs.map +1 -1
  33. package/lib/components/FileViewer/PDFViewer.js +24 -16
  34. package/lib/components/FileViewer/PDFViewer.js.map +1 -1
  35. package/lib/components/FileViewer/types.d.ts +2 -2
  36. package/lib/components/InputFile/FilePreviewDialog.cjs +2 -2
  37. package/lib/components/InputFile/FilePreviewDialog.cjs.map +1 -1
  38. package/lib/components/InputFile/FilePreviewDialog.d.ts +1 -0
  39. package/lib/components/InputFile/FilePreviewDialog.js +2 -2
  40. package/lib/components/InputFile/FilePreviewDialog.js.map +1 -1
  41. package/lib/components/InputFile/InputFile.cjs +6 -3
  42. package/lib/components/InputFile/InputFile.cjs.map +1 -1
  43. package/lib/components/InputFile/InputFile.js +6 -3
  44. package/lib/components/InputFile/InputFile.js.map +1 -1
  45. package/lib/components/InputFile/InputFileMultiplyAppendable.cjs +2 -2
  46. package/lib/components/InputFile/InputFileMultiplyAppendable.cjs.map +1 -1
  47. package/lib/components/InputFile/InputFileMultiplyAppendable.d.ts +2 -2
  48. package/lib/components/InputFile/InputFileMultiplyAppendable.js +2 -2
  49. package/lib/components/InputFile/InputFileMultiplyAppendable.js.map +1 -1
  50. package/lib/components/InputFile/InputFileNative.cjs +2 -2
  51. package/lib/components/InputFile/InputFileNative.cjs.map +1 -1
  52. package/lib/components/InputFile/InputFileNative.d.ts +2 -2
  53. package/lib/components/InputFile/InputFileNative.js +2 -2
  54. package/lib/components/InputFile/InputFileNative.js.map +1 -1
  55. package/lib/components/InputFile/types.d.ts +8 -1
  56. package/lib/components/Stepper/HorizontalStepItem.cjs +4 -4
  57. package/lib/components/Stepper/HorizontalStepItem.cjs.map +1 -1
  58. package/lib/components/Stepper/HorizontalStepItem.d.ts +3 -1
  59. package/lib/components/Stepper/HorizontalStepItem.js +4 -4
  60. package/lib/components/Stepper/HorizontalStepItem.js.map +1 -1
  61. package/lib/components/Stepper/StepCounter.cjs +4 -4
  62. package/lib/components/Stepper/StepCounter.cjs.map +1 -1
  63. package/lib/components/Stepper/StepCounter.d.ts +3 -2
  64. package/lib/components/Stepper/StepCounter.js +4 -4
  65. package/lib/components/Stepper/StepCounter.js.map +1 -1
  66. package/lib/components/Stepper/StepStatusIcon.cjs +3 -5
  67. package/lib/components/Stepper/StepStatusIcon.cjs.map +1 -1
  68. package/lib/components/Stepper/StepStatusIcon.d.ts +4 -5
  69. package/lib/components/Stepper/StepStatusIcon.js +3 -5
  70. package/lib/components/Stepper/StepStatusIcon.js.map +1 -1
  71. package/lib/components/Stepper/Stepper.cjs +7 -2
  72. package/lib/components/Stepper/Stepper.cjs.map +1 -1
  73. package/lib/components/Stepper/Stepper.js +7 -2
  74. package/lib/components/Stepper/Stepper.js.map +1 -1
  75. package/lib/components/Stepper/VerticalStepItem.cjs +4 -4
  76. package/lib/components/Stepper/VerticalStepItem.cjs.map +1 -1
  77. package/lib/components/Stepper/VerticalStepItem.d.ts +3 -1
  78. package/lib/components/Stepper/VerticalStepItem.js +4 -4
  79. package/lib/components/Stepper/VerticalStepItem.js.map +1 -1
  80. package/lib/components/Textarea/Textarea.cjs +14 -37
  81. package/lib/components/Textarea/Textarea.cjs.map +1 -1
  82. package/lib/components/Textarea/Textarea.js +14 -37
  83. package/lib/components/Textarea/Textarea.js.map +1 -1
  84. package/lib/components/Timeline/TimelineItem.cjs +11 -15
  85. package/lib/components/Timeline/TimelineItem.cjs.map +1 -1
  86. package/lib/components/Timeline/TimelineItem.d.ts +3 -3
  87. package/lib/components/Timeline/TimelineItem.js +11 -15
  88. package/lib/components/Timeline/TimelineItem.js.map +1 -1
  89. package/metadata.json +1 -1
  90. package/package.json +1 -1
  91. package/smarthr-ui.css +47 -29
@@ -6,14 +6,14 @@ var components_AppHeader_components_desktop_DesktopHeader = require('./component
6
6
  var components_AppHeader_components_mobile_MobileHeader = require('./components/mobile/MobileHeader.cjs');
7
7
  var components_AppHeader_hooks_useMediaQuery = require('./hooks/useMediaQuery.cjs');
8
8
 
9
- const AppHeader = ({ children, ...rest }) => {
9
+ const AppHeader = ({ children, desktopAdditionalContent, desktopNavigationAdditionalContent, mobileAdditionalContent, ...rest }) => {
10
10
  // NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、
11
11
  // wovn の言語切替カスタム UI の挿入対象となる DOM ("wovn-embedded-widget-anchor" クラスを持った div) が複数描画されていると、
12
12
  // wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。
13
13
  const isDesktop = components_AppHeader_hooks_useMediaQuery.useMediaQuery(components_AppHeader_hooks_useMediaQuery.mediaQuery.desktop);
14
14
  // HINT: Desktop,Mobileの両ヘッダーは常にHTML上に存在し、cssでvisibleを切り替えることでSSR環境でのレイアウトシフトが発生しないようにしています
15
15
  // 表示切替は画面幅によって決まり、SSR環境では判定出来ないためです
16
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(components_AppHeader_components_desktop_DesktopHeader.DesktopHeader, { ...rest, children: isDesktop ? children : undefined }), jsxRuntime.jsx(components_AppHeader_components_mobile_MobileHeader.MobileHeader, { ...rest, children: isDesktop ? undefined : children })] }));
16
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(components_AppHeader_components_desktop_DesktopHeader.DesktopHeader, { ...rest, desktopAdditionalContent: desktopAdditionalContent, desktopNavigationAdditionalContent: desktopNavigationAdditionalContent, children: isDesktop ? children : undefined }), jsxRuntime.jsx(components_AppHeader_components_mobile_MobileHeader.MobileHeader, { ...rest, mobileAdditionalContent: mobileAdditionalContent, children: isDesktop ? undefined : children })] }));
17
17
  };
18
18
 
19
19
  exports.AppHeader = AppHeader;
@@ -1 +1 @@
1
- {"version":3,"file":"AppHeader.cjs","sources":["../../../src/components/AppHeader/AppHeader.tsx"],"sourcesContent":["'use client'\n\nimport { DesktopHeader } from './components/desktop/DesktopHeader'\nimport { MobileHeader } from './components/mobile/MobileHeader'\nimport { mediaQuery, useMediaQuery } from './hooks/useMediaQuery'\n\nimport type { HeaderProps } from './types'\nimport type { FC } from 'react'\n\nexport const AppHeader: FC<HeaderProps> = ({ children, ...rest }) => {\n // NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、\n // wovn の言語切替カスタム UI の挿入対象となる DOM (\"wovn-embedded-widget-anchor\" クラスを持った div) が複数描画されていると、\n // wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。\n const isDesktop = useMediaQuery(mediaQuery.desktop)\n\n // HINT: Desktop,Mobileの両ヘッダーは常にHTML上に存在し、cssでvisibleを切り替えることでSSR環境でのレイアウトシフトが発生しないようにしています\n // 表示切替は画面幅によって決まり、SSR環境では判定出来ないためです\n return (\n <>\n <DesktopHeader {...rest}>{isDesktop ? children : undefined}</DesktopHeader>\n <MobileHeader {...rest}>{isDesktop ? undefined : children}</MobileHeader>\n </>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AASO;;;;;;;AAQL;AAMF;;"}
1
+ {"version":3,"file":"AppHeader.cjs","sources":["../../../src/components/AppHeader/AppHeader.tsx"],"sourcesContent":["'use client'\n\nimport { DesktopHeader } from './components/desktop/DesktopHeader'\nimport { MobileHeader } from './components/mobile/MobileHeader'\nimport { mediaQuery, useMediaQuery } from './hooks/useMediaQuery'\n\nimport type { HeaderProps } from './types'\nimport type { FC } from 'react'\n\nexport const AppHeader: FC<HeaderProps> = ({\n children,\n desktopAdditionalContent,\n desktopNavigationAdditionalContent,\n mobileAdditionalContent,\n ...rest\n}) => {\n // NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、\n // wovn の言語切替カスタム UI の挿入対象となる DOM (\"wovn-embedded-widget-anchor\" クラスを持った div) が複数描画されていると、\n // wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。\n const isDesktop = useMediaQuery(mediaQuery.desktop)\n\n // HINT: Desktop,Mobileの両ヘッダーは常にHTML上に存在し、cssでvisibleを切り替えることでSSR環境でのレイアウトシフトが発生しないようにしています\n // 表示切替は画面幅によって決まり、SSR環境では判定出来ないためです\n return (\n <>\n <DesktopHeader\n {...rest}\n desktopAdditionalContent={desktopAdditionalContent}\n desktopNavigationAdditionalContent={desktopNavigationAdditionalContent}\n >\n {isDesktop ? children : undefined}\n </DesktopHeader>\n <MobileHeader {...rest} mobileAdditionalContent={mobileAdditionalContent}>\n {isDesktop ? undefined : children}\n </MobileHeader>\n </>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AASO;;;;;;;;AA4BP;;"}
@@ -4,14 +4,14 @@ import { DesktopHeader } from './components/desktop/DesktopHeader.js';
4
4
  import { MobileHeader } from './components/mobile/MobileHeader.js';
5
5
  import { useMediaQuery, mediaQuery } from './hooks/useMediaQuery.js';
6
6
 
7
- const AppHeader = ({ children, ...rest }) => {
7
+ const AppHeader = ({ children, desktopAdditionalContent, desktopNavigationAdditionalContent, mobileAdditionalContent, ...rest }) => {
8
8
  // NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、
9
9
  // wovn の言語切替カスタム UI の挿入対象となる DOM ("wovn-embedded-widget-anchor" クラスを持った div) が複数描画されていると、
10
10
  // wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。
11
11
  const isDesktop = useMediaQuery(mediaQuery.desktop);
12
12
  // HINT: Desktop,Mobileの両ヘッダーは常にHTML上に存在し、cssでvisibleを切り替えることでSSR環境でのレイアウトシフトが発生しないようにしています
13
13
  // 表示切替は画面幅によって決まり、SSR環境では判定出来ないためです
14
- return (jsxs(Fragment, { children: [jsx(DesktopHeader, { ...rest, children: isDesktop ? children : undefined }), jsx(MobileHeader, { ...rest, children: isDesktop ? undefined : children })] }));
14
+ return (jsxs(Fragment, { children: [jsx(DesktopHeader, { ...rest, desktopAdditionalContent: desktopAdditionalContent, desktopNavigationAdditionalContent: desktopNavigationAdditionalContent, children: isDesktop ? children : undefined }), jsx(MobileHeader, { ...rest, mobileAdditionalContent: mobileAdditionalContent, children: isDesktop ? undefined : children })] }));
15
15
  };
16
16
 
17
17
  export { AppHeader };
@@ -1 +1 @@
1
- {"version":3,"file":"AppHeader.js","sources":["../../../src/components/AppHeader/AppHeader.tsx"],"sourcesContent":["'use client'\n\nimport { DesktopHeader } from './components/desktop/DesktopHeader'\nimport { MobileHeader } from './components/mobile/MobileHeader'\nimport { mediaQuery, useMediaQuery } from './hooks/useMediaQuery'\n\nimport type { HeaderProps } from './types'\nimport type { FC } from 'react'\n\nexport const AppHeader: FC<HeaderProps> = ({ children, ...rest }) => {\n // NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、\n // wovn の言語切替カスタム UI の挿入対象となる DOM (\"wovn-embedded-widget-anchor\" クラスを持った div) が複数描画されていると、\n // wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。\n const isDesktop = useMediaQuery(mediaQuery.desktop)\n\n // HINT: Desktop,Mobileの両ヘッダーは常にHTML上に存在し、cssでvisibleを切り替えることでSSR環境でのレイアウトシフトが発生しないようにしています\n // 表示切替は画面幅によって決まり、SSR環境では判定出来ないためです\n return (\n <>\n <DesktopHeader {...rest}>{isDesktop ? children : undefined}</DesktopHeader>\n <MobileHeader {...rest}>{isDesktop ? undefined : children}</MobileHeader>\n </>\n )\n}\n"],"names":[],"mappings":";;;;;;AASO;;;;;;;AAQL;AAMF;;"}
1
+ {"version":3,"file":"AppHeader.js","sources":["../../../src/components/AppHeader/AppHeader.tsx"],"sourcesContent":["'use client'\n\nimport { DesktopHeader } from './components/desktop/DesktopHeader'\nimport { MobileHeader } from './components/mobile/MobileHeader'\nimport { mediaQuery, useMediaQuery } from './hooks/useMediaQuery'\n\nimport type { HeaderProps } from './types'\nimport type { FC } from 'react'\n\nexport const AppHeader: FC<HeaderProps> = ({\n children,\n desktopAdditionalContent,\n desktopNavigationAdditionalContent,\n mobileAdditionalContent,\n ...rest\n}) => {\n // NOTE: ヘッダーの出し分けは CSS によって行われているので、useMediaQuery による children の出し分けは本来不要ですが、\n // wovn の言語切替カスタム UI の挿入対象となる DOM (\"wovn-embedded-widget-anchor\" クラスを持った div) が複数描画されていると、\n // wovn のスクリプトの仕様上1つ目の DOM にしか UI が挿入されないため、やむを得ず children のみ React のレンダリングレベルでの出し分けをしています。\n const isDesktop = useMediaQuery(mediaQuery.desktop)\n\n // HINT: Desktop,Mobileの両ヘッダーは常にHTML上に存在し、cssでvisibleを切り替えることでSSR環境でのレイアウトシフトが発生しないようにしています\n // 表示切替は画面幅によって決まり、SSR環境では判定出来ないためです\n return (\n <>\n <DesktopHeader\n {...rest}\n desktopAdditionalContent={desktopAdditionalContent}\n desktopNavigationAdditionalContent={desktopNavigationAdditionalContent}\n >\n {isDesktop ? children : undefined}\n </DesktopHeader>\n <MobileHeader {...rest} mobileAdditionalContent={mobileAdditionalContent}>\n {isDesktop ? undefined : children}\n </MobileHeader>\n </>\n )\n}\n"],"names":[],"mappings":";;;;;;AASO;;;;;;;;AA4BP;;"}
@@ -14,6 +14,7 @@ require('react-intl');
14
14
  require('../../../intl/Localizer.cjs');
15
15
  require('../../../_virtual/dayjs.min.cjs');
16
16
  require('../../../intl/TimeFormatter.cjs');
17
+ var libs_delegate = require('../../../libs/delegate.cjs');
17
18
  var libs_util = require('../../../libs/util.cjs');
18
19
  var components_Icon_FaIcon = require('../../Icon/FaIcon.cjs');
19
20
  require('../../Icon/generateIcon.cjs');
@@ -38,6 +39,7 @@ const EMPTY_INPUT_CHANGE_EVENT = {
38
39
  currentTarget: { value: '' },
39
40
  target: { value: '' },
40
41
  };
42
+ const DELETE_BUTTON_CLASSNAME = `.${components_Combobox_MultiCombobox_MultiSelectedItem.DELETE_BUTTON_SELECTOR}`;
41
43
  const classNameGenerator = index.tv({
42
44
  slots: {
43
45
  wrapper: [
@@ -92,17 +94,7 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
92
94
  inputValue,
93
95
  isItemSelected,
94
96
  });
95
- const selectedItemLength = selectedItems.length;
96
- // TODO: 完全にcreateRefを作り直すのではなく、差分更新させたい
97
- const deletionButtonRefs = React.useMemo(() => {
98
- const refs = [];
99
- for (let i = 0; i < selectedItemLength; i++) {
100
- refs[i] = React.createRef();
101
- }
102
- return refs;
103
- }, [selectedItemLength]);
104
97
  const inputRef = React.useRef(null);
105
- const [focusedIndex, setFocusedIndex] = React.useState(null);
106
98
  // eslint-disable-next-line local-rules/best-practice-for-use-latest
107
99
  const latestForListBox = hooks_useLatest.useLatest({
108
100
  onDelete,
@@ -171,43 +163,48 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
171
163
  isComposing,
172
164
  isInputEmpty,
173
165
  selectedItems,
174
- deletionButtonRefs,
175
- focusedIndex,
176
- selectedItemLength,
177
166
  setInputValueIfUncontrolled,
178
167
  handleKeyDownListBox,
179
168
  });
180
169
  const functions = React.useMemo(() => {
181
- const resetDeletionButtonFocus = () => {
182
- setFocusedIndex(null);
183
- };
184
170
  const handleDelete = listBoxFunctions.handleDelete;
171
+ const getDeletionButtons = () => {
172
+ if (triggerRef.current) {
173
+ const buttons = triggerRef.current.querySelectorAll(DELETE_BUTTON_CLASSNAME);
174
+ if (buttons.length > 0) {
175
+ const actualButtons = Array.from(buttons);
176
+ return {
177
+ buttons: actualButtons,
178
+ currentIndex: actualButtons.indexOf(document.activeElement),
179
+ };
180
+ }
181
+ }
182
+ return null;
183
+ };
185
184
  const focusPrevDeletionButton = () => {
186
- if (latest.selectedItemLength === 0) {
185
+ const result = getDeletionButtons();
186
+ if (!result)
187
187
  return;
188
- }
189
- if (latest.focusedIndex !== null) {
190
- const nextIndex = Math.max(latest.focusedIndex - 1, 0);
191
- latest.deletionButtonRefs[nextIndex].current?.focus();
192
- setFocusedIndex(nextIndex);
188
+ const { buttons, currentIndex } = result;
189
+ if (currentIndex !== -1) {
190
+ buttons[Math.max(currentIndex - 1, 0)].focus();
193
191
  }
194
192
  else if (inputRef.current?.selectionStart === 0) {
195
- const nextIndex = latest.deletionButtonRefs.length - 1;
196
- latest.deletionButtonRefs[nextIndex].current?.focus();
197
- setFocusedIndex(nextIndex);
193
+ buttons[buttons.length - 1].focus();
198
194
  }
199
195
  };
200
196
  const focusNextDeletionButton = () => {
201
- if (latest.deletionButtonRefs.length === 0 || latest.focusedIndex === null) {
197
+ const result = getDeletionButtons();
198
+ if (!result)
202
199
  return;
203
- }
204
- const nextIndex = latest.focusedIndex + 1;
205
- if (nextIndex < latest.deletionButtonRefs.length) {
206
- latest.deletionButtonRefs[nextIndex].current?.focus();
207
- setFocusedIndex(nextIndex);
200
+ const { buttons, currentIndex } = result;
201
+ if (currentIndex === -1)
202
+ return;
203
+ const nextIndex = currentIndex + 1;
204
+ if (nextIndex < buttons.length) {
205
+ buttons[nextIndex].focus();
208
206
  }
209
207
  else {
210
- setFocusedIndex(null);
211
208
  // キー入力が input に影響しないようにフォーカスタイミングを遅らせる
212
209
  setTimeout(() => {
213
210
  inputRef.current?.focus();
@@ -222,7 +219,6 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
222
219
  if (latest.isExpanded) {
223
220
  latest.onBlur?.();
224
221
  setIsExpanded(false);
225
- resetDeletionButtonFocus();
226
222
  }
227
223
  };
228
224
  return {
@@ -264,14 +260,13 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
264
260
  else {
265
261
  e.stopPropagation();
266
262
  inputRef.current?.focus();
267
- resetDeletionButtonFocus();
268
263
  }
269
264
  latest.handleKeyDownListBox(e);
270
265
  },
271
266
  handleDelegateClick: (e) => {
272
267
  if (!latest.disabled &&
273
268
  !latest.isExpanded &&
274
- !e.target.closest('.smarthr-ui-MultiCombobox-deleteButton')) {
269
+ !libs_delegate.findDelegateTarget(e, DELETE_BUTTON_CLASSNAME)) {
275
270
  focus();
276
271
  }
277
272
  },
@@ -281,7 +276,6 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
281
276
  latest.setInputValueIfUncontrolled(e.currentTarget.value);
282
277
  },
283
278
  handleFocusInput: () => {
284
- resetDeletionButtonFocus();
285
279
  if (!latest.isExpanded) {
286
280
  focus();
287
281
  }
@@ -343,7 +337,7 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
343
337
  jsxRuntime.jsxs("div", { ref: triggerRef, role: "group", onClick: functions.handleDelegateClick, onKeyDown: functions.handleDelegateKeyDown, onKeyPress: functions.handleDelegateKeyPress, className: classNames.wrapper, style: {
344
338
  ...style,
345
339
  width: typeof width === 'number' ? `${width}px` : width,
346
- }, children: [jsxRuntime.jsxs(components_Scroller_Scroller.Scroller, { className: classNames.inputArea, children: [jsxRuntime.jsx("ul", { id: selectedListId, "aria-label": localized.selectedListAriaLabel, className: classNames.selectedList, children: selectedItems.map((selectedItem, i) => (jsxRuntime.jsx("li", { children: jsxRuntime.jsx(components_Combobox_MultiCombobox_MultiSelectedItem.MultiSelectedItem, { item: selectedItem, disabled: disabled, handleDelete: functions.handleDelete, enableEllipsis: selectedItemEllipsis, buttonRef: deletionButtonRefs[i] }) }, `${selectedItem.label}-${index$1(selectedItem.value)}`))) }), jsxRuntime.jsx("div", { className: classNames.inputWrapper, children: jsxRuntime.jsx("input", { ...rest, "data-smarthr-ui-input": "true", type: "text", name: name, value: inputValue, disabled: disabled, required: required && selectedItems.length === 0, ref: inputRef, onChange: functions.handleChangeInput, onFocus: functions.handleFocusInput, onCompositionStart: functions.handleCompositionStart, onCompositionEnd: functions.handleCompositionEnd, onKeyDown: functions.handleKeyDownInput, autoComplete: autoComplete ?? 'off', tabIndex: 0, role: "combobox", "aria-activedescendant": activeOption?.id, "aria-controls": `${listBoxId} ${selectedListId}`, "aria-haspopup": "listbox", "aria-expanded": isExpanded, "aria-invalid": error || undefined, "aria-disabled": disabled, "aria-autocomplete": "list", className: classNames.input }) }), selectedItems.length === 0 && placeholder && !isExpanded && (jsxRuntime.jsx("p", { className: classNames.placeholder, children: placeholder }))] }), jsxRuntime.jsx(MemoizedCaretDown, { disabled: disabled, isExpanded: isExpanded, classNames: classNames }), jsxRuntime.jsx(components_Combobox_useListbox.ListBox, { ...listBoxProps })] }));
340
+ }, children: [jsxRuntime.jsxs(components_Scroller_Scroller.Scroller, { className: classNames.inputArea, children: [jsxRuntime.jsx("ul", { id: selectedListId, "aria-label": localized.selectedListAriaLabel, className: classNames.selectedList, children: selectedItems.map((selectedItem) => (jsxRuntime.jsx("li", { children: jsxRuntime.jsx(components_Combobox_MultiCombobox_MultiSelectedItem.MultiSelectedItem, { item: selectedItem, disabled: disabled, handleDelete: functions.handleDelete, enableEllipsis: selectedItemEllipsis }) }, `${selectedItem.label}-${index$1(selectedItem.value)}`))) }), jsxRuntime.jsx("div", { className: classNames.inputWrapper, children: jsxRuntime.jsx("input", { ...rest, "data-smarthr-ui-input": "true", type: "text", name: name, value: inputValue, disabled: disabled, required: required && selectedItems.length === 0, ref: inputRef, onChange: functions.handleChangeInput, onFocus: functions.handleFocusInput, onCompositionStart: functions.handleCompositionStart, onCompositionEnd: functions.handleCompositionEnd, onKeyDown: functions.handleKeyDownInput, autoComplete: autoComplete ?? 'off', tabIndex: 0, role: "combobox", "aria-activedescendant": activeOption?.id, "aria-controls": `${listBoxId} ${selectedListId}`, "aria-haspopup": "listbox", "aria-expanded": isExpanded, "aria-invalid": error || undefined, "aria-disabled": disabled, "aria-autocomplete": "list", className: classNames.input }) }), selectedItems.length === 0 && placeholder && !isExpanded && (jsxRuntime.jsx("p", { className: classNames.placeholder, children: placeholder }))] }), jsxRuntime.jsx(MemoizedCaretDown, { disabled: disabled, isExpanded: isExpanded, classNames: classNames }), jsxRuntime.jsx(components_Combobox_useListbox.ListBox, { ...listBoxProps })] }));
347
341
  };
348
342
  const MultiCombobox = libs_util.genericsForwardRef(ActualMultiCombobox);
349
343
  const MemoizedCaretDown = React.memo(({ disabled, isExpanded, classNames }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"MultiCombobox.cjs","sources":["../../../../src/components/Combobox/MultiCombobox/MultiCombobox.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type Ref,\n createRef,\n memo,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useOuterClick } from '../../../hooks/useOuterClick'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { useLocalize } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { FaCaretDownIcon } from '../../Icon'\nimport { Scroller } from '../../Scroller'\nimport { areItemsEqual } from '../helper'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useMultiOptions } from '../useOptions'\n\nimport { MultiSelectedItem } from './MultiSelectedItem'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテムのリスト\n */\n selectedItems: Array<ComboboxItem<T> & { deletable?: boolean }>\n /**\n * 選択されているアイテムのラベルを省略表示するかどうか\n */\n selectedItemEllipsis?: boolean\n /**\n * テキストボックスの `value` 属性の値。\n * `onChangeInput` と併せて設定することで、テキストボックスの挙動が制御可能になる。\n */\n inputValue?: string\n /**\n * 選択されているアイテムの削除ボタンがクリックされた時に発火するコールバック関数\n */\n onDelete?: (item: ComboboxItem<T>) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItems: Array<ComboboxItem<T>>) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\n /**\n * アイテムが選択されたときに選択済みかどうかを判定するコールバック関数/\n */\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n /**\n * 検索結果が0件の時に表示するコンテンツ\n */\n noResultText?: ReactNode\n}\ntype Props<T> = BaseProps<T> & Omit<ComponentPropsWithoutRef<'input'>, keyof BaseProps<unknown>>\n\nconst NOOP = () => undefined\n\nconst preventDefaultWithPressEnter = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n }\n}\n\nconst ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_LEFT_KEY_REGEX = /^(Arrow)?Left$/\nconst ARROW_RIGHT_KEY_REGEX = /^(Arrow)?Right/\nconst ARROW_UP_AND_DOWN_KEY_REGEX = /^(Arrow)?(Up|Down)$/\n\nconst EMPTY_INPUT_CHANGE_EVENT = {\n currentTarget: { value: '' },\n target: { value: '' },\n} as ChangeEvent<HTMLInputElement>\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-MultiCombobox',\n 'shr-box-border shr-inline-flex shr-min-w-[15em] shr-rounded-m shr-border shr-border-solid shr-px-0.5 shr-py-0.25 shr-align-bottom',\n 'contrast-more:shr-border-high-contrast',\n 'has-[[role=combobox][aria-expanded=true]]:shr-focus-indicator',\n 'shr-cursor-text shr-border-default shr-bg-white',\n 'has-[[role=combobox]:disabled]:shr-cursor-not-allowed',\n 'has-[[role=combobox]:disabled]:shr-border-default/50',\n 'has-[[role=combobox]:disabled]:shr-bg-white-darken',\n 'has-[[role=combobox]:disabled]:shr-text-disabled',\n // HINT: disabled と詳細度が同じ [0,2,0] のため、CSS ソース順でこちらを後に置くことで error 時の border 色を優先させる\n 'has-[[role=combobox][aria-invalid]]:shr-border-danger',\n ],\n inputArea: 'shr-flex shr-flex-1 shr-flex-wrap shr-gap-0.5',\n selectedList:\n 'smarthr-ui-MultiCombobox-selectedList shr-contents shr-list-none [&_li]:shr-min-w-0',\n inputWrapper: [\n 'shr-flex shr-flex-1 shr-items-center',\n 'has-[[role=combobox][aria-expanded=false]]:shr-pointer-events-none',\n 'has-[[role=combobox][aria-expanded=false]]:shr-absolute',\n 'has-[[role=combobox][aria-expanded=false]]:shr-opacity-0',\n ],\n input: [\n 'smarthr-ui-MultiCombobox-input',\n 'shr-w-full shr-min-w-[5em] shr-border-none shr-text-base shr-text-black shr-outline-none shr-outline-0',\n 'disabled:shr-hidden',\n ],\n placeholderEl: 'smarthr-ui-MultiCombobox-placeholder shr-my-0 shr-self-center',\n suffixWrapper: [\n 'shr-relative -shr-me-0.5 shr-ms-0.5 shr-p-0.5',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n suffixIcon: 'shr-block',\n },\n})\n\nconst ActualMultiCombobox = <T,>(\n {\n items,\n selectedItems,\n name,\n disabled = false,\n required = false,\n error = false,\n creatable = false,\n placeholder = '',\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n selectedItemEllipsis,\n width,\n dropdownWidth = 'auto',\n inputValue: controlledInputValue,\n className,\n onChange,\n onChangeInput,\n onAdd,\n onDelete,\n onSelect,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n isItemSelected,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const triggerRef = useRef<HTMLDivElement>(null)\n const [isExpanded, setIsExpanded] = useState(false)\n const [highlighted, setHighlighted] = useState(false)\n const [uncontrolledInputValue, setUncontrolledInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n\n const selectedListId = useId()\n\n const isInputControlled = controlledInputValue !== undefined\n const inputValue = isInputControlled ? controlledInputValue : uncontrolledInputValue\n const setInputValueIfUncontrolled = isInputControlled ? NOOP : setUncontrolledInputValue\n const isInputEmpty = !inputValue\n\n const { options } = useMultiOptions({\n items,\n selected: selectedItems,\n creatable,\n inputValue,\n isItemSelected,\n })\n const selectedItemLength = selectedItems.length\n\n // TODO: 完全にcreateRefを作り直すのではなく、差分更新させたい\n const deletionButtonRefs = useMemo(() => {\n const refs: Array<ReturnType<typeof createRef<HTMLButtonElement>>> = []\n\n for (let i = 0; i < selectedItemLength; i++) {\n refs[i] = createRef<HTMLButtonElement>()\n }\n\n return refs\n }, [selectedItemLength])\n const inputRef = useRef<HTMLInputElement>(null)\n\n const [focusedIndex, setFocusedIndex] = useState<number | null>(null)\n\n // eslint-disable-next-line local-rules/best-practice-for-use-latest\n const latestForListBox = useLatest({\n onDelete,\n onChangeSelected,\n onSelect,\n onChangeInput,\n selectedItems,\n })\n\n const listBoxFunctions = useMemo(() => {\n const handleDelete = (item: ComboboxItem<T>) => {\n const handlers: Array<(deletingItem: ComboboxItem<T>) => void> = []\n\n if (latestForListBox.onDelete) {\n handlers.push((deletingItem: ComboboxItem<T>) => latestForListBox.onDelete!(deletingItem))\n }\n if (latestForListBox.onChangeSelected) {\n handlers.push((deletingItem: ComboboxItem<T>) =>\n latestForListBox.onChangeSelected!(\n latestForListBox.selectedItems.filter(\n (selected) => !areItemsEqual(selected, deletingItem),\n ),\n ),\n )\n }\n\n if (handlers.length > 0) {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n handlers.forEach((h) => h(item))\n })\n }\n }\n\n return {\n handleDelete,\n handleSelect: (selected: ComboboxItem<T>) => {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n const matchedSelectedItem = latestForListBox.selectedItems.find((item) =>\n areItemsEqual(item, selected),\n )\n\n if (matchedSelectedItem === undefined) {\n latestForListBox.onSelect?.(selected)\n latestForListBox.onChangeSelected?.(latestForListBox.selectedItems.concat(selected))\n\n // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n latestForListBox.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n } else if (matchedSelectedItem.deletable !== false) {\n handleDelete(selected)\n }\n })\n },\n }\n }, [latestForListBox])\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect: listBoxFunctions.handleSelect,\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n })\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onFocus,\n onBlur,\n onKeyPress,\n disabled,\n isExpanded,\n highlighted,\n isComposing,\n isInputEmpty,\n selectedItems,\n deletionButtonRefs,\n focusedIndex,\n selectedItemLength,\n setInputValueIfUncontrolled,\n handleKeyDownListBox,\n })\n\n const functions = useMemo(() => {\n const resetDeletionButtonFocus = () => {\n setFocusedIndex(null)\n }\n\n const handleDelete = listBoxFunctions.handleDelete\n\n const focusPrevDeletionButton = () => {\n if (latest.selectedItemLength === 0) {\n return\n }\n\n if (latest.focusedIndex !== null) {\n const nextIndex = Math.max(latest.focusedIndex - 1, 0)\n\n latest.deletionButtonRefs[nextIndex].current?.focus()\n setFocusedIndex(nextIndex)\n } else if (inputRef.current?.selectionStart === 0) {\n const nextIndex = latest.deletionButtonRefs.length - 1\n\n latest.deletionButtonRefs[nextIndex].current?.focus()\n setFocusedIndex(nextIndex)\n }\n }\n\n const focusNextDeletionButton = () => {\n if (latest.deletionButtonRefs.length === 0 || latest.focusedIndex === null) {\n return\n }\n\n const nextIndex = latest.focusedIndex + 1\n\n if (nextIndex < latest.deletionButtonRefs.length) {\n latest.deletionButtonRefs[nextIndex].current?.focus()\n setFocusedIndex(nextIndex)\n } else {\n setFocusedIndex(null)\n // キー入力が input に影響しないようにフォーカスタイミングを遅らせる\n setTimeout(() => {\n inputRef.current?.focus()\n })\n }\n }\n\n const focus = () => {\n latest.onFocus?.()\n setIsExpanded(true)\n }\n\n const blur = () => {\n if (latest.isExpanded) {\n latest.onBlur?.()\n setIsExpanded(false)\n resetDeletionButtonFocus()\n }\n }\n\n return {\n handleDelete,\n blur,\n handleDelegateKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {\n if (latest.isComposing) return\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n blur()\n } else if (e.key === 'Tab') {\n if (latest.isExpanded) {\n // フォーカスがコンポーネントを抜けるように先に input をフォーカスしておく\n inputRef.current?.focus()\n }\n\n blur()\n } else if (ARROW_LEFT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusPrevDeletionButton()\n } else if (ARROW_RIGHT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusNextDeletionButton()\n } else if (\n e.key === 'Backspace' &&\n latest.isInputEmpty &&\n latest.selectedItems.length > 0 &&\n latest.selectedItems[latest.selectedItems.length - 1].deletable !== false\n ) {\n e.preventDefault()\n e.stopPropagation()\n\n const lastItem = latest.selectedItems[latest.selectedItems.length - 1]\n\n handleDelete(lastItem)\n setHighlighted(true)\n latest.setInputValueIfUncontrolled(innerText(lastItem.label))\n } else {\n e.stopPropagation()\n inputRef.current?.focus()\n resetDeletionButtonFocus()\n }\n\n latest.handleKeyDownListBox(e)\n },\n handleDelegateClick: (e: MouseEvent<HTMLElement>) => {\n if (\n !latest.disabled &&\n !latest.isExpanded &&\n !(e.target as HTMLElement).closest('.smarthr-ui-MultiCombobox-deleteButton')\n ) {\n focus()\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n const handlers = [latest.onChange, latest.onChangeInput].filter((h) => !!h)\n\n handlers.forEach((h) => h(e))\n latest.setInputValueIfUncontrolled(e.currentTarget.value)\n },\n handleFocusInput: () => {\n resetDeletionButtonFocus()\n\n if (!latest.isExpanded) {\n focus()\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (ARROW_UP_AND_DOWN_KEY_REGEX.test(e.key)) {\n // 上下キー入力はリストボックスの activeDescendant の移動に用いるため、input 内では作用させない\n e.preventDefault()\n }\n },\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleDelegateKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n preventDefaultWithPressEnter(e)\n latest.onKeyPress?.(e)\n },\n }\n }, [listBoxFunctions, latest])\n\n useOuterClick(\n useMemo(() => [triggerRef, listBoxRef], [listBoxRef]),\n functions.blur,\n )\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => inputRef.current)\n\n useEffect(() => {\n if (latest.highlighted) {\n setHighlighted(false)\n inputRef.current?.select()\n } else {\n setInputValueIfUncontrolled('')\n }\n }, [selectedItems, setInputValueIfUncontrolled, inputRef, latest])\n\n useEffect(() => {\n if (isExpanded) {\n inputRef.current?.focus()\n }\n }, [isExpanded, selectedItems, isInputControlled, inputRef])\n\n const classNames = useMemo(() => {\n const {\n wrapper,\n inputArea,\n selectedList,\n inputWrapper,\n input,\n placeholderEl,\n suffixWrapper,\n suffixIcon,\n } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputArea: inputArea(),\n selectedList: selectedList(),\n inputWrapper: inputWrapper(),\n input: input(),\n placeholder: placeholderEl(),\n suffixWrapper: suffixWrapper(),\n suffixIcon: suffixIcon(),\n }\n }, [className])\n\n const localized = useLocalize({\n selectedListAriaLabel: {\n id: 'smarthr-ui/MultiCombobox/selectedListAriaLabel',\n defaultText: '選択済みアイテム',\n },\n })\n\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions\n <div\n ref={triggerRef}\n role=\"group\"\n onClick={functions.handleDelegateClick}\n onKeyDown={functions.handleDelegateKeyDown}\n onKeyPress={functions.handleDelegateKeyPress}\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n >\n <Scroller className={classNames.inputArea}>\n <ul\n id={selectedListId}\n aria-label={localized.selectedListAriaLabel}\n className={classNames.selectedList}\n >\n {selectedItems.map((selectedItem, i) => (\n <li key={`${selectedItem.label}-${innerText(selectedItem.value)}`}>\n <MultiSelectedItem\n item={selectedItem}\n disabled={disabled}\n handleDelete={functions.handleDelete}\n enableEllipsis={selectedItemEllipsis}\n buttonRef={deletionButtonRefs[i]}\n />\n </li>\n ))}\n </ul>\n\n <div className={classNames.inputWrapper}>\n <input\n {...rest}\n data-smarthr-ui-input=\"true\"\n type=\"text\"\n name={name}\n value={inputValue}\n disabled={disabled}\n required={required && selectedItems.length === 0}\n ref={inputRef}\n onChange={functions.handleChangeInput}\n onFocus={functions.handleFocusInput}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n autoComplete={autoComplete ?? 'off'}\n tabIndex={0}\n role=\"combobox\"\n aria-activedescendant={activeOption?.id}\n aria-controls={`${listBoxId} ${selectedListId}`}\n aria-haspopup=\"listbox\"\n aria-expanded={isExpanded}\n aria-invalid={error || undefined}\n aria-disabled={disabled}\n aria-autocomplete=\"list\"\n className={classNames.input}\n />\n </div>\n\n {selectedItems.length === 0 && placeholder && !isExpanded && (\n <p className={classNames.placeholder}>{placeholder}</p>\n )}\n </Scroller>\n\n <MemoizedCaretDown disabled={disabled} isExpanded={isExpanded} classNames={classNames} />\n\n <ListBox {...listBoxProps} />\n </div>\n )\n}\n\nexport const MultiCombobox = genericsForwardRef(ActualMultiCombobox)\n\nconst MemoizedCaretDown = memo<{\n disabled: boolean\n isExpanded: boolean\n classNames: {\n suffixWrapper: string\n suffixIcon: string\n }\n}>(({ disabled, isExpanded, classNames }) => {\n const theme = useTheme()\n const caretIconColor = isExpanded\n ? theme.textColor.black\n : disabled\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n return (\n <div className={classNames.suffixWrapper}>\n <FaCaretDownIcon color={caretIconColor} className={classNames.suffixIcon} />\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAEA;AACE;;;AAGF;AAEA;AACA;AACA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;;;;;;;;;;;;AAYC;AACD;AACA;AAEA;;;;;AAKC;AACD;;;;AAIC;AACD;AACA;;;AAGC;AACD;AACD;AACF;AAED;AAkCE;;;;;AAMA;AAEA;;;AAGA;AAEA;;AAEE;;;;AAID;AACD;;AAGA;;AAGE;AACE;;AAGF;AACF;AACA;;;;;;;;;AAWC;AAED;AACE;;AAGE;AACE;;AAEF;AACE;;AASF;;;;AAII;AACF;;AAEJ;;;AAIE;;;;;AAQI;AACE;AACA;;AAGA;;AACK;;;AAGT;;;AAGN;AAEA;;;;;;;;;;AAUC;;;;;;;;;;;;;;;;;;AAmBA;AAED;;;AAGE;AAEA;;AAGE;;;AAIA;AACE;;;;;;;;;AAUJ;;AAGE;;;AAIA;;;;;;;;;AASI;AACF;;AAEJ;;AAGE;;AAEF;;AAGE;AACE;;AAEA;;AAEJ;;;;AAKE;;;;;AAKI;;AACK;AACL;;AAEE;;AAGF;;;;AAGA;;;;AAGA;;AACK;AAEL;AACA;AACA;;;AAKA;;;;;;;AAOA;AACA;;AAGF;;AAEF;;;;AAMI;;;AAGJ;;AAGE;;;;AAIA;AAEA;AACE;;;AAGJ;AACA;AACA;;;;;;;;;AASA;;AAEE;;;AAGN;;;;AAUE;;AAEE;;;;;;;;AAQA;;;AAIJ;;;AAaI;;;;;;;;;AASJ;;AAGE;AACE;AACA;AACD;AACF;;;AAIC;AAQI;AACA;;AA6DR;;AAIA;AAQE;;AAEE;AACA;AACE;AACA;;AAON;;"}
1
+ {"version":3,"file":"MultiCombobox.cjs","sources":["../../../../src/components/Combobox/MultiCombobox/MultiCombobox.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type Ref,\n memo,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useOuterClick } from '../../../hooks/useOuterClick'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { useLocalize } from '../../../intl'\nimport { findDelegateTarget } from '../../../libs/delegate'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { FaCaretDownIcon } from '../../Icon'\nimport { Scroller } from '../../Scroller'\nimport { areItemsEqual } from '../helper'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useMultiOptions } from '../useOptions'\n\nimport { DELETE_BUTTON_SELECTOR, MultiSelectedItem } from './MultiSelectedItem'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテムのリスト\n */\n selectedItems: Array<ComboboxItem<T> & { deletable?: boolean }>\n /**\n * 選択されているアイテムのラベルを省略表示するかどうか\n */\n selectedItemEllipsis?: boolean\n /**\n * テキストボックスの `value` 属性の値。\n * `onChangeInput` と併せて設定することで、テキストボックスの挙動が制御可能になる。\n */\n inputValue?: string\n /**\n * 選択されているアイテムの削除ボタンがクリックされた時に発火するコールバック関数\n */\n onDelete?: (item: ComboboxItem<T>) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItems: Array<ComboboxItem<T>>) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\n /**\n * アイテムが選択されたときに選択済みかどうかを判定するコールバック関数/\n */\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n /**\n * 検索結果が0件の時に表示するコンテンツ\n */\n noResultText?: ReactNode\n}\ntype Props<T> = BaseProps<T> & Omit<ComponentPropsWithoutRef<'input'>, keyof BaseProps<unknown>>\n\nconst NOOP = () => undefined\n\nconst preventDefaultWithPressEnter = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n }\n}\n\nconst ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_LEFT_KEY_REGEX = /^(Arrow)?Left$/\nconst ARROW_RIGHT_KEY_REGEX = /^(Arrow)?Right/\nconst ARROW_UP_AND_DOWN_KEY_REGEX = /^(Arrow)?(Up|Down)$/\n\nconst EMPTY_INPUT_CHANGE_EVENT = {\n currentTarget: { value: '' },\n target: { value: '' },\n} as ChangeEvent<HTMLInputElement>\n\nconst DELETE_BUTTON_CLASSNAME = `.${DELETE_BUTTON_SELECTOR}`\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-MultiCombobox',\n 'shr-box-border shr-inline-flex shr-min-w-[15em] shr-rounded-m shr-border shr-border-solid shr-px-0.5 shr-py-0.25 shr-align-bottom',\n 'contrast-more:shr-border-high-contrast',\n 'has-[[role=combobox][aria-expanded=true]]:shr-focus-indicator',\n 'shr-cursor-text shr-border-default shr-bg-white',\n 'has-[[role=combobox]:disabled]:shr-cursor-not-allowed',\n 'has-[[role=combobox]:disabled]:shr-border-default/50',\n 'has-[[role=combobox]:disabled]:shr-bg-white-darken',\n 'has-[[role=combobox]:disabled]:shr-text-disabled',\n // HINT: disabled と詳細度が同じ [0,2,0] のため、CSS ソース順でこちらを後に置くことで error 時の border 色を優先させる\n 'has-[[role=combobox][aria-invalid]]:shr-border-danger',\n ],\n inputArea: 'shr-flex shr-flex-1 shr-flex-wrap shr-gap-0.5',\n selectedList:\n 'smarthr-ui-MultiCombobox-selectedList shr-contents shr-list-none [&_li]:shr-min-w-0',\n inputWrapper: [\n 'shr-flex shr-flex-1 shr-items-center',\n 'has-[[role=combobox][aria-expanded=false]]:shr-pointer-events-none',\n 'has-[[role=combobox][aria-expanded=false]]:shr-absolute',\n 'has-[[role=combobox][aria-expanded=false]]:shr-opacity-0',\n ],\n input: [\n 'smarthr-ui-MultiCombobox-input',\n 'shr-w-full shr-min-w-[5em] shr-border-none shr-text-base shr-text-black shr-outline-none shr-outline-0',\n 'disabled:shr-hidden',\n ],\n placeholderEl: 'smarthr-ui-MultiCombobox-placeholder shr-my-0 shr-self-center',\n suffixWrapper: [\n 'shr-relative -shr-me-0.5 shr-ms-0.5 shr-p-0.5',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n suffixIcon: 'shr-block',\n },\n})\n\nconst ActualMultiCombobox = <T,>(\n {\n items,\n selectedItems,\n name,\n disabled = false,\n required = false,\n error = false,\n creatable = false,\n placeholder = '',\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n selectedItemEllipsis,\n width,\n dropdownWidth = 'auto',\n inputValue: controlledInputValue,\n className,\n onChange,\n onChangeInput,\n onAdd,\n onDelete,\n onSelect,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n isItemSelected,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const triggerRef = useRef<HTMLDivElement>(null)\n const [isExpanded, setIsExpanded] = useState(false)\n const [highlighted, setHighlighted] = useState(false)\n const [uncontrolledInputValue, setUncontrolledInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n\n const selectedListId = useId()\n\n const isInputControlled = controlledInputValue !== undefined\n const inputValue = isInputControlled ? controlledInputValue : uncontrolledInputValue\n const setInputValueIfUncontrolled = isInputControlled ? NOOP : setUncontrolledInputValue\n const isInputEmpty = !inputValue\n\n const { options } = useMultiOptions({\n items,\n selected: selectedItems,\n creatable,\n inputValue,\n isItemSelected,\n })\n const inputRef = useRef<HTMLInputElement>(null)\n\n // eslint-disable-next-line local-rules/best-practice-for-use-latest\n const latestForListBox = useLatest({\n onDelete,\n onChangeSelected,\n onSelect,\n onChangeInput,\n selectedItems,\n })\n\n const listBoxFunctions = useMemo(() => {\n const handleDelete = (item: ComboboxItem<T>) => {\n const handlers: Array<(deletingItem: ComboboxItem<T>) => void> = []\n\n if (latestForListBox.onDelete) {\n handlers.push((deletingItem: ComboboxItem<T>) => latestForListBox.onDelete!(deletingItem))\n }\n if (latestForListBox.onChangeSelected) {\n handlers.push((deletingItem: ComboboxItem<T>) =>\n latestForListBox.onChangeSelected!(\n latestForListBox.selectedItems.filter(\n (selected) => !areItemsEqual(selected, deletingItem),\n ),\n ),\n )\n }\n\n if (handlers.length > 0) {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n handlers.forEach((h) => h(item))\n })\n }\n }\n\n return {\n handleDelete,\n handleSelect: (selected: ComboboxItem<T>) => {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n const matchedSelectedItem = latestForListBox.selectedItems.find((item) =>\n areItemsEqual(item, selected),\n )\n\n if (matchedSelectedItem === undefined) {\n latestForListBox.onSelect?.(selected)\n latestForListBox.onChangeSelected?.(latestForListBox.selectedItems.concat(selected))\n\n // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n latestForListBox.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n } else if (matchedSelectedItem.deletable !== false) {\n handleDelete(selected)\n }\n })\n },\n }\n }, [latestForListBox])\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect: listBoxFunctions.handleSelect,\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n })\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onFocus,\n onBlur,\n onKeyPress,\n disabled,\n isExpanded,\n highlighted,\n isComposing,\n isInputEmpty,\n selectedItems,\n setInputValueIfUncontrolled,\n handleKeyDownListBox,\n })\n\n const functions = useMemo(() => {\n const handleDelete = listBoxFunctions.handleDelete\n\n const getDeletionButtons = () => {\n if (triggerRef.current) {\n const buttons =\n triggerRef.current.querySelectorAll<HTMLButtonElement>(DELETE_BUTTON_CLASSNAME)\n\n if (buttons.length > 0) {\n const actualButtons = Array.from(buttons)\n\n return {\n buttons: actualButtons,\n currentIndex: actualButtons.indexOf(document.activeElement as HTMLButtonElement),\n }\n }\n }\n\n return null\n }\n\n const focusPrevDeletionButton = () => {\n const result = getDeletionButtons()\n\n if (!result) return\n\n const { buttons, currentIndex } = result\n\n if (currentIndex !== -1) {\n buttons[Math.max(currentIndex - 1, 0)].focus()\n } else if (inputRef.current?.selectionStart === 0) {\n buttons[buttons.length - 1].focus()\n }\n }\n\n const focusNextDeletionButton = () => {\n const result = getDeletionButtons()\n\n if (!result) return\n\n const { buttons, currentIndex } = result\n\n if (currentIndex === -1) return\n\n const nextIndex = currentIndex + 1\n\n if (nextIndex < buttons.length) {\n buttons[nextIndex].focus()\n } else {\n // キー入力が input に影響しないようにフォーカスタイミングを遅らせる\n setTimeout(() => {\n inputRef.current?.focus()\n })\n }\n }\n\n const focus = () => {\n latest.onFocus?.()\n setIsExpanded(true)\n }\n\n const blur = () => {\n if (latest.isExpanded) {\n latest.onBlur?.()\n setIsExpanded(false)\n }\n }\n\n return {\n handleDelete,\n blur,\n handleDelegateKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {\n if (latest.isComposing) return\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n blur()\n } else if (e.key === 'Tab') {\n if (latest.isExpanded) {\n // フォーカスがコンポーネントを抜けるように先に input をフォーカスしておく\n inputRef.current?.focus()\n }\n\n blur()\n } else if (ARROW_LEFT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusPrevDeletionButton()\n } else if (ARROW_RIGHT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusNextDeletionButton()\n } else if (\n e.key === 'Backspace' &&\n latest.isInputEmpty &&\n latest.selectedItems.length > 0 &&\n latest.selectedItems[latest.selectedItems.length - 1].deletable !== false\n ) {\n e.preventDefault()\n e.stopPropagation()\n\n const lastItem = latest.selectedItems[latest.selectedItems.length - 1]\n\n handleDelete(lastItem)\n setHighlighted(true)\n latest.setInputValueIfUncontrolled(innerText(lastItem.label))\n } else {\n e.stopPropagation()\n inputRef.current?.focus()\n }\n\n latest.handleKeyDownListBox(e)\n },\n handleDelegateClick: (e: MouseEvent<HTMLElement>) => {\n if (\n !latest.disabled &&\n !latest.isExpanded &&\n !findDelegateTarget(e, DELETE_BUTTON_CLASSNAME)\n ) {\n focus()\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n const handlers = [latest.onChange, latest.onChangeInput].filter((h) => !!h)\n\n handlers.forEach((h) => h(e))\n latest.setInputValueIfUncontrolled(e.currentTarget.value)\n },\n handleFocusInput: () => {\n if (!latest.isExpanded) {\n focus()\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (ARROW_UP_AND_DOWN_KEY_REGEX.test(e.key)) {\n // 上下キー入力はリストボックスの activeDescendant の移動に用いるため、input 内では作用させない\n e.preventDefault()\n }\n },\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleDelegateKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n preventDefaultWithPressEnter(e)\n latest.onKeyPress?.(e)\n },\n }\n }, [listBoxFunctions, latest])\n\n useOuterClick(\n useMemo(() => [triggerRef, listBoxRef], [listBoxRef]),\n functions.blur,\n )\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => inputRef.current)\n\n useEffect(() => {\n if (latest.highlighted) {\n setHighlighted(false)\n inputRef.current?.select()\n } else {\n setInputValueIfUncontrolled('')\n }\n }, [selectedItems, setInputValueIfUncontrolled, inputRef, latest])\n\n useEffect(() => {\n if (isExpanded) {\n inputRef.current?.focus()\n }\n }, [isExpanded, selectedItems, isInputControlled, inputRef])\n\n const classNames = useMemo(() => {\n const {\n wrapper,\n inputArea,\n selectedList,\n inputWrapper,\n input,\n placeholderEl,\n suffixWrapper,\n suffixIcon,\n } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputArea: inputArea(),\n selectedList: selectedList(),\n inputWrapper: inputWrapper(),\n input: input(),\n placeholder: placeholderEl(),\n suffixWrapper: suffixWrapper(),\n suffixIcon: suffixIcon(),\n }\n }, [className])\n\n const localized = useLocalize({\n selectedListAriaLabel: {\n id: 'smarthr-ui/MultiCombobox/selectedListAriaLabel',\n defaultText: '選択済みアイテム',\n },\n })\n\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions\n <div\n ref={triggerRef}\n role=\"group\"\n onClick={functions.handleDelegateClick}\n onKeyDown={functions.handleDelegateKeyDown}\n onKeyPress={functions.handleDelegateKeyPress}\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n >\n <Scroller className={classNames.inputArea}>\n <ul\n id={selectedListId}\n aria-label={localized.selectedListAriaLabel}\n className={classNames.selectedList}\n >\n {selectedItems.map((selectedItem) => (\n <li key={`${selectedItem.label}-${innerText(selectedItem.value)}`}>\n <MultiSelectedItem\n item={selectedItem}\n disabled={disabled}\n handleDelete={functions.handleDelete}\n enableEllipsis={selectedItemEllipsis}\n />\n </li>\n ))}\n </ul>\n\n <div className={classNames.inputWrapper}>\n <input\n {...rest}\n data-smarthr-ui-input=\"true\"\n type=\"text\"\n name={name}\n value={inputValue}\n disabled={disabled}\n required={required && selectedItems.length === 0}\n ref={inputRef}\n onChange={functions.handleChangeInput}\n onFocus={functions.handleFocusInput}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n autoComplete={autoComplete ?? 'off'}\n tabIndex={0}\n role=\"combobox\"\n aria-activedescendant={activeOption?.id}\n aria-controls={`${listBoxId} ${selectedListId}`}\n aria-haspopup=\"listbox\"\n aria-expanded={isExpanded}\n aria-invalid={error || undefined}\n aria-disabled={disabled}\n aria-autocomplete=\"list\"\n className={classNames.input}\n />\n </div>\n\n {selectedItems.length === 0 && placeholder && !isExpanded && (\n <p className={classNames.placeholder}>{placeholder}</p>\n )}\n </Scroller>\n\n <MemoizedCaretDown disabled={disabled} isExpanded={isExpanded} classNames={classNames} />\n\n <ListBox {...listBoxProps} />\n </div>\n )\n}\n\nexport const MultiCombobox = genericsForwardRef(ActualMultiCombobox)\n\nconst MemoizedCaretDown = memo<{\n disabled: boolean\n isExpanded: boolean\n classNames: {\n suffixWrapper: string\n suffixIcon: string\n }\n}>(({ disabled, isExpanded, classNames }) => {\n const theme = useTheme()\n const caretIconColor = isExpanded\n ? theme.textColor.black\n : disabled\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n return (\n <div className={classNames.suffixWrapper}>\n <FaCaretDownIcon color={caretIconColor} className={classNames.suffixIcon} />\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAEA;AACE;;;AAGF;AAEA;AACA;AACA;AACA;AAEA;AACE;AACA;;AAGF;AAEA;AACE;AACE;;;;;;;;;;;;AAYC;AACD;AACA;AAEA;;;;;AAKC;AACD;;;;AAIC;AACD;AACA;;;AAGC;AACD;AACD;AACF;AAED;AAkCE;;;;;AAMA;AAEA;;;AAGA;AAEA;;AAEE;;;;AAID;AACD;;;;;;;;AASC;AAED;AACE;;AAGE;AACE;;AAEF;AACE;;AASF;;;;AAII;AACF;;AAEJ;;;AAIE;;;;;AAQI;AACE;AACA;;AAGA;;AACK;;;AAGT;;;AAGN;AAEA;;;;;;;;;;AAUC;;;;;;;;;;;;;;;AAgBA;AAED;AACE;;AAGE;;AAIE;;;AAII;;;;;AAMN;AACF;;AAGE;AAEA;;AAEA;AAEA;AACE;;;;;AAIJ;;AAGE;AAEA;;AAEA;;;AAIA;AAEA;AACE;;;;;AAIE;AACF;;AAEJ;;AAGE;;AAEF;;AAGE;AACE;;;AAGJ;;;;AAKE;;;;;AAKI;;AACK;AACL;;AAEE;;AAGF;;;;AAGA;;;;AAGA;;AACK;AAEL;AACA;AACA;;;AAKA;;;;;;;AAOA;;AAGF;;AAEF;;;AAII;AAEA;;;AAGJ;;AAGE;;;;AAIA;AACE;;;AAGJ;AACA;AACA;;;;;;;;;AASA;;AAEE;;;AAGN;;;;AAUE;;AAEE;;;;;;;;AAQA;;;AAIJ;;;AAaI;;;;;;;;;AASJ;;AAGE;AACE;AACA;AACD;AACF;;;AAIC;AAQI;AACA;;AA4DR;;AAIA;AAQE;;AAEE;AACA;AACE;AACA;;AAON;;"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { memo, useRef, useState, useId, useMemo, createRef, useImperativeHandle, useEffect } from 'react';
3
+ import { memo, useRef, useState, useId, useMemo, useImperativeHandle, useEffect } from 'react';
4
4
  import innerText from '../../../_virtual/index3.js';
5
5
  import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
6
6
  import { useLatest } from '../../../hooks/useLatest.js';
@@ -12,6 +12,7 @@ import 'react-intl';
12
12
  import '../../../intl/Localizer.js';
13
13
  import '../../../_virtual/dayjs.min.js';
14
14
  import '../../../intl/TimeFormatter.js';
15
+ import { findDelegateTarget } from '../../../libs/delegate.js';
15
16
  import { genericsForwardRef } from '../../../libs/util.js';
16
17
  import { FaCaretDownIcon } from '../../Icon/FaIcon.js';
17
18
  import '../../Icon/generateIcon.js';
@@ -20,7 +21,7 @@ import { Scroller } from '../../Scroller/Scroller.js';
20
21
  import { areItemsEqual } from '../helper.js';
21
22
  import { useListbox, ListBox } from '../useListbox.js';
22
23
  import { useMultiOptions } from '../useOptions.js';
23
- import { MultiSelectedItem } from './MultiSelectedItem.js';
24
+ import { DELETE_BUTTON_SELECTOR, MultiSelectedItem } from './MultiSelectedItem.js';
24
25
 
25
26
  const NOOP = () => undefined;
26
27
  const preventDefaultWithPressEnter = (e) => {
@@ -36,6 +37,7 @@ const EMPTY_INPUT_CHANGE_EVENT = {
36
37
  currentTarget: { value: '' },
37
38
  target: { value: '' },
38
39
  };
40
+ const DELETE_BUTTON_CLASSNAME = `.${DELETE_BUTTON_SELECTOR}`;
39
41
  const classNameGenerator = ce({
40
42
  slots: {
41
43
  wrapper: [
@@ -90,17 +92,7 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
90
92
  inputValue,
91
93
  isItemSelected,
92
94
  });
93
- const selectedItemLength = selectedItems.length;
94
- // TODO: 完全にcreateRefを作り直すのではなく、差分更新させたい
95
- const deletionButtonRefs = useMemo(() => {
96
- const refs = [];
97
- for (let i = 0; i < selectedItemLength; i++) {
98
- refs[i] = createRef();
99
- }
100
- return refs;
101
- }, [selectedItemLength]);
102
95
  const inputRef = useRef(null);
103
- const [focusedIndex, setFocusedIndex] = useState(null);
104
96
  // eslint-disable-next-line local-rules/best-practice-for-use-latest
105
97
  const latestForListBox = useLatest({
106
98
  onDelete,
@@ -169,43 +161,48 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
169
161
  isComposing,
170
162
  isInputEmpty,
171
163
  selectedItems,
172
- deletionButtonRefs,
173
- focusedIndex,
174
- selectedItemLength,
175
164
  setInputValueIfUncontrolled,
176
165
  handleKeyDownListBox,
177
166
  });
178
167
  const functions = useMemo(() => {
179
- const resetDeletionButtonFocus = () => {
180
- setFocusedIndex(null);
181
- };
182
168
  const handleDelete = listBoxFunctions.handleDelete;
169
+ const getDeletionButtons = () => {
170
+ if (triggerRef.current) {
171
+ const buttons = triggerRef.current.querySelectorAll(DELETE_BUTTON_CLASSNAME);
172
+ if (buttons.length > 0) {
173
+ const actualButtons = Array.from(buttons);
174
+ return {
175
+ buttons: actualButtons,
176
+ currentIndex: actualButtons.indexOf(document.activeElement),
177
+ };
178
+ }
179
+ }
180
+ return null;
181
+ };
183
182
  const focusPrevDeletionButton = () => {
184
- if (latest.selectedItemLength === 0) {
183
+ const result = getDeletionButtons();
184
+ if (!result)
185
185
  return;
186
- }
187
- if (latest.focusedIndex !== null) {
188
- const nextIndex = Math.max(latest.focusedIndex - 1, 0);
189
- latest.deletionButtonRefs[nextIndex].current?.focus();
190
- setFocusedIndex(nextIndex);
186
+ const { buttons, currentIndex } = result;
187
+ if (currentIndex !== -1) {
188
+ buttons[Math.max(currentIndex - 1, 0)].focus();
191
189
  }
192
190
  else if (inputRef.current?.selectionStart === 0) {
193
- const nextIndex = latest.deletionButtonRefs.length - 1;
194
- latest.deletionButtonRefs[nextIndex].current?.focus();
195
- setFocusedIndex(nextIndex);
191
+ buttons[buttons.length - 1].focus();
196
192
  }
197
193
  };
198
194
  const focusNextDeletionButton = () => {
199
- if (latest.deletionButtonRefs.length === 0 || latest.focusedIndex === null) {
195
+ const result = getDeletionButtons();
196
+ if (!result)
200
197
  return;
201
- }
202
- const nextIndex = latest.focusedIndex + 1;
203
- if (nextIndex < latest.deletionButtonRefs.length) {
204
- latest.deletionButtonRefs[nextIndex].current?.focus();
205
- setFocusedIndex(nextIndex);
198
+ const { buttons, currentIndex } = result;
199
+ if (currentIndex === -1)
200
+ return;
201
+ const nextIndex = currentIndex + 1;
202
+ if (nextIndex < buttons.length) {
203
+ buttons[nextIndex].focus();
206
204
  }
207
205
  else {
208
- setFocusedIndex(null);
209
206
  // キー入力が input に影響しないようにフォーカスタイミングを遅らせる
210
207
  setTimeout(() => {
211
208
  inputRef.current?.focus();
@@ -220,7 +217,6 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
220
217
  if (latest.isExpanded) {
221
218
  latest.onBlur?.();
222
219
  setIsExpanded(false);
223
- resetDeletionButtonFocus();
224
220
  }
225
221
  };
226
222
  return {
@@ -262,14 +258,13 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
262
258
  else {
263
259
  e.stopPropagation();
264
260
  inputRef.current?.focus();
265
- resetDeletionButtonFocus();
266
261
  }
267
262
  latest.handleKeyDownListBox(e);
268
263
  },
269
264
  handleDelegateClick: (e) => {
270
265
  if (!latest.disabled &&
271
266
  !latest.isExpanded &&
272
- !e.target.closest('.smarthr-ui-MultiCombobox-deleteButton')) {
267
+ !findDelegateTarget(e, DELETE_BUTTON_CLASSNAME)) {
273
268
  focus();
274
269
  }
275
270
  },
@@ -279,7 +274,6 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
279
274
  latest.setInputValueIfUncontrolled(e.currentTarget.value);
280
275
  },
281
276
  handleFocusInput: () => {
282
- resetDeletionButtonFocus();
283
277
  if (!latest.isExpanded) {
284
278
  focus();
285
279
  }
@@ -341,7 +335,7 @@ const ActualMultiCombobox = ({ items, selectedItems, name, disabled = false, req
341
335
  jsxs("div", { ref: triggerRef, role: "group", onClick: functions.handleDelegateClick, onKeyDown: functions.handleDelegateKeyDown, onKeyPress: functions.handleDelegateKeyPress, className: classNames.wrapper, style: {
342
336
  ...style,
343
337
  width: typeof width === 'number' ? `${width}px` : width,
344
- }, children: [jsxs(Scroller, { className: classNames.inputArea, children: [jsx("ul", { id: selectedListId, "aria-label": localized.selectedListAriaLabel, className: classNames.selectedList, children: selectedItems.map((selectedItem, i) => (jsx("li", { children: jsx(MultiSelectedItem, { item: selectedItem, disabled: disabled, handleDelete: functions.handleDelete, enableEllipsis: selectedItemEllipsis, buttonRef: deletionButtonRefs[i] }) }, `${selectedItem.label}-${innerText(selectedItem.value)}`))) }), jsx("div", { className: classNames.inputWrapper, children: jsx("input", { ...rest, "data-smarthr-ui-input": "true", type: "text", name: name, value: inputValue, disabled: disabled, required: required && selectedItems.length === 0, ref: inputRef, onChange: functions.handleChangeInput, onFocus: functions.handleFocusInput, onCompositionStart: functions.handleCompositionStart, onCompositionEnd: functions.handleCompositionEnd, onKeyDown: functions.handleKeyDownInput, autoComplete: autoComplete ?? 'off', tabIndex: 0, role: "combobox", "aria-activedescendant": activeOption?.id, "aria-controls": `${listBoxId} ${selectedListId}`, "aria-haspopup": "listbox", "aria-expanded": isExpanded, "aria-invalid": error || undefined, "aria-disabled": disabled, "aria-autocomplete": "list", className: classNames.input }) }), selectedItems.length === 0 && placeholder && !isExpanded && (jsx("p", { className: classNames.placeholder, children: placeholder }))] }), jsx(MemoizedCaretDown, { disabled: disabled, isExpanded: isExpanded, classNames: classNames }), jsx(ListBox, { ...listBoxProps })] }));
338
+ }, children: [jsxs(Scroller, { className: classNames.inputArea, children: [jsx("ul", { id: selectedListId, "aria-label": localized.selectedListAriaLabel, className: classNames.selectedList, children: selectedItems.map((selectedItem) => (jsx("li", { children: jsx(MultiSelectedItem, { item: selectedItem, disabled: disabled, handleDelete: functions.handleDelete, enableEllipsis: selectedItemEllipsis }) }, `${selectedItem.label}-${innerText(selectedItem.value)}`))) }), jsx("div", { className: classNames.inputWrapper, children: jsx("input", { ...rest, "data-smarthr-ui-input": "true", type: "text", name: name, value: inputValue, disabled: disabled, required: required && selectedItems.length === 0, ref: inputRef, onChange: functions.handleChangeInput, onFocus: functions.handleFocusInput, onCompositionStart: functions.handleCompositionStart, onCompositionEnd: functions.handleCompositionEnd, onKeyDown: functions.handleKeyDownInput, autoComplete: autoComplete ?? 'off', tabIndex: 0, role: "combobox", "aria-activedescendant": activeOption?.id, "aria-controls": `${listBoxId} ${selectedListId}`, "aria-haspopup": "listbox", "aria-expanded": isExpanded, "aria-invalid": error || undefined, "aria-disabled": disabled, "aria-autocomplete": "list", className: classNames.input }) }), selectedItems.length === 0 && placeholder && !isExpanded && (jsx("p", { className: classNames.placeholder, children: placeholder }))] }), jsx(MemoizedCaretDown, { disabled: disabled, isExpanded: isExpanded, classNames: classNames }), jsx(ListBox, { ...listBoxProps })] }));
345
339
  };
346
340
  const MultiCombobox = genericsForwardRef(ActualMultiCombobox);
347
341
  const MemoizedCaretDown = memo(({ disabled, isExpanded, classNames }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"MultiCombobox.js","sources":["../../../../src/components/Combobox/MultiCombobox/MultiCombobox.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type Ref,\n createRef,\n memo,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useOuterClick } from '../../../hooks/useOuterClick'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { useLocalize } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { FaCaretDownIcon } from '../../Icon'\nimport { Scroller } from '../../Scroller'\nimport { areItemsEqual } from '../helper'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useMultiOptions } from '../useOptions'\n\nimport { MultiSelectedItem } from './MultiSelectedItem'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテムのリスト\n */\n selectedItems: Array<ComboboxItem<T> & { deletable?: boolean }>\n /**\n * 選択されているアイテムのラベルを省略表示するかどうか\n */\n selectedItemEllipsis?: boolean\n /**\n * テキストボックスの `value` 属性の値。\n * `onChangeInput` と併せて設定することで、テキストボックスの挙動が制御可能になる。\n */\n inputValue?: string\n /**\n * 選択されているアイテムの削除ボタンがクリックされた時に発火するコールバック関数\n */\n onDelete?: (item: ComboboxItem<T>) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItems: Array<ComboboxItem<T>>) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\n /**\n * アイテムが選択されたときに選択済みかどうかを判定するコールバック関数/\n */\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n /**\n * 検索結果が0件の時に表示するコンテンツ\n */\n noResultText?: ReactNode\n}\ntype Props<T> = BaseProps<T> & Omit<ComponentPropsWithoutRef<'input'>, keyof BaseProps<unknown>>\n\nconst NOOP = () => undefined\n\nconst preventDefaultWithPressEnter = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n }\n}\n\nconst ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_LEFT_KEY_REGEX = /^(Arrow)?Left$/\nconst ARROW_RIGHT_KEY_REGEX = /^(Arrow)?Right/\nconst ARROW_UP_AND_DOWN_KEY_REGEX = /^(Arrow)?(Up|Down)$/\n\nconst EMPTY_INPUT_CHANGE_EVENT = {\n currentTarget: { value: '' },\n target: { value: '' },\n} as ChangeEvent<HTMLInputElement>\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-MultiCombobox',\n 'shr-box-border shr-inline-flex shr-min-w-[15em] shr-rounded-m shr-border shr-border-solid shr-px-0.5 shr-py-0.25 shr-align-bottom',\n 'contrast-more:shr-border-high-contrast',\n 'has-[[role=combobox][aria-expanded=true]]:shr-focus-indicator',\n 'shr-cursor-text shr-border-default shr-bg-white',\n 'has-[[role=combobox]:disabled]:shr-cursor-not-allowed',\n 'has-[[role=combobox]:disabled]:shr-border-default/50',\n 'has-[[role=combobox]:disabled]:shr-bg-white-darken',\n 'has-[[role=combobox]:disabled]:shr-text-disabled',\n // HINT: disabled と詳細度が同じ [0,2,0] のため、CSS ソース順でこちらを後に置くことで error 時の border 色を優先させる\n 'has-[[role=combobox][aria-invalid]]:shr-border-danger',\n ],\n inputArea: 'shr-flex shr-flex-1 shr-flex-wrap shr-gap-0.5',\n selectedList:\n 'smarthr-ui-MultiCombobox-selectedList shr-contents shr-list-none [&_li]:shr-min-w-0',\n inputWrapper: [\n 'shr-flex shr-flex-1 shr-items-center',\n 'has-[[role=combobox][aria-expanded=false]]:shr-pointer-events-none',\n 'has-[[role=combobox][aria-expanded=false]]:shr-absolute',\n 'has-[[role=combobox][aria-expanded=false]]:shr-opacity-0',\n ],\n input: [\n 'smarthr-ui-MultiCombobox-input',\n 'shr-w-full shr-min-w-[5em] shr-border-none shr-text-base shr-text-black shr-outline-none shr-outline-0',\n 'disabled:shr-hidden',\n ],\n placeholderEl: 'smarthr-ui-MultiCombobox-placeholder shr-my-0 shr-self-center',\n suffixWrapper: [\n 'shr-relative -shr-me-0.5 shr-ms-0.5 shr-p-0.5',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n suffixIcon: 'shr-block',\n },\n})\n\nconst ActualMultiCombobox = <T,>(\n {\n items,\n selectedItems,\n name,\n disabled = false,\n required = false,\n error = false,\n creatable = false,\n placeholder = '',\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n selectedItemEllipsis,\n width,\n dropdownWidth = 'auto',\n inputValue: controlledInputValue,\n className,\n onChange,\n onChangeInput,\n onAdd,\n onDelete,\n onSelect,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n isItemSelected,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const triggerRef = useRef<HTMLDivElement>(null)\n const [isExpanded, setIsExpanded] = useState(false)\n const [highlighted, setHighlighted] = useState(false)\n const [uncontrolledInputValue, setUncontrolledInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n\n const selectedListId = useId()\n\n const isInputControlled = controlledInputValue !== undefined\n const inputValue = isInputControlled ? controlledInputValue : uncontrolledInputValue\n const setInputValueIfUncontrolled = isInputControlled ? NOOP : setUncontrolledInputValue\n const isInputEmpty = !inputValue\n\n const { options } = useMultiOptions({\n items,\n selected: selectedItems,\n creatable,\n inputValue,\n isItemSelected,\n })\n const selectedItemLength = selectedItems.length\n\n // TODO: 完全にcreateRefを作り直すのではなく、差分更新させたい\n const deletionButtonRefs = useMemo(() => {\n const refs: Array<ReturnType<typeof createRef<HTMLButtonElement>>> = []\n\n for (let i = 0; i < selectedItemLength; i++) {\n refs[i] = createRef<HTMLButtonElement>()\n }\n\n return refs\n }, [selectedItemLength])\n const inputRef = useRef<HTMLInputElement>(null)\n\n const [focusedIndex, setFocusedIndex] = useState<number | null>(null)\n\n // eslint-disable-next-line local-rules/best-practice-for-use-latest\n const latestForListBox = useLatest({\n onDelete,\n onChangeSelected,\n onSelect,\n onChangeInput,\n selectedItems,\n })\n\n const listBoxFunctions = useMemo(() => {\n const handleDelete = (item: ComboboxItem<T>) => {\n const handlers: Array<(deletingItem: ComboboxItem<T>) => void> = []\n\n if (latestForListBox.onDelete) {\n handlers.push((deletingItem: ComboboxItem<T>) => latestForListBox.onDelete!(deletingItem))\n }\n if (latestForListBox.onChangeSelected) {\n handlers.push((deletingItem: ComboboxItem<T>) =>\n latestForListBox.onChangeSelected!(\n latestForListBox.selectedItems.filter(\n (selected) => !areItemsEqual(selected, deletingItem),\n ),\n ),\n )\n }\n\n if (handlers.length > 0) {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n handlers.forEach((h) => h(item))\n })\n }\n }\n\n return {\n handleDelete,\n handleSelect: (selected: ComboboxItem<T>) => {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n const matchedSelectedItem = latestForListBox.selectedItems.find((item) =>\n areItemsEqual(item, selected),\n )\n\n if (matchedSelectedItem === undefined) {\n latestForListBox.onSelect?.(selected)\n latestForListBox.onChangeSelected?.(latestForListBox.selectedItems.concat(selected))\n\n // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n latestForListBox.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n } else if (matchedSelectedItem.deletable !== false) {\n handleDelete(selected)\n }\n })\n },\n }\n }, [latestForListBox])\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect: listBoxFunctions.handleSelect,\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n })\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onFocus,\n onBlur,\n onKeyPress,\n disabled,\n isExpanded,\n highlighted,\n isComposing,\n isInputEmpty,\n selectedItems,\n deletionButtonRefs,\n focusedIndex,\n selectedItemLength,\n setInputValueIfUncontrolled,\n handleKeyDownListBox,\n })\n\n const functions = useMemo(() => {\n const resetDeletionButtonFocus = () => {\n setFocusedIndex(null)\n }\n\n const handleDelete = listBoxFunctions.handleDelete\n\n const focusPrevDeletionButton = () => {\n if (latest.selectedItemLength === 0) {\n return\n }\n\n if (latest.focusedIndex !== null) {\n const nextIndex = Math.max(latest.focusedIndex - 1, 0)\n\n latest.deletionButtonRefs[nextIndex].current?.focus()\n setFocusedIndex(nextIndex)\n } else if (inputRef.current?.selectionStart === 0) {\n const nextIndex = latest.deletionButtonRefs.length - 1\n\n latest.deletionButtonRefs[nextIndex].current?.focus()\n setFocusedIndex(nextIndex)\n }\n }\n\n const focusNextDeletionButton = () => {\n if (latest.deletionButtonRefs.length === 0 || latest.focusedIndex === null) {\n return\n }\n\n const nextIndex = latest.focusedIndex + 1\n\n if (nextIndex < latest.deletionButtonRefs.length) {\n latest.deletionButtonRefs[nextIndex].current?.focus()\n setFocusedIndex(nextIndex)\n } else {\n setFocusedIndex(null)\n // キー入力が input に影響しないようにフォーカスタイミングを遅らせる\n setTimeout(() => {\n inputRef.current?.focus()\n })\n }\n }\n\n const focus = () => {\n latest.onFocus?.()\n setIsExpanded(true)\n }\n\n const blur = () => {\n if (latest.isExpanded) {\n latest.onBlur?.()\n setIsExpanded(false)\n resetDeletionButtonFocus()\n }\n }\n\n return {\n handleDelete,\n blur,\n handleDelegateKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {\n if (latest.isComposing) return\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n blur()\n } else if (e.key === 'Tab') {\n if (latest.isExpanded) {\n // フォーカスがコンポーネントを抜けるように先に input をフォーカスしておく\n inputRef.current?.focus()\n }\n\n blur()\n } else if (ARROW_LEFT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusPrevDeletionButton()\n } else if (ARROW_RIGHT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusNextDeletionButton()\n } else if (\n e.key === 'Backspace' &&\n latest.isInputEmpty &&\n latest.selectedItems.length > 0 &&\n latest.selectedItems[latest.selectedItems.length - 1].deletable !== false\n ) {\n e.preventDefault()\n e.stopPropagation()\n\n const lastItem = latest.selectedItems[latest.selectedItems.length - 1]\n\n handleDelete(lastItem)\n setHighlighted(true)\n latest.setInputValueIfUncontrolled(innerText(lastItem.label))\n } else {\n e.stopPropagation()\n inputRef.current?.focus()\n resetDeletionButtonFocus()\n }\n\n latest.handleKeyDownListBox(e)\n },\n handleDelegateClick: (e: MouseEvent<HTMLElement>) => {\n if (\n !latest.disabled &&\n !latest.isExpanded &&\n !(e.target as HTMLElement).closest('.smarthr-ui-MultiCombobox-deleteButton')\n ) {\n focus()\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n const handlers = [latest.onChange, latest.onChangeInput].filter((h) => !!h)\n\n handlers.forEach((h) => h(e))\n latest.setInputValueIfUncontrolled(e.currentTarget.value)\n },\n handleFocusInput: () => {\n resetDeletionButtonFocus()\n\n if (!latest.isExpanded) {\n focus()\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (ARROW_UP_AND_DOWN_KEY_REGEX.test(e.key)) {\n // 上下キー入力はリストボックスの activeDescendant の移動に用いるため、input 内では作用させない\n e.preventDefault()\n }\n },\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleDelegateKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n preventDefaultWithPressEnter(e)\n latest.onKeyPress?.(e)\n },\n }\n }, [listBoxFunctions, latest])\n\n useOuterClick(\n useMemo(() => [triggerRef, listBoxRef], [listBoxRef]),\n functions.blur,\n )\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => inputRef.current)\n\n useEffect(() => {\n if (latest.highlighted) {\n setHighlighted(false)\n inputRef.current?.select()\n } else {\n setInputValueIfUncontrolled('')\n }\n }, [selectedItems, setInputValueIfUncontrolled, inputRef, latest])\n\n useEffect(() => {\n if (isExpanded) {\n inputRef.current?.focus()\n }\n }, [isExpanded, selectedItems, isInputControlled, inputRef])\n\n const classNames = useMemo(() => {\n const {\n wrapper,\n inputArea,\n selectedList,\n inputWrapper,\n input,\n placeholderEl,\n suffixWrapper,\n suffixIcon,\n } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputArea: inputArea(),\n selectedList: selectedList(),\n inputWrapper: inputWrapper(),\n input: input(),\n placeholder: placeholderEl(),\n suffixWrapper: suffixWrapper(),\n suffixIcon: suffixIcon(),\n }\n }, [className])\n\n const localized = useLocalize({\n selectedListAriaLabel: {\n id: 'smarthr-ui/MultiCombobox/selectedListAriaLabel',\n defaultText: '選択済みアイテム',\n },\n })\n\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions\n <div\n ref={triggerRef}\n role=\"group\"\n onClick={functions.handleDelegateClick}\n onKeyDown={functions.handleDelegateKeyDown}\n onKeyPress={functions.handleDelegateKeyPress}\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n >\n <Scroller className={classNames.inputArea}>\n <ul\n id={selectedListId}\n aria-label={localized.selectedListAriaLabel}\n className={classNames.selectedList}\n >\n {selectedItems.map((selectedItem, i) => (\n <li key={`${selectedItem.label}-${innerText(selectedItem.value)}`}>\n <MultiSelectedItem\n item={selectedItem}\n disabled={disabled}\n handleDelete={functions.handleDelete}\n enableEllipsis={selectedItemEllipsis}\n buttonRef={deletionButtonRefs[i]}\n />\n </li>\n ))}\n </ul>\n\n <div className={classNames.inputWrapper}>\n <input\n {...rest}\n data-smarthr-ui-input=\"true\"\n type=\"text\"\n name={name}\n value={inputValue}\n disabled={disabled}\n required={required && selectedItems.length === 0}\n ref={inputRef}\n onChange={functions.handleChangeInput}\n onFocus={functions.handleFocusInput}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n autoComplete={autoComplete ?? 'off'}\n tabIndex={0}\n role=\"combobox\"\n aria-activedescendant={activeOption?.id}\n aria-controls={`${listBoxId} ${selectedListId}`}\n aria-haspopup=\"listbox\"\n aria-expanded={isExpanded}\n aria-invalid={error || undefined}\n aria-disabled={disabled}\n aria-autocomplete=\"list\"\n className={classNames.input}\n />\n </div>\n\n {selectedItems.length === 0 && placeholder && !isExpanded && (\n <p className={classNames.placeholder}>{placeholder}</p>\n )}\n </Scroller>\n\n <MemoizedCaretDown disabled={disabled} isExpanded={isExpanded} classNames={classNames} />\n\n <ListBox {...listBoxProps} />\n </div>\n )\n}\n\nexport const MultiCombobox = genericsForwardRef(ActualMultiCombobox)\n\nconst MemoizedCaretDown = memo<{\n disabled: boolean\n isExpanded: boolean\n classNames: {\n suffixWrapper: string\n suffixIcon: string\n }\n}>(({ disabled, isExpanded, classNames }) => {\n const theme = useTheme()\n const caretIconColor = isExpanded\n ? theme.textColor.black\n : disabled\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n return (\n <div className={classNames.suffixWrapper}>\n <FaCaretDownIcon color={caretIconColor} className={classNames.suffixIcon} />\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAEA;AACE;;;AAGF;AAEA;AACA;AACA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;;;;;;;;;;;;AAYC;AACD;AACA;AAEA;;;;;AAKC;AACD;;;;AAIC;AACD;AACA;;;AAGC;AACD;AACD;AACF;AAED;AAkCE;;;;;AAMA;AAEA;;;AAGA;AAEA;;AAEE;;;;AAID;AACD;;AAGA;;AAGE;AACE;;AAGF;AACF;AACA;;;;;;;;;AAWC;AAED;AACE;;AAGE;AACE;;AAEF;AACE;;AASF;;;;AAII;AACF;;AAEJ;;;AAIE;;;;;AAQI;AACE;AACA;;AAGA;;AACK;;;AAGT;;;AAGN;AAEA;;;;;;;;;;AAUC;;;;;;;;;;;;;;;;;;AAmBA;AAED;;;AAGE;AAEA;;AAGE;;;AAIA;AACE;;;;;;;;;AAUJ;;AAGE;;;AAIA;;;;;;;;;AASI;AACF;;AAEJ;;AAGE;;AAEF;;AAGE;AACE;;AAEA;;AAEJ;;;;AAKE;;;;;AAKI;;AACK;AACL;;AAEE;;AAGF;;;;AAGA;;;;AAGA;;AACK;AAEL;AACA;AACA;;;AAKA;;;;;;;AAOA;AACA;;AAGF;;AAEF;;;;AAMI;;;AAGJ;;AAGE;;;;AAIA;AAEA;AACE;;;AAGJ;AACA;AACA;;;;;;;;;AASA;;AAEE;;;AAGN;;;;AAUE;;AAEE;;;;;;;;AAQA;;;AAIJ;;;AAaI;;;;;;;;;AASJ;;AAGE;AACE;AACA;AACD;AACF;;;AAIC;AAQI;AACA;;AA6DR;;AAIA;AAQE;;AAEE;AACA;AACE;AACA;;AAON;;"}
1
+ {"version":3,"file":"MultiCombobox.js","sources":["../../../../src/components/Combobox/MultiCombobox/MultiCombobox.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithoutRef,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type Ref,\n memo,\n useEffect,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useOuterClick } from '../../../hooks/useOuterClick'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { useLocalize } from '../../../intl'\nimport { findDelegateTarget } from '../../../libs/delegate'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { FaCaretDownIcon } from '../../Icon'\nimport { Scroller } from '../../Scroller'\nimport { areItemsEqual } from '../helper'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useMultiOptions } from '../useOptions'\n\nimport { DELETE_BUTTON_SELECTOR, MultiSelectedItem } from './MultiSelectedItem'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテムのリスト\n */\n selectedItems: Array<ComboboxItem<T> & { deletable?: boolean }>\n /**\n * 選択されているアイテムのラベルを省略表示するかどうか\n */\n selectedItemEllipsis?: boolean\n /**\n * テキストボックスの `value` 属性の値。\n * `onChangeInput` と併せて設定することで、テキストボックスの挙動が制御可能になる。\n */\n inputValue?: string\n /**\n * 選択されているアイテムの削除ボタンがクリックされた時に発火するコールバック関数\n */\n onDelete?: (item: ComboboxItem<T>) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItems: Array<ComboboxItem<T>>) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\n /**\n * アイテムが選択されたときに選択済みかどうかを判定するコールバック関数/\n */\n isItemSelected?: (targetItem: ComboboxItem<T>, selectedItems: Array<ComboboxItem<T>>) => boolean\n /**\n * 検索結果が0件の時に表示するコンテンツ\n */\n noResultText?: ReactNode\n}\ntype Props<T> = BaseProps<T> & Omit<ComponentPropsWithoutRef<'input'>, keyof BaseProps<unknown>>\n\nconst NOOP = () => undefined\n\nconst preventDefaultWithPressEnter = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n }\n}\n\nconst ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_LEFT_KEY_REGEX = /^(Arrow)?Left$/\nconst ARROW_RIGHT_KEY_REGEX = /^(Arrow)?Right/\nconst ARROW_UP_AND_DOWN_KEY_REGEX = /^(Arrow)?(Up|Down)$/\n\nconst EMPTY_INPUT_CHANGE_EVENT = {\n currentTarget: { value: '' },\n target: { value: '' },\n} as ChangeEvent<HTMLInputElement>\n\nconst DELETE_BUTTON_CLASSNAME = `.${DELETE_BUTTON_SELECTOR}`\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-MultiCombobox',\n 'shr-box-border shr-inline-flex shr-min-w-[15em] shr-rounded-m shr-border shr-border-solid shr-px-0.5 shr-py-0.25 shr-align-bottom',\n 'contrast-more:shr-border-high-contrast',\n 'has-[[role=combobox][aria-expanded=true]]:shr-focus-indicator',\n 'shr-cursor-text shr-border-default shr-bg-white',\n 'has-[[role=combobox]:disabled]:shr-cursor-not-allowed',\n 'has-[[role=combobox]:disabled]:shr-border-default/50',\n 'has-[[role=combobox]:disabled]:shr-bg-white-darken',\n 'has-[[role=combobox]:disabled]:shr-text-disabled',\n // HINT: disabled と詳細度が同じ [0,2,0] のため、CSS ソース順でこちらを後に置くことで error 時の border 色を優先させる\n 'has-[[role=combobox][aria-invalid]]:shr-border-danger',\n ],\n inputArea: 'shr-flex shr-flex-1 shr-flex-wrap shr-gap-0.5',\n selectedList:\n 'smarthr-ui-MultiCombobox-selectedList shr-contents shr-list-none [&_li]:shr-min-w-0',\n inputWrapper: [\n 'shr-flex shr-flex-1 shr-items-center',\n 'has-[[role=combobox][aria-expanded=false]]:shr-pointer-events-none',\n 'has-[[role=combobox][aria-expanded=false]]:shr-absolute',\n 'has-[[role=combobox][aria-expanded=false]]:shr-opacity-0',\n ],\n input: [\n 'smarthr-ui-MultiCombobox-input',\n 'shr-w-full shr-min-w-[5em] shr-border-none shr-text-base shr-text-black shr-outline-none shr-outline-0',\n 'disabled:shr-hidden',\n ],\n placeholderEl: 'smarthr-ui-MultiCombobox-placeholder shr-my-0 shr-self-center',\n suffixWrapper: [\n 'shr-relative -shr-me-0.5 shr-ms-0.5 shr-p-0.5',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n suffixIcon: 'shr-block',\n },\n})\n\nconst ActualMultiCombobox = <T,>(\n {\n items,\n selectedItems,\n name,\n disabled = false,\n required = false,\n error = false,\n creatable = false,\n placeholder = '',\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n selectedItemEllipsis,\n width,\n dropdownWidth = 'auto',\n inputValue: controlledInputValue,\n className,\n onChange,\n onChangeInput,\n onAdd,\n onDelete,\n onSelect,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n isItemSelected,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const triggerRef = useRef<HTMLDivElement>(null)\n const [isExpanded, setIsExpanded] = useState(false)\n const [highlighted, setHighlighted] = useState(false)\n const [uncontrolledInputValue, setUncontrolledInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n\n const selectedListId = useId()\n\n const isInputControlled = controlledInputValue !== undefined\n const inputValue = isInputControlled ? controlledInputValue : uncontrolledInputValue\n const setInputValueIfUncontrolled = isInputControlled ? NOOP : setUncontrolledInputValue\n const isInputEmpty = !inputValue\n\n const { options } = useMultiOptions({\n items,\n selected: selectedItems,\n creatable,\n inputValue,\n isItemSelected,\n })\n const inputRef = useRef<HTMLInputElement>(null)\n\n // eslint-disable-next-line local-rules/best-practice-for-use-latest\n const latestForListBox = useLatest({\n onDelete,\n onChangeSelected,\n onSelect,\n onChangeInput,\n selectedItems,\n })\n\n const listBoxFunctions = useMemo(() => {\n const handleDelete = (item: ComboboxItem<T>) => {\n const handlers: Array<(deletingItem: ComboboxItem<T>) => void> = []\n\n if (latestForListBox.onDelete) {\n handlers.push((deletingItem: ComboboxItem<T>) => latestForListBox.onDelete!(deletingItem))\n }\n if (latestForListBox.onChangeSelected) {\n handlers.push((deletingItem: ComboboxItem<T>) =>\n latestForListBox.onChangeSelected!(\n latestForListBox.selectedItems.filter(\n (selected) => !areItemsEqual(selected, deletingItem),\n ),\n ),\n )\n }\n\n if (handlers.length > 0) {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n handlers.forEach((h) => h(item))\n })\n }\n }\n\n return {\n handleDelete,\n handleSelect: (selected: ComboboxItem<T>) => {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n const matchedSelectedItem = latestForListBox.selectedItems.find((item) =>\n areItemsEqual(item, selected),\n )\n\n if (matchedSelectedItem === undefined) {\n latestForListBox.onSelect?.(selected)\n latestForListBox.onChangeSelected?.(latestForListBox.selectedItems.concat(selected))\n\n // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n latestForListBox.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n } else if (matchedSelectedItem.deletable !== false) {\n handleDelete(selected)\n }\n })\n },\n }\n }, [latestForListBox])\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect: listBoxFunctions.handleSelect,\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n })\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onFocus,\n onBlur,\n onKeyPress,\n disabled,\n isExpanded,\n highlighted,\n isComposing,\n isInputEmpty,\n selectedItems,\n setInputValueIfUncontrolled,\n handleKeyDownListBox,\n })\n\n const functions = useMemo(() => {\n const handleDelete = listBoxFunctions.handleDelete\n\n const getDeletionButtons = () => {\n if (triggerRef.current) {\n const buttons =\n triggerRef.current.querySelectorAll<HTMLButtonElement>(DELETE_BUTTON_CLASSNAME)\n\n if (buttons.length > 0) {\n const actualButtons = Array.from(buttons)\n\n return {\n buttons: actualButtons,\n currentIndex: actualButtons.indexOf(document.activeElement as HTMLButtonElement),\n }\n }\n }\n\n return null\n }\n\n const focusPrevDeletionButton = () => {\n const result = getDeletionButtons()\n\n if (!result) return\n\n const { buttons, currentIndex } = result\n\n if (currentIndex !== -1) {\n buttons[Math.max(currentIndex - 1, 0)].focus()\n } else if (inputRef.current?.selectionStart === 0) {\n buttons[buttons.length - 1].focus()\n }\n }\n\n const focusNextDeletionButton = () => {\n const result = getDeletionButtons()\n\n if (!result) return\n\n const { buttons, currentIndex } = result\n\n if (currentIndex === -1) return\n\n const nextIndex = currentIndex + 1\n\n if (nextIndex < buttons.length) {\n buttons[nextIndex].focus()\n } else {\n // キー入力が input に影響しないようにフォーカスタイミングを遅らせる\n setTimeout(() => {\n inputRef.current?.focus()\n })\n }\n }\n\n const focus = () => {\n latest.onFocus?.()\n setIsExpanded(true)\n }\n\n const blur = () => {\n if (latest.isExpanded) {\n latest.onBlur?.()\n setIsExpanded(false)\n }\n }\n\n return {\n handleDelete,\n blur,\n handleDelegateKeyDown: (e: KeyboardEvent<HTMLDivElement>) => {\n if (latest.isComposing) return\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n blur()\n } else if (e.key === 'Tab') {\n if (latest.isExpanded) {\n // フォーカスがコンポーネントを抜けるように先に input をフォーカスしておく\n inputRef.current?.focus()\n }\n\n blur()\n } else if (ARROW_LEFT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusPrevDeletionButton()\n } else if (ARROW_RIGHT_KEY_REGEX.test(e.key)) {\n e.stopPropagation()\n focusNextDeletionButton()\n } else if (\n e.key === 'Backspace' &&\n latest.isInputEmpty &&\n latest.selectedItems.length > 0 &&\n latest.selectedItems[latest.selectedItems.length - 1].deletable !== false\n ) {\n e.preventDefault()\n e.stopPropagation()\n\n const lastItem = latest.selectedItems[latest.selectedItems.length - 1]\n\n handleDelete(lastItem)\n setHighlighted(true)\n latest.setInputValueIfUncontrolled(innerText(lastItem.label))\n } else {\n e.stopPropagation()\n inputRef.current?.focus()\n }\n\n latest.handleKeyDownListBox(e)\n },\n handleDelegateClick: (e: MouseEvent<HTMLElement>) => {\n if (\n !latest.disabled &&\n !latest.isExpanded &&\n !findDelegateTarget(e, DELETE_BUTTON_CLASSNAME)\n ) {\n focus()\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n const handlers = [latest.onChange, latest.onChangeInput].filter((h) => !!h)\n\n handlers.forEach((h) => h(e))\n latest.setInputValueIfUncontrolled(e.currentTarget.value)\n },\n handleFocusInput: () => {\n if (!latest.isExpanded) {\n focus()\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (ARROW_UP_AND_DOWN_KEY_REGEX.test(e.key)) {\n // 上下キー入力はリストボックスの activeDescendant の移動に用いるため、input 内では作用させない\n e.preventDefault()\n }\n },\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleDelegateKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n preventDefaultWithPressEnter(e)\n latest.onKeyPress?.(e)\n },\n }\n }, [listBoxFunctions, latest])\n\n useOuterClick(\n useMemo(() => [triggerRef, listBoxRef], [listBoxRef]),\n functions.blur,\n )\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => inputRef.current)\n\n useEffect(() => {\n if (latest.highlighted) {\n setHighlighted(false)\n inputRef.current?.select()\n } else {\n setInputValueIfUncontrolled('')\n }\n }, [selectedItems, setInputValueIfUncontrolled, inputRef, latest])\n\n useEffect(() => {\n if (isExpanded) {\n inputRef.current?.focus()\n }\n }, [isExpanded, selectedItems, isInputControlled, inputRef])\n\n const classNames = useMemo(() => {\n const {\n wrapper,\n inputArea,\n selectedList,\n inputWrapper,\n input,\n placeholderEl,\n suffixWrapper,\n suffixIcon,\n } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputArea: inputArea(),\n selectedList: selectedList(),\n inputWrapper: inputWrapper(),\n input: input(),\n placeholder: placeholderEl(),\n suffixWrapper: suffixWrapper(),\n suffixIcon: suffixIcon(),\n }\n }, [className])\n\n const localized = useLocalize({\n selectedListAriaLabel: {\n id: 'smarthr-ui/MultiCombobox/selectedListAriaLabel',\n defaultText: '選択済みアイテム',\n },\n })\n\n return (\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions\n <div\n ref={triggerRef}\n role=\"group\"\n onClick={functions.handleDelegateClick}\n onKeyDown={functions.handleDelegateKeyDown}\n onKeyPress={functions.handleDelegateKeyPress}\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n >\n <Scroller className={classNames.inputArea}>\n <ul\n id={selectedListId}\n aria-label={localized.selectedListAriaLabel}\n className={classNames.selectedList}\n >\n {selectedItems.map((selectedItem) => (\n <li key={`${selectedItem.label}-${innerText(selectedItem.value)}`}>\n <MultiSelectedItem\n item={selectedItem}\n disabled={disabled}\n handleDelete={functions.handleDelete}\n enableEllipsis={selectedItemEllipsis}\n />\n </li>\n ))}\n </ul>\n\n <div className={classNames.inputWrapper}>\n <input\n {...rest}\n data-smarthr-ui-input=\"true\"\n type=\"text\"\n name={name}\n value={inputValue}\n disabled={disabled}\n required={required && selectedItems.length === 0}\n ref={inputRef}\n onChange={functions.handleChangeInput}\n onFocus={functions.handleFocusInput}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n autoComplete={autoComplete ?? 'off'}\n tabIndex={0}\n role=\"combobox\"\n aria-activedescendant={activeOption?.id}\n aria-controls={`${listBoxId} ${selectedListId}`}\n aria-haspopup=\"listbox\"\n aria-expanded={isExpanded}\n aria-invalid={error || undefined}\n aria-disabled={disabled}\n aria-autocomplete=\"list\"\n className={classNames.input}\n />\n </div>\n\n {selectedItems.length === 0 && placeholder && !isExpanded && (\n <p className={classNames.placeholder}>{placeholder}</p>\n )}\n </Scroller>\n\n <MemoizedCaretDown disabled={disabled} isExpanded={isExpanded} classNames={classNames} />\n\n <ListBox {...listBoxProps} />\n </div>\n )\n}\n\nexport const MultiCombobox = genericsForwardRef(ActualMultiCombobox)\n\nconst MemoizedCaretDown = memo<{\n disabled: boolean\n isExpanded: boolean\n classNames: {\n suffixWrapper: string\n suffixIcon: string\n }\n}>(({ disabled, isExpanded, classNames }) => {\n const theme = useTheme()\n const caretIconColor = isExpanded\n ? theme.textColor.black\n : disabled\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n return (\n <div className={classNames.suffixWrapper}>\n <FaCaretDownIcon color={caretIconColor} className={classNames.suffixIcon} />\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAEA;AACE;;;AAGF;AAEA;AACA;AACA;AACA;AAEA;AACE;AACA;;AAGF;AAEA;AACE;AACE;;;;;;;;;;;;AAYC;AACD;AACA;AAEA;;;;;AAKC;AACD;;;;AAIC;AACD;AACA;;;AAGC;AACD;AACD;AACF;AAED;AAkCE;;;;;AAMA;AAEA;;;AAGA;AAEA;;AAEE;;;;AAID;AACD;;;;;;;;AASC;AAED;AACE;;AAGE;AACE;;AAEF;AACE;;AASF;;;;AAII;AACF;;AAEJ;;;AAIE;;;;;AAQI;AACE;AACA;;AAGA;;AACK;;;AAGT;;;AAGN;AAEA;;;;;;;;;;AAUC;;;;;;;;;;;;;;;AAgBA;AAED;AACE;;AAGE;;AAIE;;;AAII;;;;;AAMN;AACF;;AAGE;AAEA;;AAEA;AAEA;AACE;;;;;AAIJ;;AAGE;AAEA;;AAEA;;;AAIA;AAEA;AACE;;;;;AAIE;AACF;;AAEJ;;AAGE;;AAEF;;AAGE;AACE;;;AAGJ;;;;AAKE;;;;;AAKI;;AACK;AACL;;AAEE;;AAGF;;;;AAGA;;;;AAGA;;AACK;AAEL;AACA;AACA;;;AAKA;;;;;;;AAOA;;AAGF;;AAEF;;;AAII;AAEA;;;AAGJ;;AAGE;;;;AAIA;AACE;;;AAGJ;AACA;AACA;;;;;;;;;AASA;;AAEE;;;AAGN;;;;AAUE;;AAEE;;;;;;;;AAQA;;;AAIJ;;;AAaI;;;;;;;;;AASJ;;AAGE;AACE;AACA;AACD;AACF;;;AAIC;AAQI;AACA;;AA4DR;;AAIA;AAQE;;AAEE;AACA;AACE;AACA;;AAON;;"}