smarthr-ui 97.0.0 → 98.0.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 (208) hide show
  1. package/lib/components/AppHeader/components/common/AppLauncherFeatures.cjs +1 -1
  2. package/lib/components/AppHeader/components/common/AppLauncherFeatures.js +1 -1
  3. package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.cjs +1 -1
  4. package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.js +1 -1
  5. package/lib/components/AppHeader/components/desktop/AppLauncher.cjs +5 -5
  6. package/lib/components/AppHeader/components/desktop/AppLauncher.cjs.map +1 -1
  7. package/lib/components/AppHeader/components/desktop/AppLauncher.js +5 -5
  8. package/lib/components/AppHeader/components/desktop/AppLauncher.js.map +1 -1
  9. package/lib/components/AppHeader/components/desktop/DesktopHeader.cjs +1 -1
  10. package/lib/components/AppHeader/components/desktop/DesktopHeader.js +1 -1
  11. package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.cjs +1 -1
  12. package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.js +1 -1
  13. package/lib/components/AppHeader/components/desktop/UserInfo.cjs +1 -1
  14. package/lib/components/AppHeader/components/desktop/UserInfo.js +1 -1
  15. package/lib/components/AppHeader/components/mobile/AppLauncher.cjs +1 -1
  16. package/lib/components/AppHeader/components/mobile/AppLauncher.js +1 -1
  17. package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.cjs +1 -1
  18. package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.js +1 -1
  19. package/lib/components/AppHeader/components/mobile/Help.cjs +1 -1
  20. package/lib/components/AppHeader/components/mobile/Help.js +1 -1
  21. package/lib/components/AppHeader/components/mobile/LanguageSelector.cjs +1 -1
  22. package/lib/components/AppHeader/components/mobile/LanguageSelector.js +1 -1
  23. package/lib/components/AppHeader/components/mobile/Menu.cjs +1 -1
  24. package/lib/components/AppHeader/components/mobile/Menu.js +1 -1
  25. package/lib/components/AppHeader/components/mobile/MenuAccordion.cjs +1 -1
  26. package/lib/components/AppHeader/components/mobile/MenuAccordion.js +1 -1
  27. package/lib/components/AppHeader/components/mobile/MenuDialog.cjs +2 -2
  28. package/lib/components/AppHeader/components/mobile/MenuDialog.js +2 -2
  29. package/lib/components/AppHeader/components/mobile/MenuSubHeading.cjs +1 -1
  30. package/lib/components/AppHeader/components/mobile/MenuSubHeading.js +1 -1
  31. package/lib/components/AppHeader/components/mobile/ReleaseNote.cjs +1 -1
  32. package/lib/components/AppHeader/components/mobile/ReleaseNote.js +1 -1
  33. package/lib/components/AppHeader/components/mobile/UserInfo.cjs +2 -2
  34. package/lib/components/AppHeader/components/mobile/UserInfo.js +2 -2
  35. package/lib/components/Browser/Browser.cjs +11 -7
  36. package/lib/components/Browser/Browser.cjs.map +1 -1
  37. package/lib/components/Browser/Browser.js +12 -8
  38. package/lib/components/Browser/Browser.js.map +1 -1
  39. package/lib/components/Calendar/Calendar.cjs +1 -1
  40. package/lib/components/Calendar/Calendar.js +1 -1
  41. package/lib/components/Calendar/CalendarTable.cjs +1 -1
  42. package/lib/components/Calendar/CalendarTable.js +1 -1
  43. package/lib/components/Calendar/YearPicker.cjs +1 -1
  44. package/lib/components/Calendar/YearPicker.js +1 -1
  45. package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs +122 -134
  46. package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs.map +1 -1
  47. package/lib/components/Combobox/MultiCombobox/MultiCombobox.js +123 -135
  48. package/lib/components/Combobox/MultiCombobox/MultiCombobox.js.map +1 -1
  49. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.cjs +1 -1
  50. package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.js +1 -1
  51. package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs +96 -46
  52. package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs.map +1 -1
  53. package/lib/components/Combobox/SingleCombobox/SingleCombobox.js +98 -48
  54. package/lib/components/Combobox/SingleCombobox/SingleCombobox.js.map +1 -1
  55. package/lib/components/Combobox/useListbox.cjs +1 -1
  56. package/lib/components/Combobox/useListbox.js +1 -1
  57. package/lib/components/Combobox/useOptions.cjs +3 -1
  58. package/lib/components/Combobox/useOptions.cjs.map +1 -1
  59. package/lib/components/Combobox/useOptions.js +4 -2
  60. package/lib/components/Combobox/useOptions.js.map +1 -1
  61. package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.cjs +3 -3
  62. package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.cjs.map +1 -1
  63. package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.js +3 -3
  64. package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.js.map +1 -1
  65. package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs +3 -3
  66. package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs.map +1 -1
  67. package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js +3 -3
  68. package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js.map +1 -1
  69. package/lib/components/Dialog/ControlledMessageDialog/MessageDialogContentInner.cjs +1 -1
  70. package/lib/components/Dialog/ControlledMessageDialog/MessageDialogContentInner.js +1 -1
  71. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.cjs +1 -1
  72. package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.js +1 -1
  73. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs +2 -2
  74. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs.map +1 -1
  75. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js +2 -2
  76. package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js.map +1 -1
  77. package/lib/components/Dialog/DialogContentResponseStatusMessage.cjs +3 -8
  78. package/lib/components/Dialog/DialogContentResponseStatusMessage.cjs.map +1 -1
  79. package/lib/components/Dialog/DialogContentResponseStatusMessage.d.ts +1 -1
  80. package/lib/components/Dialog/DialogContentResponseStatusMessage.js +3 -8
  81. package/lib/components/Dialog/DialogContentResponseStatusMessage.js.map +1 -1
  82. package/lib/components/Dialog/FocusTrap.cjs +4 -1
  83. package/lib/components/Dialog/FocusTrap.cjs.map +1 -1
  84. package/lib/components/Dialog/FocusTrap.js +4 -1
  85. package/lib/components/Dialog/FocusTrap.js.map +1 -1
  86. package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs +1 -1
  87. package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +1 -1
  88. package/lib/components/DropZone/DropZone.cjs +1 -1
  89. package/lib/components/DropZone/DropZone.js +1 -1
  90. package/lib/components/Dropdown/Dropdown.cjs +36 -34
  91. package/lib/components/Dropdown/Dropdown.cjs.map +1 -1
  92. package/lib/components/Dropdown/Dropdown.js +37 -35
  93. package/lib/components/Dropdown/Dropdown.js.map +1 -1
  94. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +1 -1
  95. package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +1 -1
  96. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs +3 -5
  97. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs.map +1 -1
  98. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.d.ts +1 -1
  99. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js +3 -5
  100. package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js.map +1 -1
  101. package/lib/components/Dropdown/SortDropdown/useSortDropdown.cjs +1 -1
  102. package/lib/components/Dropdown/SortDropdown/useSortDropdown.js +1 -1
  103. package/lib/components/ErrorScreen/AuthErrorScreen.cjs +1 -1
  104. package/lib/components/ErrorScreen/AuthErrorScreen.js +1 -1
  105. package/lib/components/ErrorScreen/ForbiddenErrorScreen.cjs +1 -1
  106. package/lib/components/ErrorScreen/ForbiddenErrorScreen.js +1 -1
  107. package/lib/components/ErrorScreen/NotFoundErrorScreen.cjs +1 -1
  108. package/lib/components/ErrorScreen/NotFoundErrorScreen.js +1 -1
  109. package/lib/components/ErrorScreen/UnauthorizedErrorScreen.cjs +1 -1
  110. package/lib/components/ErrorScreen/UnauthorizedErrorScreen.js +1 -1
  111. package/lib/components/FormControl/FormControl.cjs +2 -22
  112. package/lib/components/FormControl/FormControl.cjs.map +1 -1
  113. package/lib/components/FormControl/FormControl.js +2 -22
  114. package/lib/components/FormControl/FormControl.js.map +1 -1
  115. package/lib/components/Header/AppLauncher/AppLauncher.cjs +1 -1
  116. package/lib/components/Header/AppLauncher/AppLauncher.js +1 -1
  117. package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.cjs +1 -1
  118. package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.js +1 -1
  119. package/lib/components/Icon/OpenInNewTabIcon.cjs +1 -1
  120. package/lib/components/Icon/OpenInNewTabIcon.js +1 -1
  121. package/lib/components/InformationPanel/InformationPanel.cjs +1 -1
  122. package/lib/components/InformationPanel/InformationPanel.js +1 -1
  123. package/lib/components/Input/SearchInput/SearchInputIcon.cjs +1 -1
  124. package/lib/components/Input/SearchInput/SearchInputIcon.js +1 -1
  125. package/lib/components/InputFile/InputFileMultiplyAppendable.cjs +1 -1
  126. package/lib/components/InputFile/InputFileMultiplyAppendable.js +1 -1
  127. package/lib/components/InputFile/InputFileNative.cjs +1 -1
  128. package/lib/components/InputFile/InputFileNative.js +1 -1
  129. package/lib/components/Layout/Cluster/Cluster.cjs +0 -1
  130. package/lib/components/Layout/Cluster/Cluster.cjs.map +1 -1
  131. package/lib/components/Layout/Cluster/Cluster.js +0 -1
  132. package/lib/components/Layout/Cluster/Cluster.js.map +1 -1
  133. package/lib/components/Layout/Container/Container.cjs +1 -5
  134. package/lib/components/Layout/Container/Container.cjs.map +1 -1
  135. package/lib/components/Layout/Container/Container.js +1 -5
  136. package/lib/components/Layout/Container/Container.js.map +1 -1
  137. package/lib/components/Layout/Stack/Stack.cjs +22 -22
  138. package/lib/components/Layout/Stack/Stack.cjs.map +1 -1
  139. package/lib/components/Layout/Stack/Stack.js +22 -22
  140. package/lib/components/Layout/Stack/Stack.js.map +1 -1
  141. package/lib/components/Loader/LoaderSpinner.cjs +1 -1
  142. package/lib/components/Loader/LoaderSpinner.js +1 -1
  143. package/lib/components/Pagination/Pagination.cjs +1 -1
  144. package/lib/components/Pagination/Pagination.js +1 -1
  145. package/lib/components/Pagination/PaginationItemButton.cjs +1 -1
  146. package/lib/components/Pagination/PaginationItemButton.js +1 -1
  147. package/lib/components/SideNav/SideNav.cjs +3 -22
  148. package/lib/components/SideNav/SideNav.cjs.map +1 -1
  149. package/lib/components/SideNav/SideNav.d.ts +2 -9
  150. package/lib/components/SideNav/SideNav.js +4 -23
  151. package/lib/components/SideNav/SideNav.js.map +1 -1
  152. package/lib/components/SideNav/SideNavContext.cjs +11 -0
  153. package/lib/components/SideNav/SideNavContext.cjs.map +1 -0
  154. package/lib/components/SideNav/SideNavContext.d.ts +7 -0
  155. package/lib/components/SideNav/SideNavContext.js +8 -0
  156. package/lib/components/SideNav/SideNavContext.js.map +1 -0
  157. package/lib/components/SideNav/SideNavItemButton.cjs +12 -7
  158. package/lib/components/SideNav/SideNavItemButton.cjs.map +1 -1
  159. package/lib/components/SideNav/SideNavItemButton.d.ts +5 -11
  160. package/lib/components/SideNav/SideNavItemButton.js +12 -7
  161. package/lib/components/SideNav/SideNavItemButton.js.map +1 -1
  162. package/lib/components/Table/Th.cjs +2 -2
  163. package/lib/components/Table/Th.cjs.map +1 -1
  164. package/lib/components/Table/Th.d.ts +0 -2
  165. package/lib/components/Table/Th.js +2 -2
  166. package/lib/components/Table/Th.js.map +1 -1
  167. package/lib/components/Table/ThCheckbox.cjs +1 -1
  168. package/lib/components/Table/ThCheckbox.js +1 -1
  169. package/lib/components/Table/ThSortButton.cjs +5 -6
  170. package/lib/components/Table/ThSortButton.cjs.map +1 -1
  171. package/lib/components/Table/ThSortButton.d.ts +1 -6
  172. package/lib/components/Table/ThSortButton.js +5 -6
  173. package/lib/components/Table/ThSortButton.js.map +1 -1
  174. package/lib/components/WarekiPicker/WarekiPicker.cjs +1 -1
  175. package/lib/components/WarekiPicker/WarekiPicker.js +1 -1
  176. package/lib/index.cjs +1 -6
  177. package/lib/index.cjs.map +1 -1
  178. package/lib/index.d.ts +0 -1
  179. package/lib/index.js +1 -4
  180. package/lib/index.js.map +1 -1
  181. package/lib/intl/Localizer.cjs +5 -1
  182. package/lib/intl/Localizer.cjs.map +1 -1
  183. package/lib/intl/Localizer.d.ts +1 -1
  184. package/lib/intl/Localizer.js +5 -1
  185. package/lib/intl/Localizer.js.map +1 -1
  186. package/metadata.json +1 -1
  187. package/package.json +1 -1
  188. package/smarthr-ui.css +0 -94
  189. package/lib/hooks/useDecorators.cjs +0 -18
  190. package/lib/hooks/useDecorators.cjs.map +0 -1
  191. package/lib/hooks/useDecorators.d.ts +0 -6
  192. package/lib/hooks/useDecorators.js +0 -16
  193. package/lib/hooks/useDecorators.js.map +0 -1
  194. package/lib/hooks/useDevice/DeviceProvider.cjs +0 -37
  195. package/lib/hooks/useDevice/DeviceProvider.cjs.map +0 -1
  196. package/lib/hooks/useDevice/DeviceProvider.d.ts +0 -4
  197. package/lib/hooks/useDevice/DeviceProvider.js +0 -35
  198. package/lib/hooks/useDevice/DeviceProvider.js.map +0 -1
  199. package/lib/hooks/useDevice/index.cjs +0 -10
  200. package/lib/hooks/useDevice/index.cjs.map +0 -1
  201. package/lib/hooks/useDevice/index.d.ts +0 -2
  202. package/lib/hooks/useDevice/index.js +0 -3
  203. package/lib/hooks/useDevice/index.js.map +0 -1
  204. package/lib/hooks/useDevice/useDevice.cjs +0 -11
  205. package/lib/hooks/useDevice/useDevice.cjs.map +0 -1
  206. package/lib/hooks/useDevice/useDevice.d.ts +0 -4
  207. package/lib/hooks/useDevice/useDevice.js +0 -8
  208. package/lib/hooks/useDevice/useDevice.js.map +0 -1
@@ -9,7 +9,7 @@ var hooks_useClick = require('../../../hooks/useClick.cjs');
9
9
  var hooks_useTheme = require('../../../hooks/useTheme.cjs');
10
10
  require('../../../intl/IntlProvider.cjs');
11
11
  var intl_useIntl = require('../../../intl/useIntl.cjs');
12
- require('react-intl');
12
+ require('../../../intl/Localizer.cjs');
13
13
  require('../../../_virtual/dayjs.min.cjs');
14
14
  require('../../../intl/TimeFormatter.cjs');
15
15
  var libs_util = require('../../../libs/util.cjs');
@@ -66,6 +66,7 @@ const classNameGenerator = index.tv({
66
66
  },
67
67
  },
68
68
  });
69
+ const SuffixButtons = React.memo(({ onClickClear, clearButtonRef, onClickIcon: onDelegateClickIcon, caretIconColor, destroyButtonIconAlt, classNames, }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(components_Button_UnstyledButton.UnstyledButton, { onClick: onClickClear, ref: clearButtonRef, className: classNames.clearButton, children: jsxRuntime.jsx(components_Icon_FaIcon.FaCircleXmarkIcon, { color: "TEXT_BLACK", alt: destroyButtonIconAlt, className: classNames.clearButtonIcon }) }), jsxRuntime.jsx("span", { role: "presentation", onClick: onDelegateClickIcon, className: classNames.caretDownLayout, children: jsxRuntime.jsx(components_Icon_FaIcon.FaCaretDownIcon, { color: caretIconColor, className: classNames.caretDownIcon }) })] })));
69
70
  const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled, readOnly, required, prefix, error, creatable, placeholder, autoComplete, dropdownHelpMessage, isLoading, width, dropdownWidth = 'auto', className, onChange, onChangeInput, onAdd, onSelect, onClear, onClearClick, onChangeSelected, onFocus, onBlur, onKeyPress, noResultText, style, ...rest }, ref) => {
70
71
  const theme = hooks_useTheme.useTheme();
71
72
  const { localize } = intl_useIntl.useIntl();
@@ -77,6 +78,42 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
77
78
  const [inputValue, setInputValue] = React.useState('');
78
79
  const [isComposing, setIsComposing] = React.useState(false);
79
80
  const [isEditing, setIsEditing] = React.useState(false);
81
+ const unstableRef = React.useRef({
82
+ onChange,
83
+ onChangeInput,
84
+ onAdd,
85
+ onSelect,
86
+ onClear,
87
+ onClearClick,
88
+ onChangeSelected,
89
+ onFocus,
90
+ onBlur,
91
+ onKeyPress,
92
+ defaultItem,
93
+ selectedItem,
94
+ isFocused,
95
+ isExpanded,
96
+ isComposing,
97
+ isEditing,
98
+ });
99
+ unstableRef.current = {
100
+ onChange,
101
+ onChangeInput,
102
+ onAdd,
103
+ onSelect,
104
+ onClear,
105
+ onClearClick,
106
+ onChangeSelected,
107
+ onFocus,
108
+ onBlur,
109
+ onKeyPress,
110
+ defaultItem,
111
+ selectedItem,
112
+ isFocused,
113
+ isExpanded,
114
+ isComposing,
115
+ isEditing,
116
+ };
80
117
  React.useImperativeHandle(ref, () => inputRef.current);
81
118
  const { options } = components_Combobox_useOptions.useSingleOptions({
82
119
  items,
@@ -91,49 +128,58 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
91
128
  dropdownWidth,
92
129
  onAdd,
93
130
  onSelect: React.useCallback((selected) => {
94
- onSelect?.(selected);
95
- onChangeSelected?.(selected);
96
- // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する
97
- onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT);
131
+ unstableRef.current.onSelect?.(selected);
132
+ unstableRef.current.onChangeSelected?.(selected);
98
133
  // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう
99
134
  // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
100
135
  requestAnimationFrame(() => {
101
136
  setIsExpanded(false);
137
+ // HINT:
138
+ // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する
139
+ // - 対応するdropdownを閉じて以降にonChangeInputを発火する必要がある
140
+ // - 先にclearしてしまうと意図せずこの要素のドロップダウンを閉じる前に他要素の再レンダリングを引き起こす可能性がある
141
+ // - 例えばFilterDropdownなどで当comboboxを使っている場合、レイアウト上comboboxのdropdown以下の要素がクリックされた扱いになってしまい
142
+ // FilterDropdownを意図せず閉じてしまうなどの挙動のバグを引き起こす可能性がある
143
+ unstableRef.current.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT);
102
144
  });
103
145
  setIsEditing(false);
104
- }, [onChangeSelected, onSelect, onChangeInput]),
146
+ }, []),
105
147
  isExpanded,
106
148
  isLoading,
107
149
  triggerRef: outerRef,
108
150
  noResultText,
109
151
  });
110
- const selectDefaultItem = React.useMemo(() => (onSelect && defaultItem ? () => onSelect(defaultItem) : NOOP), [onSelect, defaultItem]);
152
+ const selectDefaultItem = React.useCallback(() => {
153
+ if (unstableRef.current.onSelect && unstableRef.current.defaultItem) {
154
+ unstableRef.current.onSelect(unstableRef.current.defaultItem);
155
+ }
156
+ }, []);
111
157
  const focus = React.useCallback(() => {
112
- onFocus?.();
158
+ unstableRef.current.onFocus?.();
113
159
  inputRef.current?.focus();
114
160
  setIsFocused(true);
115
- if (!isFocused) {
161
+ if (!unstableRef.current.isFocused) {
116
162
  setIsExpanded(true);
117
163
  }
118
- }, [onFocus, isFocused]);
164
+ }, []);
119
165
  const unfocus = React.useCallback(() => {
120
- if (!isFocused)
166
+ if (!unstableRef.current.isFocused)
121
167
  return;
122
- onBlur?.();
168
+ unstableRef.current.onBlur?.();
123
169
  setIsFocused(false);
124
170
  setIsExpanded(false);
125
171
  setIsEditing(false);
126
- if (selectedItem) {
127
- setInputValue(index$1(selectedItem.label));
172
+ if (unstableRef.current.selectedItem) {
173
+ setInputValue(index$1(unstableRef.current.selectedItem.label));
128
174
  }
129
175
  else {
130
176
  selectDefaultItem();
131
177
  }
132
- }, [isFocused, onBlur, selectedItem, selectDefaultItem]);
178
+ }, [selectDefaultItem]);
133
179
  const onClickClear = React.useCallback((e) => {
134
180
  e.stopPropagation();
135
181
  let isExecutedPreventDefault = false;
136
- onClearClick?.({
182
+ unstableRef.current.onClearClick?.({
137
183
  ...e,
138
184
  preventDefault: () => {
139
185
  e.preventDefault();
@@ -141,44 +187,43 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
141
187
  },
142
188
  });
143
189
  if (!isExecutedPreventDefault) {
144
- onClear?.();
145
- onChangeSelected?.(null);
190
+ unstableRef.current.onClear?.();
191
+ unstableRef.current.onChangeSelected?.(null);
146
192
  inputRef.current?.focus();
147
193
  setIsFocused(true);
148
194
  setIsExpanded(true);
149
195
  }
150
- }, [onClearClick, onClear, onChangeSelected]);
196
+ }, []);
151
197
  const onClickInput = React.useCallback((e) => {
152
198
  if (disabled || readOnly) {
153
199
  e.stopPropagation();
154
200
  return;
155
201
  }
156
202
  inputRef.current?.focus();
157
- if (!isExpanded) {
203
+ if (!unstableRef.current.isExpanded) {
158
204
  setIsExpanded(true);
159
205
  }
160
- }, [disabled, readOnly, inputRef, isExpanded]);
161
- const onDelegateClickIcon = onClickInput;
206
+ }, [disabled, readOnly]);
162
207
  const actualOnChangeInput = React.useCallback((e) => {
163
- onChange?.(e);
164
- onChangeInput?.(e);
165
- if (!isEditing)
208
+ unstableRef.current.onChange?.(e);
209
+ unstableRef.current.onChangeInput?.(e);
210
+ if (!unstableRef.current.isEditing)
166
211
  setIsEditing(true);
167
212
  const { value } = e.currentTarget;
168
213
  setInputValue(value);
169
214
  if (value === '') {
170
- onClear?.();
171
- onChangeSelected?.(null);
215
+ unstableRef.current.onClear?.();
216
+ unstableRef.current.onChangeSelected?.(null);
172
217
  }
173
- }, [isEditing, onChange, onChangeInput, onClear, onChangeSelected]);
218
+ }, []);
174
219
  const onCompositionStart = React.useCallback(() => setIsComposing(true), []);
175
220
  const onCompositionEnd = React.useCallback(() => setIsComposing(false), []);
176
221
  const onKeyDownInput = React.useCallback((e) => {
177
- if (isComposing) {
222
+ if (unstableRef.current.isComposing) {
178
223
  return;
179
224
  }
180
225
  if (ESCAPE_KEY_REGEX.test(e.key)) {
181
- if (isExpanded) {
226
+ if (unstableRef.current.isExpanded) {
182
227
  e.stopPropagation();
183
228
  setIsExpanded(false);
184
229
  }
@@ -191,28 +236,26 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
191
236
  e.preventDefault();
192
237
  }
193
238
  inputRef.current?.focus();
194
- if (!isExpanded) {
239
+ if (!unstableRef.current.isExpanded) {
195
240
  setIsExpanded(true);
196
241
  }
197
242
  }
198
243
  onKeyDownListBox(e);
199
- }, [isComposing, isExpanded, unfocus, onKeyDownListBox]);
244
+ }, [unfocus, onKeyDownListBox]);
200
245
  // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で
201
246
  // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、
202
247
  // submitイベントが発生し、formが送信される場合がある
203
248
  const handleKeyPress = React.useCallback((e) => {
204
249
  if (e.key === 'Enter')
205
250
  e.preventDefault();
206
- onKeyPress?.(e);
207
- }, [onKeyPress]);
208
- const caretIconColor = React.useMemo(() => {
209
- if (isFocused)
210
- return theme.textColor.black;
211
- if (disabled || readOnly)
212
- return theme.textColor.disabled;
213
- return theme.textColor.grey;
214
- }, [disabled, readOnly, isFocused, theme.textColor]);
215
- hooks_useClick.useClick(React.useMemo(() => [outerRef, listBoxRef, clearButtonRef], [outerRef, listBoxRef, clearButtonRef]), isFocused || selectedItem ? NOOP : selectDefaultItem, unfocus);
251
+ unstableRef.current.onKeyPress?.(e);
252
+ }, []);
253
+ const caretIconColor = isFocused
254
+ ? theme.textColor.black
255
+ : disabled || readOnly
256
+ ? theme.textColor.disabled
257
+ : theme.textColor.grey;
258
+ hooks_useClick.useClick(React.useMemo(() => [outerRef, listBoxRef, clearButtonRef], [listBoxRef]), isFocused || selectedItem ? NOOP : selectDefaultItem, unfocus);
216
259
  // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。
217
260
  const selectedItemLabelText = index$1(selectedItem?.label);
218
261
  React.useEffect(() => {
@@ -222,7 +265,7 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
222
265
  ...style,
223
266
  width: typeof width === 'number' ? `${width}px` : width,
224
267
  }), [style, width]);
225
- const notSelected = selectedItem === null;
268
+ const hasSelectedItem = selectedItem !== null;
226
269
  const classNames = React.useMemo(() => {
227
270
  const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } = classNameGenerator();
228
271
  return {
@@ -230,17 +273,24 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
230
273
  input: input(),
231
274
  caretDownLayout: caretDownLayout(),
232
275
  caretDownIcon: caretDownIcon(),
233
- clearButton: clearButton({ hidden: notSelected || disabled || readOnly }),
276
+ clearButton: clearButton({
277
+ hidden: !hasSelectedItem || disabled || readOnly,
278
+ }),
234
279
  clearButtonIcon: clearButtonIcon(),
235
280
  };
236
- }, [notSelected, disabled, readOnly, className]);
281
+ }, [hasSelectedItem, disabled, readOnly, className]);
237
282
  const destroyButtonIconAlt = React.useMemo(() => localize({
238
283
  id: 'smarthr-ui/SingleCombobox/destroyButtonIconAlt',
239
284
  defaultText: 'クリア',
240
285
  }), [localize]);
241
286
  return (jsxRuntime.jsxs("div", { role: "group", className: classNames.wrapper, style: wrapperStyle, ref: outerRef, children: [jsxRuntime.jsx(components_Input_Input.Input, { ...rest, ref: inputRef, type: "text", role: "combobox", name: name, value: inputValue, disabled: disabled, readOnly: readOnly, required: required, autoComplete: autoComplete ?? 'off', "aria-haspopup": "listbox", "aria-controls": listBoxId, "aria-expanded": isFocused, "aria-activedescendant": activeOption?.id, "aria-autocomplete": "list",
242
287
  /* eslint-disable-next-line smarthr/a11y-prohibit-input-placeholder */
243
- placeholder: placeholder, onClick: onClickInput, onChange: actualOnChangeInput, onFocus: isFocused ? undefined : focus, onCompositionStart: onCompositionStart, onCompositionEnd: onCompositionEnd, onKeyDown: onKeyDownInput, onKeyPress: handleKeyPress, error: error, prefix: prefix, suffix: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(components_Button_UnstyledButton.UnstyledButton, { onClick: onClickClear, ref: clearButtonRef, className: classNames.clearButton, children: jsxRuntime.jsx(components_Icon_FaIcon.FaCircleXmarkIcon, { color: "TEXT_BLACK", alt: destroyButtonIconAlt, className: classNames.clearButtonIcon }) }), jsxRuntime.jsx("span", { role: "presentation", onClick: onDelegateClickIcon, className: classNames.caretDownLayout, children: jsxRuntime.jsx(components_Icon_FaIcon.FaCaretDownIcon, { color: caretIconColor, className: classNames.caretDownIcon }) })] }), className: classNames.input, "data-smarthr-ui-input": "true" }), !readOnly && renderListBox()] }));
288
+ placeholder: placeholder, onClick: onClickInput, onChange: actualOnChangeInput, onFocus: isFocused ? undefined : focus, onCompositionStart: onCompositionStart, onCompositionEnd: onCompositionEnd, onKeyDown: onKeyDownInput, onKeyPress: handleKeyPress, error: error, prefix: prefix, suffix: jsxRuntime.jsx(SuffixButtons, { onClickClear: onClickClear, clearButtonRef: clearButtonRef, onClickIcon: onClickInput, caretIconColor: caretIconColor, destroyButtonIconAlt: destroyButtonIconAlt, classNames: {
289
+ clearButton: classNames.clearButton,
290
+ clearButtonIcon: classNames.clearButtonIcon,
291
+ caretDownLayout: classNames.caretDownLayout,
292
+ caretDownIcon: classNames.caretDownIcon,
293
+ } }), className: classNames.input, "data-smarthr-ui-input": "true" }), !readOnly && renderListBox()] }));
244
294
  };
245
295
  const SingleCombobox = libs_util.genericsForwardRef(ActualSingleCombobox);
246
296
 
@@ -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 useCallback,\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 { useTheme } from '../../../hooks/useTheme'\nimport { useIntl } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { UnstyledButton } from '../../Button'\nimport { FaCaretDownIcon, FaCircleXmarkIcon } from '../../Icon'\nimport { Input } from '../../Input'\nimport { useListbox } from '../useListbox'\nimport { useSingleOptions } from '../useOptions'\n\nimport type { ComboboxItem, AbstractProps as ComboboxProps } from '../types'\n\ntype AbstractProps<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> = AbstractProps<T> &\n Omit<ComponentPropsWithoutRef<'input'>, keyof AbstractProps<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: 'smarthr-ui-SingleCombobox shr-inline-block',\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 ],\n clearButtonIcon: [\n 'shr-block',\n 'group-focus-visible/clearButton:shr-focus-indicator group-focus-visible/clearButton:shr-rounded-full',\n ],\n },\n variants: {\n disabled: {\n true: {\n wrapper: 'shr-cursor-not-allowed',\n },\n },\n hidden: {\n true: {\n clearButton: 'shr-hidden',\n },\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 { localize } = useIntl()\n const outerRef = 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>(ref, () => inputRef.current)\n\n const { options } = useSingleOptions({\n items,\n selected: selectedItem,\n creatable,\n inputValue,\n isFilteringDisabled: !isEditing,\n })\n\n const { renderListBox, activeOption, onKeyDownListBox, listBoxId, listBoxRef } = useListbox<T>({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect: useCallback(\n (selected: ComboboxItem<T>) => {\n onSelect?.(selected)\n onChangeSelected?.(selected)\n\n // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する\n onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n\n // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう\n // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする\n requestAnimationFrame(() => {\n setIsExpanded(false)\n })\n\n setIsEditing(false)\n },\n [onChangeSelected, onSelect, onChangeInput],\n ),\n isExpanded,\n isLoading,\n triggerRef: outerRef,\n noResultText,\n })\n\n const selectDefaultItem = useMemo(\n () => (onSelect && defaultItem ? () => onSelect(defaultItem) : NOOP),\n [onSelect, defaultItem],\n )\n\n const focus = useCallback(() => {\n onFocus?.()\n inputRef.current?.focus()\n setIsFocused(true)\n\n if (!isFocused) {\n setIsExpanded(true)\n }\n }, [onFocus, isFocused])\n const unfocus = useCallback(() => {\n if (!isFocused) return\n\n onBlur?.()\n\n setIsFocused(false)\n setIsExpanded(false)\n setIsEditing(false)\n\n if (selectedItem) {\n setInputValue(innerText(selectedItem.label))\n } else {\n selectDefaultItem()\n }\n }, [isFocused, onBlur, selectedItem, selectDefaultItem])\n const onClickClear = useCallback(\n (e: MouseEvent) => {\n e.stopPropagation()\n\n let isExecutedPreventDefault = false\n\n onClearClick?.({\n ...e,\n preventDefault: () => {\n e.preventDefault()\n isExecutedPreventDefault = true\n },\n })\n\n if (!isExecutedPreventDefault) {\n onClear?.()\n onChangeSelected?.(null)\n\n inputRef.current?.focus()\n\n setIsFocused(true)\n setIsExpanded(true)\n }\n },\n [onClearClick, onClear, onChangeSelected],\n )\n const onClickInput = useCallback(\n (e: MouseEvent) => {\n if (disabled || readOnly) {\n e.stopPropagation()\n\n return\n }\n\n inputRef.current?.focus()\n\n if (!isExpanded) {\n setIsExpanded(true)\n }\n },\n [disabled, readOnly, inputRef, isExpanded],\n )\n const onDelegateClickIcon = onClickInput\n const actualOnChangeInput = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n onChange?.(e)\n onChangeInput?.(e)\n\n if (!isEditing) setIsEditing(true)\n\n const { value } = e.currentTarget\n\n setInputValue(value)\n\n if (value === '') {\n onClear?.()\n onChangeSelected?.(null)\n }\n },\n [isEditing, onChange, onChangeInput, onClear, onChangeSelected],\n )\n const onCompositionStart = useCallback(() => setIsComposing(true), [])\n const onCompositionEnd = useCallback(() => setIsComposing(false), [])\n const onKeyDownInput = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (isComposing) {\n return\n }\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n if (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 (!isExpanded) {\n setIsExpanded(true)\n }\n }\n onKeyDownListBox(e)\n },\n [isComposing, isExpanded, unfocus, onKeyDownListBox],\n )\n\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n const handleKeyPress = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') e.preventDefault()\n\n onKeyPress?.(e)\n },\n [onKeyPress],\n )\n\n const caretIconColor = useMemo(() => {\n if (isFocused) return theme.textColor.black\n if (disabled || readOnly) return theme.textColor.disabled\n\n return theme.textColor.grey\n }, [disabled, readOnly, isFocused, theme.textColor])\n\n useClick(\n useMemo(() => [outerRef, listBoxRef, clearButtonRef], [outerRef, listBoxRef, clearButtonRef]),\n isFocused || selectedItem ? NOOP : selectDefaultItem,\n unfocus,\n )\n\n // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。\n const selectedItemLabelText = innerText(selectedItem?.label)\n useEffect(() => {\n setInputValue(selectedItemLabelText)\n }, [selectedItemLabelText])\n\n const wrapperStyle = useMemo(\n () => ({\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }),\n [style, width],\n )\n\n const notSelected = selectedItem === null\n\n const classNames = useMemo(() => {\n const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } =\n classNameGenerator()\n\n return {\n wrapper: wrapper({ disabled, className }),\n input: input(),\n caretDownLayout: caretDownLayout(),\n caretDownIcon: caretDownIcon(),\n clearButton: clearButton({ hidden: notSelected || disabled || readOnly }),\n clearButtonIcon: clearButtonIcon(),\n }\n }, [notSelected, disabled, readOnly, className])\n\n const destroyButtonIconAlt = useMemo(\n () =>\n localize({\n id: 'smarthr-ui/SingleCombobox/destroyButtonIconAlt',\n defaultText: 'クリア',\n }),\n [localize],\n )\n\n return (\n <div role=\"group\" className={classNames.wrapper} style={wrapperStyle} ref={outerRef}>\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={onClickInput}\n onChange={actualOnChangeInput}\n onFocus={isFocused ? undefined : focus}\n onCompositionStart={onCompositionStart}\n onCompositionEnd={onCompositionEnd}\n onKeyDown={onKeyDownInput}\n onKeyPress={handleKeyPress}\n error={error}\n prefix={prefix}\n suffix={\n <>\n <UnstyledButton\n onClick={onClickClear}\n ref={clearButtonRef}\n className={classNames.clearButton}\n >\n <FaCircleXmarkIcon\n color=\"TEXT_BLACK\"\n alt={destroyButtonIconAlt}\n className={classNames.clearButtonIcon}\n />\n </UnstyledButton>\n <span\n role=\"presentation\"\n onClick={onDelegateClickIcon}\n className={classNames.caretDownLayout}\n >\n <FaCaretDownIcon color={caretIconColor} className={classNames.caretDownIcon} />\n </span>\n </>\n }\n className={classNames.input}\n data-smarthr-ui-input=\"true\"\n />\n {!readOnly && renderListBox()}\n </div>\n )\n}\n\nexport const SingleCombobox = genericsForwardRef(ActualSingleCombobox)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyEA;AAEA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;AACA;AACA;;;;AAIC;AACD;AACA;;;;;AAKC;AACD;;;AAGC;AACF;AACD;AACE;AACE;AACE;AACD;AACF;AACD;AACE;AACE;AACD;AACF;AACF;AACF;AAED;AAmCE;AACA;AACA;AACA;AACA;;;;;;;AASA;;AAEE;;;;AAID;AAED;;;;;AAKE;AAEI;AACA;;AAGA;;;;;AAMA;;;;;AAQJ;;AAED;AAED;AAKA;;AAEE;;;;;AAMF;AACA;AACE;;;;;;;;;;AAWE;;;AAGJ;;;AAMI;AACE;;;;;AAKD;;;AAIC;AAEA;;;;;AAQN;AAEI;;;;AAMA;;;;;;AASJ;AAEI;AACA;AAEA;;AAEA;;AAIA;;AAEE;;AAEJ;AAGF;AACA;AACA;;;;;;;;;;AAWW;AACL;;;;;;AAMA;;;;;;;;;;AAcN;AAEI;;AAEA;AACF;AAIF;AACE;AAAe;;AACW;AAE1B;AACF;AAEA;;;;;AAUA;AAEA;AAEI;AACA;AACD;AAIH;AAEA;AACE;;;;;;AAQE;;;;;AAQE;AACA;AACD;AAIL;;AAmBM;AAsCR;;;"}
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 useCallback,\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 { useTheme } from '../../../hooks/useTheme'\nimport { useIntl } from '../../../intl'\nimport { genericsForwardRef } from '../../../libs/util'\nimport { UnstyledButton } from '../../Button'\nimport { FaCaretDownIcon, FaCircleXmarkIcon } from '../../Icon'\nimport { Input } from '../../Input'\nimport { useListbox } from '../useListbox'\nimport { useSingleOptions } from '../useOptions'\n\nimport type { ComboboxItem, AbstractProps as ComboboxProps } from '../types'\n\ntype AbstractProps<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> = AbstractProps<T> &\n Omit<ComponentPropsWithoutRef<'input'>, keyof AbstractProps<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: 'smarthr-ui-SingleCombobox shr-inline-block',\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 ],\n clearButtonIcon: [\n 'shr-block',\n 'group-focus-visible/clearButton:shr-focus-indicator group-focus-visible/clearButton:shr-rounded-full',\n ],\n },\n variants: {\n disabled: {\n true: {\n wrapper: 'shr-cursor-not-allowed',\n },\n },\n hidden: {\n true: {\n clearButton: 'shr-hidden',\n },\n },\n },\n})\n\ntype SuffixButtonsProps = {\n onClickClear: (e: MouseEvent) => void\n clearButtonRef: RefObject<HTMLButtonElement>\n onClickIcon: (e: MouseEvent) => void\n caretIconColor: string\n destroyButtonIconAlt: string\n classNames: {\n clearButton: string\n clearButtonIcon: string\n caretDownLayout: string\n caretDownIcon: string\n }\n}\n\nconst SuffixButtons = memo<SuffixButtonsProps>(\n ({\n onClickClear,\n clearButtonRef,\n onClickIcon: onDelegateClickIcon,\n caretIconColor,\n destroyButtonIconAlt,\n classNames,\n }) => (\n <>\n <UnstyledButton\n onClick={onClickClear}\n ref={clearButtonRef}\n className={classNames.clearButton}\n >\n <FaCircleXmarkIcon\n color=\"TEXT_BLACK\"\n alt={destroyButtonIconAlt}\n className={classNames.clearButtonIcon}\n />\n </UnstyledButton>\n <span\n role=\"presentation\"\n onClick={onDelegateClickIcon}\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 { localize } = useIntl()\n const outerRef = 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 const unstableRef = useRef({\n onChange,\n onChangeInput,\n onAdd,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n defaultItem,\n selectedItem,\n isFocused,\n isExpanded,\n isComposing,\n isEditing,\n })\n unstableRef.current = {\n onChange,\n onChangeInput,\n onAdd,\n onSelect,\n onClear,\n onClearClick,\n onChangeSelected,\n onFocus,\n onBlur,\n onKeyPress,\n defaultItem,\n selectedItem,\n isFocused,\n isExpanded,\n isComposing,\n isEditing,\n }\n\n useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(ref, () => inputRef.current)\n\n const { options } = useSingleOptions({\n items,\n selected: selectedItem,\n creatable,\n inputValue,\n isFilteringDisabled: !isEditing,\n })\n\n const { renderListBox, activeOption, onKeyDownListBox, listBoxId, listBoxRef } = useListbox<T>({\n options,\n dropdownHelpMessage,\n dropdownWidth,\n onAdd,\n onSelect: useCallback((selected: ComboboxItem<T>) => {\n unstableRef.current.onSelect?.(selected)\n unstableRef.current.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 unstableRef.current.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT)\n })\n\n setIsEditing(false)\n }, []),\n isExpanded,\n isLoading,\n triggerRef: outerRef,\n noResultText,\n })\n\n const selectDefaultItem = useCallback(() => {\n if (unstableRef.current.onSelect && unstableRef.current.defaultItem) {\n unstableRef.current.onSelect(unstableRef.current.defaultItem)\n }\n }, [])\n\n const focus = useCallback(() => {\n unstableRef.current.onFocus?.()\n inputRef.current?.focus()\n setIsFocused(true)\n\n if (!unstableRef.current.isFocused) {\n setIsExpanded(true)\n }\n }, [])\n const unfocus = useCallback(() => {\n if (!unstableRef.current.isFocused) return\n\n unstableRef.current.onBlur?.()\n\n setIsFocused(false)\n setIsExpanded(false)\n setIsEditing(false)\n\n if (unstableRef.current.selectedItem) {\n setInputValue(innerText(unstableRef.current.selectedItem.label))\n } else {\n selectDefaultItem()\n }\n }, [selectDefaultItem])\n const onClickClear = useCallback((e: MouseEvent) => {\n e.stopPropagation()\n\n let isExecutedPreventDefault = false\n\n unstableRef.current.onClearClick?.({\n ...e,\n preventDefault: () => {\n e.preventDefault()\n isExecutedPreventDefault = true\n },\n })\n\n if (!isExecutedPreventDefault) {\n unstableRef.current.onClear?.()\n unstableRef.current.onChangeSelected?.(null)\n\n inputRef.current?.focus()\n\n setIsFocused(true)\n setIsExpanded(true)\n }\n }, [])\n const onClickInput = useCallback(\n (e: MouseEvent) => {\n if (disabled || readOnly) {\n e.stopPropagation()\n\n return\n }\n\n inputRef.current?.focus()\n\n if (!unstableRef.current.isExpanded) {\n setIsExpanded(true)\n }\n },\n [disabled, readOnly],\n )\n const actualOnChangeInput = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n unstableRef.current.onChange?.(e)\n unstableRef.current.onChangeInput?.(e)\n\n if (!unstableRef.current.isEditing) setIsEditing(true)\n\n const { value } = e.currentTarget\n\n setInputValue(value)\n\n if (value === '') {\n unstableRef.current.onClear?.()\n unstableRef.current.onChangeSelected?.(null)\n }\n }, [])\n const onCompositionStart = useCallback(() => setIsComposing(true), [])\n const onCompositionEnd = useCallback(() => setIsComposing(false), [])\n const onKeyDownInput = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (unstableRef.current.isComposing) {\n return\n }\n\n if (ESCAPE_KEY_REGEX.test(e.key)) {\n if (unstableRef.current.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 (!unstableRef.current.isExpanded) {\n setIsExpanded(true)\n }\n }\n onKeyDownListBox(e)\n },\n [unfocus, onKeyDownListBox],\n )\n\n // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で\n // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、\n // submitイベントが発生し、formが送信される場合がある\n const handleKeyPress = useCallback((e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') e.preventDefault()\n\n unstableRef.current.onKeyPress?.(e)\n }, [])\n\n const caretIconColor = isFocused\n ? theme.textColor.black\n : disabled || readOnly\n ? theme.textColor.disabled\n : theme.textColor.grey\n\n useClick(\n useMemo(() => [outerRef, listBoxRef, clearButtonRef], [listBoxRef]),\n isFocused || selectedItem ? NOOP : selectDefaultItem,\n unfocus,\n )\n\n // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。\n const selectedItemLabelText = innerText(selectedItem?.label)\n useEffect(() => {\n setInputValue(selectedItemLabelText)\n }, [selectedItemLabelText])\n\n const wrapperStyle = useMemo(\n () => ({\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n }),\n [style, width],\n )\n\n const hasSelectedItem = selectedItem !== null\n\n const classNames = useMemo(() => {\n const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } =\n classNameGenerator()\n\n return {\n wrapper: wrapper({ disabled, className }),\n input: input(),\n caretDownLayout: caretDownLayout(),\n caretDownIcon: caretDownIcon(),\n clearButton: clearButton({\n hidden: !hasSelectedItem || disabled || readOnly,\n }),\n clearButtonIcon: clearButtonIcon(),\n }\n }, [hasSelectedItem, disabled, readOnly, className])\n\n const destroyButtonIconAlt = useMemo(\n () =>\n localize({\n id: 'smarthr-ui/SingleCombobox/destroyButtonIconAlt',\n defaultText: 'クリア',\n }),\n [localize],\n )\n\n return (\n <div role=\"group\" className={classNames.wrapper} style={wrapperStyle} ref={outerRef}>\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={onClickInput}\n onChange={actualOnChangeInput}\n onFocus={isFocused ? undefined : focus}\n onCompositionStart={onCompositionStart}\n onCompositionEnd={onCompositionEnd}\n onKeyDown={onKeyDownInput}\n onKeyPress={handleKeyPress}\n error={error}\n prefix={prefix}\n suffix={\n <SuffixButtons\n onClickClear={onClickClear}\n clearButtonRef={clearButtonRef}\n onClickIcon={onClickInput}\n caretIconColor={caretIconColor}\n destroyButtonIconAlt={destroyButtonIconAlt}\n classNames={{\n clearButton: classNames.clearButton,\n clearButtonIcon: classNames.clearButtonIcon,\n caretDownLayout: classNames.caretDownLayout,\n caretDownIcon: classNames.caretDownIcon,\n }}\n />\n }\n className={classNames.input}\n data-smarthr-ui-input=\"true\"\n />\n {!readOnly && renderListBox()}\n </div>\n )\n}\n\nexport const SingleCombobox = genericsForwardRef(ActualSingleCombobox)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA;AAEA;AACA;AAEA;AACE;AACA;;AAGF;AACE;AACE;AACA;AACA;;;;AAIC;AACD;AACA;;;;;AAKC;AACD;;;AAGC;AACF;AACD;AACE;AACE;AACE;AACD;AACF;AACD;AACE;AACE;AACD;AACF;AACF;AACF;AAgBD;AAgCA;AAmCE;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;AAwBC;;;;;;;;;;;;;;;;;;;;AAsBD;;AAEE;;;;AAID;AAED;;;;;AAKE;;;;;;;;;;;;;;AAeE;;;;;AAMF;;AAED;AAED;AACE;;;;AAKF;AACE;AACA;;AAGA;;;;AAIF;AACE;;AAEA;;;;AAMA;AACE;;;AAEA;;AAEJ;AACA;;;AAKE;AACE;;;;;AAKD;;AAGC;;AAGA;;;;;AAMJ;AAEI;;;;AAMA;AAEA;;;AAGF;AAGF;;;AAIE;;AAEA;;AAIA;AACE;;;;AAIJ;AACA;AACA;AAEI;;;;AAKE;;;;;AAIK;AACL;;;;;;AAMA;AAEA;;;;;AAKJ;;;;AAOF;AACE;;;;;AAMA;;AAEE;AACA;AAEJ;;;;;AAUA;AAEA;AAEI;AACA;AACD;AAIH;AAEA;AACE;;;;;;;AASI;;;;;;AASA;AACA;AACD;AAIL;;AAmBM;;;;;AAsBK;AASb;;;"}
@@ -1,13 +1,13 @@
1
1
  "use client";
2
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import { useRef, useState, useImperativeHandle, useCallback, useMemo, useEffect } from 'react';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import { memo, useRef, useState, useImperativeHandle, useCallback, useMemo, useEffect } from 'react';
4
4
  import innerText from '../../../_virtual/index3.js';
5
5
  import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
6
6
  import { useClick } from '../../../hooks/useClick.js';
7
7
  import { useTheme } from '../../../hooks/useTheme.js';
8
8
  import '../../../intl/IntlProvider.js';
9
9
  import { useIntl } from '../../../intl/useIntl.js';
10
- import 'react-intl';
10
+ import '../../../intl/Localizer.js';
11
11
  import '../../../_virtual/dayjs.min.js';
12
12
  import '../../../intl/TimeFormatter.js';
13
13
  import { genericsForwardRef } from '../../../libs/util.js';
@@ -64,6 +64,7 @@ const classNameGenerator = ce({
64
64
  },
65
65
  },
66
66
  });
67
+ const SuffixButtons = memo(({ onClickClear, clearButtonRef, onClickIcon: onDelegateClickIcon, caretIconColor, destroyButtonIconAlt, classNames, }) => (jsxs(Fragment, { children: [jsx(UnstyledButton, { onClick: onClickClear, ref: clearButtonRef, className: classNames.clearButton, children: jsx(FaCircleXmarkIcon, { color: "TEXT_BLACK", alt: destroyButtonIconAlt, className: classNames.clearButtonIcon }) }), jsx("span", { role: "presentation", onClick: onDelegateClickIcon, className: classNames.caretDownLayout, children: jsx(FaCaretDownIcon, { color: caretIconColor, className: classNames.caretDownIcon }) })] })));
67
68
  const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled, readOnly, required, prefix, error, creatable, placeholder, autoComplete, dropdownHelpMessage, isLoading, width, dropdownWidth = 'auto', className, onChange, onChangeInput, onAdd, onSelect, onClear, onClearClick, onChangeSelected, onFocus, onBlur, onKeyPress, noResultText, style, ...rest }, ref) => {
68
69
  const theme = useTheme();
69
70
  const { localize } = useIntl();
@@ -75,6 +76,42 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
75
76
  const [inputValue, setInputValue] = useState('');
76
77
  const [isComposing, setIsComposing] = useState(false);
77
78
  const [isEditing, setIsEditing] = useState(false);
79
+ const unstableRef = useRef({
80
+ onChange,
81
+ onChangeInput,
82
+ onAdd,
83
+ onSelect,
84
+ onClear,
85
+ onClearClick,
86
+ onChangeSelected,
87
+ onFocus,
88
+ onBlur,
89
+ onKeyPress,
90
+ defaultItem,
91
+ selectedItem,
92
+ isFocused,
93
+ isExpanded,
94
+ isComposing,
95
+ isEditing,
96
+ });
97
+ unstableRef.current = {
98
+ onChange,
99
+ onChangeInput,
100
+ onAdd,
101
+ onSelect,
102
+ onClear,
103
+ onClearClick,
104
+ onChangeSelected,
105
+ onFocus,
106
+ onBlur,
107
+ onKeyPress,
108
+ defaultItem,
109
+ selectedItem,
110
+ isFocused,
111
+ isExpanded,
112
+ isComposing,
113
+ isEditing,
114
+ };
78
115
  useImperativeHandle(ref, () => inputRef.current);
79
116
  const { options } = useSingleOptions({
80
117
  items,
@@ -89,49 +126,58 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
89
126
  dropdownWidth,
90
127
  onAdd,
91
128
  onSelect: useCallback((selected) => {
92
- onSelect?.(selected);
93
- onChangeSelected?.(selected);
94
- // 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する
95
- onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT);
129
+ unstableRef.current.onSelect?.(selected);
130
+ unstableRef.current.onChangeSelected?.(selected);
96
131
  // HINT: Dropdown系コンポーネント内でComboboxを使うと、選択肢がportalで表現されている関係上Dropdownが閉じてしまう
97
132
  // requestAnimationFrameを追加、処理を遅延させることで正常に閉じる/閉じないの判定を行えるようにする
98
133
  requestAnimationFrame(() => {
99
134
  setIsExpanded(false);
135
+ // HINT:
136
+ // - 制御コンポーネントの場合に親側でinputValueを更新できるように、選択時にonChangeInputを空文字で発火する
137
+ // - 対応するdropdownを閉じて以降にonChangeInputを発火する必要がある
138
+ // - 先にclearしてしまうと意図せずこの要素のドロップダウンを閉じる前に他要素の再レンダリングを引き起こす可能性がある
139
+ // - 例えばFilterDropdownなどで当comboboxを使っている場合、レイアウト上comboboxのdropdown以下の要素がクリックされた扱いになってしまい
140
+ // FilterDropdownを意図せず閉じてしまうなどの挙動のバグを引き起こす可能性がある
141
+ unstableRef.current.onChangeInput?.(EMPTY_INPUT_CHANGE_EVENT);
100
142
  });
101
143
  setIsEditing(false);
102
- }, [onChangeSelected, onSelect, onChangeInput]),
144
+ }, []),
103
145
  isExpanded,
104
146
  isLoading,
105
147
  triggerRef: outerRef,
106
148
  noResultText,
107
149
  });
108
- const selectDefaultItem = useMemo(() => (onSelect && defaultItem ? () => onSelect(defaultItem) : NOOP), [onSelect, defaultItem]);
150
+ const selectDefaultItem = useCallback(() => {
151
+ if (unstableRef.current.onSelect && unstableRef.current.defaultItem) {
152
+ unstableRef.current.onSelect(unstableRef.current.defaultItem);
153
+ }
154
+ }, []);
109
155
  const focus = useCallback(() => {
110
- onFocus?.();
156
+ unstableRef.current.onFocus?.();
111
157
  inputRef.current?.focus();
112
158
  setIsFocused(true);
113
- if (!isFocused) {
159
+ if (!unstableRef.current.isFocused) {
114
160
  setIsExpanded(true);
115
161
  }
116
- }, [onFocus, isFocused]);
162
+ }, []);
117
163
  const unfocus = useCallback(() => {
118
- if (!isFocused)
164
+ if (!unstableRef.current.isFocused)
119
165
  return;
120
- onBlur?.();
166
+ unstableRef.current.onBlur?.();
121
167
  setIsFocused(false);
122
168
  setIsExpanded(false);
123
169
  setIsEditing(false);
124
- if (selectedItem) {
125
- setInputValue(innerText(selectedItem.label));
170
+ if (unstableRef.current.selectedItem) {
171
+ setInputValue(innerText(unstableRef.current.selectedItem.label));
126
172
  }
127
173
  else {
128
174
  selectDefaultItem();
129
175
  }
130
- }, [isFocused, onBlur, selectedItem, selectDefaultItem]);
176
+ }, [selectDefaultItem]);
131
177
  const onClickClear = useCallback((e) => {
132
178
  e.stopPropagation();
133
179
  let isExecutedPreventDefault = false;
134
- onClearClick?.({
180
+ unstableRef.current.onClearClick?.({
135
181
  ...e,
136
182
  preventDefault: () => {
137
183
  e.preventDefault();
@@ -139,44 +185,43 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
139
185
  },
140
186
  });
141
187
  if (!isExecutedPreventDefault) {
142
- onClear?.();
143
- onChangeSelected?.(null);
188
+ unstableRef.current.onClear?.();
189
+ unstableRef.current.onChangeSelected?.(null);
144
190
  inputRef.current?.focus();
145
191
  setIsFocused(true);
146
192
  setIsExpanded(true);
147
193
  }
148
- }, [onClearClick, onClear, onChangeSelected]);
194
+ }, []);
149
195
  const onClickInput = useCallback((e) => {
150
196
  if (disabled || readOnly) {
151
197
  e.stopPropagation();
152
198
  return;
153
199
  }
154
200
  inputRef.current?.focus();
155
- if (!isExpanded) {
201
+ if (!unstableRef.current.isExpanded) {
156
202
  setIsExpanded(true);
157
203
  }
158
- }, [disabled, readOnly, inputRef, isExpanded]);
159
- const onDelegateClickIcon = onClickInput;
204
+ }, [disabled, readOnly]);
160
205
  const actualOnChangeInput = useCallback((e) => {
161
- onChange?.(e);
162
- onChangeInput?.(e);
163
- if (!isEditing)
206
+ unstableRef.current.onChange?.(e);
207
+ unstableRef.current.onChangeInput?.(e);
208
+ if (!unstableRef.current.isEditing)
164
209
  setIsEditing(true);
165
210
  const { value } = e.currentTarget;
166
211
  setInputValue(value);
167
212
  if (value === '') {
168
- onClear?.();
169
- onChangeSelected?.(null);
213
+ unstableRef.current.onClear?.();
214
+ unstableRef.current.onChangeSelected?.(null);
170
215
  }
171
- }, [isEditing, onChange, onChangeInput, onClear, onChangeSelected]);
216
+ }, []);
172
217
  const onCompositionStart = useCallback(() => setIsComposing(true), []);
173
218
  const onCompositionEnd = useCallback(() => setIsComposing(false), []);
174
219
  const onKeyDownInput = useCallback((e) => {
175
- if (isComposing) {
220
+ if (unstableRef.current.isComposing) {
176
221
  return;
177
222
  }
178
223
  if (ESCAPE_KEY_REGEX.test(e.key)) {
179
- if (isExpanded) {
224
+ if (unstableRef.current.isExpanded) {
180
225
  e.stopPropagation();
181
226
  setIsExpanded(false);
182
227
  }
@@ -189,28 +234,26 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
189
234
  e.preventDefault();
190
235
  }
191
236
  inputRef.current?.focus();
192
- if (!isExpanded) {
237
+ if (!unstableRef.current.isExpanded) {
193
238
  setIsExpanded(true);
194
239
  }
195
240
  }
196
241
  onKeyDownListBox(e);
197
- }, [isComposing, isExpanded, unfocus, onKeyDownListBox]);
242
+ }, [unfocus, onKeyDownListBox]);
198
243
  // HINT: form内にcomboboxを設置 & 検索inputにfocusした状態で
199
244
  // アイテムをキーボードで選択し、Enterを押すとinput上でEnterを押したことになるため、
200
245
  // submitイベントが発生し、formが送信される場合がある
201
246
  const handleKeyPress = useCallback((e) => {
202
247
  if (e.key === 'Enter')
203
248
  e.preventDefault();
204
- onKeyPress?.(e);
205
- }, [onKeyPress]);
206
- const caretIconColor = useMemo(() => {
207
- if (isFocused)
208
- return theme.textColor.black;
209
- if (disabled || readOnly)
210
- return theme.textColor.disabled;
211
- return theme.textColor.grey;
212
- }, [disabled, readOnly, isFocused, theme.textColor]);
213
- useClick(useMemo(() => [outerRef, listBoxRef, clearButtonRef], [outerRef, listBoxRef, clearButtonRef]), isFocused || selectedItem ? NOOP : selectDefaultItem, unfocus);
249
+ unstableRef.current.onKeyPress?.(e);
250
+ }, []);
251
+ const caretIconColor = isFocused
252
+ ? theme.textColor.black
253
+ : disabled || readOnly
254
+ ? theme.textColor.disabled
255
+ : theme.textColor.grey;
256
+ useClick(useMemo(() => [outerRef, listBoxRef, clearButtonRef], [listBoxRef]), isFocused || selectedItem ? NOOP : selectDefaultItem, unfocus);
214
257
  // selectedItem.label はプリミティブ値でないデータ型の可能性があり、そのまま useEffect の依存配列に入れると意図せぬエフェクトの実行を引き起こしてしまう可能性があるので、プリミティブ値である string 型に変換したものを依存配列に入れています。
215
258
  const selectedItemLabelText = innerText(selectedItem?.label);
216
259
  useEffect(() => {
@@ -220,7 +263,7 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
220
263
  ...style,
221
264
  width: typeof width === 'number' ? `${width}px` : width,
222
265
  }), [style, width]);
223
- const notSelected = selectedItem === null;
266
+ const hasSelectedItem = selectedItem !== null;
224
267
  const classNames = useMemo(() => {
225
268
  const { wrapper, input, caretDownLayout, caretDownIcon, clearButton, clearButtonIcon } = classNameGenerator();
226
269
  return {
@@ -228,17 +271,24 @@ const ActualSingleCombobox = ({ items, selectedItem, defaultItem, name, disabled
228
271
  input: input(),
229
272
  caretDownLayout: caretDownLayout(),
230
273
  caretDownIcon: caretDownIcon(),
231
- clearButton: clearButton({ hidden: notSelected || disabled || readOnly }),
274
+ clearButton: clearButton({
275
+ hidden: !hasSelectedItem || disabled || readOnly,
276
+ }),
232
277
  clearButtonIcon: clearButtonIcon(),
233
278
  };
234
- }, [notSelected, disabled, readOnly, className]);
279
+ }, [hasSelectedItem, disabled, readOnly, className]);
235
280
  const destroyButtonIconAlt = useMemo(() => localize({
236
281
  id: 'smarthr-ui/SingleCombobox/destroyButtonIconAlt',
237
282
  defaultText: 'クリア',
238
283
  }), [localize]);
239
284
  return (jsxs("div", { role: "group", className: classNames.wrapper, style: wrapperStyle, ref: outerRef, children: [jsx(Input, { ...rest, ref: inputRef, type: "text", role: "combobox", name: name, value: inputValue, disabled: disabled, readOnly: readOnly, required: required, autoComplete: autoComplete ?? 'off', "aria-haspopup": "listbox", "aria-controls": listBoxId, "aria-expanded": isFocused, "aria-activedescendant": activeOption?.id, "aria-autocomplete": "list",
240
285
  /* eslint-disable-next-line smarthr/a11y-prohibit-input-placeholder */
241
- placeholder: placeholder, onClick: onClickInput, onChange: actualOnChangeInput, onFocus: isFocused ? undefined : focus, onCompositionStart: onCompositionStart, onCompositionEnd: onCompositionEnd, onKeyDown: onKeyDownInput, onKeyPress: handleKeyPress, error: error, prefix: prefix, suffix: jsxs(Fragment, { children: [jsx(UnstyledButton, { onClick: onClickClear, ref: clearButtonRef, className: classNames.clearButton, children: jsx(FaCircleXmarkIcon, { color: "TEXT_BLACK", alt: destroyButtonIconAlt, className: classNames.clearButtonIcon }) }), jsx("span", { role: "presentation", onClick: onDelegateClickIcon, className: classNames.caretDownLayout, children: jsx(FaCaretDownIcon, { color: caretIconColor, className: classNames.caretDownIcon }) })] }), className: classNames.input, "data-smarthr-ui-input": "true" }), !readOnly && renderListBox()] }));
286
+ placeholder: placeholder, onClick: onClickInput, onChange: actualOnChangeInput, onFocus: isFocused ? undefined : focus, onCompositionStart: onCompositionStart, onCompositionEnd: onCompositionEnd, onKeyDown: onKeyDownInput, onKeyPress: handleKeyPress, error: error, prefix: prefix, suffix: jsx(SuffixButtons, { onClickClear: onClickClear, clearButtonRef: clearButtonRef, onClickIcon: onClickInput, caretIconColor: caretIconColor, destroyButtonIconAlt: destroyButtonIconAlt, classNames: {
287
+ clearButton: classNames.clearButton,
288
+ clearButtonIcon: classNames.clearButtonIcon,
289
+ caretDownLayout: classNames.caretDownLayout,
290
+ caretDownIcon: classNames.caretDownIcon,
291
+ } }), className: classNames.input, "data-smarthr-ui-input": "true" }), !readOnly && renderListBox()] }));
242
292
  };
243
293
  const SingleCombobox = genericsForwardRef(ActualSingleCombobox);
244
294