smarthr-ui 99.3.2 → 99.4.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 (475) hide show
  1. package/lib/_virtual/plugin.cjs +1 -1
  2. package/lib/_virtual/plugin.js +1 -1
  3. package/lib/components/AccordionPanel/AccordionPanel.cjs +1 -1
  4. package/lib/components/AccordionPanel/AccordionPanel.js +1 -1
  5. package/lib/components/AccordionPanel/AccordionPanelContent.cjs +1 -1
  6. package/lib/components/AccordionPanel/AccordionPanelContent.js +1 -1
  7. package/lib/components/AccordionPanel/AccordionPanelItem.cjs +1 -1
  8. package/lib/components/AccordionPanel/AccordionPanelItem.js +1 -1
  9. package/lib/components/AccordionPanel/AccordionPanelTrigger.cjs +1 -1
  10. package/lib/components/AccordionPanel/AccordionPanelTrigger.js +1 -1
  11. package/lib/components/AppHeader/components/common/AppLauncherFeatures.cjs +1 -1
  12. package/lib/components/AppHeader/components/common/AppLauncherFeatures.js +1 -1
  13. package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.cjs +1 -1
  14. package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.js +1 -1
  15. package/lib/components/AppHeader/components/common/CommonButton.cjs +1 -1
  16. package/lib/components/AppHeader/components/common/CommonButton.js +1 -1
  17. package/lib/components/AppHeader/components/desktop/AppLauncher.cjs +3 -3
  18. package/lib/components/AppHeader/components/desktop/AppLauncher.cjs.map +1 -1
  19. package/lib/components/AppHeader/components/desktop/AppLauncher.js +3 -3
  20. package/lib/components/AppHeader/components/desktop/AppLauncher.js.map +1 -1
  21. package/lib/components/AppHeader/components/desktop/DesktopHeader.cjs +1 -1
  22. package/lib/components/AppHeader/components/desktop/DesktopHeader.js +1 -1
  23. package/lib/components/AppHeader/components/desktop/Navigation.cjs +1 -1
  24. package/lib/components/AppHeader/components/desktop/Navigation.js +1 -1
  25. package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.cjs +1 -1
  26. package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.js +1 -1
  27. package/lib/components/AppHeader/components/desktop/UserInfo.cjs +1 -1
  28. package/lib/components/AppHeader/components/desktop/UserInfo.js +1 -1
  29. package/lib/components/AppHeader/components/mobile/AppLauncher.cjs +3 -3
  30. package/lib/components/AppHeader/components/mobile/AppLauncher.cjs.map +1 -1
  31. package/lib/components/AppHeader/components/mobile/AppLauncher.js +3 -3
  32. package/lib/components/AppHeader/components/mobile/AppLauncher.js.map +1 -1
  33. package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.cjs +1 -1
  34. package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.js +1 -1
  35. package/lib/components/AppHeader/components/mobile/LanguageSelector.cjs +1 -1
  36. package/lib/components/AppHeader/components/mobile/LanguageSelector.js +1 -1
  37. package/lib/components/AppHeader/components/mobile/Menu.cjs +1 -1
  38. package/lib/components/AppHeader/components/mobile/Menu.js +1 -1
  39. package/lib/components/AppHeader/components/mobile/MenuDialog.cjs +1 -1
  40. package/lib/components/AppHeader/components/mobile/MenuDialog.js +1 -1
  41. package/lib/components/AppHeader/components/mobile/MobileHeader.cjs +1 -1
  42. package/lib/components/AppHeader/components/mobile/MobileHeader.cjs.map +1 -1
  43. package/lib/components/AppHeader/components/mobile/MobileHeader.js +1 -1
  44. package/lib/components/AppHeader/components/mobile/MobileHeader.js.map +1 -1
  45. package/lib/components/AppHeader/components/mobile/Navigation.cjs +1 -1
  46. package/lib/components/AppHeader/components/mobile/Navigation.js +1 -1
  47. package/lib/components/AppHeader/components/mobile/NavigationItem.cjs +1 -1
  48. package/lib/components/AppHeader/components/mobile/NavigationItem.js +1 -1
  49. package/lib/components/AppHeader/components/mobile/ReleaseNote.cjs +1 -1
  50. package/lib/components/AppHeader/components/mobile/ReleaseNote.js +1 -1
  51. package/lib/components/AppHeader/components/mobile/TenantSelector.cjs +1 -1
  52. package/lib/components/AppHeader/components/mobile/TenantSelector.js +1 -1
  53. package/lib/components/AppHeader/components/mobile/UserInfo.cjs +1 -1
  54. package/lib/components/AppHeader/components/mobile/UserInfo.js +1 -1
  55. package/lib/components/AppHeader/hooks/useAppLauncher.cjs +10 -2
  56. package/lib/components/AppHeader/hooks/useAppLauncher.cjs.map +1 -1
  57. package/lib/components/AppHeader/hooks/useAppLauncher.d.ts +1 -0
  58. package/lib/components/AppHeader/hooks/useAppLauncher.js +10 -2
  59. package/lib/components/AppHeader/hooks/useAppLauncher.js.map +1 -1
  60. package/lib/components/AppNavi/AppNavi.cjs +1 -1
  61. package/lib/components/AppNavi/AppNavi.js +1 -1
  62. package/lib/components/AppNavi/AppNaviAnchor.cjs +1 -1
  63. package/lib/components/AppNavi/AppNaviAnchor.js +1 -1
  64. package/lib/components/AppNavi/AppNaviButton.cjs +1 -1
  65. package/lib/components/AppNavi/AppNaviButton.js +1 -1
  66. package/lib/components/AppNavi/AppNaviCustomTag.cjs +1 -1
  67. package/lib/components/AppNavi/AppNaviCustomTag.js +1 -1
  68. package/lib/components/AppNavi/AppNaviDropdown.cjs +1 -1
  69. package/lib/components/AppNavi/AppNaviDropdown.js +1 -1
  70. package/lib/components/AppNavi/AppNaviDropdownMenuButton.cjs +1 -1
  71. package/lib/components/AppNavi/AppNaviDropdownMenuButton.js +1 -1
  72. package/lib/components/AppNavi/itemClassNameGenerator.cjs +1 -1
  73. package/lib/components/AppNavi/itemClassNameGenerator.js +1 -1
  74. package/lib/components/Badge/Badge.cjs +1 -1
  75. package/lib/components/Badge/Badge.js +1 -1
  76. package/lib/components/BottomFixedArea/BottomFixedArea.cjs +1 -1
  77. package/lib/components/BottomFixedArea/BottomFixedArea.js +1 -1
  78. package/lib/components/Browser/Browser.cjs +1 -1
  79. package/lib/components/Browser/Browser.js +1 -1
  80. package/lib/components/Browser/BrowserColumn.cjs +1 -1
  81. package/lib/components/Browser/BrowserColumn.js +1 -1
  82. package/lib/components/Browser/BrowserItem.cjs +1 -1
  83. package/lib/components/Browser/BrowserItem.js +1 -1
  84. package/lib/components/Button/AnchorButton.cjs +1 -1
  85. package/lib/components/Button/AnchorButton.js +1 -1
  86. package/lib/components/Button/Button.cjs +1 -1
  87. package/lib/components/Button/Button.js +1 -1
  88. package/lib/components/Button/DisabledReason.cjs +1 -1
  89. package/lib/components/Button/DisabledReason.js +1 -1
  90. package/lib/components/Button/UnstyledButton.cjs +1 -1
  91. package/lib/components/Button/UnstyledButton.js +1 -1
  92. package/lib/components/Button/useButtonWrapper.cjs +1 -1
  93. package/lib/components/Button/useButtonWrapper.js +1 -1
  94. package/lib/components/Calendar/Calendar.cjs +1 -1
  95. package/lib/components/Calendar/Calendar.js +1 -1
  96. package/lib/components/Calendar/CalendarTable.cjs +1 -1
  97. package/lib/components/Calendar/CalendarTable.js +1 -1
  98. package/lib/components/Calendar/YearPicker.cjs +1 -1
  99. package/lib/components/Calendar/YearPicker.js +1 -1
  100. package/lib/components/Checkbox/Checkbox.cjs +1 -1
  101. package/lib/components/Checkbox/Checkbox.js +1 -1
  102. package/lib/components/Chip/Chip.cjs +1 -1
  103. package/lib/components/Chip/Chip.js +1 -1
  104. package/lib/components/Combobox/ItemButton.cjs +1 -1
  105. package/lib/components/Combobox/ItemButton.js +1 -1
  106. package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs +15 -5
  107. package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs.map +1 -1
  108. package/lib/components/Combobox/MultiCombobox/MultiCombobox.js +16 -6
  109. package/lib/components/Combobox/MultiCombobox/MultiCombobox.js.map +1 -1
  110. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.cjs +1 -1
  111. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.js +1 -1
  112. package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs +7 -3
  113. package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs.map +1 -1
  114. package/lib/components/Combobox/SingleCombobox/SingleCombobox.js +8 -4
  115. package/lib/components/Combobox/SingleCombobox/SingleCombobox.js.map +1 -1
  116. package/lib/components/Combobox/useListbox.cjs +8 -4
  117. package/lib/components/Combobox/useListbox.cjs.map +1 -1
  118. package/lib/components/Combobox/useListbox.js +8 -4
  119. package/lib/components/Combobox/useListbox.js.map +1 -1
  120. package/lib/components/DatePicker/DatePicker.cjs +7 -3
  121. package/lib/components/DatePicker/DatePicker.cjs.map +1 -1
  122. package/lib/components/DatePicker/DatePicker.js +7 -3
  123. package/lib/components/DatePicker/DatePicker.js.map +1 -1
  124. package/lib/components/DefinitionList/DefinitionList.cjs +1 -1
  125. package/lib/components/DefinitionList/DefinitionList.js +1 -1
  126. package/lib/components/DefinitionList/DefinitionListItem.cjs +8 -9
  127. package/lib/components/DefinitionList/DefinitionListItem.cjs.map +1 -1
  128. package/lib/components/DefinitionList/DefinitionListItem.js +8 -9
  129. package/lib/components/DefinitionList/DefinitionListItem.js.map +1 -1
  130. package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs +1 -1
  131. package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js +1 -1
  132. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.cjs +32 -4
  133. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.cjs.map +1 -1
  134. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.d.ts +1 -1
  135. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.js +29 -1
  136. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.js.map +1 -1
  137. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs.map +1 -1
  138. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.d.ts +1 -2
  139. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js.map +1 -1
  140. package/lib/components/Dialog/ControlledStepFormDialog/type.cjs +3 -0
  141. package/lib/components/Dialog/ControlledStepFormDialog/type.cjs.map +1 -0
  142. package/lib/components/Dialog/ControlledStepFormDialog/type.d.ts +25 -0
  143. package/lib/components/Dialog/ControlledStepFormDialog/type.js +2 -0
  144. package/lib/components/Dialog/ControlledStepFormDialog/type.js.map +1 -0
  145. package/lib/components/Dialog/DialogBody.cjs +1 -1
  146. package/lib/components/Dialog/DialogBody.js +1 -1
  147. package/lib/components/Dialog/DialogContentInner.cjs +1 -1
  148. package/lib/components/Dialog/DialogContentInner.js +1 -1
  149. package/lib/components/Dialog/DialogOverlap.cjs +1 -1
  150. package/lib/components/Dialog/DialogOverlap.js +1 -1
  151. package/lib/components/Dialog/FocusTrap.cjs +61 -44
  152. package/lib/components/Dialog/FocusTrap.cjs.map +1 -1
  153. package/lib/components/Dialog/FocusTrap.js +62 -45
  154. package/lib/components/Dialog/FocusTrap.js.map +1 -1
  155. package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs +1 -1
  156. package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +1 -1
  157. package/lib/components/Dialog/RemoteDialogTrigger/useRemoteTrigger.cjs +20 -10
  158. package/lib/components/Dialog/RemoteDialogTrigger/useRemoteTrigger.cjs.map +1 -1
  159. package/lib/components/Dialog/RemoteDialogTrigger/useRemoteTrigger.d.ts +1 -1
  160. package/lib/components/Dialog/RemoteDialogTrigger/useRemoteTrigger.js +20 -10
  161. package/lib/components/Dialog/RemoteDialogTrigger/useRemoteTrigger.js.map +1 -1
  162. package/lib/components/Dialog/dialogInnerStyle.cjs +1 -1
  163. package/lib/components/Dialog/dialogInnerStyle.js +1 -1
  164. package/lib/components/Disclosure/useDisclosure.cjs +6 -3
  165. package/lib/components/Disclosure/useDisclosure.cjs.map +1 -1
  166. package/lib/components/Disclosure/useDisclosure.js +6 -3
  167. package/lib/components/Disclosure/useDisclosure.js.map +1 -1
  168. package/lib/components/DropZone/DropZone.cjs +1 -1
  169. package/lib/components/DropZone/DropZone.js +1 -1
  170. package/lib/components/Dropdown/Dropdown.cjs +20 -9
  171. package/lib/components/Dropdown/Dropdown.cjs.map +1 -1
  172. package/lib/components/Dropdown/Dropdown.js +20 -9
  173. package/lib/components/Dropdown/Dropdown.js.map +1 -1
  174. package/lib/components/Dropdown/DropdownCloser.cjs +1 -1
  175. package/lib/components/Dropdown/DropdownCloser.js +1 -1
  176. package/lib/components/Dropdown/DropdownContentInner.cjs +79 -3
  177. package/lib/components/Dropdown/DropdownContentInner.cjs.map +1 -1
  178. package/lib/components/Dropdown/DropdownContentInner.js +81 -5
  179. package/lib/components/Dropdown/DropdownContentInner.js.map +1 -1
  180. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +70 -3
  181. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs.map +1 -1
  182. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +71 -4
  183. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js.map +1 -1
  184. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuGroup.cjs +1 -1
  185. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuGroup.js +1 -1
  186. package/lib/components/Dropdown/DropdownTrigger.cjs +1 -1
  187. package/lib/components/Dropdown/DropdownTrigger.js +1 -1
  188. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs +1 -1
  189. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js +1 -1
  190. package/lib/components/Dropdown/SortDropdown/SortDropdown.cjs +68 -14
  191. package/lib/components/Dropdown/SortDropdown/SortDropdown.cjs.map +1 -1
  192. package/lib/components/Dropdown/SortDropdown/SortDropdown.js +69 -15
  193. package/lib/components/Dropdown/SortDropdown/SortDropdown.js.map +1 -1
  194. package/lib/components/ErrorScreen/ErrorScreen.cjs +1 -1
  195. package/lib/components/ErrorScreen/ErrorScreen.js +1 -1
  196. package/lib/components/FileViewer/PDFViewer.cjs +27 -26
  197. package/lib/components/FileViewer/PDFViewer.cjs.map +1 -1
  198. package/lib/components/FileViewer/PDFViewer.js +28 -27
  199. package/lib/components/FileViewer/PDFViewer.js.map +1 -1
  200. package/lib/components/FileViewer/SearchController.cjs +1 -1
  201. package/lib/components/FileViewer/SearchController.js +1 -1
  202. package/lib/components/FloatArea/FloatArea.cjs +26 -85
  203. package/lib/components/FloatArea/FloatArea.cjs.map +1 -1
  204. package/lib/components/FloatArea/FloatArea.js +26 -85
  205. package/lib/components/FloatArea/FloatArea.js.map +1 -1
  206. package/lib/components/FormControl/FormControl.cjs +60 -41
  207. package/lib/components/FormControl/FormControl.cjs.map +1 -1
  208. package/lib/components/FormControl/FormControl.js +60 -41
  209. package/lib/components/FormControl/FormControl.js.map +1 -1
  210. package/lib/components/Header/AppLauncher/AppLauncher.cjs +5 -5
  211. package/lib/components/Header/AppLauncher/AppLauncher.cjs.map +1 -1
  212. package/lib/components/Header/AppLauncher/AppLauncher.js +5 -5
  213. package/lib/components/Header/AppLauncher/AppLauncher.js.map +1 -1
  214. package/lib/components/Header/Header.cjs +1 -1
  215. package/lib/components/Header/Header.js +1 -1
  216. package/lib/components/Header/HeaderDropdownMenuButton.cjs +1 -1
  217. package/lib/components/Header/HeaderDropdownMenuButton.js +1 -1
  218. package/lib/components/Header/HeaderLink.cjs +1 -1
  219. package/lib/components/Header/HeaderLink.js +1 -1
  220. package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.cjs +1 -1
  221. package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.js +1 -1
  222. package/lib/components/Heading/Heading.cjs +1 -1
  223. package/lib/components/Heading/Heading.js +1 -1
  224. package/lib/components/Heading/PageHeading/PageHeading.cjs +43 -27
  225. package/lib/components/Heading/PageHeading/PageHeading.cjs.map +1 -1
  226. package/lib/components/Heading/PageHeading/PageHeading.js +44 -28
  227. package/lib/components/Heading/PageHeading/PageHeading.js.map +1 -1
  228. package/lib/components/Icon/generateIcon.cjs +1 -1
  229. package/lib/components/Icon/generateIcon.js +1 -1
  230. package/lib/components/InformationPanel/InformationPanel.cjs +7 -7
  231. package/lib/components/InformationPanel/InformationPanel.cjs.map +1 -1
  232. package/lib/components/InformationPanel/InformationPanel.js +8 -8
  233. package/lib/components/InformationPanel/InformationPanel.js.map +1 -1
  234. package/lib/components/Input/CurrencyInput/CurrencyInput.cjs +17 -16
  235. package/lib/components/Input/CurrencyInput/CurrencyInput.cjs.map +1 -1
  236. package/lib/components/Input/CurrencyInput/CurrencyInput.js +18 -17
  237. package/lib/components/Input/CurrencyInput/CurrencyInput.js.map +1 -1
  238. package/lib/components/Input/Input.cjs +15 -16
  239. package/lib/components/Input/Input.cjs.map +1 -1
  240. package/lib/components/Input/Input.js +16 -17
  241. package/lib/components/Input/Input.js.map +1 -1
  242. package/lib/components/Input/InputWithTooltip/InputWithTooltip.cjs +3 -3
  243. package/lib/components/Input/InputWithTooltip/InputWithTooltip.cjs.map +1 -1
  244. package/lib/components/Input/InputWithTooltip/InputWithTooltip.js +3 -3
  245. package/lib/components/Input/InputWithTooltip/InputWithTooltip.js.map +1 -1
  246. package/lib/components/Input/SearchInput/SearchInput.cjs +10 -3
  247. package/lib/components/Input/SearchInput/SearchInput.cjs.map +1 -1
  248. package/lib/components/Input/SearchInput/SearchInput.js +10 -3
  249. package/lib/components/Input/SearchInput/SearchInput.js.map +1 -1
  250. package/lib/components/InputFile/style.cjs +1 -1
  251. package/lib/components/InputFile/style.js +1 -1
  252. package/lib/components/Layout/Center/Center.cjs +1 -1
  253. package/lib/components/Layout/Center/Center.js +1 -1
  254. package/lib/components/Layout/Cluster/Cluster.cjs +1 -1
  255. package/lib/components/Layout/Cluster/Cluster.js +1 -1
  256. package/lib/components/Layout/Container/Container.cjs +1 -1
  257. package/lib/components/Layout/Container/Container.js +1 -1
  258. package/lib/components/Layout/Reel/Reel.cjs +1 -1
  259. package/lib/components/Layout/Reel/Reel.js +1 -1
  260. package/lib/components/Layout/Sidebar/Sidebar.cjs +1 -1
  261. package/lib/components/Layout/Sidebar/Sidebar.js +1 -1
  262. package/lib/components/Layout/Stack/Stack.cjs +1 -1
  263. package/lib/components/Layout/Stack/Stack.js +1 -1
  264. package/lib/components/LineClamp/LineClamp.cjs +1 -1
  265. package/lib/components/LineClamp/LineClamp.js +1 -1
  266. package/lib/components/Loader/Loader.cjs +1 -1
  267. package/lib/components/Loader/Loader.js +1 -1
  268. package/lib/components/Loader/LoaderSpinner.cjs +1 -1
  269. package/lib/components/Loader/LoaderSpinner.js +1 -1
  270. package/lib/components/NotificationBar/NotificationBar.cjs +1 -1
  271. package/lib/components/NotificationBar/NotificationBar.js +1 -1
  272. package/lib/components/PageCounter/PageCounter.cjs +1 -1
  273. package/lib/components/PageCounter/PageCounter.js +1 -1
  274. package/lib/components/Pagination/Pagination.cjs +1 -1
  275. package/lib/components/Pagination/Pagination.js +1 -1
  276. package/lib/components/Panel/Groupbox/Groupbox.cjs +1 -1
  277. package/lib/components/Panel/Groupbox/Groupbox.js +1 -1
  278. package/lib/components/Panel/Panel.cjs +1 -1
  279. package/lib/components/Panel/Panel.js +1 -1
  280. package/lib/components/Picker/style.cjs +1 -1
  281. package/lib/components/Picker/style.js +1 -1
  282. package/lib/components/RadioButton/RadioButton.cjs +1 -1
  283. package/lib/components/RadioButton/RadioButton.js +1 -1
  284. package/lib/components/RadioButtonPanel/RadioButtonPanel.cjs +5 -4
  285. package/lib/components/RadioButtonPanel/RadioButtonPanel.cjs.map +1 -1
  286. package/lib/components/RadioButtonPanel/RadioButtonPanel.js +6 -5
  287. package/lib/components/RadioButtonPanel/RadioButtonPanel.js.map +1 -1
  288. package/lib/components/ResponseMessage/ResponseMessage.cjs +1 -1
  289. package/lib/components/ResponseMessage/ResponseMessage.js +1 -1
  290. package/lib/components/Scroller/Scroller.cjs +23 -13
  291. package/lib/components/Scroller/Scroller.cjs.map +1 -1
  292. package/lib/components/Scroller/Scroller.js +24 -14
  293. package/lib/components/Scroller/Scroller.js.map +1 -1
  294. package/lib/components/SegmentedControl/SegmentedControl.cjs +30 -23
  295. package/lib/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
  296. package/lib/components/SegmentedControl/SegmentedControl.js +31 -24
  297. package/lib/components/SegmentedControl/SegmentedControl.js.map +1 -1
  298. package/lib/components/Select/Select.cjs +1 -1
  299. package/lib/components/Select/Select.js +1 -1
  300. package/lib/components/SideMenu/SideMenu.cjs +1 -1
  301. package/lib/components/SideMenu/SideMenu.js +1 -1
  302. package/lib/components/SideMenu/SideMenuGroup.cjs +1 -1
  303. package/lib/components/SideMenu/SideMenuGroup.js +1 -1
  304. package/lib/components/SideMenu/SideMenuItem.cjs +1 -1
  305. package/lib/components/SideMenu/SideMenuItem.js +1 -1
  306. package/lib/components/SideNav/SideNav.cjs +1 -1
  307. package/lib/components/SideNav/SideNav.js +1 -1
  308. package/lib/components/SideNav/SideNavItemButton.cjs +1 -1
  309. package/lib/components/SideNav/SideNavItemButton.js +1 -1
  310. package/lib/components/SmartHRLogo/SmartHRLogo.cjs +1 -1
  311. package/lib/components/SmartHRLogo/SmartHRLogo.js +1 -1
  312. package/lib/components/SpreadsheetTable/SpreadsheetTable.cjs +1 -1
  313. package/lib/components/SpreadsheetTable/SpreadsheetTable.js +1 -1
  314. package/lib/components/StatusLabel/StatusLabel.cjs +1 -1
  315. package/lib/components/StatusLabel/StatusLabel.js +1 -1
  316. package/lib/components/Stepper/HorizontalStepItem.cjs +1 -1
  317. package/lib/components/Stepper/HorizontalStepItem.js +1 -1
  318. package/lib/components/Stepper/StepCounter.cjs +1 -1
  319. package/lib/components/Stepper/StepCounter.js +1 -1
  320. package/lib/components/Stepper/StepStatusIcon.cjs +1 -1
  321. package/lib/components/Stepper/StepStatusIcon.js +1 -1
  322. package/lib/components/Stepper/Stepper.cjs +1 -1
  323. package/lib/components/Stepper/Stepper.js +1 -1
  324. package/lib/components/Stepper/VerticalStepItem.cjs +1 -1
  325. package/lib/components/Stepper/VerticalStepItem.js +1 -1
  326. package/lib/components/Switch/Switch.cjs +1 -1
  327. package/lib/components/Switch/Switch.js +1 -1
  328. package/lib/components/TabBar/TabBar.cjs +1 -1
  329. package/lib/components/TabBar/TabBar.js +1 -1
  330. package/lib/components/TabBar/TabItem.cjs +1 -1
  331. package/lib/components/TabBar/TabItem.js +1 -1
  332. package/lib/components/Table/BulkActionRow.cjs +1 -1
  333. package/lib/components/Table/BulkActionRow.js +1 -1
  334. package/lib/components/Table/EmptyTableBody.cjs +1 -1
  335. package/lib/components/Table/EmptyTableBody.js +1 -1
  336. package/lib/components/Table/Table.cjs +1 -1
  337. package/lib/components/Table/Table.js +1 -1
  338. package/lib/components/Table/TableReel.cjs +1 -1
  339. package/lib/components/Table/TableReel.js +1 -1
  340. package/lib/components/Table/Td.cjs +1 -1
  341. package/lib/components/Table/Td.js +1 -1
  342. package/lib/components/Table/TdCheckbox.cjs +1 -1
  343. package/lib/components/Table/TdCheckbox.js +1 -1
  344. package/lib/components/Table/TdRadioButton.cjs +1 -1
  345. package/lib/components/Table/TdRadioButton.js +1 -1
  346. package/lib/components/Table/Th.cjs +1 -1
  347. package/lib/components/Table/Th.js +1 -1
  348. package/lib/components/Table/ThCheckbox.cjs +1 -1
  349. package/lib/components/Table/ThCheckbox.js +1 -1
  350. package/lib/components/Table/ThSortButton.cjs +1 -1
  351. package/lib/components/Table/ThSortButton.js +1 -1
  352. package/lib/components/Table/reelShadowStyle.cjs +1 -1
  353. package/lib/components/Table/reelShadowStyle.js +1 -1
  354. package/lib/components/Text/Text.cjs +1 -1
  355. package/lib/components/Text/Text.js +1 -1
  356. package/lib/components/TextLink/TextLink.cjs +1 -1
  357. package/lib/components/TextLink/TextLink.js +1 -1
  358. package/lib/components/TextLink/UpwardLink/UpwardLink.cjs +1 -1
  359. package/lib/components/TextLink/UpwardLink/UpwardLink.js +1 -1
  360. package/lib/components/Textarea/Textarea.cjs +1 -1
  361. package/lib/components/Textarea/Textarea.js +1 -1
  362. package/lib/components/Timeline/Timeline.cjs +1 -1
  363. package/lib/components/Timeline/Timeline.js +1 -1
  364. package/lib/components/Timeline/TimelineItem.cjs +1 -1
  365. package/lib/components/Timeline/TimelineItem.js +1 -1
  366. package/lib/components/Tooltip/ControlledTooltip.cjs +1 -1
  367. package/lib/components/Tooltip/ControlledTooltip.js +1 -1
  368. package/lib/components/Tooltip/Tooltip.cjs +1 -1
  369. package/lib/components/Tooltip/Tooltip.js +1 -1
  370. package/lib/components/Tooltip/TooltipPortal.cjs +1 -1
  371. package/lib/components/Tooltip/TooltipPortal.js +1 -1
  372. package/lib/components/VisuallyHiddenText/VisuallyHiddenText.cjs +1 -1
  373. package/lib/components/VisuallyHiddenText/VisuallyHiddenText.js +1 -1
  374. package/lib/configureTwMerge.cjs +1 -1
  375. package/lib/configureTwMerge.js +1 -1
  376. package/lib/hooks/useAnimationFrame.cjs +40 -0
  377. package/lib/hooks/useAnimationFrame.cjs.map +1 -0
  378. package/lib/hooks/useAnimationFrame.d.ts +19 -0
  379. package/lib/hooks/useAnimationFrame.js +38 -0
  380. package/lib/hooks/useAnimationFrame.js.map +1 -0
  381. package/lib/hooks/useLatest.cjs +1 -2
  382. package/lib/hooks/useLatest.cjs.map +1 -1
  383. package/lib/hooks/useLatest.js +1 -2
  384. package/lib/hooks/useLatest.js.map +1 -1
  385. package/lib/hooks/useMediaQueries/useMediaQueries.cjs +41 -29
  386. package/lib/hooks/useMediaQueries/useMediaQueries.cjs.map +1 -1
  387. package/lib/hooks/useMediaQueries/useMediaQueries.js +42 -30
  388. package/lib/hooks/useMediaQueries/useMediaQueries.js.map +1 -1
  389. package/lib/hooks/useMergeRefs.cjs +56 -0
  390. package/lib/hooks/useMergeRefs.cjs.map +1 -0
  391. package/lib/hooks/useMergeRefs.d.ts +18 -0
  392. package/lib/hooks/useMergeRefs.js +54 -0
  393. package/lib/hooks/useMergeRefs.js.map +1 -0
  394. package/lib/hooks/useOnce.cjs +42 -0
  395. package/lib/hooks/useOnce.cjs.map +1 -0
  396. package/lib/hooks/useOnce.d.ts +19 -0
  397. package/lib/hooks/useOnce.js +40 -0
  398. package/lib/hooks/useOnce.js.map +1 -0
  399. package/lib/hooks/useSV/useSV.cjs +1 -1
  400. package/lib/hooks/useSV/useSV.js +1 -1
  401. package/lib/index.cjs +2 -0
  402. package/lib/index.cjs.map +1 -1
  403. package/lib/index.d.ts +1 -0
  404. package/lib/index.js +1 -0
  405. package/lib/index.js.map +1 -1
  406. package/lib/{components/Input/CurrencyInput/currencyInputHelper.js → libs/formatNumericString.cjs} +11 -3
  407. package/lib/libs/formatNumericString.cjs.map +1 -0
  408. package/lib/libs/formatNumericString.d.ts +7 -0
  409. package/lib/{components/Input/CurrencyInput/currencyInputHelper.cjs → libs/formatNumericString.js} +9 -5
  410. package/lib/libs/formatNumericString.js.map +1 -0
  411. package/lib/vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/chunk-I2QGXAA3.cjs.map +1 -0
  412. package/lib/vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/chunk-I2QGXAA3.js.map +1 -0
  413. package/lib/vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.cjs.map +1 -0
  414. package/lib/vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.js.map +1 -0
  415. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/public/create-plugin.cjs.map +1 -0
  416. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/public/create-plugin.js.map +1 -0
  417. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/util/createPlugin.cjs.map +1 -0
  418. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/util/createPlugin.js.map +1 -0
  419. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/plugin.cjs.map +1 -0
  420. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/plugin.js.map +1 -0
  421. package/metadata.json +1 -1
  422. package/package.json +1 -1
  423. package/lib/components/Dialog/ControlledStepFormDialog/useStepFormDialogButton.cjs +0 -36
  424. package/lib/components/Dialog/ControlledStepFormDialog/useStepFormDialogButton.cjs.map +0 -1
  425. package/lib/components/Dialog/ControlledStepFormDialog/useStepFormDialogButton.d.ts +0 -32
  426. package/lib/components/Dialog/ControlledStepFormDialog/useStepFormDialogButton.js +0 -34
  427. package/lib/components/Dialog/ControlledStepFormDialog/useStepFormDialogButton.js.map +0 -1
  428. package/lib/components/Dropdown/DropdownMenuButton/useKeyboardNavigation.cjs +0 -78
  429. package/lib/components/Dropdown/DropdownMenuButton/useKeyboardNavigation.cjs.map +0 -1
  430. package/lib/components/Dropdown/DropdownMenuButton/useKeyboardNavigation.d.ts +0 -3
  431. package/lib/components/Dropdown/DropdownMenuButton/useKeyboardNavigation.js +0 -76
  432. package/lib/components/Dropdown/DropdownMenuButton/useKeyboardNavigation.js.map +0 -1
  433. package/lib/components/Dropdown/SortDropdown/useSortDropdown.cjs +0 -136
  434. package/lib/components/Dropdown/SortDropdown/useSortDropdown.cjs.map +0 -1
  435. package/lib/components/Dropdown/SortDropdown/useSortDropdown.d.ts +0 -34
  436. package/lib/components/Dropdown/SortDropdown/useSortDropdown.js +0 -134
  437. package/lib/components/Dropdown/SortDropdown/useSortDropdown.js.map +0 -1
  438. package/lib/components/Dropdown/useKeyboardNavigation.cjs +0 -90
  439. package/lib/components/Dropdown/useKeyboardNavigation.cjs.map +0 -1
  440. package/lib/components/Dropdown/useKeyboardNavigation.d.ts +0 -2
  441. package/lib/components/Dropdown/useKeyboardNavigation.js +0 -88
  442. package/lib/components/Dropdown/useKeyboardNavigation.js.map +0 -1
  443. package/lib/components/Input/CurrencyInput/currencyInputHelper.cjs.map +0 -1
  444. package/lib/components/Input/CurrencyInput/currencyInputHelper.d.ts +0 -1
  445. package/lib/components/Input/CurrencyInput/currencyInputHelper.js.map +0 -1
  446. package/lib/components/Input/SearchInput/SearchInputIcon.cjs +0 -18
  447. package/lib/components/Input/SearchInput/SearchInputIcon.cjs.map +0 -1
  448. package/lib/components/Input/SearchInput/SearchInputIcon.d.ts +0 -1
  449. package/lib/components/Input/SearchInput/SearchInputIcon.js +0 -16
  450. package/lib/components/Input/SearchInput/SearchInputIcon.js.map +0 -1
  451. package/lib/libs/object.cjs +0 -8
  452. package/lib/libs/object.cjs.map +0 -1
  453. package/lib/libs/object.d.ts +0 -6
  454. package/lib/libs/object.js +0 -5
  455. package/lib/libs/object.js.map +0 -1
  456. package/lib/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/chunk-I2QGXAA3.cjs.map +0 -1
  457. package/lib/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/chunk-I2QGXAA3.js.map +0 -1
  458. package/lib/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.cjs.map +0 -1
  459. package/lib/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.map +0 -1
  460. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/public/create-plugin.cjs.map +0 -1
  461. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/public/create-plugin.js.map +0 -1
  462. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/util/createPlugin.cjs.map +0 -1
  463. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/lib/util/createPlugin.js.map +0 -1
  464. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/plugin.cjs.map +0 -1
  465. package/lib/vendor/.pnpm/tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_/vendor/tailwindcss/plugin.js.map +0 -1
  466. /package/lib/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__ → tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c}/vendor/tailwind-variants/dist/chunk-I2QGXAA3.cjs +0 -0
  467. /package/lib/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__ → tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c}/vendor/tailwind-variants/dist/chunk-I2QGXAA3.js +0 -0
  468. /package/lib/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__ → tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c}/vendor/tailwind-variants/dist/index.cjs +0 -0
  469. /package/lib/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__ → tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c}/vendor/tailwind-variants/dist/index.js +0 -0
  470. /package/lib/vendor/.pnpm/{tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_ → tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_}/vendor/tailwindcss/lib/public/create-plugin.cjs +0 -0
  471. /package/lib/vendor/.pnpm/{tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_ → tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_}/vendor/tailwindcss/lib/public/create-plugin.js +0 -0
  472. /package/lib/vendor/.pnpm/{tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_ → tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_}/vendor/tailwindcss/lib/util/createPlugin.cjs +0 -0
  473. /package/lib/vendor/.pnpm/{tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_ → tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_}/vendor/tailwindcss/lib/util/createPlugin.js +0 -0
  474. /package/lib/vendor/.pnpm/{tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_ → tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_}/vendor/tailwindcss/plugin.cjs +0 -0
  475. /package/lib/vendor/.pnpm/{tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@types_node@20.19.43_typescript@5.9.3_ → tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helpers@0.5.23__@types_node@20.19.43_typescript@5.9.3_}/vendor/tailwindcss/plugin.js +0 -0
@@ -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 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([triggerRef, listBoxRef], functions.blur)\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n [],\n )\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;;AAIA;;AAOE;;AAEE;;;;;;;;AAQA;;;AAIJ;;;AAaI;;;;;;;;;AASJ;;AAGE;AACE;AACA;AACD;AACF;;;AAIC;AAQI;AACA;;AA4DR;;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 { useAnimationFrame } from '../../../hooks/useAnimationFrame'\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 const deleteFrame = useAnimationFrame()\n const selectFrame = useAnimationFrame()\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 deleteFrame,\n selectFrame,\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 // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n latestForListBox.deleteFrame.request(() => {\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 // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n latestForListBox.selectFrame.request(() => {\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 // TODO: callbackRefにまとめたい\n useEffect(\n () => () => {\n latestForListBox.deleteFrame.cancel()\n latestForListBox.selectFrame.cancel()\n },\n [latestForListBox],\n )\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([triggerRef, listBoxRef], functions.blur)\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n [],\n )\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":";;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA;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;AACA;AACA;;;;;;;;;;AAWC;AAED;AACE;;AAGE;AACE;;AAEF;AACE;;AASF;;;AAGE;AACE;AACF;;AAEJ;;;AAIE;;;AAGE;;AAKE;AACE;AACA;;AAGA;;AACK;;;AAGT;;;AAGN;;AAGA;AAEI;AACA;AACF;AAIF;;;;;;;;;;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;;AAIA;;AAOE;;AAEE;;;;;;;;AAQA;;;AAIJ;;;AAaI;;;;;;;;;AASJ;;AAGE;AACE;AACA;AACD;AACF;;;AAIC;AAQI;AACA;;AA4DR;;AAIA;AAQE;;AAEE;AACA;AACE;AACA;;AAON;;"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
- var index = require('./../../../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.cjs');
5
+ var index = require('./../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.cjs');
6
6
  var hooks_useLatest = require('../../../hooks/useLatest.cjs');
7
7
  require('../../../intl/IntlProvider.cjs');
8
8
  require('react-intl');
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { memo, useState, useRef, useEffect, useId, useMemo } from 'react';
3
- 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';
3
+ 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_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.js';
4
4
  import { useLatest } from '../../../hooks/useLatest.js';
5
5
  import '../../../intl/IntlProvider.js';
6
6
  import 'react-intl';
@@ -4,7 +4,8 @@
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var React = require('react');
6
6
  var index$1 = require('../../../_virtual/index3.cjs');
7
- var index = require('./../../../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.cjs');
7
+ var index = require('./../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.cjs');
8
+ var hooks_useAnimationFrame = require('../../../hooks/useAnimationFrame.cjs');
8
9
  var hooks_useClick = require('../../../hooks/useClick.cjs');
9
10
  var hooks_useLatest = require('../../../hooks/useLatest.cjs');
10
11
  var hooks_useTheme = require('../../../hooks/useTheme.cjs');
@@ -75,6 +76,7 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
75
76
  inputValue,
76
77
  isFilteringDisabled: !isEditing,
77
78
  });
79
+ const selectFrame = hooks_useAnimationFrame.useAnimationFrame();
78
80
  const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = components_Combobox_useListbox.useListbox({
79
81
  options,
80
82
  dropdownHelpMessage,
@@ -85,8 +87,8 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
85
87
  onSelect?.(selected);
86
88
  onChangeSelected?.(selected);
87
89
  // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう
88
- // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
89
- requestAnimationFrame(() => {
90
+ // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
91
+ selectFrame.request(() => {
90
92
  setIsExpanded(false);
91
93
  // HINT:
92
94
  // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する
@@ -103,6 +105,8 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
103
105
  triggerRef,
104
106
  noResultText,
105
107
  });
108
+ // TODO: callbackRefにまとめ直したい
109
+ React.useEffect(() => selectFrame.cancel, [selectFrame.cancel]);
106
110
  const latest = hooks_useLatest.useLatest({
107
111
  onChange,
108
112
  onChangeInput,
@@ -1 +1 @@
1
- {"version":3,"file":"SingleCombobox.cjs","sources":["../../../../src/components/Combobox/SingleCombobox/SingleCombobox.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 type RefObject,\n memo,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useClick } from '../../../hooks/useClick'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { Localizer } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { UnstyledButton } from '../../Button'\nimport { FaCaretDownIcon, FaCircleXmarkIcon } from '../../Icon'\nimport { Input } from '../../Input'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useSingleOptions } from '../useOptions'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテム\n */\n selectedItem: ComboboxItem<T> | null\n /**\n * デフォルトで選択されるアイテム\n */\n defaultItem?: ComboboxItem<T>\n /**\n * コンポーネント内の先頭に表示する内容\n */\n prefix?: ReactNode\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n */\n onClear?: () => void\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n * 指定している場合、クリア時にonClickを実行せずにonClearClickのみ実行する\n */\n onClearClick?: (e: MouseEvent) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItem: ComboboxItem<T> | null) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\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 ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_UP_DOWN_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-SingleCombobox shr-inline-block [&:has(.smarthr-ui-Input-input:disabled)]:shr-cursor-not-allowed',\n input: 'smarthr-ui-SingleCombobox-input shr-w-full',\n caretDownLayout: [\n 'shr-relative -shr-me-0.5 shr-p-0.5',\n 'before:shr-border-0',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n caretDownIcon: 'shr-block',\n clearButton: [\n 'smarthr-ui-SingleCombobox-clearButton',\n 'shr-group/clearButton',\n 'shr-me-0.5',\n 'focus-visible:shr-shadow-none',\n '[[data-clear-button-hidden=true]_&]:shr-hidden',\n ],\n clearButtonIcon: [\n 'shr-block',\n 'group-focus-visible/clearButton:shr-focus-indicator group-focus-visible/clearButton:shr-rounded-full',\n ],\n },\n})\n\ntype SuffixButtonsProps = {\n clearButtonRef: RefObject<HTMLButtonElement>\n caretIconColor: string\n handleClickClear: (e: MouseEvent) => void\n handleClickIcon: (e: MouseEvent) => void\n classNames: {\n clearButton: string\n clearButtonIcon: string\n caretDownLayout: string\n caretDownIcon: string\n }\n}\n\nconst SuffixButtons = memo<SuffixButtonsProps>(\n ({\n clearButtonRef,\n caretIconColor,\n handleClickClear,\n handleClickIcon: handleDelegateClickIcon,\n classNames,\n }) => (\n <>\n <UnstyledButton\n onClick={handleClickClear}\n ref={clearButtonRef}\n className={classNames.clearButton}\n >\n <FaCircleXmarkIcon\n color=\"TEXT_BLACK\"\n alt={\n <Localizer id=\"smarthr-ui/SingleCombobox/destroyButtonIconAlt\" defaultText=\"クリア\" />\n }\n className={classNames.clearButtonIcon}\n />\n </UnstyledButton>\n <span\n role=\"presentation\"\n onClick={handleDelegateClickIcon}\n className={classNames.caretDownLayout}\n >\n <FaCaretDownIcon color={caretIconColor} className={classNames.caretDownIcon} />\n </span>\n </>\n ),\n)\n\nconst ActualSingleCombobox = <T,>(\n {\n items,\n selectedItem,\n defaultItem,\n name,\n disabled,\n readOnly,\n required,\n prefix,\n error,\n creatable,\n placeholder,\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n width,\n dropdownWidth = 'auto',\n className,\n onChange,\n onChangeInput,\n onAdd,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const theme = useTheme()\n const triggerRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const clearButtonRef = useRef<HTMLButtonElement>(null)\n const [isFocused, setIsFocused] = useState(false)\n const [isExpanded, setIsExpanded] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n const [isEditing, setIsEditing] = useState(false)\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n [],\n )\n\n const { options } = useSingleOptions({\n items,\n selected: selectedItem,\n creatable,\n inputValue,\n isFilteringDisabled: !isEditing,\n })\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox<T>(\n {\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n // HINT: memo化していないが、内部でuseLatestでstableにしているため最適化としてそのまま渡している\n onSelect: (selected: ComboboxItem<T>) => {\n onSelect?.(selected)\n onChangeSelected?.(selected)\n\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n setIsExpanded(false)\n // HINT:\n // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n // - 対応するdropdownを閉じて以降にonChangeInputを発火する必要がある\n // - 先にclearしてしまうと意図せずこの要素のドロップダウンを閉じる前に他要素の再レンダリングを引き起こす可能性がある\n // - 例えばFilterDropdownなどで当comboboxを使っている場合、レイアウト上comboboxのdropdown以下の要素がクリックされた扱いになってしまい\n // FilterDropdownを意図せず閉じてしまうなどの挙動のバグを引き起こす可能性がある\n onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n })\n\n setIsEditing(false)\n },\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n },\n )\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n handleKeyDownListBox,\n defaultItem,\n selectedItem,\n isFocused,\n isExpanded,\n isComposing,\n isEditing,\n disabled,\n readOnly,\n })\n\n const functions = useMemo(() => {\n const selectDefaultItem = () => {\n if (latest.onSelect && latest.defaultItem) {\n latest.onSelect(latest.defaultItem)\n }\n }\n const unfocus = () => {\n if (!latest.isFocused) return\n\n latest.onBlur?.()\n\n setIsFocused(false)\n setIsExpanded(false)\n setIsEditing(false)\n\n if (latest.selectedItem) {\n setInputValue(innerText(latest.selectedItem.label))\n } else {\n selectDefaultItem()\n }\n }\n\n return {\n selectDefaultItem,\n unfocus,\n handleFocus: () => {\n latest.onFocus?.()\n inputRef.current?.focus()\n setIsFocused(true)\n\n if (!latest.isFocused) {\n setIsExpanded(true)\n }\n },\n handleClickClear: (e: MouseEvent) => {\n e.stopPropagation()\n\n let isExecutedPreventDefault = false\n\n latest.onClearClick?.({\n ...e,\n preventDefault: () => {\n e.preventDefault()\n isExecutedPreventDefault = true\n },\n })\n\n if (!isExecutedPreventDefault) {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n\n inputRef.current?.focus()\n\n setIsFocused(true)\n setIsExpanded(true)\n }\n },\n handleClickInput: (e: MouseEvent) => {\n if (latest.disabled || latest.readOnly) {\n e.stopPropagation()\n\n return\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n latest.onChange?.(e)\n latest.onChangeInput?.(e)\n\n if (!latest.isEditing) setIsEditing(true)\n\n const { value } = e.currentTarget\n\n setInputValue(value)\n\n if (value === '') {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') e.preventDefault()\n\n latest.onKeyPress?.(e)\n },\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (latest.isComposing) {\n return\n }\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n if (latest.isExpanded) {\n e.stopPropagation()\n setIsExpanded(false)\n }\n } else if (e.key === 'Tab') {\n unfocus()\n } else {\n if (ARROW_UP_DOWN_REGEX.test(e.key)) {\n e.preventDefault()\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n }\n\n latest.handleKeyDownListBox(e)\n },\n }\n }, [latest])\n\n const caretIconColor = isFocused\n ? theme.textColor.black\n : disabled || readOnly\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n useClick(\n [triggerRef, listBoxRef, clearButtonRef],\n isFocused || selectedItem ? NOOP : functions.selectDefaultItem,\n functions.unfocus,\n )\n\n // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。\n const selectedItemLabelText = innerText(selectedItem?.label)\n useEffect(() => {\n setInputValue(selectedItemLabelText)\n }, [selectedItemLabelText])\n\n const classNames = useMemo(() => {\n const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } =\n classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n caretDownLayout: caretDownLayout(),\n caretDownIcon: caretDownIcon(),\n clearButton: clearButton(),\n clearButtonIcon: clearButtonIcon(),\n }\n }, [className])\n\n return (\n <div\n ref={triggerRef}\n role=\"group\"\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n data-clear-button-hidden={(selectedItem === null || disabled || readOnly || false).toString()}\n >\n <Input\n {...rest}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n name={name}\n value={inputValue}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n autoComplete={autoComplete ?? 'off'}\n aria-haspopup=\"listbox\"\n aria-controls={listBoxId}\n aria-expanded={isFocused}\n aria-activedescendant={activeOption?.id}\n aria-autocomplete=\"list\"\n /* eslint-disable-next-line smarthr/a11y-prohibit-input-placeholder */\n placeholder={placeholder}\n onClick={functions.handleClickInput}\n onChange={functions.handleChangeInput}\n onFocus={isFocused ? undefined : functions.handleFocus}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n onKeyPress={functions.handleKeyPress}\n error={error}\n prefix={prefix}\n suffix={\n <SuffixButtons\n clearButtonRef={clearButtonRef}\n caretIconColor={caretIconColor}\n handleClickClear={functions.handleClickClear}\n handleClickIcon={functions.handleClickInput}\n classNames={classNames}\n />\n }\n className={classNames.input}\n data-smarthr-ui-input=\"true\"\n />\n {!readOnly && <ListBox {...listBoxProps} />}\n </div>\n )\n}\n\nexport const SingleCombobox = genericsForwardRef(ActualSingleCombobox)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA;AAEA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;AAEA;AACA;;;;AAIC;AACD;AACA;;;;;;AAMC;AACD;;;AAGC;AACF;AACF;AAeD;AAiCA;AAmCE;AACA;AACA;AACA;;;;;;AAOA;AAMA;;AAEE;;;;AAID;AAED;;;;;;AAOI;AACE;AACA;;;;;;;;;;;AAYE;AACF;;;;;;;AAQH;;;;;;;;;;;;;;;;;;;;AAsBF;AAED;;;AAGM;;AAEJ;;;;AAIE;;;;AAMA;;;;AAGE;;AAEJ;;;;;AAMI;AACA;;AAGA;;;;AAIF;;;;AAMI;;;;;AAKD;;AAGC;AACA;AAEA;;;;;AAMJ;;;;;AAOE;AAEA;;;;AAIF;AACE;AACA;;;AAIA;;AAIA;AACE;AACA;;;AAGJ;AACA;;;;AAIA;AACE;;AAEA;;AAEF;AACE;;;;AAKE;;;;;AAIK;AACL;;;;;;AAMA;AAEA;;;;AAKF;;;AAGN;;AAGE;;AAEE;AACA;;;;;;AAYJ;AAEA;AACE;;AAIE;;;;;;;AAOJ;AAEA;AAMM;AACA;AACD;;;AA6CP;;;"}
1
+ {"version":3,"file":"SingleCombobox.cjs","sources":["../../../../src/components/Combobox/SingleCombobox/SingleCombobox.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 type RefObject,\n memo,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useAnimationFrame } from '../../../hooks/useAnimationFrame'\nimport { useClick } from '../../../hooks/useClick'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { Localizer } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { UnstyledButton } from '../../Button'\nimport { FaCaretDownIcon, FaCircleXmarkIcon } from '../../Icon'\nimport { Input } from '../../Input'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useSingleOptions } from '../useOptions'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテム\n */\n selectedItem: ComboboxItem<T> | null\n /**\n * デフォルトで選択されるアイテム\n */\n defaultItem?: ComboboxItem<T>\n /**\n * コンポーネント内の先頭に表示する内容\n */\n prefix?: ReactNode\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n */\n onClear?: () => void\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n * 指定している場合、クリア時にonClickを実行せずにonClearClickのみ実行する\n */\n onClearClick?: (e: MouseEvent) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItem: ComboboxItem<T> | null) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\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 ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_UP_DOWN_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-SingleCombobox shr-inline-block [&:has(.smarthr-ui-Input-input:disabled)]:shr-cursor-not-allowed',\n input: 'smarthr-ui-SingleCombobox-input shr-w-full',\n caretDownLayout: [\n 'shr-relative -shr-me-0.5 shr-p-0.5',\n 'before:shr-border-0',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n caretDownIcon: 'shr-block',\n clearButton: [\n 'smarthr-ui-SingleCombobox-clearButton',\n 'shr-group/clearButton',\n 'shr-me-0.5',\n 'focus-visible:shr-shadow-none',\n '[[data-clear-button-hidden=true]_&]:shr-hidden',\n ],\n clearButtonIcon: [\n 'shr-block',\n 'group-focus-visible/clearButton:shr-focus-indicator group-focus-visible/clearButton:shr-rounded-full',\n ],\n },\n})\n\ntype SuffixButtonsProps = {\n clearButtonRef: RefObject<HTMLButtonElement>\n caretIconColor: string\n handleClickClear: (e: MouseEvent) => void\n handleClickIcon: (e: MouseEvent) => void\n classNames: {\n clearButton: string\n clearButtonIcon: string\n caretDownLayout: string\n caretDownIcon: string\n }\n}\n\nconst SuffixButtons = memo<SuffixButtonsProps>(\n ({\n clearButtonRef,\n caretIconColor,\n handleClickClear,\n handleClickIcon: handleDelegateClickIcon,\n classNames,\n }) => (\n <>\n <UnstyledButton\n onClick={handleClickClear}\n ref={clearButtonRef}\n className={classNames.clearButton}\n >\n <FaCircleXmarkIcon\n color=\"TEXT_BLACK\"\n alt={\n <Localizer id=\"smarthr-ui/SingleCombobox/destroyButtonIconAlt\" defaultText=\"クリア\" />\n }\n className={classNames.clearButtonIcon}\n />\n </UnstyledButton>\n <span\n role=\"presentation\"\n onClick={handleDelegateClickIcon}\n className={classNames.caretDownLayout}\n >\n <FaCaretDownIcon color={caretIconColor} className={classNames.caretDownIcon} />\n </span>\n </>\n ),\n)\n\nconst ActualSingleCombobox = <T,>(\n {\n items,\n selectedItem,\n defaultItem,\n name,\n disabled,\n readOnly,\n required,\n prefix,\n error,\n creatable,\n placeholder,\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n width,\n dropdownWidth = 'auto',\n className,\n onChange,\n onChangeInput,\n onAdd,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const theme = useTheme()\n const triggerRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const clearButtonRef = useRef<HTMLButtonElement>(null)\n const [isFocused, setIsFocused] = useState(false)\n const [isExpanded, setIsExpanded] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n const [isEditing, setIsEditing] = useState(false)\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n [],\n )\n\n const { options } = useSingleOptions({\n items,\n selected: selectedItem,\n creatable,\n inputValue,\n isFilteringDisabled: !isEditing,\n })\n\n const selectFrame = useAnimationFrame()\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox<T>(\n {\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n // HINT: memo化していないが、内部でuseLatestでstableにしているため最適化としてそのまま渡している\n onSelect: (selected: ComboboxItem<T>) => {\n onSelect?.(selected)\n onChangeSelected?.(selected)\n\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n selectFrame.request(() => {\n setIsExpanded(false)\n // HINT:\n // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n // - 対応するdropdownを閉じて以降にonChangeInputを発火する必要がある\n // - 先にclearしてしまうと意図せずこの要素のドロップダウンを閉じる前に他要素の再レンダリングを引き起こす可能性がある\n // - 例えばFilterDropdownなどで当comboboxを使っている場合、レイアウト上comboboxのdropdown以下の要素がクリックされた扱いになってしまい\n // FilterDropdownを意図せず閉じてしまうなどの挙動のバグを引き起こす可能性がある\n onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n })\n\n setIsEditing(false)\n },\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n },\n )\n\n // TODO: callbackRefにまとめ直したい\n useEffect(() => selectFrame.cancel, [selectFrame.cancel])\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n handleKeyDownListBox,\n defaultItem,\n selectedItem,\n isFocused,\n isExpanded,\n isComposing,\n isEditing,\n disabled,\n readOnly,\n })\n\n const functions = useMemo(() => {\n const selectDefaultItem = () => {\n if (latest.onSelect && latest.defaultItem) {\n latest.onSelect(latest.defaultItem)\n }\n }\n const unfocus = () => {\n if (!latest.isFocused) return\n\n latest.onBlur?.()\n\n setIsFocused(false)\n setIsExpanded(false)\n setIsEditing(false)\n\n if (latest.selectedItem) {\n setInputValue(innerText(latest.selectedItem.label))\n } else {\n selectDefaultItem()\n }\n }\n\n return {\n selectDefaultItem,\n unfocus,\n handleFocus: () => {\n latest.onFocus?.()\n inputRef.current?.focus()\n setIsFocused(true)\n\n if (!latest.isFocused) {\n setIsExpanded(true)\n }\n },\n handleClickClear: (e: MouseEvent) => {\n e.stopPropagation()\n\n let isExecutedPreventDefault = false\n\n latest.onClearClick?.({\n ...e,\n preventDefault: () => {\n e.preventDefault()\n isExecutedPreventDefault = true\n },\n })\n\n if (!isExecutedPreventDefault) {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n\n inputRef.current?.focus()\n\n setIsFocused(true)\n setIsExpanded(true)\n }\n },\n handleClickInput: (e: MouseEvent) => {\n if (latest.disabled || latest.readOnly) {\n e.stopPropagation()\n\n return\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n latest.onChange?.(e)\n latest.onChangeInput?.(e)\n\n if (!latest.isEditing) setIsEditing(true)\n\n const { value } = e.currentTarget\n\n setInputValue(value)\n\n if (value === '') {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') e.preventDefault()\n\n latest.onKeyPress?.(e)\n },\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (latest.isComposing) {\n return\n }\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n if (latest.isExpanded) {\n e.stopPropagation()\n setIsExpanded(false)\n }\n } else if (e.key === 'Tab') {\n unfocus()\n } else {\n if (ARROW_UP_DOWN_REGEX.test(e.key)) {\n e.preventDefault()\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n }\n\n latest.handleKeyDownListBox(e)\n },\n }\n }, [latest])\n\n const caretIconColor = isFocused\n ? theme.textColor.black\n : disabled || readOnly\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n useClick(\n [triggerRef, listBoxRef, clearButtonRef],\n isFocused || selectedItem ? NOOP : functions.selectDefaultItem,\n functions.unfocus,\n )\n\n // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。\n const selectedItemLabelText = innerText(selectedItem?.label)\n useEffect(() => {\n setInputValue(selectedItemLabelText)\n }, [selectedItemLabelText])\n\n const classNames = useMemo(() => {\n const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } =\n classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n caretDownLayout: caretDownLayout(),\n caretDownIcon: caretDownIcon(),\n clearButton: clearButton(),\n clearButtonIcon: clearButtonIcon(),\n }\n }, [className])\n\n return (\n <div\n ref={triggerRef}\n role=\"group\"\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n data-clear-button-hidden={(selectedItem === null || disabled || readOnly || false).toString()}\n >\n <Input\n {...rest}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n name={name}\n value={inputValue}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n autoComplete={autoComplete ?? 'off'}\n aria-haspopup=\"listbox\"\n aria-controls={listBoxId}\n aria-expanded={isFocused}\n aria-activedescendant={activeOption?.id}\n aria-autocomplete=\"list\"\n /* eslint-disable-next-line smarthr/a11y-prohibit-input-placeholder */\n placeholder={placeholder}\n onClick={functions.handleClickInput}\n onChange={functions.handleChangeInput}\n onFocus={isFocused ? undefined : functions.handleFocus}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n onKeyPress={functions.handleKeyPress}\n error={error}\n prefix={prefix}\n suffix={\n <SuffixButtons\n clearButtonRef={clearButtonRef}\n caretIconColor={caretIconColor}\n handleClickClear={functions.handleClickClear}\n handleClickIcon={functions.handleClickInput}\n classNames={classNames}\n />\n }\n className={classNames.input}\n data-smarthr-ui-input=\"true\"\n />\n {!readOnly && <ListBox {...listBoxProps} />}\n </div>\n )\n}\n\nexport const SingleCombobox = genericsForwardRef(ActualSingleCombobox)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA;AAEA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;AAEA;AACA;;;;AAIC;AACD;AACA;;;;;;AAMC;AACD;;;AAGC;AACF;AACF;AAeD;AAiCA;AAmCE;AACA;AACA;AACA;;;;;;AAOA;AAMA;;AAEE;;;;AAID;AAED;AAEA;;;;;;AAOI;AACE;AACA;;;AAIA;;;;;;;;AAQE;AACF;;;;;;;AAQH;;AAIH;;;;;;;;;;;;;;;;;;;;AAqBC;AAED;;;AAGM;;AAEJ;;;;AAIE;;;;AAMA;;;;AAGE;;AAEJ;;;;;AAMI;AACA;;AAGA;;;;AAIF;;;;AAMI;;;;;AAKD;;AAGC;AACA;AAEA;;;;;AAMJ;;;;;AAOE;AAEA;;;;AAIF;AACE;AACA;;;AAIA;;AAIA;AACE;AACA;;;AAGJ;AACA;;;;AAIA;AACE;;AAEA;;AAEF;AACE;;;;AAKE;;;;;AAIK;AACL;;;;;;AAMA;AAEA;;;;AAKF;;;AAGN;;AAGE;;AAEE;AACA;;;;;;AAYJ;AAEA;AACE;;AAIE;;;;;;;AAOJ;AAEA;AAMM;AACA;AACD;;;AA6CP;;;"}
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
- import { memo, useRef, useState, useImperativeHandle, useMemo, useEffect } from 'react';
3
+ import { memo, useRef, useState, useImperativeHandle, useEffect, useMemo } from 'react';
4
4
  import innerText from '../../../_virtual/index3.js';
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';
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_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.js';
6
+ import { useAnimationFrame } from '../../../hooks/useAnimationFrame.js';
6
7
  import { useClick } from '../../../hooks/useClick.js';
7
8
  import { useLatest } from '../../../hooks/useLatest.js';
8
9
  import { useTheme } from '../../../hooks/useTheme.js';
@@ -73,6 +74,7 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
73
74
  inputValue,
74
75
  isFilteringDisabled: !isEditing,
75
76
  });
77
+ const selectFrame = useAnimationFrame();
76
78
  const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox({
77
79
  options,
78
80
  dropdownHelpMessage,
@@ -83,8 +85,8 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
83
85
  onSelect?.(selected);
84
86
  onChangeSelected?.(selected);
85
87
  // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう
86
- // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
87
- requestAnimationFrame(() => {
88
+ // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
89
+ selectFrame.request(() => {
88
90
  setIsExpanded(false);
89
91
  // HINT:
90
92
  // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する
@@ -101,6 +103,8 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
101
103
  triggerRef,
102
104
  noResultText,
103
105
  });
106
+ // TODO: callbackRefにまとめ直したい
107
+ useEffect(() => selectFrame.cancel, [selectFrame.cancel]);
104
108
  const latest = useLatest({
105
109
  onChange,
106
110
  onChangeInput,
@@ -1 +1 @@
1
- {"version":3,"file":"SingleCombobox.js","sources":["../../../../src/components/Combobox/SingleCombobox/SingleCombobox.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 type RefObject,\n memo,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useClick } from '../../../hooks/useClick'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { Localizer } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { UnstyledButton } from '../../Button'\nimport { FaCaretDownIcon, FaCircleXmarkIcon } from '../../Icon'\nimport { Input } from '../../Input'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useSingleOptions } from '../useOptions'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテム\n */\n selectedItem: ComboboxItem<T> | null\n /**\n * デフォルトで選択されるアイテム\n */\n defaultItem?: ComboboxItem<T>\n /**\n * コンポーネント内の先頭に表示する内容\n */\n prefix?: ReactNode\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n */\n onClear?: () => void\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n * 指定している場合、クリア時にonClickを実行せずにonClearClickのみ実行する\n */\n onClearClick?: (e: MouseEvent) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItem: ComboboxItem<T> | null) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\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 ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_UP_DOWN_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-SingleCombobox shr-inline-block [&:has(.smarthr-ui-Input-input:disabled)]:shr-cursor-not-allowed',\n input: 'smarthr-ui-SingleCombobox-input shr-w-full',\n caretDownLayout: [\n 'shr-relative -shr-me-0.5 shr-p-0.5',\n 'before:shr-border-0',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n caretDownIcon: 'shr-block',\n clearButton: [\n 'smarthr-ui-SingleCombobox-clearButton',\n 'shr-group/clearButton',\n 'shr-me-0.5',\n 'focus-visible:shr-shadow-none',\n '[[data-clear-button-hidden=true]_&]:shr-hidden',\n ],\n clearButtonIcon: [\n 'shr-block',\n 'group-focus-visible/clearButton:shr-focus-indicator group-focus-visible/clearButton:shr-rounded-full',\n ],\n },\n})\n\ntype SuffixButtonsProps = {\n clearButtonRef: RefObject<HTMLButtonElement>\n caretIconColor: string\n handleClickClear: (e: MouseEvent) => void\n handleClickIcon: (e: MouseEvent) => void\n classNames: {\n clearButton: string\n clearButtonIcon: string\n caretDownLayout: string\n caretDownIcon: string\n }\n}\n\nconst SuffixButtons = memo<SuffixButtonsProps>(\n ({\n clearButtonRef,\n caretIconColor,\n handleClickClear,\n handleClickIcon: handleDelegateClickIcon,\n classNames,\n }) => (\n <>\n <UnstyledButton\n onClick={handleClickClear}\n ref={clearButtonRef}\n className={classNames.clearButton}\n >\n <FaCircleXmarkIcon\n color=\"TEXT_BLACK\"\n alt={\n <Localizer id=\"smarthr-ui/SingleCombobox/destroyButtonIconAlt\" defaultText=\"クリア\" />\n }\n className={classNames.clearButtonIcon}\n />\n </UnstyledButton>\n <span\n role=\"presentation\"\n onClick={handleDelegateClickIcon}\n className={classNames.caretDownLayout}\n >\n <FaCaretDownIcon color={caretIconColor} className={classNames.caretDownIcon} />\n </span>\n </>\n ),\n)\n\nconst ActualSingleCombobox = <T,>(\n {\n items,\n selectedItem,\n defaultItem,\n name,\n disabled,\n readOnly,\n required,\n prefix,\n error,\n creatable,\n placeholder,\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n width,\n dropdownWidth = 'auto',\n className,\n onChange,\n onChangeInput,\n onAdd,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const theme = useTheme()\n const triggerRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const clearButtonRef = useRef<HTMLButtonElement>(null)\n const [isFocused, setIsFocused] = useState(false)\n const [isExpanded, setIsExpanded] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n const [isEditing, setIsEditing] = useState(false)\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n [],\n )\n\n const { options } = useSingleOptions({\n items,\n selected: selectedItem,\n creatable,\n inputValue,\n isFilteringDisabled: !isEditing,\n })\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox<T>(\n {\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n // HINT: memo化していないが、内部でuseLatestでstableにしているため最適化としてそのまま渡している\n onSelect: (selected: ComboboxItem<T>) => {\n onSelect?.(selected)\n onChangeSelected?.(selected)\n\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n setIsExpanded(false)\n // HINT:\n // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n // - 対応するdropdownを閉じて以降にonChangeInputを発火する必要がある\n // - 先にclearしてしまうと意図せずこの要素のドロップダウンを閉じる前に他要素の再レンダリングを引き起こす可能性がある\n // - 例えばFilterDropdownなどで当comboboxを使っている場合、レイアウト上comboboxのdropdown以下の要素がクリックされた扱いになってしまい\n // FilterDropdownを意図せず閉じてしまうなどの挙動のバグを引き起こす可能性がある\n onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n })\n\n setIsEditing(false)\n },\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n },\n )\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n handleKeyDownListBox,\n defaultItem,\n selectedItem,\n isFocused,\n isExpanded,\n isComposing,\n isEditing,\n disabled,\n readOnly,\n })\n\n const functions = useMemo(() => {\n const selectDefaultItem = () => {\n if (latest.onSelect && latest.defaultItem) {\n latest.onSelect(latest.defaultItem)\n }\n }\n const unfocus = () => {\n if (!latest.isFocused) return\n\n latest.onBlur?.()\n\n setIsFocused(false)\n setIsExpanded(false)\n setIsEditing(false)\n\n if (latest.selectedItem) {\n setInputValue(innerText(latest.selectedItem.label))\n } else {\n selectDefaultItem()\n }\n }\n\n return {\n selectDefaultItem,\n unfocus,\n handleFocus: () => {\n latest.onFocus?.()\n inputRef.current?.focus()\n setIsFocused(true)\n\n if (!latest.isFocused) {\n setIsExpanded(true)\n }\n },\n handleClickClear: (e: MouseEvent) => {\n e.stopPropagation()\n\n let isExecutedPreventDefault = false\n\n latest.onClearClick?.({\n ...e,\n preventDefault: () => {\n e.preventDefault()\n isExecutedPreventDefault = true\n },\n })\n\n if (!isExecutedPreventDefault) {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n\n inputRef.current?.focus()\n\n setIsFocused(true)\n setIsExpanded(true)\n }\n },\n handleClickInput: (e: MouseEvent) => {\n if (latest.disabled || latest.readOnly) {\n e.stopPropagation()\n\n return\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n latest.onChange?.(e)\n latest.onChangeInput?.(e)\n\n if (!latest.isEditing) setIsEditing(true)\n\n const { value } = e.currentTarget\n\n setInputValue(value)\n\n if (value === '') {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') e.preventDefault()\n\n latest.onKeyPress?.(e)\n },\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (latest.isComposing) {\n return\n }\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n if (latest.isExpanded) {\n e.stopPropagation()\n setIsExpanded(false)\n }\n } else if (e.key === 'Tab') {\n unfocus()\n } else {\n if (ARROW_UP_DOWN_REGEX.test(e.key)) {\n e.preventDefault()\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n }\n\n latest.handleKeyDownListBox(e)\n },\n }\n }, [latest])\n\n const caretIconColor = isFocused\n ? theme.textColor.black\n : disabled || readOnly\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n useClick(\n [triggerRef, listBoxRef, clearButtonRef],\n isFocused || selectedItem ? NOOP : functions.selectDefaultItem,\n functions.unfocus,\n )\n\n // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。\n const selectedItemLabelText = innerText(selectedItem?.label)\n useEffect(() => {\n setInputValue(selectedItemLabelText)\n }, [selectedItemLabelText])\n\n const classNames = useMemo(() => {\n const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } =\n classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n caretDownLayout: caretDownLayout(),\n caretDownIcon: caretDownIcon(),\n clearButton: clearButton(),\n clearButtonIcon: clearButtonIcon(),\n }\n }, [className])\n\n return (\n <div\n ref={triggerRef}\n role=\"group\"\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n data-clear-button-hidden={(selectedItem === null || disabled || readOnly || false).toString()}\n >\n <Input\n {...rest}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n name={name}\n value={inputValue}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n autoComplete={autoComplete ?? 'off'}\n aria-haspopup=\"listbox\"\n aria-controls={listBoxId}\n aria-expanded={isFocused}\n aria-activedescendant={activeOption?.id}\n aria-autocomplete=\"list\"\n /* eslint-disable-next-line smarthr/a11y-prohibit-input-placeholder */\n placeholder={placeholder}\n onClick={functions.handleClickInput}\n onChange={functions.handleChangeInput}\n onFocus={isFocused ? undefined : functions.handleFocus}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n onKeyPress={functions.handleKeyPress}\n error={error}\n prefix={prefix}\n suffix={\n <SuffixButtons\n clearButtonRef={clearButtonRef}\n caretIconColor={caretIconColor}\n handleClickClear={functions.handleClickClear}\n handleClickIcon={functions.handleClickInput}\n classNames={classNames}\n />\n }\n className={classNames.input}\n data-smarthr-ui-input=\"true\"\n />\n {!readOnly && <ListBox {...listBoxProps} />}\n </div>\n )\n}\n\nexport const SingleCombobox = genericsForwardRef(ActualSingleCombobox)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA;AAEA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;AAEA;AACA;;;;AAIC;AACD;AACA;;;;;;AAMC;AACD;;;AAGC;AACF;AACF;AAeD;AAiCA;AAmCE;AACA;AACA;AACA;;;;;;AAOA;AAMA;;AAEE;;;;AAID;AAED;;;;;;AAOI;AACE;AACA;;;;;;;;;;;AAYE;AACF;;;;;;;AAQH;;;;;;;;;;;;;;;;;;;;AAsBF;AAED;;;AAGM;;AAEJ;;;;AAIE;;;;AAMA;;;;AAGE;;AAEJ;;;;;AAMI;AACA;;AAGA;;;;AAIF;;;;AAMI;;;;;AAKD;;AAGC;AACA;AAEA;;;;;AAMJ;;;;;AAOE;AAEA;;;;AAIF;AACE;AACA;;;AAIA;;AAIA;AACE;AACA;;;AAGJ;AACA;;;;AAIA;AACE;;AAEA;;AAEF;AACE;;;;AAKE;;;;;AAIK;AACL;;;;;;AAMA;AAEA;;;;AAKF;;;AAGN;;AAGE;;AAEE;AACA;;;;;;AAYJ;AAEA;AACE;;AAIE;;;;;;;AAOJ;AAEA;AAMM;AACA;AACD;;;AA6CP;;;"}
1
+ {"version":3,"file":"SingleCombobox.js","sources":["../../../../src/components/Combobox/SingleCombobox/SingleCombobox.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 type RefObject,\n memo,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport innerText from 'react-innertext'\nimport { tv } from 'tailwind-variants'\n\nimport { useAnimationFrame } from '../../../hooks/useAnimationFrame'\nimport { useClick } from '../../../hooks/useClick'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { useTheme } from '../../../hooks/useTheme'\nimport { Localizer } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { UnstyledButton } from '../../Button'\nimport { FaCaretDownIcon, FaCircleXmarkIcon } from '../../Icon'\nimport { Input } from '../../Input'\nimport { ListBox, useListbox } from '../useListbox'\nimport { useSingleOptions } from '../useOptions'\n\nimport type { ComboboxItem, BaseProps as ComboboxProps } from '../types'\n\ntype BaseProps<T> = ComboboxProps<T> & {\n /**\n * 選択されているアイテム\n */\n selectedItem: ComboboxItem<T> | null\n /**\n * デフォルトで選択されるアイテム\n */\n defaultItem?: ComboboxItem<T>\n /**\n * コンポーネント内の先頭に表示する内容\n */\n prefix?: ReactNode\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n */\n onClear?: () => void\n /**\n * 選択されているアイテムがクリアされた時に発火するコールバック関数\n * 指定している場合、クリア時にonClickを実行せずにonClearClickのみ実行する\n */\n onClearClick?: (e: MouseEvent) => void\n /**\n * 選択されているアイテムのリストが変わった時に発火するコールバック関数\n */\n onChangeSelected?: (selectedItem: ComboboxItem<T> | null) => void\n /**\n * コンポーネントがフォーカスされたときに発火するコールバック関数\n */\n onFocus?: () => void\n /**\n * コンポーネントからフォーカスが外れた時に発火するコールバック関数\n */\n onBlur?: () => void\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 ESCAPE_KEY_REGEX = /^Esc(ape)?$/\nconst ARROW_UP_DOWN_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-SingleCombobox shr-inline-block [&:has(.smarthr-ui-Input-input:disabled)]:shr-cursor-not-allowed',\n input: 'smarthr-ui-SingleCombobox-input shr-w-full',\n caretDownLayout: [\n 'shr-relative -shr-me-0.5 shr-p-0.5',\n 'before:shr-border-0',\n 'before:shr-absolute before:shr-inset-x-0 before:shr-inset-y-0.25 before:shr-w-0 before:shr-border-l before:shr-border-solid before:shr-border-default before:shr-content-[\"\"]',\n ],\n caretDownIcon: 'shr-block',\n clearButton: [\n 'smarthr-ui-SingleCombobox-clearButton',\n 'shr-group/clearButton',\n 'shr-me-0.5',\n 'focus-visible:shr-shadow-none',\n '[[data-clear-button-hidden=true]_&]:shr-hidden',\n ],\n clearButtonIcon: [\n 'shr-block',\n 'group-focus-visible/clearButton:shr-focus-indicator group-focus-visible/clearButton:shr-rounded-full',\n ],\n },\n})\n\ntype SuffixButtonsProps = {\n clearButtonRef: RefObject<HTMLButtonElement>\n caretIconColor: string\n handleClickClear: (e: MouseEvent) => void\n handleClickIcon: (e: MouseEvent) => void\n classNames: {\n clearButton: string\n clearButtonIcon: string\n caretDownLayout: string\n caretDownIcon: string\n }\n}\n\nconst SuffixButtons = memo<SuffixButtonsProps>(\n ({\n clearButtonRef,\n caretIconColor,\n handleClickClear,\n handleClickIcon: handleDelegateClickIcon,\n classNames,\n }) => (\n <>\n <UnstyledButton\n onClick={handleClickClear}\n ref={clearButtonRef}\n className={classNames.clearButton}\n >\n <FaCircleXmarkIcon\n color=\"TEXT_BLACK\"\n alt={\n <Localizer id=\"smarthr-ui/SingleCombobox/destroyButtonIconAlt\" defaultText=\"クリア\" />\n }\n className={classNames.clearButtonIcon}\n />\n </UnstyledButton>\n <span\n role=\"presentation\"\n onClick={handleDelegateClickIcon}\n className={classNames.caretDownLayout}\n >\n <FaCaretDownIcon color={caretIconColor} className={classNames.caretDownIcon} />\n </span>\n </>\n ),\n)\n\nconst ActualSingleCombobox = <T,>(\n {\n items,\n selectedItem,\n defaultItem,\n name,\n disabled,\n readOnly,\n required,\n prefix,\n error,\n creatable,\n placeholder,\n autoComplete,\n dropdownHelpMessage,\n isLoading,\n width,\n dropdownWidth = 'auto',\n className,\n onChange,\n onChangeInput,\n onAdd,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n noResultText,\n style,\n ...rest\n }: Props<T>,\n ref: Ref<HTMLInputElement>,\n) => {\n const theme = useTheme()\n const triggerRef = useRef<HTMLDivElement>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const clearButtonRef = useRef<HTMLButtonElement>(null)\n const [isFocused, setIsFocused] = useState(false)\n const [isExpanded, setIsExpanded] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const [isComposing, setIsComposing] = useState(false)\n const [isEditing, setIsEditing] = useState(false)\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n ref,\n () => inputRef.current,\n [],\n )\n\n const { options } = useSingleOptions({\n items,\n selected: selectedItem,\n creatable,\n inputValue,\n isFilteringDisabled: !isEditing,\n })\n\n const selectFrame = useAnimationFrame()\n\n const { listBoxProps, activeOption, handleKeyDownListBox, listBoxId, listBoxRef } = useListbox<T>(\n {\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n // HINT: memo化していないが、内部でuseLatestでstableにしているため最適化としてそのまま渡している\n onSelect: (selected: ComboboxItem<T>) => {\n onSelect?.(selected)\n onChangeSelected?.(selected)\n\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n selectFrame.request(() => {\n setIsExpanded(false)\n // HINT:\n // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n // - 対応するdropdownを閉じて以降にonChangeInputを発火する必要がある\n // - 先にclearしてしまうと意図せずこの要素のドロップダウンを閉じる前に他要素の再レンダリングを引き起こす可能性がある\n // - 例えばFilterDropdownなどで当comboboxを使っている場合、レイアウト上comboboxのdropdown以下の要素がクリックされた扱いになってしまい\n // FilterDropdownを意図せず閉じてしまうなどの挙動のバグを引き起こす可能性がある\n onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n })\n\n setIsEditing(false)\n },\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n },\n )\n\n // TODO: callbackRefにまとめ直したい\n useEffect(() => selectFrame.cancel, [selectFrame.cancel])\n\n const latest = useLatest({\n onChange,\n onChangeInput,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n handleKeyDownListBox,\n defaultItem,\n selectedItem,\n isFocused,\n isExpanded,\n isComposing,\n isEditing,\n disabled,\n readOnly,\n })\n\n const functions = useMemo(() => {\n const selectDefaultItem = () => {\n if (latest.onSelect && latest.defaultItem) {\n latest.onSelect(latest.defaultItem)\n }\n }\n const unfocus = () => {\n if (!latest.isFocused) return\n\n latest.onBlur?.()\n\n setIsFocused(false)\n setIsExpanded(false)\n setIsEditing(false)\n\n if (latest.selectedItem) {\n setInputValue(innerText(latest.selectedItem.label))\n } else {\n selectDefaultItem()\n }\n }\n\n return {\n selectDefaultItem,\n unfocus,\n handleFocus: () => {\n latest.onFocus?.()\n inputRef.current?.focus()\n setIsFocused(true)\n\n if (!latest.isFocused) {\n setIsExpanded(true)\n }\n },\n handleClickClear: (e: MouseEvent) => {\n e.stopPropagation()\n\n let isExecutedPreventDefault = false\n\n latest.onClearClick?.({\n ...e,\n preventDefault: () => {\n e.preventDefault()\n isExecutedPreventDefault = true\n },\n })\n\n if (!isExecutedPreventDefault) {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n\n inputRef.current?.focus()\n\n setIsFocused(true)\n setIsExpanded(true)\n }\n },\n handleClickInput: (e: MouseEvent) => {\n if (latest.disabled || latest.readOnly) {\n e.stopPropagation()\n\n return\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n },\n handleChangeInput: (e: ChangeEvent<HTMLInputElement>) => {\n latest.onChange?.(e)\n latest.onChangeInput?.(e)\n\n if (!latest.isEditing) setIsEditing(true)\n\n const { value } = e.currentTarget\n\n setInputValue(value)\n\n if (value === '') {\n latest.onClear?.()\n latest.onChangeSelected?.(null)\n }\n },\n handleCompositionStart: () => setIsComposing(true),\n handleCompositionEnd: () => setIsComposing(false),\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n handleKeyPress: (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') e.preventDefault()\n\n latest.onKeyPress?.(e)\n },\n handleKeyDownInput: (e: KeyboardEvent<HTMLInputElement>) => {\n if (latest.isComposing) {\n return\n }\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n if (latest.isExpanded) {\n e.stopPropagation()\n setIsExpanded(false)\n }\n } else if (e.key === 'Tab') {\n unfocus()\n } else {\n if (ARROW_UP_DOWN_REGEX.test(e.key)) {\n e.preventDefault()\n }\n\n inputRef.current?.focus()\n\n if (!latest.isExpanded) {\n setIsExpanded(true)\n }\n }\n\n latest.handleKeyDownListBox(e)\n },\n }\n }, [latest])\n\n const caretIconColor = isFocused\n ? theme.textColor.black\n : disabled || readOnly\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n useClick(\n [triggerRef, listBoxRef, clearButtonRef],\n isFocused || selectedItem ? NOOP : functions.selectDefaultItem,\n functions.unfocus,\n )\n\n // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。\n const selectedItemLabelText = innerText(selectedItem?.label)\n useEffect(() => {\n setInputValue(selectedItemLabelText)\n }, [selectedItemLabelText])\n\n const classNames = useMemo(() => {\n const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } =\n classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n caretDownLayout: caretDownLayout(),\n caretDownIcon: caretDownIcon(),\n clearButton: clearButton(),\n clearButtonIcon: clearButtonIcon(),\n }\n }, [className])\n\n return (\n <div\n ref={triggerRef}\n role=\"group\"\n className={classNames.wrapper}\n style={{\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }}\n data-clear-button-hidden={(selectedItem === null || disabled || readOnly || false).toString()}\n >\n <Input\n {...rest}\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n name={name}\n value={inputValue}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n autoComplete={autoComplete ?? 'off'}\n aria-haspopup=\"listbox\"\n aria-controls={listBoxId}\n aria-expanded={isFocused}\n aria-activedescendant={activeOption?.id}\n aria-autocomplete=\"list\"\n /* eslint-disable-next-line smarthr/a11y-prohibit-input-placeholder */\n placeholder={placeholder}\n onClick={functions.handleClickInput}\n onChange={functions.handleChangeInput}\n onFocus={isFocused ? undefined : functions.handleFocus}\n onCompositionStart={functions.handleCompositionStart}\n onCompositionEnd={functions.handleCompositionEnd}\n onKeyDown={functions.handleKeyDownInput}\n onKeyPress={functions.handleKeyPress}\n error={error}\n prefix={prefix}\n suffix={\n <SuffixButtons\n clearButtonRef={clearButtonRef}\n caretIconColor={caretIconColor}\n handleClickClear={functions.handleClickClear}\n handleClickIcon={functions.handleClickInput}\n classNames={classNames}\n />\n }\n className={classNames.input}\n data-smarthr-ui-input=\"true\"\n />\n {!readOnly && <ListBox {...listBoxProps} />}\n </div>\n )\n}\n\nexport const SingleCombobox = genericsForwardRef(ActualSingleCombobox)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA;AAEA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;AAEA;AACA;;;;AAIC;AACD;AACA;;;;;;AAMC;AACD;;;AAGC;AACF;AACF;AAeD;AAiCA;AAmCE;AACA;AACA;AACA;;;;;;AAOA;AAMA;;AAEE;;;;AAID;AAED;AAEA;;;;;;AAOI;AACE;AACA;;;AAIA;;;;;;;;AAQE;AACF;;;;;;;AAQH;;AAIH;;;;;;;;;;;;;;;;;;;;AAqBC;AAED;;;AAGM;;AAEJ;;;;AAIE;;;;AAMA;;;;AAGE;;AAEJ;;;;;AAMI;AACA;;AAGA;;;;AAIF;;;;AAMI;;;;;AAKD;;AAGC;AACA;AAEA;;;;;AAMJ;;;;;AAOE;AAEA;;;;AAIF;AACE;AACA;;;AAIA;;AAIA;AACE;AACA;;;AAGJ;AACA;;;;AAIA;AACE;;AAEA;;AAEF;AACE;;;;AAKE;;;;;AAIK;AACL;;;;;;AAMA;AAEA;;;;AAKF;;;AAGN;;AAGE;;AAEE;AACA;;;;;;AAYJ;AAEA;AACE;;AAIE;;;;;;;AAOJ;AAEA;AAMM;AACA;AACD;;;AA6CP;;;"}
@@ -3,7 +3,8 @@
3
3
 
4
4
  var React = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
- var index = require('./../../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.cjs');
6
+ var index = require('./../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.46_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.cjs');
7
+ var hooks_useAnimationFrame = require('../../hooks/useAnimationFrame.cjs');
7
8
  var hooks_useEnhancedEffect = require('../../hooks/useEnhancedEffect.cjs');
8
9
  var hooks_useLatest = require('../../hooks/useLatest.cjs');
9
10
  var hooks_usePortal = require('../../hooks/usePortal.cjs');
@@ -65,7 +66,8 @@ const useListbox = ({ options, dropdownHelpMessage, dropdownWidth, onAdd, onSele
65
66
  const listBoxRef = React.useRef(null);
66
67
  const activeRef = React.useRef(null);
67
68
  const theme = hooks_useTheme.useTheme();
68
- const latest = hooks_useLatest.useLatest({ onAdd, onSelect, activeOption, options, triggerRef, theme });
69
+ const addFrame = hooks_useAnimationFrame.useAnimationFrame();
70
+ const latest = hooks_useLatest.useLatest({ onAdd, onSelect, activeOption, options, triggerRef, theme, addFrame });
69
71
  const hasOnAdd = !!onAdd;
70
72
  const functions = React.useMemo(() => {
71
73
  const moveActiveOptionIndex = (currentActive, delta) => {
@@ -176,8 +178,8 @@ const useListbox = ({ options, dropdownHelpMessage, dropdownWidth, onAdd, onSele
176
178
  handleAdd: hasOnAdd
177
179
  ? (option) => {
178
180
  // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう
179
- // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
180
- requestAnimationFrame(() => {
181
+ // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
182
+ latest.addFrame.request(() => {
181
183
  latest.onAdd(option.item.value);
182
184
  });
183
185
  }
@@ -191,6 +193,8 @@ const useListbox = ({ options, dropdownHelpMessage, dropdownWidth, onAdd, onSele
191
193
  },
192
194
  };
193
195
  }, [hasOnAdd, latest]);
196
+ // TODO: callbackRefにまとめ直したい
197
+ React.useEffect(() => addFrame.cancel, [addFrame.cancel]);
194
198
  React.useEffect(() => {
195
199
  // props の変更によって activeOption の状態が変わりうるので、実態を反映する
196
200
  setActiveOption((current) => {
@@ -1 +1 @@
1
- {"version":3,"file":"useListbox.cjs","sources":["../../../src/components/Combobox/useListbox.tsx"],"sourcesContent":["'use client'\n\nimport {\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type RefObject,\n memo,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { useLatest } from '../../hooks/useLatest'\nimport { usePortal } from '../../hooks/usePortal'\nimport { useTheme } from '../../hooks/useTheme'\nimport { Localizer } from '../../intl'\nimport { findDelegateTarget } from '../../libs/delegate'\nimport { FaCircleInfoIcon } from '../Icon'\nimport { Loader } from '../Loader'\nimport { Scroller } from '../Scroller'\nimport { Text } from '../Text'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { ItemButton } from './ItemButton'\n\nimport type { ComboboxItem, ComboboxOption } from './types'\n\ntype Props<T> = {\n options: Array<ComboboxOption<T>>\n dropdownHelpMessage?: ReactNode\n dropdownWidth?: string | number\n onAdd?: (label: string) => void\n onSelect: (item: ComboboxItem<T>) => void\n isExpanded: boolean\n isLoading?: boolean\n triggerRef: RefObject<HTMLElement>\n /** 検索結果が0件の時に表示するコンテンツ */\n noResultText?: ReactNode\n}\n\ntype Rect = {\n top: number\n left: number\n height?: number\n}\n\nconst KEY_DOWN_REGEX = /^(Arrow)?Down$/\nconst KEY_UP_REGEX = /^(Arrow)?Up/\n\nconst OPTION_INCREMENT_AMOUNT = 100\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: 'shr-absolute',\n dropdownList: [\n 'smarthr-ui-Combobox-dropdownList',\n 'shr-absolute shr-z-overlap shr-box-border shr-min-w-full shr-rounded-m shr-bg-white shr-py-0.5 shr-shadow-layer-3 forced-colors:shr-outline forced-colors:shr-outline-1',\n /* 縦スクロールに気づきやすくするために8個目のアイテムが半分見切れるように max-height を算出\n = (アイテムのフォントサイズ + アイテムの上下padding) * 7.5 + コンテナの上padding */\n 'shr-max-h-[calc((theme(fontSize.base)_+_theme(spacing[0.5])_*_2)_*_7.5_+_theme(spacing[0.5]))]',\n 'aria-hidden:shr-hidden',\n ],\n helpMessage:\n 'shr-whitespace-[initial] shr-border-b-shorthand shr-mx-0.5 shr-mb-0.5 shr-mt-0 shr-px-0.5 shr-pb-0.5 shr-pt-0 shr-text-sm',\n loaderWrapper: 'shr-flex shr-items-center shr-justify-center shr-p-1',\n noItems: 'smarthr-ui-Combobox-noItems shr-my-0 shr-bg-white shr-px-1 shr-py-0.5 shr-text-base',\n },\n})\n\nconst CLASS_NAMES = (() => {\n const { wrapper, dropdownList, helpMessage, loaderWrapper, noItems } = classNameGenerator()\n\n return {\n wrapper: wrapper(),\n dropdownList: dropdownList(),\n helpMessage: helpMessage(),\n loaderWrapper: loaderWrapper(),\n noItems: noItems(),\n }\n})()\n\nexport const useListbox = <T,>({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect,\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n}: Props<T>) => {\n const listBoxId = useId()\n\n const [navigationType, setNavigationType] = useState<'pointer' | 'key'>('pointer')\n const [activeOption, setActiveOption] = useState<ComboboxOption<T> | null>(null)\n const [listBoxRect, setListBoxRect] = useState<Rect>({\n top: 0,\n left: 0,\n })\n // HINT: calculateRectで同時に計算するとwidthの幅が変更されるタイミングの問題でlistBoxHeightが変化する場合がある\n const [triggerWidth, setTriggerWidth] = useState(0)\n\n const listBoxRef = useRef<HTMLDivElement>(null)\n const activeRef = useRef<HTMLButtonElement>(null)\n\n const theme = useTheme()\n\n const latest = useLatest({ onAdd, onSelect, activeOption, options, triggerRef, theme })\n const hasOnAdd = !!onAdd\n\n const functions = useMemo(() => {\n const moveActiveOptionIndex = (currentActive: ComboboxOption<T> | null, delta: -1 | 1) => {\n if (latest.options.every((option) => option.item.disabled)) {\n return\n }\n\n const currentActiveIndex =\n currentActive === null\n ? -1\n : latest.options.findIndex((option) => option.id === currentActive.id)\n let nextIndex = 0\n\n if (currentActiveIndex !== -1) {\n nextIndex = (currentActiveIndex + delta + latest.options.length) % latest.options.length\n } else if (delta !== 1) {\n nextIndex = latest.options.length - 1\n }\n\n const nextActive = latest.options[nextIndex]\n\n if (nextActive) {\n if (nextActive.item.disabled) {\n moveActiveOptionIndex(nextActive, delta)\n } else {\n setActiveOption(nextActive)\n }\n }\n }\n\n return {\n calculateRect: () => {\n if (!listBoxRef.current || !latest.triggerRef.current) {\n return\n }\n const rect = latest.triggerRef.current.getBoundingClientRect()\n const bottomSpace = window.innerHeight - rect.bottom\n const topSpace = rect.top\n const listBoxHeight = Math.min(\n listBoxRef.current.scrollHeight,\n parseInt(getComputedStyle(listBoxRef.current).maxHeight, 10),\n )\n const offset = 2\n\n let top = 0\n let height: number | undefined = undefined\n\n if (bottomSpace >= listBoxHeight) {\n // 下側に十分なスペースがある場合は下側に通常表示\n top = rect.top + rect.height - offset + window.pageYOffset\n } else if (topSpace >= listBoxHeight) {\n // 上側に十分なスペースがある場合は上側に通常表示\n top = rect.top - listBoxHeight + offset + window.pageYOffset\n } else if (topSpace > bottomSpace) {\n // 上下に十分なスペースがなく、上側の方がスペースが大きい場合は上側に縮めて表示\n top = rect.top - topSpace + offset + window.pageYOffset\n height = topSpace\n } else {\n // 下側に縮めて表示\n top = rect.top + rect.height - offset + window.pageYOffset\n height = bottomSpace\n }\n\n // HINT: dropdownWidth は 'auto' や '%' などの CSS 値を取りうるため、算出済みの幅を実測して判定する\n const listBoxWidth = listBoxRef.current.getBoundingClientRect().width\n // ドロップダウンの幅は maxWidth でビューポート右端から余白分を残すよう制限しているため、位置の判定にも同じ余白を使う\n const viewportMargin = parseInt(latest.theme.spacingByChar(0.5), 10)\n // 入力欄の左端を起点に右方向へ表示する場合に使える幅\n const rightSpace = window.innerWidth - rect.left - viewportMargin\n // 入力欄の右端を起点に左方向へ表示する場合に使える幅\n const leftSpace = rect.right\n\n // ビューポートの左端を基準に計算\n let left = window.pageXOffset\n\n if (listBoxWidth <= rightSpace) {\n // 右側に十分なスペースがある場合は入力欄の左端に揃えて通常表示\n left += rect.left\n } else if (listBoxWidth <= leftSpace) {\n // 左側に収まる場合は入力欄の右端に揃えて表示\n left += rect.right - listBoxWidth\n }\n\n setListBoxRect({\n top,\n left,\n height,\n })\n setTriggerWidth(rect.width)\n },\n handleKeyDownListBox: (e: KeyboardEvent<HTMLElement>) => {\n setNavigationType('key')\n\n if (KEY_DOWN_REGEX.test(e.key)) {\n e.stopPropagation()\n moveActiveOptionIndex(latest.activeOption, 1)\n } else if (KEY_UP_REGEX.test(e.key)) {\n e.stopPropagation()\n moveActiveOptionIndex(latest.activeOption, -1)\n } else if (e.key === 'Enter') {\n if (latest.activeOption === null) {\n return\n }\n\n e.stopPropagation()\n\n if (!latest.activeOption.isNew) {\n latest.onSelect(latest.activeOption.item)\n } else if (latest.onAdd) {\n latest.onAdd(latest.activeOption.item.value)\n }\n } else {\n setActiveOption(null)\n }\n },\n handleAdd: hasOnAdd\n ? (option: ComboboxOption<T>) => {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n latest.onAdd!(option.item.value)\n })\n }\n : undefined,\n handleSelect: (option: ComboboxOption<T>) => {\n latest.onSelect(option.item)\n },\n handleHoverOption: (option: ComboboxOption<T>) => {\n setNavigationType('pointer')\n setActiveOption(option)\n },\n }\n }, [hasOnAdd, latest])\n\n useEffect(() => {\n // props の変更によって activeOption の状態が変わりうるので、実態を反映する\n setActiveOption((current) => {\n if (current === null) {\n return null\n }\n\n return options.find((option) => current.id === option.id) ?? null\n })\n }, [options])\n\n useEffect(() => {\n // actionOption の要素が表示される位置までリストボックス内をスクロールさせる\n if (\n !activeRef.current ||\n !listBoxRef.current ||\n activeOption === null ||\n navigationType !== 'key'\n ) {\n return\n }\n\n const activeRect = activeRef.current.getBoundingClientRect()\n const containerRect = listBoxRef.current.getBoundingClientRect()\n\n if (activeRect.top < containerRect.top) {\n listBoxRef.current.scrollTop -= containerRect.top - activeRect.top\n } else if (activeRect.bottom > containerRect.bottom) {\n listBoxRef.current.scrollTop += activeRect.bottom - containerRect.bottom\n }\n }, [activeOption, navigationType])\n\n useEnhancedEffect(() => {\n // 閉じたときに activeOption を初期化\n if (!isExpanded) {\n return setActiveOption(null)\n }\n\n functions.calculateRect()\n\n const scrollOption = { capture: true, passive: true }\n window.addEventListener('scroll', functions.calculateRect, scrollOption)\n window.addEventListener('resize', functions.calculateRect, { passive: true })\n\n return () => {\n window.removeEventListener('scroll', functions.calculateRect, scrollOption)\n window.removeEventListener('resize', functions.calculateRect)\n }\n // HINT: optionsが変わる場合メニューのサイズが変わる可能性がある\n }, [isExpanded, options, functions])\n\n return {\n listBoxProps: {\n activeOptionId: activeOption?.id,\n options,\n isExpanded,\n isLoading,\n dropdownHelpMessage,\n noResultText,\n listBoxId,\n listBoxRef,\n handleAdd: functions.handleAdd,\n handleHoverOption: functions.handleHoverOption,\n handleSelect: functions.handleSelect,\n activeRef,\n listBoxRect,\n triggerWidth,\n dropdownWidth,\n },\n activeOption,\n handleKeyDownListBox: functions.handleKeyDownListBox,\n listBoxId,\n listBoxRef,\n }\n}\n\ntype ListBoxProps<T> = {\n activeOptionId: string | undefined\n options: Array<ComboboxOption<T>>\n isExpanded: boolean\n isLoading?: boolean\n noResultText?: ReactNode\n dropdownHelpMessage?: ReactNode\n listBoxId: string\n listBoxRef: RefObject<HTMLDivElement>\n handleAdd: ((option: ComboboxOption<T>) => void) | undefined\n handleHoverOption: (option: ComboboxOption<T>) => void\n handleSelect: (option: ComboboxOption<T>) => void\n activeRef: RefObject<HTMLButtonElement>\n listBoxRect: { top: number; left: number; height?: number }\n triggerWidth: number\n dropdownWidth?: string | number\n}\n\nexport const ListBox = memo(\n <T,>({\n activeOptionId,\n options,\n isExpanded,\n isLoading,\n noResultText,\n dropdownHelpMessage,\n listBoxId,\n listBoxRef,\n handleAdd,\n handleHoverOption,\n handleSelect,\n activeRef,\n listBoxRect,\n triggerWidth,\n dropdownWidth,\n }: ListBoxProps<T>) => {\n const { createPortal } = usePortal()\n const theme = useTheme()\n\n const minLength = useMemo(\n () =>\n (activeOptionId === undefined ? 0 : options.findIndex((o) => o.id === activeOptionId)) + 1,\n [activeOptionId, options],\n )\n const [currentItemLength, setCurrentItemLength] = useState(() =>\n Math.max(OPTION_INCREMENT_AMOUNT, minLength),\n )\n const items = useMemo(() => options.slice(0, currentItemLength), [currentItemLength, options])\n\n const styles = useMemo(() => {\n const { top, left, height } = listBoxRect\n const dropdownListWidth = dropdownWidth || triggerWidth\n\n return {\n wrapper: {\n top: `${top}px`,\n left: `${left}px`,\n width: `${triggerWidth}px`,\n },\n dropdownList: {\n width:\n typeof dropdownListWidth === 'string' ? dropdownListWidth : `${dropdownListWidth}px`,\n /* HINT: left に依存させると、算出した幅がさらに maxWidth を縮めて再計算の度に幅が縮んでいくため、\n ビューポート幅のみから算出する */\n maxWidth: `calc(100vw - ${theme.spacingByChar(0.5)})`,\n height: height ? `${height}px` : undefined,\n },\n }\n }, [listBoxRect, triggerWidth, dropdownWidth, theme])\n\n const latest = useLatest({ handleAdd, handleHoverOption, handleSelect, options, minLength })\n\n const functions = useMemo(() => {\n const resolveOption = (e: MouseEvent) => {\n const el = findDelegateTarget<HTMLButtonElement>(e, 'button[role=\"option\"]')\n if (!el || el.disabled) return null\n return latest.options.find((o) => o.id === el.id) ?? null\n }\n\n return {\n handleDelegateClick: (e: MouseEvent) => {\n const option = resolveOption(e)\n if (option) {\n if (option.isNew) {\n latest.handleAdd?.(option)\n } else {\n latest.handleSelect(option)\n }\n }\n },\n handleDelegateMouseOver: (e: MouseEvent) => {\n const option = resolveOption(e)\n if (option) {\n latest.handleHoverOption(option)\n }\n },\n handleIntersect: () => {\n setCurrentItemLength((current) =>\n Math.max(current + OPTION_INCREMENT_AMOUNT, latest.minLength),\n )\n },\n }\n }, [latest])\n\n useEffect(() => {\n setCurrentItemLength((current) => Math.max(current, minLength))\n }, [minLength])\n\n return createPortal(\n <div className={CLASS_NAMES.wrapper} style={styles.wrapper}>\n {isExpanded && isLoading && (\n <VisuallyHiddenText role=\"status\">\n <Localizer id=\"smarthr-ui/Combobox/loadingText\" defaultText=\"処理中\" />\n </VisuallyHiddenText>\n )}\n <Scroller\n id={listBoxId}\n ref={listBoxRef}\n role=\"listbox\"\n aria-hidden={!isExpanded}\n className={CLASS_NAMES.dropdownList}\n style={styles.dropdownList}\n onMouseOver={functions.handleDelegateMouseOver}\n onClick={functions.handleDelegateClick}\n >\n {dropdownHelpMessage && (\n <Text\n className={CLASS_NAMES.helpMessage}\n icon={<FaCircleInfoIcon color=\"TEXT_GREY\" />}\n as=\"p\"\n >\n {dropdownHelpMessage}\n </Text>\n )}\n {isExpanded ? (\n isLoading ? (\n <div className={CLASS_NAMES.loaderWrapper}>\n <Loader aria-hidden />\n </div>\n ) : options.length === 0 ? (\n <p role=\"alert\" aria-live=\"polite\" className={CLASS_NAMES.noItems}>\n {noResultText ?? (\n <Localizer\n id=\"smarthr-ui/Combobox/noResultsText\"\n defaultText=\"一致する選択肢がありません。\"\n />\n )}\n </p>\n ) : (\n items.map(({ item: { label, disabled }, id, ...optionRest }) => (\n <ItemButton\n {...optionRest}\n label={label}\n disabled={disabled}\n key={id}\n id={id}\n activeRef={id === activeOptionId ? activeRef : undefined}\n />\n ))\n )\n ) : null}\n {currentItemLength < options.length && (\n <Intersection handleIntersect={functions.handleIntersect} />\n )}\n </Scroller>\n </div>,\n )\n },\n) as <T>(props: ListBoxProps<T>) => ReactNode\n\nconst Intersection = memo<{ handleIntersect: () => void }>(({ handleIntersect }) => {\n const ref = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const target = ref.current\n\n if (target === null) {\n return\n }\n\n const observer = new IntersectionObserver(([entry]) => {\n if (entry.isIntersecting) {\n handleIntersect()\n }\n })\n\n observer.observe(target)\n\n return () => observer.disconnect()\n }, [handleIntersect])\n\n return <div ref={ref} />\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAmDA;AACA;AAEA;AAEA;AACE;AACE;AACA;;;AAGE;AAC0D;;;AAG3D;AACD;AAEA;AACA;AACD;AACF;AAED;AACE;;;;;;;;AASF;AAEO;AAWL;;;AAIA;AACE;AACA;AACD;;;AAID;AACA;AAEA;AAEA;AACA;AAEA;AACE;AACE;;;AAIA;;;;AAMA;AACE;;AACK;;;;;AAOL;AACE;;;;;;AAKN;;;AAII;;;;;AAKA;;;;;AAUA;;AAEE;;AACK;;AAEL;;AACK;;AAEL;;;;;AAIA;;;;;;AAOF;;;;AAIA;;AAGA;AAEA;;AAEE;;AACK;;AAEL;;AAGF;;;;AAIC;AACD;;AAEF;;;;AAKI;;;;;;AAIK;AACL;;;;AAMA;;;AAEO;;;;;;;;AAOX;AACE;;;;;AAKI;;AAEJ;AACF;AACE;;AAEF;;;;;AAKJ;;;AAIE;AACE;AACE;;AAGF;AACF;AACF;;;;;AAOI;;;;;;;AAUA;;;AAEA;;AAEJ;;;;AAKI;;;;;AAOF;AAEA;;;AAGA;;;;AAKA;;;;;;;;;;;;;;;;AAgBC;;;;;;AAML;AAoBO;AAkBH;AACA;AAEA;;;AAUA;;AAEE;;AAGE;;;;AAIC;AACD;AACE;AAEA;AACkB;;;AAGnB;;;AAIL;AAEA;AACE;;AAEE;AAAwB;;AAE1B;;AAGE;AACE;;AAEE;AACE;;;AAEA;;;;AAIN;AACE;;AAEE;;;;AAIF;;;AAKN;;AAGE;AACF;AAEA;AA2DF;AAGF;AACE;;AAGE;AAEA;;;;AAKE;AACE;;AAEJ;AAEA;AAEA;AACF;AAEA;AACF;;;"}
1
+ {"version":3,"file":"useListbox.cjs","sources":["../../../src/components/Combobox/useListbox.tsx"],"sourcesContent":["'use client'\n\nimport {\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n type RefObject,\n memo,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useAnimationFrame } from '../../hooks/useAnimationFrame'\nimport { useEnhancedEffect } from '../../hooks/useEnhancedEffect'\nimport { useLatest } from '../../hooks/useLatest'\nimport { usePortal } from '../../hooks/usePortal'\nimport { useTheme } from '../../hooks/useTheme'\nimport { Localizer } from '../../intl'\nimport { findDelegateTarget } from '../../libs/delegate'\nimport { FaCircleInfoIcon } from '../Icon'\nimport { Loader } from '../Loader'\nimport { Scroller } from '../Scroller'\nimport { Text } from '../Text'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { ItemButton } from './ItemButton'\n\nimport type { ComboboxItem, ComboboxOption } from './types'\n\ntype Props<T> = {\n options: Array<ComboboxOption<T>>\n dropdownHelpMessage?: ReactNode\n dropdownWidth?: string | number\n onAdd?: (label: string) => void\n onSelect: (item: ComboboxItem<T>) => void\n isExpanded: boolean\n isLoading?: boolean\n triggerRef: RefObject<HTMLElement>\n /** 検索結果が0件の時に表示するコンテンツ */\n noResultText?: ReactNode\n}\n\ntype Rect = {\n top: number\n left: number\n height?: number\n}\n\nconst KEY_DOWN_REGEX = /^(Arrow)?Down$/\nconst KEY_UP_REGEX = /^(Arrow)?Up/\n\nconst OPTION_INCREMENT_AMOUNT = 100\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: 'shr-absolute',\n dropdownList: [\n 'smarthr-ui-Combobox-dropdownList',\n 'shr-absolute shr-z-overlap shr-box-border shr-min-w-full shr-rounded-m shr-bg-white shr-py-0.5 shr-shadow-layer-3 forced-colors:shr-outline forced-colors:shr-outline-1',\n /* 縦スクロールに気づきやすくするために8個目のアイテムが半分見切れるように max-height を算出\n = (アイテムのフォントサイズ + アイテムの上下padding) * 7.5 + コンテナの上padding */\n 'shr-max-h-[calc((theme(fontSize.base)_+_theme(spacing[0.5])_*_2)_*_7.5_+_theme(spacing[0.5]))]',\n 'aria-hidden:shr-hidden',\n ],\n helpMessage:\n 'shr-whitespace-[initial] shr-border-b-shorthand shr-mx-0.5 shr-mb-0.5 shr-mt-0 shr-px-0.5 shr-pb-0.5 shr-pt-0 shr-text-sm',\n loaderWrapper: 'shr-flex shr-items-center shr-justify-center shr-p-1',\n noItems: 'smarthr-ui-Combobox-noItems shr-my-0 shr-bg-white shr-px-1 shr-py-0.5 shr-text-base',\n },\n})\n\nconst CLASS_NAMES = (() => {\n const { wrapper, dropdownList, helpMessage, loaderWrapper, noItems } = classNameGenerator()\n\n return {\n wrapper: wrapper(),\n dropdownList: dropdownList(),\n helpMessage: helpMessage(),\n loaderWrapper: loaderWrapper(),\n noItems: noItems(),\n }\n})()\n\nexport const useListbox = <T,>({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect,\n isExpanded,\n isLoading,\n triggerRef,\n noResultText,\n}: Props<T>) => {\n const listBoxId = useId()\n\n const [navigationType, setNavigationType] = useState<'pointer' | 'key'>('pointer')\n const [activeOption, setActiveOption] = useState<ComboboxOption<T> | null>(null)\n const [listBoxRect, setListBoxRect] = useState<Rect>({\n top: 0,\n left: 0,\n })\n // HINT: calculateRectで同時に計算するとwidthの幅が変更されるタイミングの問題でlistBoxHeightが変化する場合がある\n const [triggerWidth, setTriggerWidth] = useState(0)\n\n const listBoxRef = useRef<HTMLDivElement>(null)\n const activeRef = useRef<HTMLButtonElement>(null)\n\n const theme = useTheme()\n\n const addFrame = useAnimationFrame()\n\n const latest = useLatest({ onAdd, onSelect, activeOption, options, triggerRef, theme, addFrame })\n const hasOnAdd = !!onAdd\n\n const functions = useMemo(() => {\n const moveActiveOptionIndex = (currentActive: ComboboxOption<T> | null, delta: -1 | 1) => {\n if (latest.options.every((option) => option.item.disabled)) {\n return\n }\n\n const currentActiveIndex =\n currentActive === null\n ? -1\n : latest.options.findIndex((option) => option.id === currentActive.id)\n let nextIndex = 0\n\n if (currentActiveIndex !== -1) {\n nextIndex = (currentActiveIndex + delta + latest.options.length) % latest.options.length\n } else if (delta !== 1) {\n nextIndex = latest.options.length - 1\n }\n\n const nextActive = latest.options[nextIndex]\n\n if (nextActive) {\n if (nextActive.item.disabled) {\n moveActiveOptionIndex(nextActive, delta)\n } else {\n setActiveOption(nextActive)\n }\n }\n }\n\n return {\n calculateRect: () => {\n if (!listBoxRef.current || !latest.triggerRef.current) {\n return\n }\n const rect = latest.triggerRef.current.getBoundingClientRect()\n const bottomSpace = window.innerHeight - rect.bottom\n const topSpace = rect.top\n const listBoxHeight = Math.min(\n listBoxRef.current.scrollHeight,\n parseInt(getComputedStyle(listBoxRef.current).maxHeight, 10),\n )\n const offset = 2\n\n let top = 0\n let height: number | undefined = undefined\n\n if (bottomSpace >= listBoxHeight) {\n // 下側に十分なスペースがある場合は下側に通常表示\n top = rect.top + rect.height - offset + window.pageYOffset\n } else if (topSpace >= listBoxHeight) {\n // 上側に十分なスペースがある場合は上側に通常表示\n top = rect.top - listBoxHeight + offset + window.pageYOffset\n } else if (topSpace > bottomSpace) {\n // 上下に十分なスペースがなく、上側の方がスペースが大きい場合は上側に縮めて表示\n top = rect.top - topSpace + offset + window.pageYOffset\n height = topSpace\n } else {\n // 下側に縮めて表示\n top = rect.top + rect.height - offset + window.pageYOffset\n height = bottomSpace\n }\n\n // HINT: dropdownWidth は 'auto' や '%' などの CSS 値を取りうるため、算出済みの幅を実測して判定する\n const listBoxWidth = listBoxRef.current.getBoundingClientRect().width\n // ドロップダウンの幅は maxWidth でビューポート右端から余白分を残すよう制限しているため、位置の判定にも同じ余白を使う\n const viewportMargin = parseInt(latest.theme.spacingByChar(0.5), 10)\n // 入力欄の左端を起点に右方向へ表示する場合に使える幅\n const rightSpace = window.innerWidth - rect.left - viewportMargin\n // 入力欄の右端を起点に左方向へ表示する場合に使える幅\n const leftSpace = rect.right\n\n // ビューポートの左端を基準に計算\n let left = window.pageXOffset\n\n if (listBoxWidth <= rightSpace) {\n // 右側に十分なスペースがある場合は入力欄の左端に揃えて通常表示\n left += rect.left\n } else if (listBoxWidth <= leftSpace) {\n // 左側に収まる場合は入力欄の右端に揃えて表示\n left += rect.right - listBoxWidth\n }\n\n setListBoxRect({\n top,\n left,\n height,\n })\n setTriggerWidth(rect.width)\n },\n handleKeyDownListBox: (e: KeyboardEvent<HTMLElement>) => {\n setNavigationType('key')\n\n if (KEY_DOWN_REGEX.test(e.key)) {\n e.stopPropagation()\n moveActiveOptionIndex(latest.activeOption, 1)\n } else if (KEY_UP_REGEX.test(e.key)) {\n e.stopPropagation()\n moveActiveOptionIndex(latest.activeOption, -1)\n } else if (e.key === 'Enter') {\n if (latest.activeOption === null) {\n return\n }\n\n e.stopPropagation()\n\n if (!latest.activeOption.isNew) {\n latest.onSelect(latest.activeOption.item)\n } else if (latest.onAdd) {\n latest.onAdd(latest.activeOption.item.value)\n }\n } else {\n setActiveOption(null)\n }\n },\n handleAdd: hasOnAdd\n ? (option: ComboboxOption<T>) => {\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n latest.addFrame.request(() => {\n latest.onAdd!(option.item.value)\n })\n }\n : undefined,\n handleSelect: (option: ComboboxOption<T>) => {\n latest.onSelect(option.item)\n },\n handleHoverOption: (option: ComboboxOption<T>) => {\n setNavigationType('pointer')\n setActiveOption(option)\n },\n }\n }, [hasOnAdd, latest])\n\n // TODO: callbackRefにまとめ直したい\n useEffect(() => addFrame.cancel, [addFrame.cancel])\n\n useEffect(() => {\n // props の変更によって activeOption の状態が変わりうるので、実態を反映する\n setActiveOption((current) => {\n if (current === null) {\n return null\n }\n\n return options.find((option) => current.id === option.id) ?? null\n })\n }, [options])\n\n useEffect(() => {\n // actionOption の要素が表示される位置までリストボックス内をスクロールさせる\n if (\n !activeRef.current ||\n !listBoxRef.current ||\n activeOption === null ||\n navigationType !== 'key'\n ) {\n return\n }\n\n const activeRect = activeRef.current.getBoundingClientRect()\n const containerRect = listBoxRef.current.getBoundingClientRect()\n\n if (activeRect.top < containerRect.top) {\n listBoxRef.current.scrollTop -= containerRect.top - activeRect.top\n } else if (activeRect.bottom > containerRect.bottom) {\n listBoxRef.current.scrollTop += activeRect.bottom - containerRect.bottom\n }\n }, [activeOption, navigationType])\n\n useEnhancedEffect(() => {\n // 閉じたときに activeOption を初期化\n if (!isExpanded) {\n return setActiveOption(null)\n }\n\n functions.calculateRect()\n\n const scrollOption = { capture: true, passive: true }\n window.addEventListener('scroll', functions.calculateRect, scrollOption)\n window.addEventListener('resize', functions.calculateRect, { passive: true })\n\n return () => {\n window.removeEventListener('scroll', functions.calculateRect, scrollOption)\n window.removeEventListener('resize', functions.calculateRect)\n }\n // HINT: optionsが変わる場合メニューのサイズが変わる可能性がある\n }, [isExpanded, options, functions])\n\n return {\n listBoxProps: {\n activeOptionId: activeOption?.id,\n options,\n isExpanded,\n isLoading,\n dropdownHelpMessage,\n noResultText,\n listBoxId,\n listBoxRef,\n handleAdd: functions.handleAdd,\n handleHoverOption: functions.handleHoverOption,\n handleSelect: functions.handleSelect,\n activeRef,\n listBoxRect,\n triggerWidth,\n dropdownWidth,\n },\n activeOption,\n handleKeyDownListBox: functions.handleKeyDownListBox,\n listBoxId,\n listBoxRef,\n }\n}\n\ntype ListBoxProps<T> = {\n activeOptionId: string | undefined\n options: Array<ComboboxOption<T>>\n isExpanded: boolean\n isLoading?: boolean\n noResultText?: ReactNode\n dropdownHelpMessage?: ReactNode\n listBoxId: string\n listBoxRef: RefObject<HTMLDivElement>\n handleAdd: ((option: ComboboxOption<T>) => void) | undefined\n handleHoverOption: (option: ComboboxOption<T>) => void\n handleSelect: (option: ComboboxOption<T>) => void\n activeRef: RefObject<HTMLButtonElement>\n listBoxRect: { top: number; left: number; height?: number }\n triggerWidth: number\n dropdownWidth?: string | number\n}\n\nexport const ListBox = memo(\n <T,>({\n activeOptionId,\n options,\n isExpanded,\n isLoading,\n noResultText,\n dropdownHelpMessage,\n listBoxId,\n listBoxRef,\n handleAdd,\n handleHoverOption,\n handleSelect,\n activeRef,\n listBoxRect,\n triggerWidth,\n dropdownWidth,\n }: ListBoxProps<T>) => {\n const { createPortal } = usePortal()\n const theme = useTheme()\n\n const minLength = useMemo(\n () =>\n (activeOptionId === undefined ? 0 : options.findIndex((o) => o.id === activeOptionId)) + 1,\n [activeOptionId, options],\n )\n const [currentItemLength, setCurrentItemLength] = useState(() =>\n Math.max(OPTION_INCREMENT_AMOUNT, minLength),\n )\n const items = useMemo(() => options.slice(0, currentItemLength), [currentItemLength, options])\n\n const styles = useMemo(() => {\n const { top, left, height } = listBoxRect\n const dropdownListWidth = dropdownWidth || triggerWidth\n\n return {\n wrapper: {\n top: `${top}px`,\n left: `${left}px`,\n width: `${triggerWidth}px`,\n },\n dropdownList: {\n width:\n typeof dropdownListWidth === 'string' ? dropdownListWidth : `${dropdownListWidth}px`,\n /* HINT: left に依存させると、算出した幅がさらに maxWidth を縮めて再計算の度に幅が縮んでいくため、\n ビューポート幅のみから算出する */\n maxWidth: `calc(100vw - ${theme.spacingByChar(0.5)})`,\n height: height ? `${height}px` : undefined,\n },\n }\n }, [listBoxRect, triggerWidth, dropdownWidth, theme])\n\n const latest = useLatest({ handleAdd, handleHoverOption, handleSelect, options, minLength })\n\n const functions = useMemo(() => {\n const resolveOption = (e: MouseEvent) => {\n const el = findDelegateTarget<HTMLButtonElement>(e, 'button[role=\"option\"]')\n if (!el || el.disabled) return null\n return latest.options.find((o) => o.id === el.id) ?? null\n }\n\n return {\n handleDelegateClick: (e: MouseEvent) => {\n const option = resolveOption(e)\n if (option) {\n if (option.isNew) {\n latest.handleAdd?.(option)\n } else {\n latest.handleSelect(option)\n }\n }\n },\n handleDelegateMouseOver: (e: MouseEvent) => {\n const option = resolveOption(e)\n if (option) {\n latest.handleHoverOption(option)\n }\n },\n handleIntersect: () => {\n setCurrentItemLength((current) =>\n Math.max(current + OPTION_INCREMENT_AMOUNT, latest.minLength),\n )\n },\n }\n }, [latest])\n\n useEffect(() => {\n setCurrentItemLength((current) => Math.max(current, minLength))\n }, [minLength])\n\n return createPortal(\n <div className={CLASS_NAMES.wrapper} style={styles.wrapper}>\n {isExpanded && isLoading && (\n <VisuallyHiddenText role=\"status\">\n <Localizer id=\"smarthr-ui/Combobox/loadingText\" defaultText=\"処理中\" />\n </VisuallyHiddenText>\n )}\n <Scroller\n id={listBoxId}\n ref={listBoxRef}\n role=\"listbox\"\n aria-hidden={!isExpanded}\n className={CLASS_NAMES.dropdownList}\n style={styles.dropdownList}\n onMouseOver={functions.handleDelegateMouseOver}\n onClick={functions.handleDelegateClick}\n >\n {dropdownHelpMessage && (\n <Text\n className={CLASS_NAMES.helpMessage}\n icon={<FaCircleInfoIcon color=\"TEXT_GREY\" />}\n as=\"p\"\n >\n {dropdownHelpMessage}\n </Text>\n )}\n {isExpanded ? (\n isLoading ? (\n <div className={CLASS_NAMES.loaderWrapper}>\n <Loader aria-hidden />\n </div>\n ) : options.length === 0 ? (\n <p role=\"alert\" aria-live=\"polite\" className={CLASS_NAMES.noItems}>\n {noResultText ?? (\n <Localizer\n id=\"smarthr-ui/Combobox/noResultsText\"\n defaultText=\"一致する選択肢がありません。\"\n />\n )}\n </p>\n ) : (\n items.map(({ item: { label, disabled }, id, ...optionRest }) => (\n <ItemButton\n {...optionRest}\n label={label}\n disabled={disabled}\n key={id}\n id={id}\n activeRef={id === activeOptionId ? activeRef : undefined}\n />\n ))\n )\n ) : null}\n {currentItemLength < options.length && (\n <Intersection handleIntersect={functions.handleIntersect} />\n )}\n </Scroller>\n </div>,\n )\n },\n) as <T>(props: ListBoxProps<T>) => ReactNode\n\nconst Intersection = memo<{ handleIntersect: () => void }>(({ handleIntersect }) => {\n const ref = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const target = ref.current\n\n if (target === null) {\n return\n }\n\n const observer = new IntersectionObserver(([entry]) => {\n if (entry.isIntersecting) {\n handleIntersect()\n }\n })\n\n observer.observe(target)\n\n return () => observer.disconnect()\n }, [handleIntersect])\n\n return <div ref={ref} />\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA;AACA;AAEA;AAEA;AACE;AACE;AACA;;;AAGE;AAC0D;;;AAG3D;AACD;AAEA;AACA;AACD;AACF;AAED;AACE;;;;;;;;AASF;AAEO;AAWL;;;AAIA;AACE;AACA;AACD;;;AAID;AACA;AAEA;AAEA;;AAGA;AAEA;AACE;AACE;;;AAIA;;;;AAMA;AACE;;AACK;;;;;AAOL;AACE;;;;;;AAKN;;;AAII;;;;;AAKA;;;;;AAUA;;AAEE;;AACK;;AAEL;;AACK;;AAEL;;;;;AAIA;;;;;;AAOF;;;;AAIA;;AAGA;AAEA;;AAEE;;AACK;;AAEL;;AAGF;;;;AAIC;AACD;;AAEF;;;;AAKI;;;;;;AAIK;AACL;;;;AAMA;;;AAEO;;;;;;;;AAOX;AACE;;;AAGI;;AAEA;;AAEJ;AACF;AACE;;AAEF;;;;;AAKJ;;AAGA;;;AAIE;AACE;AACE;;AAGF;AACF;AACF;;;;;AAOI;;;;;;;AAUA;;;AAEA;;AAEJ;;;;AAKI;;;;;AAOF;AAEA;;;AAGA;;;;AAKA;;;;;;;;;;;;;;;;AAgBC;;;;;;AAML;AAoBO;AAkBH;AACA;AAEA;;;AAUA;;AAEE;;AAGE;;;;AAIC;AACD;AACE;AAEA;AACkB;;;AAGnB;;;AAIL;AAEA;AACE;;AAEE;AAAwB;;AAE1B;;AAGE;AACE;;AAEE;AACE;;;AAEA;;;;AAIN;AACE;;AAEE;;;;AAIF;;;AAKN;;AAGE;AACF;AAEA;AA2DF;AAGF;AACE;;AAGE;AAEA;;;;AAKE;AACE;;AAEJ;AAEA;AAEA;AACF;AAEA;AACF;;;"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { memo, useMemo, useState, useEffect, createElement, useRef, useId } from 'react';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
- 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';
4
+ 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_@swc_helper_93b9c5ee15e3d3d2221e194197be3b5c/vendor/tailwind-variants/dist/index.js';
5
+ import { useAnimationFrame } from '../../hooks/useAnimationFrame.js';
5
6
  import { useEnhancedEffect } from '../../hooks/useEnhancedEffect.js';
6
7
  import { useLatest } from '../../hooks/useLatest.js';
7
8
  import { usePortal } from '../../hooks/usePortal.js';
@@ -63,7 +64,8 @@ const useListbox = ({ options, dropdownHelpMessage, dropdownWidth, onAdd, onSele
63
64
  const listBoxRef = useRef(null);
64
65
  const activeRef = useRef(null);
65
66
  const theme = useTheme();
66
- const latest = useLatest({ onAdd, onSelect, activeOption, options, triggerRef, theme });
67
+ const addFrame = useAnimationFrame();
68
+ const latest = useLatest({ onAdd, onSelect, activeOption, options, triggerRef, theme, addFrame });
67
69
  const hasOnAdd = !!onAdd;
68
70
  const functions = useMemo(() => {
69
71
  const moveActiveOptionIndex = (currentActive, delta) => {
@@ -174,8 +176,8 @@ const useListbox = ({ options, dropdownHelpMessage, dropdownWidth, onAdd, onSele
174
176
  handleAdd: hasOnAdd
175
177
  ? (option) => {
176
178
  // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう
177
- // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
178
- requestAnimationFrame(() => {
179
+ // 処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
180
+ latest.addFrame.request(() => {
179
181
  latest.onAdd(option.item.value);
180
182
  });
181
183
  }
@@ -189,6 +191,8 @@ const useListbox = ({ options, dropdownHelpMessage, dropdownWidth, onAdd, onSele
189
191
  },
190
192
  };
191
193
  }, [hasOnAdd, latest]);
194
+ // TODO: callbackRefにまとめ直したい
195
+ useEffect(() => addFrame.cancel, [addFrame.cancel]);
192
196
  useEffect(() => {
193
197
  // props の変更によって activeOption の状態が変わりうるので、実態を反映する
194
198
  setActiveOption((current) => {