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.
- package/lib/components/AppHeader/components/common/AppLauncherFeatures.cjs +1 -1
- package/lib/components/AppHeader/components/common/AppLauncherFeatures.js +1 -1
- package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.cjs +1 -1
- package/lib/components/AppHeader/components/common/AppLauncherSortDropdown.js +1 -1
- package/lib/components/AppHeader/components/desktop/AppLauncher.cjs +5 -5
- package/lib/components/AppHeader/components/desktop/AppLauncher.cjs.map +1 -1
- package/lib/components/AppHeader/components/desktop/AppLauncher.js +5 -5
- package/lib/components/AppHeader/components/desktop/AppLauncher.js.map +1 -1
- package/lib/components/AppHeader/components/desktop/DesktopHeader.cjs +1 -1
- package/lib/components/AppHeader/components/desktop/DesktopHeader.js +1 -1
- package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.cjs +1 -1
- package/lib/components/AppHeader/components/desktop/ReleaseNotesDropdown.js +1 -1
- package/lib/components/AppHeader/components/desktop/UserInfo.cjs +1 -1
- package/lib/components/AppHeader/components/desktop/UserInfo.js +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncher.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncher.js +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/AppLauncherFilterDropdown.js +1 -1
- package/lib/components/AppHeader/components/mobile/Help.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/Help.js +1 -1
- package/lib/components/AppHeader/components/mobile/LanguageSelector.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/LanguageSelector.js +1 -1
- package/lib/components/AppHeader/components/mobile/Menu.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/Menu.js +1 -1
- package/lib/components/AppHeader/components/mobile/MenuAccordion.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/MenuAccordion.js +1 -1
- package/lib/components/AppHeader/components/mobile/MenuDialog.cjs +2 -2
- package/lib/components/AppHeader/components/mobile/MenuDialog.js +2 -2
- package/lib/components/AppHeader/components/mobile/MenuSubHeading.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/MenuSubHeading.js +1 -1
- package/lib/components/AppHeader/components/mobile/ReleaseNote.cjs +1 -1
- package/lib/components/AppHeader/components/mobile/ReleaseNote.js +1 -1
- package/lib/components/AppHeader/components/mobile/UserInfo.cjs +2 -2
- package/lib/components/AppHeader/components/mobile/UserInfo.js +2 -2
- package/lib/components/Browser/Browser.cjs +11 -7
- package/lib/components/Browser/Browser.cjs.map +1 -1
- package/lib/components/Browser/Browser.js +12 -8
- package/lib/components/Browser/Browser.js.map +1 -1
- package/lib/components/Calendar/Calendar.cjs +1 -1
- package/lib/components/Calendar/Calendar.js +1 -1
- package/lib/components/Calendar/CalendarTable.cjs +1 -1
- package/lib/components/Calendar/CalendarTable.js +1 -1
- package/lib/components/Calendar/YearPicker.cjs +1 -1
- package/lib/components/Calendar/YearPicker.js +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs +122 -134
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs.map +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.js +123 -135
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.js.map +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.cjs +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiSelectedItem.js +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs +96 -46
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs.map +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js +98 -48
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js.map +1 -1
- package/lib/components/Combobox/useListbox.cjs +1 -1
- package/lib/components/Combobox/useListbox.js +1 -1
- package/lib/components/Combobox/useOptions.cjs +3 -1
- package/lib/components/Combobox/useOptions.cjs.map +1 -1
- package/lib/components/Combobox/useOptions.js +4 -2
- package/lib/components/Combobox/useOptions.js.map +1 -1
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.cjs +3 -3
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.js +3 -3
- package/lib/components/Dialog/ControlledActionDialog/ActionDialogContentInner.js.map +1 -1
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs +3 -3
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js +3 -3
- package/lib/components/Dialog/ControlledFormDialog/FormDialogContentInner.js.map +1 -1
- package/lib/components/Dialog/ControlledMessageDialog/MessageDialogContentInner.cjs +1 -1
- package/lib/components/Dialog/ControlledMessageDialog/MessageDialogContentInner.js +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.cjs +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/ControlledStepFormDialog.js +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs +2 -2
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js +2 -2
- package/lib/components/Dialog/ControlledStepFormDialog/StepFormDialogContentInner.js.map +1 -1
- package/lib/components/Dialog/DialogContentResponseStatusMessage.cjs +3 -8
- package/lib/components/Dialog/DialogContentResponseStatusMessage.cjs.map +1 -1
- package/lib/components/Dialog/DialogContentResponseStatusMessage.d.ts +1 -1
- package/lib/components/Dialog/DialogContentResponseStatusMessage.js +3 -8
- package/lib/components/Dialog/DialogContentResponseStatusMessage.js.map +1 -1
- package/lib/components/Dialog/FocusTrap.cjs +4 -1
- package/lib/components/Dialog/FocusTrap.cjs.map +1 -1
- package/lib/components/Dialog/FocusTrap.js +4 -1
- package/lib/components/Dialog/FocusTrap.js.map +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +1 -1
- package/lib/components/DropZone/DropZone.cjs +1 -1
- package/lib/components/DropZone/DropZone.js +1 -1
- package/lib/components/Dropdown/Dropdown.cjs +36 -34
- package/lib/components/Dropdown/Dropdown.cjs.map +1 -1
- package/lib/components/Dropdown/Dropdown.js +37 -35
- package/lib/components/Dropdown/Dropdown.js.map +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +1 -1
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs +3 -5
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.cjs.map +1 -1
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.d.ts +1 -1
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js +3 -5
- package/lib/components/Dropdown/FilterDropdown/FilterDropdown.js.map +1 -1
- package/lib/components/Dropdown/SortDropdown/useSortDropdown.cjs +1 -1
- package/lib/components/Dropdown/SortDropdown/useSortDropdown.js +1 -1
- package/lib/components/ErrorScreen/AuthErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/AuthErrorScreen.js +1 -1
- package/lib/components/ErrorScreen/ForbiddenErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/ForbiddenErrorScreen.js +1 -1
- package/lib/components/ErrorScreen/NotFoundErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/NotFoundErrorScreen.js +1 -1
- package/lib/components/ErrorScreen/UnauthorizedErrorScreen.cjs +1 -1
- package/lib/components/ErrorScreen/UnauthorizedErrorScreen.js +1 -1
- package/lib/components/FormControl/FormControl.cjs +2 -22
- package/lib/components/FormControl/FormControl.cjs.map +1 -1
- package/lib/components/FormControl/FormControl.js +2 -22
- package/lib/components/FormControl/FormControl.js.map +1 -1
- package/lib/components/Header/AppLauncher/AppLauncher.cjs +1 -1
- package/lib/components/Header/AppLauncher/AppLauncher.js +1 -1
- package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.cjs +1 -1
- package/lib/components/Header/LanguageSwitcher/LanguageSwitcher.js +1 -1
- package/lib/components/Icon/OpenInNewTabIcon.cjs +1 -1
- package/lib/components/Icon/OpenInNewTabIcon.js +1 -1
- package/lib/components/InformationPanel/InformationPanel.cjs +1 -1
- package/lib/components/InformationPanel/InformationPanel.js +1 -1
- package/lib/components/Input/SearchInput/SearchInputIcon.cjs +1 -1
- package/lib/components/Input/SearchInput/SearchInputIcon.js +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.cjs +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.js +1 -1
- package/lib/components/InputFile/InputFileNative.cjs +1 -1
- package/lib/components/InputFile/InputFileNative.js +1 -1
- package/lib/components/Layout/Cluster/Cluster.cjs +0 -1
- package/lib/components/Layout/Cluster/Cluster.cjs.map +1 -1
- package/lib/components/Layout/Cluster/Cluster.js +0 -1
- package/lib/components/Layout/Cluster/Cluster.js.map +1 -1
- package/lib/components/Layout/Container/Container.cjs +1 -5
- package/lib/components/Layout/Container/Container.cjs.map +1 -1
- package/lib/components/Layout/Container/Container.js +1 -5
- package/lib/components/Layout/Container/Container.js.map +1 -1
- package/lib/components/Layout/Stack/Stack.cjs +22 -22
- package/lib/components/Layout/Stack/Stack.cjs.map +1 -1
- package/lib/components/Layout/Stack/Stack.js +22 -22
- package/lib/components/Layout/Stack/Stack.js.map +1 -1
- package/lib/components/Loader/LoaderSpinner.cjs +1 -1
- package/lib/components/Loader/LoaderSpinner.js +1 -1
- package/lib/components/Pagination/Pagination.cjs +1 -1
- package/lib/components/Pagination/Pagination.js +1 -1
- package/lib/components/Pagination/PaginationItemButton.cjs +1 -1
- package/lib/components/Pagination/PaginationItemButton.js +1 -1
- package/lib/components/SideNav/SideNav.cjs +3 -22
- package/lib/components/SideNav/SideNav.cjs.map +1 -1
- package/lib/components/SideNav/SideNav.d.ts +2 -9
- package/lib/components/SideNav/SideNav.js +4 -23
- package/lib/components/SideNav/SideNav.js.map +1 -1
- package/lib/components/SideNav/SideNavContext.cjs +11 -0
- package/lib/components/SideNav/SideNavContext.cjs.map +1 -0
- package/lib/components/SideNav/SideNavContext.d.ts +7 -0
- package/lib/components/SideNav/SideNavContext.js +8 -0
- package/lib/components/SideNav/SideNavContext.js.map +1 -0
- package/lib/components/SideNav/SideNavItemButton.cjs +12 -7
- package/lib/components/SideNav/SideNavItemButton.cjs.map +1 -1
- package/lib/components/SideNav/SideNavItemButton.d.ts +5 -11
- package/lib/components/SideNav/SideNavItemButton.js +12 -7
- package/lib/components/SideNav/SideNavItemButton.js.map +1 -1
- package/lib/components/Table/Th.cjs +2 -2
- package/lib/components/Table/Th.cjs.map +1 -1
- package/lib/components/Table/Th.d.ts +0 -2
- package/lib/components/Table/Th.js +2 -2
- package/lib/components/Table/Th.js.map +1 -1
- package/lib/components/Table/ThCheckbox.cjs +1 -1
- package/lib/components/Table/ThCheckbox.js +1 -1
- package/lib/components/Table/ThSortButton.cjs +5 -6
- package/lib/components/Table/ThSortButton.cjs.map +1 -1
- package/lib/components/Table/ThSortButton.d.ts +1 -6
- package/lib/components/Table/ThSortButton.js +5 -6
- package/lib/components/Table/ThSortButton.js.map +1 -1
- package/lib/components/WarekiPicker/WarekiPicker.cjs +1 -1
- package/lib/components/WarekiPicker/WarekiPicker.js +1 -1
- package/lib/index.cjs +1 -6
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +1 -4
- package/lib/index.js.map +1 -1
- package/lib/intl/Localizer.cjs +5 -1
- package/lib/intl/Localizer.cjs.map +1 -1
- package/lib/intl/Localizer.d.ts +1 -1
- package/lib/intl/Localizer.js +5 -1
- package/lib/intl/Localizer.js.map +1 -1
- package/metadata.json +1 -1
- package/package.json +1 -1
- package/smarthr-ui.css +0 -94
- package/lib/hooks/useDecorators.cjs +0 -18
- package/lib/hooks/useDecorators.cjs.map +0 -1
- package/lib/hooks/useDecorators.d.ts +0 -6
- package/lib/hooks/useDecorators.js +0 -16
- package/lib/hooks/useDecorators.js.map +0 -1
- package/lib/hooks/useDevice/DeviceProvider.cjs +0 -37
- package/lib/hooks/useDevice/DeviceProvider.cjs.map +0 -1
- package/lib/hooks/useDevice/DeviceProvider.d.ts +0 -4
- package/lib/hooks/useDevice/DeviceProvider.js +0 -35
- package/lib/hooks/useDevice/DeviceProvider.js.map +0 -1
- package/lib/hooks/useDevice/index.cjs +0 -10
- package/lib/hooks/useDevice/index.cjs.map +0 -1
- package/lib/hooks/useDevice/index.d.ts +0 -2
- package/lib/hooks/useDevice/index.js +0 -3
- package/lib/hooks/useDevice/index.js.map +0 -1
- package/lib/hooks/useDevice/useDevice.cjs +0 -11
- package/lib/hooks/useDevice/useDevice.cjs.map +0 -1
- package/lib/hooks/useDevice/useDevice.d.ts +0 -4
- package/lib/hooks/useDevice/useDevice.js +0 -8
- 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('
|
|
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
|
-
}, [
|
|
146
|
+
}, []),
|
|
105
147
|
isExpanded,
|
|
106
148
|
isLoading,
|
|
107
149
|
triggerRef: outerRef,
|
|
108
150
|
noResultText,
|
|
109
151
|
});
|
|
110
|
-
const selectDefaultItem = React.
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
208
|
-
const caretIconColor =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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({
|
|
276
|
+
clearButton: clearButton({
|
|
277
|
+
hidden: !hasSelectedItem || disabled || readOnly,
|
|
278
|
+
}),
|
|
234
279
|
clearButtonIcon: clearButtonIcon(),
|
|
235
280
|
};
|
|
236
|
-
}, [
|
|
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.
|
|
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,
|
|
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 '
|
|
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
|
-
}, [
|
|
144
|
+
}, []),
|
|
103
145
|
isExpanded,
|
|
104
146
|
isLoading,
|
|
105
147
|
triggerRef: outerRef,
|
|
106
148
|
noResultText,
|
|
107
149
|
});
|
|
108
|
-
const selectDefaultItem =
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
206
|
-
const caretIconColor =
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
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({
|
|
274
|
+
clearButton: clearButton({
|
|
275
|
+
hidden: !hasSelectedItem || disabled || readOnly,
|
|
276
|
+
}),
|
|
232
277
|
clearButtonIcon: clearButtonIcon(),
|
|
233
278
|
};
|
|
234
|
-
}, [
|
|
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:
|
|
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
|
|