smarthr-ui 96.1.3 → 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/helper.cjs +29 -13
- package/lib/components/Combobox/helper.cjs.map +1 -1
- package/lib/components/Combobox/helper.d.ts +1 -1
- package/lib/components/Combobox/helper.js +29 -13
- package/lib/components/Combobox/helper.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 +24 -18
- package/lib/components/Combobox/useOptions.cjs.map +1 -1
- package/lib/components/Combobox/useOptions.js +25 -19
- 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/DialogContentInner.cjs +8 -1
- package/lib/components/Dialog/DialogContentInner.cjs.map +1 -1
- package/lib/components/Dialog/DialogContentInner.js +9 -2
- package/lib/components/Dialog/DialogContentInner.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 +14 -12
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs.map +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +14 -12
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js.map +1 -1
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs +5 -2
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs.map +1 -1
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js +5 -2
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js.map +1 -1
- package/lib/components/Disclosure/DisclosureTrigger.cjs +39 -18
- package/lib/components/Disclosure/DisclosureTrigger.cjs.map +1 -1
- package/lib/components/Disclosure/DisclosureTrigger.js +40 -19
- package/lib/components/Disclosure/DisclosureTrigger.js.map +1 -1
- package/lib/components/Disclosure/useDisclosure.cjs +8 -14
- package/lib/components/Disclosure/useDisclosure.cjs.map +1 -1
- package/lib/components/Disclosure/useDisclosure.js +8 -14
- package/lib/components/Disclosure/useDisclosure.js.map +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 +5 -3
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs.map +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +5 -3
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js.map +1 -1
- package/lib/components/Dropdown/DropdownTrigger.cjs +3 -0
- package/lib/components/Dropdown/DropdownTrigger.cjs.map +1 -1
- package/lib/components/Dropdown/DropdownTrigger.js +3 -0
- package/lib/components/Dropdown/DropdownTrigger.js.map +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 +13 -18
- package/lib/components/InputFile/InputFileMultiplyAppendable.cjs.map +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.js +13 -18
- package/lib/components/InputFile/InputFileMultiplyAppendable.js.map +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/LineClamp/LineClamp.cjs +14 -3
- package/lib/components/LineClamp/LineClamp.cjs.map +1 -1
- package/lib/components/LineClamp/LineClamp.js +14 -3
- package/lib/components/LineClamp/LineClamp.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/Select/Select.cjs +11 -11
- package/lib/components/Select/Select.cjs.map +1 -1
- package/lib/components/Select/Select.js +12 -12
- package/lib/components/Select/Select.js.map +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/TabBar/TabItem.cjs +11 -9
- package/lib/components/TabBar/TabItem.cjs.map +1 -1
- package/lib/components/TabBar/TabItem.d.ts +2 -2
- package/lib/components/TabBar/TabItem.js +13 -11
- package/lib/components/TabBar/TabItem.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/hooks/useHandleEscape.cjs +4 -4
- package/lib/hooks/useHandleEscape.cjs.map +1 -1
- package/lib/hooks/useHandleEscape.d.ts +1 -1
- package/lib/hooks/useHandleEscape.js +4 -4
- package/lib/hooks/useHandleEscape.js.map +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 +4 -4
- package/lib/intl/Localizer.js +5 -1
- package/lib/intl/Localizer.js.map +1 -1
- package/lib/intl/locales/en_us.json.cjs +133 -0
- package/lib/intl/locales/en_us.json.cjs.map +1 -0
- package/lib/intl/locales/en_us.json.js +131 -0
- package/lib/intl/locales/en_us.json.js.map +1 -0
- package/lib/intl/locales/id_id.json.cjs +133 -0
- package/lib/intl/locales/id_id.json.cjs.map +1 -0
- package/lib/intl/locales/id_id.json.js +131 -0
- package/lib/intl/locales/id_id.json.js.map +1 -0
- package/lib/intl/locales/index.cjs +24 -18
- package/lib/intl/locales/index.cjs.map +1 -1
- package/lib/intl/locales/index.d.ts +1135 -1136
- package/lib/intl/locales/index.js +25 -19
- package/lib/intl/locales/index.js.map +1 -1
- package/lib/intl/locales/ja.json.cjs +133 -0
- package/lib/intl/locales/ja.json.cjs.map +1 -0
- package/lib/intl/locales/ja.json.js +131 -0
- package/lib/intl/locales/ja.json.js.map +1 -0
- package/lib/intl/locales/ja_easy.json.cjs +133 -0
- package/lib/intl/locales/ja_easy.json.cjs.map +1 -0
- package/lib/intl/locales/ja_easy.json.js +131 -0
- package/lib/intl/locales/ja_easy.json.js.map +1 -0
- package/lib/intl/locales/ko_kr.json.cjs +133 -0
- package/lib/intl/locales/ko_kr.json.cjs.map +1 -0
- package/lib/intl/locales/ko_kr.json.js +131 -0
- package/lib/intl/locales/ko_kr.json.js.map +1 -0
- package/lib/intl/locales/pt_br.json.cjs +133 -0
- package/lib/intl/locales/pt_br.json.cjs.map +1 -0
- package/lib/intl/locales/pt_br.json.js +131 -0
- package/lib/intl/locales/pt_br.json.js.map +1 -0
- package/lib/intl/locales/vi_vn.json.cjs +133 -0
- package/lib/intl/locales/vi_vn.json.cjs.map +1 -0
- package/lib/intl/locales/vi_vn.json.js +131 -0
- package/lib/intl/locales/vi_vn.json.js.map +1 -0
- package/lib/intl/locales/zh_hans_cn.json.cjs +133 -0
- package/lib/intl/locales/zh_hans_cn.json.cjs.map +1 -0
- package/lib/intl/locales/zh_hans_cn.json.js +131 -0
- package/lib/intl/locales/zh_hans_cn.json.js.map +1 -0
- package/lib/intl/locales/zh_hant_tw.json.cjs +133 -0
- package/lib/intl/locales/zh_hant_tw.json.cjs.map +1 -0
- package/lib/intl/locales/zh_hant_tw.json.js +131 -0
- package/lib/intl/locales/zh_hant_tw.json.js.map +1 -0
- package/lib/intl/useIntl.cjs.map +1 -1
- package/lib/intl/useIntl.d.ts +3 -3
- package/lib/intl/useIntl.js.map +1 -1
- package/metadata.json +1 -1
- package/package.json +2 -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
- package/lib/intl/locales/en_us.cjs +0 -135
- package/lib/intl/locales/en_us.cjs.map +0 -1
- package/lib/intl/locales/en_us.d.ts +0 -128
- package/lib/intl/locales/en_us.js +0 -133
- package/lib/intl/locales/en_us.js.map +0 -1
- package/lib/intl/locales/id_id.cjs +0 -135
- package/lib/intl/locales/id_id.cjs.map +0 -1
- package/lib/intl/locales/id_id.d.ts +0 -128
- package/lib/intl/locales/id_id.js +0 -133
- package/lib/intl/locales/id_id.js.map +0 -1
- package/lib/intl/locales/ja_easy.cjs +0 -135
- package/lib/intl/locales/ja_easy.cjs.map +0 -1
- package/lib/intl/locales/ja_easy.d.ts +0 -128
- package/lib/intl/locales/ja_easy.js +0 -133
- package/lib/intl/locales/ja_easy.js.map +0 -1
- package/lib/intl/locales/ko_kr.cjs +0 -135
- package/lib/intl/locales/ko_kr.cjs.map +0 -1
- package/lib/intl/locales/ko_kr.d.ts +0 -128
- package/lib/intl/locales/ko_kr.js +0 -133
- package/lib/intl/locales/ko_kr.js.map +0 -1
- package/lib/intl/locales/pt_br.cjs +0 -135
- package/lib/intl/locales/pt_br.cjs.map +0 -1
- package/lib/intl/locales/pt_br.d.ts +0 -128
- package/lib/intl/locales/pt_br.js +0 -133
- package/lib/intl/locales/pt_br.js.map +0 -1
- package/lib/intl/locales/vi_vn.cjs +0 -135
- package/lib/intl/locales/vi_vn.cjs.map +0 -1
- package/lib/intl/locales/vi_vn.d.ts +0 -128
- package/lib/intl/locales/vi_vn.js +0 -133
- package/lib/intl/locales/vi_vn.js.map +0 -1
- package/lib/intl/locales/zh_hans_cn.cjs +0 -135
- package/lib/intl/locales/zh_hans_cn.cjs.map +0 -1
- package/lib/intl/locales/zh_hans_cn.d.ts +0 -128
- package/lib/intl/locales/zh_hans_cn.js +0 -133
- package/lib/intl/locales/zh_hans_cn.js.map +0 -1
- package/lib/intl/locales/zh_hant_tw.cjs +0 -135
- package/lib/intl/locales/zh_hant_tw.cjs.map +0 -1
- package/lib/intl/locales/zh_hant_tw.d.ts +0 -128
- package/lib/intl/locales/zh_hant_tw.js +0 -133
- package/lib/intl/locales/zh_hant_tw.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FocusTrap.js","sources":["../../../src/components/Dialog/FocusTrap.tsx"],"sourcesContent":["import {\n type PropsWithChildren,\n type RefObject,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react'\n\nimport { tabbable } from '../../libs/tabbable'\n\ntype Props = PropsWithChildren<{\n firstFocusTarget?: RefObject<HTMLElement>\n}>\n\nexport type FocusTrapRef = {\n focus: () => void\n}\n\nexport const FocusTrap = forwardRef<FocusTrapRef, Props>(({ firstFocusTarget, children }, ref) => {\n const innerRef = useRef<HTMLDivElement | null>(null)\n const dummyFocusRef = useRef<HTMLDivElement>(null)\n\n useImperativeHandle(ref, () => ({\n focus: () => {\n if (firstFocusTarget?.current) {\n firstFocusTarget.current.focus()\n } else {\n dummyFocusRef.current?.focus()\n }\n },\n }))\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Tab' || innerRef.current === null) {\n return\n }\n\n const tabbables = tabbable(innerRef.current).filter((elm) => elm.tabIndex >= 0)\n\n if (tabbables.length === 0) {\n return\n }\n\n const firstTabbable = tabbables[0]\n const lastTabbable = tabbables[tabbables.length - 1]\n const currentFocused = tabbables.find((elm) => elm === e.target)\n\n if (e.shiftKey) {\n if (currentFocused === firstTabbable || document.activeElement === dummyFocusRef.current) {\n e.preventDefault()\n lastTabbable.focus()\n }\n } else if (currentFocused === lastTabbable) {\n e.preventDefault()\n firstTabbable.focus()\n }\n }\n\n window.addEventListener('keydown', handleKeyDown)\n\n return () => {\n window.removeEventListener('keydown', handleKeyDown)\n }\n }, [])\n\n useEffect(() => {\n const triggerElement = document.activeElement\n\n if (firstFocusTarget?.current) {\n firstFocusTarget.current.focus()\n } else {\n dummyFocusRef.current?.focus()\n }\n\n return () => {\n // フォーカストラップ終了時にトリガにフォーカスを戻す\n if (triggerElement instanceof HTMLElement) {\n triggerElement.focus()\n }\n }\n }, [firstFocusTarget])\n\n return (\n <div ref={innerRef}>\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div ref={dummyFocusRef} tabIndex={-1} />\n {children}\n </div>\n )\n})\n"],"names":["_jsxs","_jsx"],"mappings":";;;;AAmBO,MAAM,SAAS,GAAG,UAAU,CAAsB,CAAC,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAI;AAC/F,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAwB,IAAI,CAAC;AACpD,IAAA,MAAM,aAAa,GAAG,MAAM,CAAiB,IAAI,CAAC;AAElD,IAAA,mBAAmB,CAAC,GAAG,EAAE,OAAO;QAC9B,KAAK,EAAE,MAAK;AACV,YAAA,IAAI,gBAAgB,EAAE,OAAO,EAAE;AAC7B,gBAAA,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE;YAClC;iBAAO;AACL,gBAAA,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE;YAChC;QACF,CAAC;AACF,KAAA,CAAC,CAAC;IAEH,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,aAAa,GAAG,CAAC,CAAgB,KAAI
|
|
1
|
+
{"version":3,"file":"FocusTrap.js","sources":["../../../src/components/Dialog/FocusTrap.tsx"],"sourcesContent":["import {\n type PropsWithChildren,\n type RefObject,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react'\n\nimport { tabbable } from '../../libs/tabbable'\n\ntype Props = PropsWithChildren<{\n firstFocusTarget?: RefObject<HTMLElement>\n}>\n\nexport type FocusTrapRef = {\n focus: () => void\n}\n\nexport const FocusTrap = forwardRef<FocusTrapRef, Props>(({ firstFocusTarget, children }, ref) => {\n const innerRef = useRef<HTMLDivElement | null>(null)\n const dummyFocusRef = useRef<HTMLDivElement>(null)\n\n useImperativeHandle(ref, () => ({\n focus: () => {\n if (firstFocusTarget?.current) {\n firstFocusTarget.current.focus()\n } else {\n dummyFocusRef.current?.focus()\n }\n },\n }))\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n // IME 変換中の Tab は変換候補の選択に使われるため、フォーカストラップの対象外にする。\n // ここで preventDefault してしまうと、Dialog 内で日本語入力中に Tab を押しても\n // 変換候補が確定されず、未確定文字列がそのまま入力されてしまう。\n if (e.key !== 'Tab' || e.isComposing || innerRef.current === null) {\n return\n }\n\n const tabbables = tabbable(innerRef.current).filter((elm) => elm.tabIndex >= 0)\n\n if (tabbables.length === 0) {\n return\n }\n\n const firstTabbable = tabbables[0]\n const lastTabbable = tabbables[tabbables.length - 1]\n const currentFocused = tabbables.find((elm) => elm === e.target)\n\n if (e.shiftKey) {\n if (currentFocused === firstTabbable || document.activeElement === dummyFocusRef.current) {\n e.preventDefault()\n lastTabbable.focus()\n }\n } else if (currentFocused === lastTabbable) {\n e.preventDefault()\n firstTabbable.focus()\n }\n }\n\n window.addEventListener('keydown', handleKeyDown)\n\n return () => {\n window.removeEventListener('keydown', handleKeyDown)\n }\n }, [])\n\n useEffect(() => {\n const triggerElement = document.activeElement\n\n if (firstFocusTarget?.current) {\n firstFocusTarget.current.focus()\n } else {\n dummyFocusRef.current?.focus()\n }\n\n return () => {\n // フォーカストラップ終了時にトリガにフォーカスを戻す\n if (triggerElement instanceof HTMLElement) {\n triggerElement.focus()\n }\n }\n }, [firstFocusTarget])\n\n return (\n <div ref={innerRef}>\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div ref={dummyFocusRef} tabIndex={-1} />\n {children}\n </div>\n )\n})\n"],"names":["_jsxs","_jsx"],"mappings":";;;;AAmBO,MAAM,SAAS,GAAG,UAAU,CAAsB,CAAC,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAI;AAC/F,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAwB,IAAI,CAAC;AACpD,IAAA,MAAM,aAAa,GAAG,MAAM,CAAiB,IAAI,CAAC;AAElD,IAAA,mBAAmB,CAAC,GAAG,EAAE,OAAO;QAC9B,KAAK,EAAE,MAAK;AACV,YAAA,IAAI,gBAAgB,EAAE,OAAO,EAAE;AAC7B,gBAAA,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE;YAClC;iBAAO;AACL,gBAAA,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE;YAChC;QACF,CAAC;AACF,KAAA,CAAC,CAAC;IAEH,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,aAAa,GAAG,CAAC,CAAgB,KAAI;;;;AAIzC,YAAA,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE;gBACjE;YACF;YAEA,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC;AAE/E,YAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE;gBAC1B;YACF;AAEA,YAAA,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC;YAClC,MAAM,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;AACpD,YAAA,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC;AAEhE,YAAA,IAAI,CAAC,CAAC,QAAQ,EAAE;AACd,gBAAA,IAAI,cAAc,KAAK,aAAa,IAAI,QAAQ,CAAC,aAAa,KAAK,aAAa,CAAC,OAAO,EAAE;oBACxF,CAAC,CAAC,cAAc,EAAE;oBAClB,YAAY,CAAC,KAAK,EAAE;gBACtB;YACF;AAAO,iBAAA,IAAI,cAAc,KAAK,YAAY,EAAE;gBAC1C,CAAC,CAAC,cAAc,EAAE;gBAClB,aAAa,CAAC,KAAK,EAAE;YACvB;AACF,QAAA,CAAC;AAED,QAAA,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC;AAEjD,QAAA,OAAO,MAAK;AACV,YAAA,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC;AACtD,QAAA,CAAC;IACH,CAAC,EAAE,EAAE,CAAC;IAEN,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa;AAE7C,QAAA,IAAI,gBAAgB,EAAE,OAAO,EAAE;AAC7B,YAAA,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE;QAClC;aAAO;AACL,YAAA,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE;QAChC;AAEA,QAAA,OAAO,MAAK;;AAEV,YAAA,IAAI,cAAc,YAAY,WAAW,EAAE;gBACzC,cAAc,CAAC,KAAK,EAAE;YACxB;AACF,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAEtB,QACEA,cAAK,GAAG,EAAE,QAAQ,EAAA,QAAA,EAAA,CAEhBC,GAAA,CAAA,KAAA,EAAA,EAAK,GAAG,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,EAAA,CAAI,EACxC,QAAQ,CAAA,EAAA,CACL;AAEV,CAAC;;;;"}
|
|
@@ -8,7 +8,7 @@ var index = require('./../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss
|
|
|
8
8
|
var hooks_useHandleEscape = require('../../../hooks/useHandleEscape.cjs');
|
|
9
9
|
require('../../../intl/IntlProvider.cjs');
|
|
10
10
|
var intl_useIntl = require('../../../intl/useIntl.cjs');
|
|
11
|
-
require('
|
|
11
|
+
require('../../../intl/Localizer.cjs');
|
|
12
12
|
require('../../../_virtual/dayjs.min.cjs');
|
|
13
13
|
require('../../../intl/TimeFormatter.cjs');
|
|
14
14
|
var libs_debounce = require('../../../libs/debounce.cjs');
|
|
@@ -118,6 +118,9 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
118
118
|
});
|
|
119
119
|
debounceLiveRegionText(txt);
|
|
120
120
|
}, [localize, wrapperPosition, debounceLiveRegionText]);
|
|
121
|
+
// 外部propsをrefに保存
|
|
122
|
+
const unstableRef = React.useRef({ isOpen, onClickClose, onPressEscape });
|
|
123
|
+
unstableRef.current = { isOpen, onClickClose, onPressEscape };
|
|
121
124
|
const positionStyle = React.useMemo(() => ({
|
|
122
125
|
top: centering.top ?? top,
|
|
123
126
|
left: centering.left ?? left,
|
|
@@ -127,7 +130,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
127
130
|
height,
|
|
128
131
|
}), [centering, top, left, right, bottom, width, height, size]);
|
|
129
132
|
const handleArrowKey = React.useCallback((e) => {
|
|
130
|
-
if (!isOpen || document.activeElement !== e.currentTarget) {
|
|
133
|
+
if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
136
|
const movingDistance = 20;
|
|
@@ -161,7 +164,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
161
164
|
e.preventDefault();
|
|
162
165
|
break;
|
|
163
166
|
}
|
|
164
|
-
}, [
|
|
167
|
+
}, []);
|
|
165
168
|
React.useEffect(() => {
|
|
166
169
|
if (wrapperRef.current instanceof Element) {
|
|
167
170
|
setWrapperPosition(wrapperRef.current.getBoundingClientRect());
|
|
@@ -202,15 +205,14 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
202
205
|
}, [isOpen]);
|
|
203
206
|
const actualOnClickClose = React.useCallback((e) => {
|
|
204
207
|
lastFocusElementRef.current?.focus();
|
|
205
|
-
onClickClose?.(e);
|
|
206
|
-
}, [
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
hooks_useHandleEscape.useHandleEscape(React.useMemo(() => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined), [isOpen, actualOnPressEscape]));
|
|
208
|
+
unstableRef.current.onClickClose?.(e);
|
|
209
|
+
}, []);
|
|
210
|
+
// stableなcallbackを作成
|
|
211
|
+
const memoizedOnPressEscape = React.useCallback(() => {
|
|
212
|
+
lastFocusElementRef.current?.focus();
|
|
213
|
+
unstableRef.current.onPressEscape?.();
|
|
214
|
+
}, []);
|
|
215
|
+
hooks_useHandleEscape.useHandleEscape(isOpen ? memoizedOnPressEscape : undefined);
|
|
214
216
|
React.useEffect(() => {
|
|
215
217
|
const focusHandler = (e) => {
|
|
216
218
|
// e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelessDialog.cjs","sources":["../../../../src/components/Dialog/ModelessDialog/ModelessDialog.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n type RefObject,\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Draggable from 'react-draggable'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../../hooks/useHandleEscape'\nimport { useIntl } from '../../../intl'\nimport { debounce } from '../../../libs/debounce'\nimport { dialogSize } from '../../../tailwind'\nimport { Base, type BaseElementProps } from '../../Base'\nimport { Button } from '../../Button'\nimport { Heading } from '../../Heading'\nimport { FaGripIcon, FaXmarkIcon } from '../../Icon'\nimport { DialogBody, type Props as DialogBodyProps } from '../DialogBody'\nimport { DialogOverlap } from '../DialogOverlap'\nimport { useDialogPortal } from '../useDialogPortal'\n\nimport type { DialogSize } from '../types'\n\ntype AbstractProps = PropsWithChildren<{\n /**\n * ダイアログのタイトルの内容\n */\n heading: ReactNode\n /**\n * ダイアログのフッタ部分の内容\n */\n footer?: ReactNode\n /**\n * ダイアログが開かれているかどうかの真偽値\n */\n isOpen: boolean\n /**\n * 閉じるボタンを押下したときのハンドラ\n */\n onClickClose?: (e: MouseEvent<HTMLButtonElement>) => void\n /**\n * ダイアログが開いている状態で Escape キーを押下したときのハンドラ\n */\n onPressEscape?: () => void\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの高さ\n */\n height?: string | number\n /**\n * ダイアログを開いたときの初期 top 位置\n */\n top?: string | number\n /**\n * ダイアログを開いたときの初期 left 位置\n */\n left?: string | number\n /**\n * ダイアログを開いたときの初期 right 位置\n */\n right?: string | number\n /**\n * ダイアログを開いたときの初期 bottom 位置\n */\n bottom?: string | number\n /**\n * ポータルの container となる DOM 要素を追加する親要素\n */\n portalParent?: HTMLElement | RefObject<HTMLElement>\n}>\ntype Props = AbstractProps &\n Omit<DialogBodyProps, keyof AbstractProps> &\n Omit<BaseElementProps, keyof AbstractProps> &\n Omit<VariantProps<typeof classNameGenerator>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n overlap: 'shr-inset-[unset]',\n wrapper: 'smarthr-ui-ModelessDialog shr-fixed shr-flex shr-flex-col',\n headerEl: [\n 'smarthr-ui-ModelessDialog-header shr-border-b-shorthand shr-relative shr-flex shr-cursor-move shr-items-center shr-rounded-tl-l shr-rounded-tr-l shr-pe-1 shr-ps-1.5',\n 'hover:shr-bg-white-darken',\n /* DialogHandlerにフォーカスが当たっているときは、headerもフォーカス状態のスタイルにする。 */\n 'has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-focus-indicator has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-bg-white-darken has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-transition-colors has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-duration-100 has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-ease-in-out',\n ],\n dialogHandler: [\n 'smarthr-ui-ModelessDialog-handle shr-absolute shr-inset-x-0 shr-bottom-0 shr-top-[2px] shr-m-auto shr-flex shr-justify-center shr-rounded-tl-s shr-rounded-tr-s shr-border-none shr-text-grey shr-transition-colors shr-duration-100 shr-ease-in-out',\n 'focus-visible:shr-focus-indicator--none shr-cursor-[inherit] shr-bg-[unset]',\n ],\n headingEl: ['shr-my-1 shr-me-1'],\n closeButtonLayout: [\n 'shr-relative' /* DialogHandlerの上に出すためにスタッキングコンテキストを生成 */,\n 'shr-ml-auto shr-shrink-0',\n ],\n footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand',\n },\n variants: {\n size: {\n XS: { wrapper: dialogSize.XS },\n S: { wrapper: dialogSize.S },\n M: { wrapper: dialogSize.M },\n L: { wrapper: dialogSize.L },\n XL: { wrapper: dialogSize.XL },\n XXL: { wrapper: dialogSize.XXL },\n FULL: { wrapper: dialogSize.FULL },\n },\n resizable: {\n true: {\n wrapper: 'shr-resize shr-overflow-auto',\n },\n false: {},\n },\n },\n})\n\nexport const ModelessDialog: FC<Props> = ({\n heading,\n children,\n contentBgColor,\n contentPadding,\n footer,\n isOpen,\n onPressEscape,\n resizable = false,\n width,\n size,\n height,\n top,\n left,\n right,\n bottom,\n portalParent,\n className,\n id,\n onClickClose,\n ...rest\n}) => {\n const labelId = useId()\n const lastFocusElementRef = useRef<HTMLElement | null>(null)\n const { createPortal } = useDialogPortal(portalParent, id)\n const { localize } = useIntl()\n\n const classNames = useMemo(() => {\n const { overlap, wrapper, headerEl, headingEl, dialogHandler, closeButtonLayout, footerEl } =\n classNameGenerator()\n\n return {\n overlap: overlap({ className }),\n wrapper: wrapper({ size, resizable }),\n header: headerEl(),\n heading: headingEl(),\n dialogHandler: dialogHandler(),\n closeButtonLayout: closeButtonLayout(),\n footer: footerEl(),\n }\n }, [className, size, resizable])\n\n const wrapperRef = useRef<HTMLDivElement>(null)\n const focusTargetRef = useRef<HTMLDivElement>(null)\n\n const [wrapperPosition, setWrapperPosition] = useState<DOMRect | undefined>(undefined)\n const [debouncedLiveRegionText, setDebouncedLiveRegionText] = useState<string>('')\n const [centering, setCentering] = useState<{\n top?: number\n left?: number\n }>({})\n const [position, setPosition] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n })\n const [draggableBounds, setDraggableBounds] =\n useState<ComponentProps<typeof Draggable>['bounds']>()\n const debounceLiveRegionText = useMemo(() => debounce(setDebouncedLiveRegionText, 600), [])\n\n useEffect(() => {\n if (!wrapperPosition) {\n setDebouncedLiveRegionText('')\n return\n }\n\n const txt = localize(\n {\n id: 'smarthr-ui/ModelessDialog/dialogHandlerLiveRegionText',\n defaultText: '上から{top}px、左から{left}px',\n },\n {\n top: Math.trunc(wrapperPosition.top).toString(),\n left: Math.trunc(wrapperPosition.left).toString(),\n },\n )\n\n debounceLiveRegionText(txt)\n }, [localize, wrapperPosition, debounceLiveRegionText])\n\n const positionStyle = useMemo(\n () => ({\n top: centering.top ?? top,\n left: centering.left ?? left,\n right,\n bottom,\n width: size ? undefined : width,\n height,\n }),\n [centering, top, left, right, bottom, width, height, size],\n )\n\n const handleArrowKey = useCallback(\n (e: KeyboardEvent) => {\n if (!isOpen || document.activeElement !== e.currentTarget) {\n return\n }\n\n const movingDistance = 20\n\n switch (e.key) {\n case 'ArrowUp':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y - movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowDown':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y + movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowLeft':\n setPosition((prev) => ({\n x: prev.x - movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n case 'ArrowRight':\n setPosition((prev) => ({\n x: prev.x + movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n }\n },\n [isOpen],\n )\n\n useEffect(() => {\n if (wrapperRef.current instanceof Element) {\n setWrapperPosition(wrapperRef.current.getBoundingClientRect())\n }\n }, [position])\n\n useEffect(() => {\n // 中央寄せの座標計算を行う\n if (!wrapperRef.current || !isOpen) {\n return\n }\n\n const isXCenter = left === undefined && right === undefined\n const isYCenter = top === undefined && bottom === undefined\n\n if (isXCenter || isYCenter) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setCentering({\n top: isYCenter ? window.innerHeight / 2 - rect.height / 2 : undefined,\n left: isXCenter ? window.innerWidth / 2 - rect.width / 2 : undefined,\n })\n }\n }, [bottom, isOpen, left, right, top])\n\n useEffect(() => {\n if (!isOpen) return\n\n if (centering.top) {\n setDraggableBounds({ top: centering.top * -1 })\n\n return\n }\n\n if (wrapperRef.current) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setDraggableBounds({ top: rect.top * -1 })\n }\n }, [isOpen, centering.top])\n\n useEffect(() => {\n if (isOpen) {\n setPosition({ x: 0, y: 0 })\n focusTargetRef.current?.focus()\n }\n }, [isOpen])\n\n const actualOnClickClose = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n lastFocusElementRef.current?.focus()\n onClickClose?.(e)\n },\n [onClickClose],\n )\n\n const actualOnPressEscape = useMemo(\n () =>\n onPressEscape\n ? () => {\n lastFocusElementRef.current?.focus()\n onPressEscape()\n }\n : undefined,\n [onPressEscape],\n )\n\n useHandleEscape(\n useMemo(\n () => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined),\n [isOpen, actualOnPressEscape],\n ),\n )\n\n useEffect(() => {\n const focusHandler = (e: FocusEvent) => {\n // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する\n if (e.target instanceof HTMLElement && !wrapperRef?.current?.contains(e.target)) {\n lastFocusElementRef.current = e.target\n }\n }\n\n document.addEventListener('focus', focusHandler, true)\n\n return () => document.removeEventListener('focus', focusHandler, true)\n }, [])\n\n const onDragStart = useCallback((_: any, data: { x: number; y: number }) => setPosition(data), [])\n const onDrag = useCallback((_: any, data: { deltaX: number; deltaY: number }) => {\n setPosition((prev) => ({\n x: prev.x + data.deltaX,\n y: prev.y + data.deltaY,\n }))\n }, [])\n\n return createPortal(\n <DialogOverlap isOpen={isOpen} className={classNames.overlap} as=\"section\">\n <Draggable\n handle=\".smarthr-ui-ModelessDialog-handle\"\n onStart={onDragStart}\n onDrag={onDrag}\n position={position}\n bounds={draggableBounds}\n nodeRef={wrapperRef}\n >\n <Base\n {...rest}\n ref={wrapperRef}\n role=\"dialog\"\n aria-labelledby={labelId}\n radius=\"m\"\n layer={3}\n overflow=\"auto\"\n className={classNames.wrapper}\n style={positionStyle}\n >\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div tabIndex={-1} ref={focusTargetRef} />\n <div className={classNames.header}>\n <Handler onArrowKeyDown={handleArrowKey} className={classNames.dialogHandler} />\n <div id={labelId} className={classNames.heading}>\n {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */}\n <Heading>{heading}</Heading>\n </div>\n <CloseButton onClick={actualOnClickClose} className={classNames.closeButtonLayout} />\n </div>\n <DialogBody\n contentBgColor={contentBgColor}\n contentPadding={contentPadding}\n className=\"smarthr-ui-ModelessDialog-content shr-overscroll-contain\"\n >\n {children}\n </DialogBody>\n {footer && <div className={classNames.footer}>{footer}</div>}\n <LiveRegion regionText={debouncedLiveRegionText} />\n </Base>\n </Draggable>\n </DialogOverlap>,\n )\n}\n\nconst Handler = memo<{\n className: string\n onArrowKeyDown: (e: KeyboardEvent) => void\n}>(({ onArrowKeyDown: onDelegateKeyDown, ...rest }) => {\n const { localize } = useIntl()\n const accessibleDefaultTexts = useMemo(\n () => ({\n dialogHandlerAriaRoleDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaRoleDescription',\n defaultText: 'ドラッグ可能',\n }),\n dialogHandlerDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerDescription',\n defaultText: '矢印キーを押して上下左右に移動できます',\n }),\n dialogHandlerAriaLabel: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaLabel',\n defaultText: 'ダイアログの位置',\n }),\n }),\n [localize],\n )\n\n return (\n <>\n <button\n {...rest}\n type=\"button\"\n aria-label={accessibleDefaultTexts.dialogHandlerAriaLabel}\n aria-roledescription={accessibleDefaultTexts.dialogHandlerAriaRoleDescription}\n aria-describedby=\"handler-description\"\n onKeyDown={onDelegateKeyDown}\n >\n <FaGripIcon />\n </button>\n <div className=\"shr-hidden\" id=\"handler-description\">\n {accessibleDefaultTexts.dialogHandlerDescription}\n </div>\n </>\n )\n})\n\nconst LiveRegion = ({ regionText }: { regionText: string | undefined }) => (\n <div\n role=\"status\"\n className=\"shr-fixed -shr-m-px shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap\"\n >\n {regionText}\n </div>\n)\n\nconst CloseButton = memo<{\n className: string\n onClick: (e: MouseEvent<HTMLButtonElement>) => void\n}>(({ onClick, className }) => {\n const { localize } = useIntl()\n const closeButtonIconAlt = localize({\n id: 'smarthr-ui/ModelessDialog/closeButtonIconAlt',\n defaultText: '閉じる',\n })\n\n return (\n <div className={className}>\n <Button\n type=\"button\"\n size=\"S\"\n onClick={onClick}\n className=\"smarthr-ui-ModelessDialog-closeButton\"\n >\n <FaXmarkIcon alt={closeButtonIconAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA;AACE;AACE;AACA;AACA;;;;;AAKC;AACD;;;AAGC;;AAED;AACE;;AAED;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACD;AACD;AACD;AACF;AACF;;AAwBC;AACA;;AAEA;AAEA;AACE;;AAIE;;;;;;;;;AAUJ;AACA;;;;AAQA;AACE;AACA;AACD;;AAGD;;;;;;;AAUM;AACA;;;;AAKD;;;AAML;AAEI;AACA;;;;;AAKD;AAIH;;;;;AAQI;AACE;AACE;;AAEE;AACD;;;AAGH;AACE;;AAEE;AACD;;;AAGH;AACE;AACE;;AAED;;;AAGH;AACE;AACE;;AAED;;;;AAIP;;AAKA;;;AAGF;;;;;;;;AAWE;;AAGE;AACE;AACA;AACD;;AAEL;;AAGE;;AAEA;AACE;;;AAKF;;AAGE;;;;;;AAOA;;AAEJ;AAEA;AAEI;AACA;AACF;AAIF;;AAIU;AACA;;AAEJ;;;AAYJ;;AAEE;AACE;;AAEJ;;AAIA;;AAGF;;AAEE;AACE;AACA;AACD;;;AA+CL;AAEA;AAIE;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AAiBF;AAEA;AASA;AAIE;;AAEE;AACA;AACD;AAED;AAYF;;"}
|
|
1
|
+
{"version":3,"file":"ModelessDialog.cjs","sources":["../../../../src/components/Dialog/ModelessDialog/ModelessDialog.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n type RefObject,\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Draggable from 'react-draggable'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../../hooks/useHandleEscape'\nimport { useIntl } from '../../../intl'\nimport { debounce } from '../../../libs/debounce'\nimport { dialogSize } from '../../../tailwind'\nimport { Base, type BaseElementProps } from '../../Base'\nimport { Button } from '../../Button'\nimport { Heading } from '../../Heading'\nimport { FaGripIcon, FaXmarkIcon } from '../../Icon'\nimport { DialogBody, type Props as DialogBodyProps } from '../DialogBody'\nimport { DialogOverlap } from '../DialogOverlap'\nimport { useDialogPortal } from '../useDialogPortal'\n\nimport type { DialogSize } from '../types'\n\ntype AbstractProps = PropsWithChildren<{\n /**\n * ダイアログのタイトルの内容\n */\n heading: ReactNode\n /**\n * ダイアログのフッタ部分の内容\n */\n footer?: ReactNode\n /**\n * ダイアログが開かれているかどうかの真偽値\n */\n isOpen: boolean\n /**\n * 閉じるボタンを押下したときのハンドラ\n */\n onClickClose?: (e: MouseEvent<HTMLButtonElement>) => void\n /**\n * ダイアログが開いている状態で Escape キーを押下したときのハンドラ\n */\n onPressEscape?: () => void\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの高さ\n */\n height?: string | number\n /**\n * ダイアログを開いたときの初期 top 位置\n */\n top?: string | number\n /**\n * ダイアログを開いたときの初期 left 位置\n */\n left?: string | number\n /**\n * ダイアログを開いたときの初期 right 位置\n */\n right?: string | number\n /**\n * ダイアログを開いたときの初期 bottom 位置\n */\n bottom?: string | number\n /**\n * ポータルの container となる DOM 要素を追加する親要素\n */\n portalParent?: HTMLElement | RefObject<HTMLElement>\n}>\ntype Props = AbstractProps &\n Omit<DialogBodyProps, keyof AbstractProps> &\n Omit<BaseElementProps, keyof AbstractProps> &\n Omit<VariantProps<typeof classNameGenerator>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n overlap: 'shr-inset-[unset]',\n wrapper: 'smarthr-ui-ModelessDialog shr-fixed shr-flex shr-flex-col',\n headerEl: [\n 'smarthr-ui-ModelessDialog-header shr-border-b-shorthand shr-relative shr-flex shr-cursor-move shr-items-center shr-rounded-tl-l shr-rounded-tr-l shr-pe-1 shr-ps-1.5',\n 'hover:shr-bg-white-darken',\n /* DialogHandlerにフォーカスが当たっているときは、headerもフォーカス状態のスタイルにする。 */\n 'has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-focus-indicator has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-bg-white-darken has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-transition-colors has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-duration-100 has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-ease-in-out',\n ],\n dialogHandler: [\n 'smarthr-ui-ModelessDialog-handle shr-absolute shr-inset-x-0 shr-bottom-0 shr-top-[2px] shr-m-auto shr-flex shr-justify-center shr-rounded-tl-s shr-rounded-tr-s shr-border-none shr-text-grey shr-transition-colors shr-duration-100 shr-ease-in-out',\n 'focus-visible:shr-focus-indicator--none shr-cursor-[inherit] shr-bg-[unset]',\n ],\n headingEl: ['shr-my-1 shr-me-1'],\n closeButtonLayout: [\n 'shr-relative' /* DialogHandlerの上に出すためにスタッキングコンテキストを生成 */,\n 'shr-ml-auto shr-shrink-0',\n ],\n footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand',\n },\n variants: {\n size: {\n XS: { wrapper: dialogSize.XS },\n S: { wrapper: dialogSize.S },\n M: { wrapper: dialogSize.M },\n L: { wrapper: dialogSize.L },\n XL: { wrapper: dialogSize.XL },\n XXL: { wrapper: dialogSize.XXL },\n FULL: { wrapper: dialogSize.FULL },\n },\n resizable: {\n true: {\n wrapper: 'shr-resize shr-overflow-auto',\n },\n false: {},\n },\n },\n})\n\nexport const ModelessDialog: FC<Props> = ({\n heading,\n children,\n contentBgColor,\n contentPadding,\n footer,\n isOpen,\n onPressEscape,\n resizable = false,\n width,\n size,\n height,\n top,\n left,\n right,\n bottom,\n portalParent,\n className,\n id,\n onClickClose,\n ...rest\n}) => {\n const labelId = useId()\n const lastFocusElementRef = useRef<HTMLElement | null>(null)\n const { createPortal } = useDialogPortal(portalParent, id)\n const { localize } = useIntl()\n\n const classNames = useMemo(() => {\n const { overlap, wrapper, headerEl, headingEl, dialogHandler, closeButtonLayout, footerEl } =\n classNameGenerator()\n\n return {\n overlap: overlap({ className }),\n wrapper: wrapper({ size, resizable }),\n header: headerEl(),\n heading: headingEl(),\n dialogHandler: dialogHandler(),\n closeButtonLayout: closeButtonLayout(),\n footer: footerEl(),\n }\n }, [className, size, resizable])\n\n const wrapperRef = useRef<HTMLDivElement>(null)\n const focusTargetRef = useRef<HTMLDivElement>(null)\n\n const [wrapperPosition, setWrapperPosition] = useState<DOMRect | undefined>(undefined)\n const [debouncedLiveRegionText, setDebouncedLiveRegionText] = useState<string>('')\n const [centering, setCentering] = useState<{\n top?: number\n left?: number\n }>({})\n const [position, setPosition] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n })\n const [draggableBounds, setDraggableBounds] =\n useState<ComponentProps<typeof Draggable>['bounds']>()\n const debounceLiveRegionText = useMemo(() => debounce(setDebouncedLiveRegionText, 600), [])\n\n useEffect(() => {\n if (!wrapperPosition) {\n setDebouncedLiveRegionText('')\n return\n }\n\n const txt = localize(\n {\n id: 'smarthr-ui/ModelessDialog/dialogHandlerLiveRegionText',\n defaultText: '上から{top}px、左から{left}px',\n },\n {\n top: Math.trunc(wrapperPosition.top).toString(),\n left: Math.trunc(wrapperPosition.left).toString(),\n },\n )\n\n debounceLiveRegionText(txt)\n }, [localize, wrapperPosition, debounceLiveRegionText])\n\n // 外部propsをrefに保存\n const unstableRef = useRef({ isOpen, onClickClose, onPressEscape })\n unstableRef.current = { isOpen, onClickClose, onPressEscape }\n\n const positionStyle = useMemo(\n () => ({\n top: centering.top ?? top,\n left: centering.left ?? left,\n right,\n bottom,\n width: size ? undefined : width,\n height,\n }),\n [centering, top, left, right, bottom, width, height, size],\n )\n\n const handleArrowKey = useCallback((e: KeyboardEvent) => {\n if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {\n return\n }\n\n const movingDistance = 20\n\n switch (e.key) {\n case 'ArrowUp':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y - movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowDown':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y + movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowLeft':\n setPosition((prev) => ({\n x: prev.x - movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n case 'ArrowRight':\n setPosition((prev) => ({\n x: prev.x + movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n }\n }, [])\n\n useEffect(() => {\n if (wrapperRef.current instanceof Element) {\n setWrapperPosition(wrapperRef.current.getBoundingClientRect())\n }\n }, [position])\n\n useEffect(() => {\n // 中央寄せの座標計算を行う\n if (!wrapperRef.current || !isOpen) {\n return\n }\n\n const isXCenter = left === undefined && right === undefined\n const isYCenter = top === undefined && bottom === undefined\n\n if (isXCenter || isYCenter) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setCentering({\n top: isYCenter ? window.innerHeight / 2 - rect.height / 2 : undefined,\n left: isXCenter ? window.innerWidth / 2 - rect.width / 2 : undefined,\n })\n }\n }, [bottom, isOpen, left, right, top])\n\n useEffect(() => {\n if (!isOpen) return\n\n if (centering.top) {\n setDraggableBounds({ top: centering.top * -1 })\n\n return\n }\n\n if (wrapperRef.current) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setDraggableBounds({ top: rect.top * -1 })\n }\n }, [isOpen, centering.top])\n\n useEffect(() => {\n if (isOpen) {\n setPosition({ x: 0, y: 0 })\n focusTargetRef.current?.focus()\n }\n }, [isOpen])\n\n const actualOnClickClose = useCallback((e: MouseEvent<HTMLButtonElement>) => {\n lastFocusElementRef.current?.focus()\n unstableRef.current.onClickClose?.(e)\n }, [])\n\n // stableなcallbackを作成\n const memoizedOnPressEscape = useCallback(() => {\n lastFocusElementRef.current?.focus()\n unstableRef.current.onPressEscape?.()\n }, [])\n\n useHandleEscape(isOpen ? memoizedOnPressEscape : undefined)\n\n useEffect(() => {\n const focusHandler = (e: FocusEvent) => {\n // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する\n if (e.target instanceof HTMLElement && !wrapperRef?.current?.contains(e.target)) {\n lastFocusElementRef.current = e.target\n }\n }\n\n document.addEventListener('focus', focusHandler, true)\n\n return () => document.removeEventListener('focus', focusHandler, true)\n }, [])\n\n const onDragStart = useCallback((_: any, data: { x: number; y: number }) => setPosition(data), [])\n const onDrag = useCallback((_: any, data: { deltaX: number; deltaY: number }) => {\n setPosition((prev) => ({\n x: prev.x + data.deltaX,\n y: prev.y + data.deltaY,\n }))\n }, [])\n\n return createPortal(\n <DialogOverlap isOpen={isOpen} className={classNames.overlap} as=\"section\">\n <Draggable\n handle=\".smarthr-ui-ModelessDialog-handle\"\n onStart={onDragStart}\n onDrag={onDrag}\n position={position}\n bounds={draggableBounds}\n nodeRef={wrapperRef}\n >\n <Base\n {...rest}\n ref={wrapperRef}\n role=\"dialog\"\n aria-labelledby={labelId}\n radius=\"m\"\n layer={3}\n overflow=\"auto\"\n className={classNames.wrapper}\n style={positionStyle}\n >\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div tabIndex={-1} ref={focusTargetRef} />\n <div className={classNames.header}>\n <Handler onArrowKeyDown={handleArrowKey} className={classNames.dialogHandler} />\n <div id={labelId} className={classNames.heading}>\n {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */}\n <Heading>{heading}</Heading>\n </div>\n <CloseButton onClick={actualOnClickClose} className={classNames.closeButtonLayout} />\n </div>\n <DialogBody\n contentBgColor={contentBgColor}\n contentPadding={contentPadding}\n className=\"smarthr-ui-ModelessDialog-content shr-overscroll-contain\"\n >\n {children}\n </DialogBody>\n {footer && <div className={classNames.footer}>{footer}</div>}\n <LiveRegion regionText={debouncedLiveRegionText} />\n </Base>\n </Draggable>\n </DialogOverlap>,\n )\n}\n\nconst Handler = memo<{\n className: string\n onArrowKeyDown: (e: KeyboardEvent) => void\n}>(({ onArrowKeyDown: onDelegateKeyDown, ...rest }) => {\n const { localize } = useIntl()\n const accessibleDefaultTexts = useMemo(\n () => ({\n dialogHandlerAriaRoleDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaRoleDescription',\n defaultText: 'ドラッグ可能',\n }),\n dialogHandlerDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerDescription',\n defaultText: '矢印キーを押して上下左右に移動できます',\n }),\n dialogHandlerAriaLabel: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaLabel',\n defaultText: 'ダイアログの位置',\n }),\n }),\n [localize],\n )\n\n return (\n <>\n <button\n {...rest}\n type=\"button\"\n aria-label={accessibleDefaultTexts.dialogHandlerAriaLabel}\n aria-roledescription={accessibleDefaultTexts.dialogHandlerAriaRoleDescription}\n aria-describedby=\"handler-description\"\n onKeyDown={onDelegateKeyDown}\n >\n <FaGripIcon />\n </button>\n <div className=\"shr-hidden\" id=\"handler-description\">\n {accessibleDefaultTexts.dialogHandlerDescription}\n </div>\n </>\n )\n})\n\nconst LiveRegion = ({ regionText }: { regionText: string | undefined }) => (\n <div\n role=\"status\"\n className=\"shr-fixed -shr-m-px shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap\"\n >\n {regionText}\n </div>\n)\n\nconst CloseButton = memo<{\n className: string\n onClick: (e: MouseEvent<HTMLButtonElement>) => void\n}>(({ onClick, className }) => {\n const { localize } = useIntl()\n const closeButtonIconAlt = localize({\n id: 'smarthr-ui/ModelessDialog/closeButtonIconAlt',\n defaultText: '閉じる',\n })\n\n return (\n <div className={className}>\n <Button\n type=\"button\"\n size=\"S\"\n onClick={onClick}\n className=\"smarthr-ui-ModelessDialog-closeButton\"\n >\n <FaXmarkIcon alt={closeButtonIconAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA;AACE;AACE;AACA;AACA;;;;;AAKC;AACD;;;AAGC;;AAED;AACE;;AAED;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACD;AACD;AACD;AACF;AACF;;AAwBC;AACA;;AAEA;AAEA;AACE;;AAIE;;;;;;;;;AAUJ;AACA;;;;AAQA;AACE;AACA;AACD;;AAGD;;;;;;;AAUM;AACA;;;;AAKD;;;;AAOL;;AAGA;AAEI;AACA;;;;;AAKD;AAIH;AACE;;;;AAMA;AACE;AACE;;AAEE;AACD;;;AAGH;AACE;;AAEE;AACD;;;AAGH;AACE;AACE;;AAED;;;AAGH;AACE;AACE;;AAED;;;;;;AAOL;;;AAGF;;;;;;;;AAWE;;AAGE;AACE;AACA;AACD;;AAEL;;AAGE;;AAEA;AACE;;;AAKF;;AAGE;;;;;;AAOA;;AAEJ;AAEA;AACE;;;;AAKF;AACE;AACA;;;;AAMA;;AAEE;AACE;;AAEJ;;AAIA;;AAGF;;AAEE;AACE;AACA;AACD;;;AA+CL;AAEA;AAIE;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AAiBF;AAEA;AASA;AAIE;;AAEE;AACA;AACD;AAED;AAYF;;"}
|
|
@@ -6,7 +6,7 @@ import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwi
|
|
|
6
6
|
import { useHandleEscape } from '../../../hooks/useHandleEscape.js';
|
|
7
7
|
import '../../../intl/IntlProvider.js';
|
|
8
8
|
import { useIntl } from '../../../intl/useIntl.js';
|
|
9
|
-
import '
|
|
9
|
+
import '../../../intl/Localizer.js';
|
|
10
10
|
import '../../../_virtual/dayjs.min.js';
|
|
11
11
|
import '../../../intl/TimeFormatter.js';
|
|
12
12
|
import { debounce } from '../../../libs/debounce.js';
|
|
@@ -116,6 +116,9 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
116
116
|
});
|
|
117
117
|
debounceLiveRegionText(txt);
|
|
118
118
|
}, [localize, wrapperPosition, debounceLiveRegionText]);
|
|
119
|
+
// 外部propsをrefに保存
|
|
120
|
+
const unstableRef = useRef({ isOpen, onClickClose, onPressEscape });
|
|
121
|
+
unstableRef.current = { isOpen, onClickClose, onPressEscape };
|
|
119
122
|
const positionStyle = useMemo(() => ({
|
|
120
123
|
top: centering.top ?? top,
|
|
121
124
|
left: centering.left ?? left,
|
|
@@ -125,7 +128,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
125
128
|
height,
|
|
126
129
|
}), [centering, top, left, right, bottom, width, height, size]);
|
|
127
130
|
const handleArrowKey = useCallback((e) => {
|
|
128
|
-
if (!isOpen || document.activeElement !== e.currentTarget) {
|
|
131
|
+
if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {
|
|
129
132
|
return;
|
|
130
133
|
}
|
|
131
134
|
const movingDistance = 20;
|
|
@@ -159,7 +162,7 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
159
162
|
e.preventDefault();
|
|
160
163
|
break;
|
|
161
164
|
}
|
|
162
|
-
}, [
|
|
165
|
+
}, []);
|
|
163
166
|
useEffect(() => {
|
|
164
167
|
if (wrapperRef.current instanceof Element) {
|
|
165
168
|
setWrapperPosition(wrapperRef.current.getBoundingClientRect());
|
|
@@ -200,15 +203,14 @@ const ModelessDialog = ({ heading, children, contentBgColor, contentPadding, foo
|
|
|
200
203
|
}, [isOpen]);
|
|
201
204
|
const actualOnClickClose = useCallback((e) => {
|
|
202
205
|
lastFocusElementRef.current?.focus();
|
|
203
|
-
onClickClose?.(e);
|
|
204
|
-
}, [
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
useHandleEscape(useMemo(() => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined), [isOpen, actualOnPressEscape]));
|
|
206
|
+
unstableRef.current.onClickClose?.(e);
|
|
207
|
+
}, []);
|
|
208
|
+
// stableなcallbackを作成
|
|
209
|
+
const memoizedOnPressEscape = useCallback(() => {
|
|
210
|
+
lastFocusElementRef.current?.focus();
|
|
211
|
+
unstableRef.current.onPressEscape?.();
|
|
212
|
+
}, []);
|
|
213
|
+
useHandleEscape(isOpen ? memoizedOnPressEscape : undefined);
|
|
212
214
|
useEffect(() => {
|
|
213
215
|
const focusHandler = (e) => {
|
|
214
216
|
// e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelessDialog.js","sources":["../../../../src/components/Dialog/ModelessDialog/ModelessDialog.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n type RefObject,\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Draggable from 'react-draggable'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../../hooks/useHandleEscape'\nimport { useIntl } from '../../../intl'\nimport { debounce } from '../../../libs/debounce'\nimport { dialogSize } from '../../../tailwind'\nimport { Base, type BaseElementProps } from '../../Base'\nimport { Button } from '../../Button'\nimport { Heading } from '../../Heading'\nimport { FaGripIcon, FaXmarkIcon } from '../../Icon'\nimport { DialogBody, type Props as DialogBodyProps } from '../DialogBody'\nimport { DialogOverlap } from '../DialogOverlap'\nimport { useDialogPortal } from '../useDialogPortal'\n\nimport type { DialogSize } from '../types'\n\ntype AbstractProps = PropsWithChildren<{\n /**\n * ダイアログのタイトルの内容\n */\n heading: ReactNode\n /**\n * ダイアログのフッタ部分の内容\n */\n footer?: ReactNode\n /**\n * ダイアログが開かれているかどうかの真偽値\n */\n isOpen: boolean\n /**\n * 閉じるボタンを押下したときのハンドラ\n */\n onClickClose?: (e: MouseEvent<HTMLButtonElement>) => void\n /**\n * ダイアログが開いている状態で Escape キーを押下したときのハンドラ\n */\n onPressEscape?: () => void\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの高さ\n */\n height?: string | number\n /**\n * ダイアログを開いたときの初期 top 位置\n */\n top?: string | number\n /**\n * ダイアログを開いたときの初期 left 位置\n */\n left?: string | number\n /**\n * ダイアログを開いたときの初期 right 位置\n */\n right?: string | number\n /**\n * ダイアログを開いたときの初期 bottom 位置\n */\n bottom?: string | number\n /**\n * ポータルの container となる DOM 要素を追加する親要素\n */\n portalParent?: HTMLElement | RefObject<HTMLElement>\n}>\ntype Props = AbstractProps &\n Omit<DialogBodyProps, keyof AbstractProps> &\n Omit<BaseElementProps, keyof AbstractProps> &\n Omit<VariantProps<typeof classNameGenerator>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n overlap: 'shr-inset-[unset]',\n wrapper: 'smarthr-ui-ModelessDialog shr-fixed shr-flex shr-flex-col',\n headerEl: [\n 'smarthr-ui-ModelessDialog-header shr-border-b-shorthand shr-relative shr-flex shr-cursor-move shr-items-center shr-rounded-tl-l shr-rounded-tr-l shr-pe-1 shr-ps-1.5',\n 'hover:shr-bg-white-darken',\n /* DialogHandlerにフォーカスが当たっているときは、headerもフォーカス状態のスタイルにする。 */\n 'has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-focus-indicator has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-bg-white-darken has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-transition-colors has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-duration-100 has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-ease-in-out',\n ],\n dialogHandler: [\n 'smarthr-ui-ModelessDialog-handle shr-absolute shr-inset-x-0 shr-bottom-0 shr-top-[2px] shr-m-auto shr-flex shr-justify-center shr-rounded-tl-s shr-rounded-tr-s shr-border-none shr-text-grey shr-transition-colors shr-duration-100 shr-ease-in-out',\n 'focus-visible:shr-focus-indicator--none shr-cursor-[inherit] shr-bg-[unset]',\n ],\n headingEl: ['shr-my-1 shr-me-1'],\n closeButtonLayout: [\n 'shr-relative' /* DialogHandlerの上に出すためにスタッキングコンテキストを生成 */,\n 'shr-ml-auto shr-shrink-0',\n ],\n footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand',\n },\n variants: {\n size: {\n XS: { wrapper: dialogSize.XS },\n S: { wrapper: dialogSize.S },\n M: { wrapper: dialogSize.M },\n L: { wrapper: dialogSize.L },\n XL: { wrapper: dialogSize.XL },\n XXL: { wrapper: dialogSize.XXL },\n FULL: { wrapper: dialogSize.FULL },\n },\n resizable: {\n true: {\n wrapper: 'shr-resize shr-overflow-auto',\n },\n false: {},\n },\n },\n})\n\nexport const ModelessDialog: FC<Props> = ({\n heading,\n children,\n contentBgColor,\n contentPadding,\n footer,\n isOpen,\n onPressEscape,\n resizable = false,\n width,\n size,\n height,\n top,\n left,\n right,\n bottom,\n portalParent,\n className,\n id,\n onClickClose,\n ...rest\n}) => {\n const labelId = useId()\n const lastFocusElementRef = useRef<HTMLElement | null>(null)\n const { createPortal } = useDialogPortal(portalParent, id)\n const { localize } = useIntl()\n\n const classNames = useMemo(() => {\n const { overlap, wrapper, headerEl, headingEl, dialogHandler, closeButtonLayout, footerEl } =\n classNameGenerator()\n\n return {\n overlap: overlap({ className }),\n wrapper: wrapper({ size, resizable }),\n header: headerEl(),\n heading: headingEl(),\n dialogHandler: dialogHandler(),\n closeButtonLayout: closeButtonLayout(),\n footer: footerEl(),\n }\n }, [className, size, resizable])\n\n const wrapperRef = useRef<HTMLDivElement>(null)\n const focusTargetRef = useRef<HTMLDivElement>(null)\n\n const [wrapperPosition, setWrapperPosition] = useState<DOMRect | undefined>(undefined)\n const [debouncedLiveRegionText, setDebouncedLiveRegionText] = useState<string>('')\n const [centering, setCentering] = useState<{\n top?: number\n left?: number\n }>({})\n const [position, setPosition] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n })\n const [draggableBounds, setDraggableBounds] =\n useState<ComponentProps<typeof Draggable>['bounds']>()\n const debounceLiveRegionText = useMemo(() => debounce(setDebouncedLiveRegionText, 600), [])\n\n useEffect(() => {\n if (!wrapperPosition) {\n setDebouncedLiveRegionText('')\n return\n }\n\n const txt = localize(\n {\n id: 'smarthr-ui/ModelessDialog/dialogHandlerLiveRegionText',\n defaultText: '上から{top}px、左から{left}px',\n },\n {\n top: Math.trunc(wrapperPosition.top).toString(),\n left: Math.trunc(wrapperPosition.left).toString(),\n },\n )\n\n debounceLiveRegionText(txt)\n }, [localize, wrapperPosition, debounceLiveRegionText])\n\n const positionStyle = useMemo(\n () => ({\n top: centering.top ?? top,\n left: centering.left ?? left,\n right,\n bottom,\n width: size ? undefined : width,\n height,\n }),\n [centering, top, left, right, bottom, width, height, size],\n )\n\n const handleArrowKey = useCallback(\n (e: KeyboardEvent) => {\n if (!isOpen || document.activeElement !== e.currentTarget) {\n return\n }\n\n const movingDistance = 20\n\n switch (e.key) {\n case 'ArrowUp':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y - movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowDown':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y + movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowLeft':\n setPosition((prev) => ({\n x: prev.x - movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n case 'ArrowRight':\n setPosition((prev) => ({\n x: prev.x + movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n }\n },\n [isOpen],\n )\n\n useEffect(() => {\n if (wrapperRef.current instanceof Element) {\n setWrapperPosition(wrapperRef.current.getBoundingClientRect())\n }\n }, [position])\n\n useEffect(() => {\n // 中央寄せの座標計算を行う\n if (!wrapperRef.current || !isOpen) {\n return\n }\n\n const isXCenter = left === undefined && right === undefined\n const isYCenter = top === undefined && bottom === undefined\n\n if (isXCenter || isYCenter) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setCentering({\n top: isYCenter ? window.innerHeight / 2 - rect.height / 2 : undefined,\n left: isXCenter ? window.innerWidth / 2 - rect.width / 2 : undefined,\n })\n }\n }, [bottom, isOpen, left, right, top])\n\n useEffect(() => {\n if (!isOpen) return\n\n if (centering.top) {\n setDraggableBounds({ top: centering.top * -1 })\n\n return\n }\n\n if (wrapperRef.current) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setDraggableBounds({ top: rect.top * -1 })\n }\n }, [isOpen, centering.top])\n\n useEffect(() => {\n if (isOpen) {\n setPosition({ x: 0, y: 0 })\n focusTargetRef.current?.focus()\n }\n }, [isOpen])\n\n const actualOnClickClose = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => {\n lastFocusElementRef.current?.focus()\n onClickClose?.(e)\n },\n [onClickClose],\n )\n\n const actualOnPressEscape = useMemo(\n () =>\n onPressEscape\n ? () => {\n lastFocusElementRef.current?.focus()\n onPressEscape()\n }\n : undefined,\n [onPressEscape],\n )\n\n useHandleEscape(\n useMemo(\n () => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined),\n [isOpen, actualOnPressEscape],\n ),\n )\n\n useEffect(() => {\n const focusHandler = (e: FocusEvent) => {\n // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する\n if (e.target instanceof HTMLElement && !wrapperRef?.current?.contains(e.target)) {\n lastFocusElementRef.current = e.target\n }\n }\n\n document.addEventListener('focus', focusHandler, true)\n\n return () => document.removeEventListener('focus', focusHandler, true)\n }, [])\n\n const onDragStart = useCallback((_: any, data: { x: number; y: number }) => setPosition(data), [])\n const onDrag = useCallback((_: any, data: { deltaX: number; deltaY: number }) => {\n setPosition((prev) => ({\n x: prev.x + data.deltaX,\n y: prev.y + data.deltaY,\n }))\n }, [])\n\n return createPortal(\n <DialogOverlap isOpen={isOpen} className={classNames.overlap} as=\"section\">\n <Draggable\n handle=\".smarthr-ui-ModelessDialog-handle\"\n onStart={onDragStart}\n onDrag={onDrag}\n position={position}\n bounds={draggableBounds}\n nodeRef={wrapperRef}\n >\n <Base\n {...rest}\n ref={wrapperRef}\n role=\"dialog\"\n aria-labelledby={labelId}\n radius=\"m\"\n layer={3}\n overflow=\"auto\"\n className={classNames.wrapper}\n style={positionStyle}\n >\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div tabIndex={-1} ref={focusTargetRef} />\n <div className={classNames.header}>\n <Handler onArrowKeyDown={handleArrowKey} className={classNames.dialogHandler} />\n <div id={labelId} className={classNames.heading}>\n {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */}\n <Heading>{heading}</Heading>\n </div>\n <CloseButton onClick={actualOnClickClose} className={classNames.closeButtonLayout} />\n </div>\n <DialogBody\n contentBgColor={contentBgColor}\n contentPadding={contentPadding}\n className=\"smarthr-ui-ModelessDialog-content shr-overscroll-contain\"\n >\n {children}\n </DialogBody>\n {footer && <div className={classNames.footer}>{footer}</div>}\n <LiveRegion regionText={debouncedLiveRegionText} />\n </Base>\n </Draggable>\n </DialogOverlap>,\n )\n}\n\nconst Handler = memo<{\n className: string\n onArrowKeyDown: (e: KeyboardEvent) => void\n}>(({ onArrowKeyDown: onDelegateKeyDown, ...rest }) => {\n const { localize } = useIntl()\n const accessibleDefaultTexts = useMemo(\n () => ({\n dialogHandlerAriaRoleDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaRoleDescription',\n defaultText: 'ドラッグ可能',\n }),\n dialogHandlerDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerDescription',\n defaultText: '矢印キーを押して上下左右に移動できます',\n }),\n dialogHandlerAriaLabel: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaLabel',\n defaultText: 'ダイアログの位置',\n }),\n }),\n [localize],\n )\n\n return (\n <>\n <button\n {...rest}\n type=\"button\"\n aria-label={accessibleDefaultTexts.dialogHandlerAriaLabel}\n aria-roledescription={accessibleDefaultTexts.dialogHandlerAriaRoleDescription}\n aria-describedby=\"handler-description\"\n onKeyDown={onDelegateKeyDown}\n >\n <FaGripIcon />\n </button>\n <div className=\"shr-hidden\" id=\"handler-description\">\n {accessibleDefaultTexts.dialogHandlerDescription}\n </div>\n </>\n )\n})\n\nconst LiveRegion = ({ regionText }: { regionText: string | undefined }) => (\n <div\n role=\"status\"\n className=\"shr-fixed -shr-m-px shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap\"\n >\n {regionText}\n </div>\n)\n\nconst CloseButton = memo<{\n className: string\n onClick: (e: MouseEvent<HTMLButtonElement>) => void\n}>(({ onClick, className }) => {\n const { localize } = useIntl()\n const closeButtonIconAlt = localize({\n id: 'smarthr-ui/ModelessDialog/closeButtonIconAlt',\n defaultText: '閉じる',\n })\n\n return (\n <div className={className}>\n <Button\n type=\"button\"\n size=\"S\"\n onClick={onClick}\n className=\"smarthr-ui-ModelessDialog-closeButton\"\n >\n <FaXmarkIcon alt={closeButtonIconAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA;AACE;AACE;AACA;AACA;;;;;AAKC;AACD;;;AAGC;;AAED;AACE;;AAED;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACD;AACD;AACD;AACF;AACF;;AAwBC;AACA;;AAEA;AAEA;AACE;;AAIE;;;;;;;;;AAUJ;AACA;;;;AAQA;AACE;AACA;AACD;;AAGD;;;;;;;AAUM;AACA;;;;AAKD;;;AAML;AAEI;AACA;;;;;AAKD;AAIH;;;;;AAQI;AACE;AACE;;AAEE;AACD;;;AAGH;AACE;;AAEE;AACD;;;AAGH;AACE;AACE;;AAED;;;AAGH;AACE;AACE;;AAED;;;;AAIP;;AAKA;;;AAGF;;;;;;;;AAWE;;AAGE;AACE;AACA;AACD;;AAEL;;AAGE;;AAEA;AACE;;;AAKF;;AAGE;;;;;;AAOA;;AAEJ;AAEA;AAEI;AACA;AACF;AAIF;;AAIU;AACA;;AAEJ;;;AAYJ;;AAEE;AACE;;AAEJ;;AAIA;;AAGF;;AAEE;AACE;AACA;AACD;;;AA+CL;AAEA;AAIE;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AAiBF;AAEA;AASA;AAIE;;AAEE;AACA;AACD;AAED;AAYF;;"}
|
|
1
|
+
{"version":3,"file":"ModelessDialog.js","sources":["../../../../src/components/Dialog/ModelessDialog/ModelessDialog.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type PropsWithChildren,\n type ReactNode,\n type RefObject,\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport Draggable from 'react-draggable'\nimport { type VariantProps, tv } from 'tailwind-variants'\n\nimport { useHandleEscape } from '../../../hooks/useHandleEscape'\nimport { useIntl } from '../../../intl'\nimport { debounce } from '../../../libs/debounce'\nimport { dialogSize } from '../../../tailwind'\nimport { Base, type BaseElementProps } from '../../Base'\nimport { Button } from '../../Button'\nimport { Heading } from '../../Heading'\nimport { FaGripIcon, FaXmarkIcon } from '../../Icon'\nimport { DialogBody, type Props as DialogBodyProps } from '../DialogBody'\nimport { DialogOverlap } from '../DialogOverlap'\nimport { useDialogPortal } from '../useDialogPortal'\n\nimport type { DialogSize } from '../types'\n\ntype AbstractProps = PropsWithChildren<{\n /**\n * ダイアログのタイトルの内容\n */\n heading: ReactNode\n /**\n * ダイアログのフッタ部分の内容\n */\n footer?: ReactNode\n /**\n * ダイアログが開かれているかどうかの真偽値\n */\n isOpen: boolean\n /**\n * 閉じるボタンを押下したときのハンドラ\n */\n onClickClose?: (e: MouseEvent<HTMLButtonElement>) => void\n /**\n * ダイアログが開いている状態で Escape キーを押下したときのハンドラ\n */\n onPressEscape?: () => void\n /**\n * @deprecated ダイアログの幅を指定する場合は、`width` ではなく `size` を使用してください。\n * ダイアログの幅\n */\n width?: string | number\n /**\n * ダイアログの大きさ\n */\n size?: DialogSize\n /**\n * ダイアログの高さ\n */\n height?: string | number\n /**\n * ダイアログを開いたときの初期 top 位置\n */\n top?: string | number\n /**\n * ダイアログを開いたときの初期 left 位置\n */\n left?: string | number\n /**\n * ダイアログを開いたときの初期 right 位置\n */\n right?: string | number\n /**\n * ダイアログを開いたときの初期 bottom 位置\n */\n bottom?: string | number\n /**\n * ポータルの container となる DOM 要素を追加する親要素\n */\n portalParent?: HTMLElement | RefObject<HTMLElement>\n}>\ntype Props = AbstractProps &\n Omit<DialogBodyProps, keyof AbstractProps> &\n Omit<BaseElementProps, keyof AbstractProps> &\n Omit<VariantProps<typeof classNameGenerator>, keyof AbstractProps>\n\nconst classNameGenerator = tv({\n slots: {\n overlap: 'shr-inset-[unset]',\n wrapper: 'smarthr-ui-ModelessDialog shr-fixed shr-flex shr-flex-col',\n headerEl: [\n 'smarthr-ui-ModelessDialog-header shr-border-b-shorthand shr-relative shr-flex shr-cursor-move shr-items-center shr-rounded-tl-l shr-rounded-tr-l shr-pe-1 shr-ps-1.5',\n 'hover:shr-bg-white-darken',\n /* DialogHandlerにフォーカスが当たっているときは、headerもフォーカス状態のスタイルにする。 */\n 'has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-focus-indicator has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-bg-white-darken has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-transition-colors has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-duration-100 has-[.smarthr-ui-ModelessDialog-handle:focus-visible]:shr-ease-in-out',\n ],\n dialogHandler: [\n 'smarthr-ui-ModelessDialog-handle shr-absolute shr-inset-x-0 shr-bottom-0 shr-top-[2px] shr-m-auto shr-flex shr-justify-center shr-rounded-tl-s shr-rounded-tr-s shr-border-none shr-text-grey shr-transition-colors shr-duration-100 shr-ease-in-out',\n 'focus-visible:shr-focus-indicator--none shr-cursor-[inherit] shr-bg-[unset]',\n ],\n headingEl: ['shr-my-1 shr-me-1'],\n closeButtonLayout: [\n 'shr-relative' /* DialogHandlerの上に出すためにスタッキングコンテキストを生成 */,\n 'shr-ml-auto shr-shrink-0',\n ],\n footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand',\n },\n variants: {\n size: {\n XS: { wrapper: dialogSize.XS },\n S: { wrapper: dialogSize.S },\n M: { wrapper: dialogSize.M },\n L: { wrapper: dialogSize.L },\n XL: { wrapper: dialogSize.XL },\n XXL: { wrapper: dialogSize.XXL },\n FULL: { wrapper: dialogSize.FULL },\n },\n resizable: {\n true: {\n wrapper: 'shr-resize shr-overflow-auto',\n },\n false: {},\n },\n },\n})\n\nexport const ModelessDialog: FC<Props> = ({\n heading,\n children,\n contentBgColor,\n contentPadding,\n footer,\n isOpen,\n onPressEscape,\n resizable = false,\n width,\n size,\n height,\n top,\n left,\n right,\n bottom,\n portalParent,\n className,\n id,\n onClickClose,\n ...rest\n}) => {\n const labelId = useId()\n const lastFocusElementRef = useRef<HTMLElement | null>(null)\n const { createPortal } = useDialogPortal(portalParent, id)\n const { localize } = useIntl()\n\n const classNames = useMemo(() => {\n const { overlap, wrapper, headerEl, headingEl, dialogHandler, closeButtonLayout, footerEl } =\n classNameGenerator()\n\n return {\n overlap: overlap({ className }),\n wrapper: wrapper({ size, resizable }),\n header: headerEl(),\n heading: headingEl(),\n dialogHandler: dialogHandler(),\n closeButtonLayout: closeButtonLayout(),\n footer: footerEl(),\n }\n }, [className, size, resizable])\n\n const wrapperRef = useRef<HTMLDivElement>(null)\n const focusTargetRef = useRef<HTMLDivElement>(null)\n\n const [wrapperPosition, setWrapperPosition] = useState<DOMRect | undefined>(undefined)\n const [debouncedLiveRegionText, setDebouncedLiveRegionText] = useState<string>('')\n const [centering, setCentering] = useState<{\n top?: number\n left?: number\n }>({})\n const [position, setPosition] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n })\n const [draggableBounds, setDraggableBounds] =\n useState<ComponentProps<typeof Draggable>['bounds']>()\n const debounceLiveRegionText = useMemo(() => debounce(setDebouncedLiveRegionText, 600), [])\n\n useEffect(() => {\n if (!wrapperPosition) {\n setDebouncedLiveRegionText('')\n return\n }\n\n const txt = localize(\n {\n id: 'smarthr-ui/ModelessDialog/dialogHandlerLiveRegionText',\n defaultText: '上から{top}px、左から{left}px',\n },\n {\n top: Math.trunc(wrapperPosition.top).toString(),\n left: Math.trunc(wrapperPosition.left).toString(),\n },\n )\n\n debounceLiveRegionText(txt)\n }, [localize, wrapperPosition, debounceLiveRegionText])\n\n // 外部propsをrefに保存\n const unstableRef = useRef({ isOpen, onClickClose, onPressEscape })\n unstableRef.current = { isOpen, onClickClose, onPressEscape }\n\n const positionStyle = useMemo(\n () => ({\n top: centering.top ?? top,\n left: centering.left ?? left,\n right,\n bottom,\n width: size ? undefined : width,\n height,\n }),\n [centering, top, left, right, bottom, width, height, size],\n )\n\n const handleArrowKey = useCallback((e: KeyboardEvent) => {\n if (!unstableRef.current.isOpen || document.activeElement !== e.currentTarget) {\n return\n }\n\n const movingDistance = 20\n\n switch (e.key) {\n case 'ArrowUp':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y - movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowDown':\n setPosition((prev) => ({\n x: prev.x,\n y: prev.y + movingDistance,\n }))\n e.preventDefault()\n break\n case 'ArrowLeft':\n setPosition((prev) => ({\n x: prev.x - movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n case 'ArrowRight':\n setPosition((prev) => ({\n x: prev.x + movingDistance,\n y: prev.y,\n }))\n e.preventDefault()\n break\n }\n }, [])\n\n useEffect(() => {\n if (wrapperRef.current instanceof Element) {\n setWrapperPosition(wrapperRef.current.getBoundingClientRect())\n }\n }, [position])\n\n useEffect(() => {\n // 中央寄せの座標計算を行う\n if (!wrapperRef.current || !isOpen) {\n return\n }\n\n const isXCenter = left === undefined && right === undefined\n const isYCenter = top === undefined && bottom === undefined\n\n if (isXCenter || isYCenter) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setCentering({\n top: isYCenter ? window.innerHeight / 2 - rect.height / 2 : undefined,\n left: isXCenter ? window.innerWidth / 2 - rect.width / 2 : undefined,\n })\n }\n }, [bottom, isOpen, left, right, top])\n\n useEffect(() => {\n if (!isOpen) return\n\n if (centering.top) {\n setDraggableBounds({ top: centering.top * -1 })\n\n return\n }\n\n if (wrapperRef.current) {\n const rect = wrapperRef.current.getBoundingClientRect()\n\n setDraggableBounds({ top: rect.top * -1 })\n }\n }, [isOpen, centering.top])\n\n useEffect(() => {\n if (isOpen) {\n setPosition({ x: 0, y: 0 })\n focusTargetRef.current?.focus()\n }\n }, [isOpen])\n\n const actualOnClickClose = useCallback((e: MouseEvent<HTMLButtonElement>) => {\n lastFocusElementRef.current?.focus()\n unstableRef.current.onClickClose?.(e)\n }, [])\n\n // stableなcallbackを作成\n const memoizedOnPressEscape = useCallback(() => {\n lastFocusElementRef.current?.focus()\n unstableRef.current.onPressEscape?.()\n }, [])\n\n useHandleEscape(isOpen ? memoizedOnPressEscape : undefined)\n\n useEffect(() => {\n const focusHandler = (e: FocusEvent) => {\n // e.target(現在フォーカスがあたっている要素)がModeless dialog外の要素であれば、lastFocusElementRefに代入する\n if (e.target instanceof HTMLElement && !wrapperRef?.current?.contains(e.target)) {\n lastFocusElementRef.current = e.target\n }\n }\n\n document.addEventListener('focus', focusHandler, true)\n\n return () => document.removeEventListener('focus', focusHandler, true)\n }, [])\n\n const onDragStart = useCallback((_: any, data: { x: number; y: number }) => setPosition(data), [])\n const onDrag = useCallback((_: any, data: { deltaX: number; deltaY: number }) => {\n setPosition((prev) => ({\n x: prev.x + data.deltaX,\n y: prev.y + data.deltaY,\n }))\n }, [])\n\n return createPortal(\n <DialogOverlap isOpen={isOpen} className={classNames.overlap} as=\"section\">\n <Draggable\n handle=\".smarthr-ui-ModelessDialog-handle\"\n onStart={onDragStart}\n onDrag={onDrag}\n position={position}\n bounds={draggableBounds}\n nodeRef={wrapperRef}\n >\n <Base\n {...rest}\n ref={wrapperRef}\n role=\"dialog\"\n aria-labelledby={labelId}\n radius=\"m\"\n layer={3}\n overflow=\"auto\"\n className={classNames.wrapper}\n style={positionStyle}\n >\n {/* eslint-disable-next-line smarthr/a11y-scroller-has-tabindex -- dummy element for focus management. */}\n <div tabIndex={-1} ref={focusTargetRef} />\n <div className={classNames.header}>\n <Handler onArrowKeyDown={handleArrowKey} className={classNames.dialogHandler} />\n <div id={labelId} className={classNames.heading}>\n {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */}\n <Heading>{heading}</Heading>\n </div>\n <CloseButton onClick={actualOnClickClose} className={classNames.closeButtonLayout} />\n </div>\n <DialogBody\n contentBgColor={contentBgColor}\n contentPadding={contentPadding}\n className=\"smarthr-ui-ModelessDialog-content shr-overscroll-contain\"\n >\n {children}\n </DialogBody>\n {footer && <div className={classNames.footer}>{footer}</div>}\n <LiveRegion regionText={debouncedLiveRegionText} />\n </Base>\n </Draggable>\n </DialogOverlap>,\n )\n}\n\nconst Handler = memo<{\n className: string\n onArrowKeyDown: (e: KeyboardEvent) => void\n}>(({ onArrowKeyDown: onDelegateKeyDown, ...rest }) => {\n const { localize } = useIntl()\n const accessibleDefaultTexts = useMemo(\n () => ({\n dialogHandlerAriaRoleDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaRoleDescription',\n defaultText: 'ドラッグ可能',\n }),\n dialogHandlerDescription: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerDescription',\n defaultText: '矢印キーを押して上下左右に移動できます',\n }),\n dialogHandlerAriaLabel: localize({\n id: 'smarthr-ui/ModelessDialog/dialogHandlerAriaLabel',\n defaultText: 'ダイアログの位置',\n }),\n }),\n [localize],\n )\n\n return (\n <>\n <button\n {...rest}\n type=\"button\"\n aria-label={accessibleDefaultTexts.dialogHandlerAriaLabel}\n aria-roledescription={accessibleDefaultTexts.dialogHandlerAriaRoleDescription}\n aria-describedby=\"handler-description\"\n onKeyDown={onDelegateKeyDown}\n >\n <FaGripIcon />\n </button>\n <div className=\"shr-hidden\" id=\"handler-description\">\n {accessibleDefaultTexts.dialogHandlerDescription}\n </div>\n </>\n )\n})\n\nconst LiveRegion = ({ regionText }: { regionText: string | undefined }) => (\n <div\n role=\"status\"\n className=\"shr-fixed -shr-m-px shr-h-px shr-w-px shr-overflow-hidden shr-whitespace-nowrap\"\n >\n {regionText}\n </div>\n)\n\nconst CloseButton = memo<{\n className: string\n onClick: (e: MouseEvent<HTMLButtonElement>) => void\n}>(({ onClick, className }) => {\n const { localize } = useIntl()\n const closeButtonIconAlt = localize({\n id: 'smarthr-ui/ModelessDialog/closeButtonIconAlt',\n defaultText: '閉じる',\n })\n\n return (\n <div className={className}>\n <Button\n type=\"button\"\n size=\"S\"\n onClick={onClick}\n className=\"smarthr-ui-ModelessDialog-closeButton\"\n >\n <FaXmarkIcon alt={closeButtonIconAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FA;AACE;AACE;AACA;AACA;;;;;AAKC;AACD;;;AAGC;;AAED;AACE;;AAED;AACD;AACD;AACD;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACE;AACD;AACD;AACD;AACF;AACF;;AAwBC;AACA;;AAEA;AAEA;AACE;;AAIE;;;;;;;;;AAUJ;AACA;;;;AAQA;AACE;AACA;AACD;;AAGD;;;;;;;AAUM;AACA;;;;AAKD;;;;AAOL;;AAGA;AAEI;AACA;;;;;AAKD;AAIH;AACE;;;;AAMA;AACE;AACE;;AAEE;AACD;;;AAGH;AACE;;AAEE;AACD;;;AAGH;AACE;AACE;;AAED;;;AAGH;AACE;AACE;;AAED;;;;;;AAOL;;;AAGF;;;;;;;;AAWE;;AAGE;AACE;AACA;AACD;;AAEL;;AAGE;;AAEA;AACE;;;AAKF;;AAGE;;;;;;AAOA;;AAEJ;AAEA;AACE;;;;AAKF;AACE;AACA;;;;AAMA;;AAEE;AACE;;AAEJ;;AAIA;;AAGF;;AAEE;AACE;AACA;AACD;;;AA+CL;AAEA;AAIE;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AAiBF;AAEA;AASA;AAIE;;AAEE;AACA;AACD;AAED;AAYF;;"}
|
|
@@ -59,8 +59,11 @@ const RemoteDialogTrigger = ({ targetId, children, onClick }) => {
|
|
|
59
59
|
setupElement();
|
|
60
60
|
});
|
|
61
61
|
observer.observe(currentRef, {
|
|
62
|
-
childList: true,
|
|
63
|
-
subtree: true,
|
|
62
|
+
childList: true,
|
|
63
|
+
subtree: true,
|
|
64
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
65
|
+
attributes: true,
|
|
66
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
64
67
|
});
|
|
65
68
|
return () => {
|
|
66
69
|
observer.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteDialogTrigger.cjs","sources":["../../../../src/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type PropsWithChildren, useCallback, useEffect, useRef } from 'react'\n\nimport { TRIGGER_EVENT } from '../useRemoteTrigger'\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\nconst onClickRemoteDialogTrigger = (ariaControls: string) => {\n document.dispatchEvent(\n new CustomEvent(TRIGGER_EVENT, {\n detail: { id: ariaControls },\n }),\n )\n}\n\nexport const RemoteDialogTrigger: FC<\n PropsWithChildren<{\n targetId: string\n onClick?: (open: () => void) => void\n }>\n> = ({ targetId, children, onClick }) => {\n const ref = useRef<HTMLSpanElement | null>(null)\n const onClickRef = useRef(onClick)\n onClickRef.current = onClick\n\n const actualOnClick = useCallback((e: Event) => {\n // HINT: onClick内で非同期処理される場合、e.currentTargetがnullになってしまう可能性があるため\n // 先にariaControlsを取得しておく\n const ariaControls = (e.currentTarget as HTMLElement).getAttribute('aria-controls') as string\n\n if (onClickRef.current) {\n return onClickRef.current(() => {\n onClickRemoteDialogTrigger(ariaControls)\n })\n }\n\n onClickRemoteDialogTrigger(ariaControls)\n }, [])\n\n useEffect(() => {\n const currentRef = ref.current\n if (!currentRef) {\n return\n }\n\n const getClickableElement = () =>\n currentRef.querySelector<HTMLButtonElement | HTMLAnchorElement>('button, a')\n\n // 現在の子要素に対して処理を実行\n const setupElement = () => {\n const element = getClickableElement()\n if (element) {\n element.setAttribute('aria-haspopup', 'dialog')\n element.setAttribute('aria-controls', targetId)\n // HINT: DropdownCloser のonClickより先に実行するため、キャプチャフェーズで処理する\n element.addEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n const clearEventListener = () => {\n // 既存のイベントリスナーをクリーンアップ\n const element = getClickableElement()\n if (element) {\n element.removeEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n // 初回セットアップ\n setupElement()\n\n // MutationObserverでDOM変更を監視\n const observer = new MutationObserver(() => {\n clearEventListener()\n setupElement()\n })\n\n observer.observe(currentRef, {\n childList: true
|
|
1
|
+
{"version":3,"file":"RemoteDialogTrigger.cjs","sources":["../../../../src/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type PropsWithChildren, useCallback, useEffect, useRef } from 'react'\n\nimport { TRIGGER_EVENT } from '../useRemoteTrigger'\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\nconst onClickRemoteDialogTrigger = (ariaControls: string) => {\n document.dispatchEvent(\n new CustomEvent(TRIGGER_EVENT, {\n detail: { id: ariaControls },\n }),\n )\n}\n\nexport const RemoteDialogTrigger: FC<\n PropsWithChildren<{\n targetId: string\n onClick?: (open: () => void) => void\n }>\n> = ({ targetId, children, onClick }) => {\n const ref = useRef<HTMLSpanElement | null>(null)\n const onClickRef = useRef(onClick)\n onClickRef.current = onClick\n\n const actualOnClick = useCallback((e: Event) => {\n // HINT: onClick内で非同期処理される場合、e.currentTargetがnullになってしまう可能性があるため\n // 先にariaControlsを取得しておく\n const ariaControls = (e.currentTarget as HTMLElement).getAttribute('aria-controls') as string\n\n if (onClickRef.current) {\n return onClickRef.current(() => {\n onClickRemoteDialogTrigger(ariaControls)\n })\n }\n\n onClickRemoteDialogTrigger(ariaControls)\n }, [])\n\n useEffect(() => {\n const currentRef = ref.current\n if (!currentRef) {\n return\n }\n\n const getClickableElement = () =>\n currentRef.querySelector<HTMLButtonElement | HTMLAnchorElement>('button, a')\n\n // 現在の子要素に対して処理を実行\n const setupElement = () => {\n const element = getClickableElement()\n if (element) {\n element.setAttribute('aria-haspopup', 'dialog')\n element.setAttribute('aria-controls', targetId)\n // HINT: DropdownCloser のonClickより先に実行するため、キャプチャフェーズで処理する\n element.addEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n const clearEventListener = () => {\n // 既存のイベントリスナーをクリーンアップ\n const element = getClickableElement()\n if (element) {\n element.removeEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n // 初回セットアップ\n setupElement()\n\n // MutationObserverでDOM変更を監視\n const observer = new MutationObserver(() => {\n clearEventListener()\n setupElement()\n })\n\n observer.observe(currentRef, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n observer.disconnect()\n clearEventListener()\n }\n }, [targetId, actualOnClick])\n\n return (\n <span className=\"smarthr-ui-RemoteDialogTrigger shr-contents\" ref={ref}>\n {children}\n </span>\n )\n}\n"],"names":[],"mappings":";;;;;;;AAMA;AACE;;AAGF;AACE;AAEI;AACD;AAEL;AAEO;AAML;AACA;AACA;AAEA;;;;AAKE;AACE;;AAEA;;;;;AAOF;;;;;;;AAUE;;AAEE;AACA;;;;AAIJ;;;AAIE;;;;AAIF;;AAGA;;AAGA;AACE;AACA;AACF;AAEA;AACE;AACA;;AAEA;AACA;AACD;AAED;;AAEE;AACF;AACF;AAEA;AAKF;;"}
|
|
@@ -57,8 +57,11 @@ const RemoteDialogTrigger = ({ targetId, children, onClick }) => {
|
|
|
57
57
|
setupElement();
|
|
58
58
|
});
|
|
59
59
|
observer.observe(currentRef, {
|
|
60
|
-
childList: true,
|
|
61
|
-
subtree: true,
|
|
60
|
+
childList: true,
|
|
61
|
+
subtree: true,
|
|
62
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
63
|
+
attributes: true,
|
|
64
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
62
65
|
});
|
|
63
66
|
return () => {
|
|
64
67
|
observer.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteDialogTrigger.js","sources":["../../../../src/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type PropsWithChildren, useCallback, useEffect, useRef } from 'react'\n\nimport { TRIGGER_EVENT } from '../useRemoteTrigger'\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\nconst onClickRemoteDialogTrigger = (ariaControls: string) => {\n document.dispatchEvent(\n new CustomEvent(TRIGGER_EVENT, {\n detail: { id: ariaControls },\n }),\n )\n}\n\nexport const RemoteDialogTrigger: FC<\n PropsWithChildren<{\n targetId: string\n onClick?: (open: () => void) => void\n }>\n> = ({ targetId, children, onClick }) => {\n const ref = useRef<HTMLSpanElement | null>(null)\n const onClickRef = useRef(onClick)\n onClickRef.current = onClick\n\n const actualOnClick = useCallback((e: Event) => {\n // HINT: onClick内で非同期処理される場合、e.currentTargetがnullになってしまう可能性があるため\n // 先にariaControlsを取得しておく\n const ariaControls = (e.currentTarget as HTMLElement).getAttribute('aria-controls') as string\n\n if (onClickRef.current) {\n return onClickRef.current(() => {\n onClickRemoteDialogTrigger(ariaControls)\n })\n }\n\n onClickRemoteDialogTrigger(ariaControls)\n }, [])\n\n useEffect(() => {\n const currentRef = ref.current\n if (!currentRef) {\n return\n }\n\n const getClickableElement = () =>\n currentRef.querySelector<HTMLButtonElement | HTMLAnchorElement>('button, a')\n\n // 現在の子要素に対して処理を実行\n const setupElement = () => {\n const element = getClickableElement()\n if (element) {\n element.setAttribute('aria-haspopup', 'dialog')\n element.setAttribute('aria-controls', targetId)\n // HINT: DropdownCloser のonClickより先に実行するため、キャプチャフェーズで処理する\n element.addEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n const clearEventListener = () => {\n // 既存のイベントリスナーをクリーンアップ\n const element = getClickableElement()\n if (element) {\n element.removeEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n // 初回セットアップ\n setupElement()\n\n // MutationObserverでDOM変更を監視\n const observer = new MutationObserver(() => {\n clearEventListener()\n setupElement()\n })\n\n observer.observe(currentRef, {\n childList: true
|
|
1
|
+
{"version":3,"file":"RemoteDialogTrigger.js","sources":["../../../../src/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type PropsWithChildren, useCallback, useEffect, useRef } from 'react'\n\nimport { TRIGGER_EVENT } from '../useRemoteTrigger'\n\nconst CAPTURE_OPTION = {\n capture: true,\n}\n\nconst onClickRemoteDialogTrigger = (ariaControls: string) => {\n document.dispatchEvent(\n new CustomEvent(TRIGGER_EVENT, {\n detail: { id: ariaControls },\n }),\n )\n}\n\nexport const RemoteDialogTrigger: FC<\n PropsWithChildren<{\n targetId: string\n onClick?: (open: () => void) => void\n }>\n> = ({ targetId, children, onClick }) => {\n const ref = useRef<HTMLSpanElement | null>(null)\n const onClickRef = useRef(onClick)\n onClickRef.current = onClick\n\n const actualOnClick = useCallback((e: Event) => {\n // HINT: onClick内で非同期処理される場合、e.currentTargetがnullになってしまう可能性があるため\n // 先にariaControlsを取得しておく\n const ariaControls = (e.currentTarget as HTMLElement).getAttribute('aria-controls') as string\n\n if (onClickRef.current) {\n return onClickRef.current(() => {\n onClickRemoteDialogTrigger(ariaControls)\n })\n }\n\n onClickRemoteDialogTrigger(ariaControls)\n }, [])\n\n useEffect(() => {\n const currentRef = ref.current\n if (!currentRef) {\n return\n }\n\n const getClickableElement = () =>\n currentRef.querySelector<HTMLButtonElement | HTMLAnchorElement>('button, a')\n\n // 現在の子要素に対して処理を実行\n const setupElement = () => {\n const element = getClickableElement()\n if (element) {\n element.setAttribute('aria-haspopup', 'dialog')\n element.setAttribute('aria-controls', targetId)\n // HINT: DropdownCloser のonClickより先に実行するため、キャプチャフェーズで処理する\n element.addEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n const clearEventListener = () => {\n // 既存のイベントリスナーをクリーンアップ\n const element = getClickableElement()\n if (element) {\n element.removeEventListener('click', actualOnClick, CAPTURE_OPTION)\n }\n }\n\n // 初回セットアップ\n setupElement()\n\n // MutationObserverでDOM変更を監視\n const observer = new MutationObserver(() => {\n clearEventListener()\n setupElement()\n })\n\n observer.observe(currentRef, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n observer.disconnect()\n clearEventListener()\n }\n }, [targetId, actualOnClick])\n\n return (\n <span className=\"smarthr-ui-RemoteDialogTrigger shr-contents\" ref={ref}>\n {children}\n </span>\n )\n}\n"],"names":[],"mappings":";;;;;AAMA;AACE;;AAGF;AACE;AAEI;AACD;AAEL;AAEO;AAML;AACA;AACA;AAEA;;;;AAKE;AACE;;AAEA;;;;;AAOF;;;;;;;AAUE;;AAEE;AACA;;;;AAIJ;;;AAIE;;;;AAIF;;AAGA;;AAGA;AACE;AACA;AACF;AAEA;AACE;AACA;;AAEA;AACA;AACD;AAED;;AAEE;AACF;AACF;AAEA;AAKF;;"}
|
|
@@ -8,32 +8,53 @@ var components_Disclosure_useDisclosure = require('./useDisclosure.cjs');
|
|
|
8
8
|
const DisclosureTrigger = ({ targetId, children, onClick }) => {
|
|
9
9
|
const [expanded, setExpanded] = components_Disclosure_useDisclosure.useDisclosure(targetId);
|
|
10
10
|
const ref = React.useRef(null);
|
|
11
|
-
const
|
|
11
|
+
const unstableRef = React.useRef({ onClick, setExpanded });
|
|
12
|
+
unstableRef.current = { onClick, setExpanded };
|
|
13
|
+
const actualOnClick = React.useCallback((e) => {
|
|
12
14
|
const toggleExpanded = () => {
|
|
13
|
-
setExpanded((current) => !current);
|
|
15
|
+
unstableRef.current.setExpanded((current) => !current);
|
|
14
16
|
};
|
|
15
|
-
if (onClick) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (unstableRef.current.onClick) {
|
|
18
|
+
unstableRef.current.onClick(toggleExpanded, e);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
toggleExpanded();
|
|
19
22
|
}
|
|
20
|
-
|
|
21
|
-
}, [onClick, setExpanded]);
|
|
23
|
+
}, []);
|
|
22
24
|
React.useEffect(() => {
|
|
23
|
-
|
|
25
|
+
const wrapper = ref.current;
|
|
26
|
+
if (!wrapper) {
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
let currentCleanup;
|
|
30
|
+
const setupButton = () => {
|
|
31
|
+
currentCleanup?.();
|
|
32
|
+
currentCleanup = undefined;
|
|
33
|
+
const button = wrapper.querySelector('button');
|
|
34
|
+
if (!button) {
|
|
35
|
+
throw new Error('DisclosureTriggerのchildrenにbutton要素を設置してください');
|
|
36
|
+
}
|
|
37
|
+
button.setAttribute('aria-expanded', expanded.toString());
|
|
38
|
+
button.setAttribute('aria-controls', targetId);
|
|
39
|
+
button.addEventListener('click', actualOnClick);
|
|
40
|
+
currentCleanup = () => {
|
|
41
|
+
button.removeEventListener('click', actualOnClick);
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
setupButton();
|
|
45
|
+
const observer = new MutationObserver(setupButton);
|
|
46
|
+
observer.observe(wrapper, {
|
|
47
|
+
childList: true,
|
|
48
|
+
subtree: true,
|
|
49
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
50
|
+
attributes: true,
|
|
51
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
52
|
+
});
|
|
33
53
|
return () => {
|
|
34
|
-
|
|
54
|
+
currentCleanup?.();
|
|
55
|
+
observer.disconnect();
|
|
35
56
|
};
|
|
36
|
-
}, [expanded,
|
|
57
|
+
}, [expanded, targetId, actualOnClick]);
|
|
37
58
|
// HINT: 念の為spanに対して外部からstyleを当てられるようにしておく。
|
|
38
59
|
// Fragmentにrefが渡せるようになったタイミングでclassNameも不要になる
|
|
39
60
|
// TODO: 将来的にspan -> Fragmentに変更する
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DisclosureTrigger.cjs","sources":["../../../src/components/Disclosure/DisclosureTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type ReactElement,
|
|
1
|
+
{"version":3,"file":"DisclosureTrigger.cjs","sources":["../../../src/components/Disclosure/DisclosureTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type ReactElement, useCallback, useEffect, useRef } from 'react'\n\nimport { useDisclosure } from './useDisclosure'\n\ntype DisclosureTriggerNodeChildren = Omit<\n ReactElement,\n 'onClick' | 'aria-expanded' | 'aria-controls'\n>\ntype DisclosureTriggerFuncChildren = (args: { expanded: boolean }) => DisclosureTriggerNodeChildren\n\ntype DisclosureTriggerProps = {\n /** DisclosureContentのidと紐づける文字列 */\n targetId: string\n /** 開閉時のハンドラー */\n onClick?: (open: () => void, e: MouseEvent) => void\n children: DisclosureTriggerNodeChildren | DisclosureTriggerFuncChildren\n}\n\nexport const DisclosureTrigger: FC<DisclosureTriggerProps> = ({ targetId, children, onClick }) => {\n const [expanded, setExpanded] = useDisclosure(targetId)\n const ref = useRef<HTMLSpanElement | null>(null)\n\n const unstableRef = useRef({ onClick, setExpanded })\n unstableRef.current = { onClick, setExpanded }\n\n const actualOnClick = useCallback((e: MouseEvent) => {\n const toggleExpanded = () => {\n unstableRef.current.setExpanded((current) => !current)\n }\n\n if (unstableRef.current.onClick) {\n unstableRef.current.onClick(toggleExpanded, e)\n } else {\n toggleExpanded()\n }\n }, [])\n\n useEffect(() => {\n const wrapper = ref.current\n if (!wrapper) {\n return\n }\n\n let currentCleanup: (() => void) | undefined\n\n const setupButton = () => {\n currentCleanup?.()\n currentCleanup = undefined\n\n const button = wrapper.querySelector('button')\n\n if (!button) {\n throw new Error('DisclosureTriggerのchildrenにbutton要素を設置してください')\n }\n\n button.setAttribute('aria-expanded', expanded.toString())\n button.setAttribute('aria-controls', targetId)\n button.addEventListener('click', actualOnClick)\n\n currentCleanup = () => {\n button.removeEventListener('click', actualOnClick)\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n observer.observe(wrapper, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n currentCleanup?.()\n observer.disconnect()\n }\n }, [expanded, targetId, actualOnClick])\n\n // HINT: 念の為spanに対して外部からstyleを当てられるようにしておく。\n // Fragmentにrefが渡せるようになったタイミングでclassNameも不要になる\n // TODO: 将来的にspan -> Fragmentに変更する\n return (\n <span className=\"smarthr-ui-DisclosureTriggerWrapper shr-contents\" ref={ref}>\n {children instanceof Function ? children({ expanded }) : children}\n </span>\n )\n}\n"],"names":[],"mappings":";;;;;;;AAoBO;;AAEL;;;AAKA;;AAEI;AACF;AAEA;;;;AAGE;;;;AAKF;;;;AAKA;;;;;;AASI;;;AAIF;AACA;;AAGE;AACF;AACF;AAEA;AAEA;AACA;AACE;AACA;;AAEA;AACA;AACD;AAED;;;AAGA;;;;;AAMF;AAKF;;"}
|
|
@@ -1,37 +1,58 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { useRef,
|
|
3
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
4
4
|
import { useDisclosure } from './useDisclosure.js';
|
|
5
5
|
|
|
6
6
|
const DisclosureTrigger = ({ targetId, children, onClick }) => {
|
|
7
7
|
const [expanded, setExpanded] = useDisclosure(targetId);
|
|
8
8
|
const ref = useRef(null);
|
|
9
|
-
const
|
|
9
|
+
const unstableRef = useRef({ onClick, setExpanded });
|
|
10
|
+
unstableRef.current = { onClick, setExpanded };
|
|
11
|
+
const actualOnClick = useCallback((e) => {
|
|
10
12
|
const toggleExpanded = () => {
|
|
11
|
-
setExpanded((current) => !current);
|
|
13
|
+
unstableRef.current.setExpanded((current) => !current);
|
|
12
14
|
};
|
|
13
|
-
if (onClick) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
if (unstableRef.current.onClick) {
|
|
16
|
+
unstableRef.current.onClick(toggleExpanded, e);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
toggleExpanded();
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
}, [onClick, setExpanded]);
|
|
21
|
+
}, []);
|
|
20
22
|
useEffect(() => {
|
|
21
|
-
|
|
23
|
+
const wrapper = ref.current;
|
|
24
|
+
if (!wrapper) {
|
|
22
25
|
return;
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
let currentCleanup;
|
|
28
|
+
const setupButton = () => {
|
|
29
|
+
currentCleanup?.();
|
|
30
|
+
currentCleanup = undefined;
|
|
31
|
+
const button = wrapper.querySelector('button');
|
|
32
|
+
if (!button) {
|
|
33
|
+
throw new Error('DisclosureTriggerのchildrenにbutton要素を設置してください');
|
|
34
|
+
}
|
|
35
|
+
button.setAttribute('aria-expanded', expanded.toString());
|
|
36
|
+
button.setAttribute('aria-controls', targetId);
|
|
37
|
+
button.addEventListener('click', actualOnClick);
|
|
38
|
+
currentCleanup = () => {
|
|
39
|
+
button.removeEventListener('click', actualOnClick);
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
setupButton();
|
|
43
|
+
const observer = new MutationObserver(setupButton);
|
|
44
|
+
observer.observe(wrapper, {
|
|
45
|
+
childList: true,
|
|
46
|
+
subtree: true,
|
|
47
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
48
|
+
attributes: true,
|
|
49
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
50
|
+
});
|
|
31
51
|
return () => {
|
|
32
|
-
|
|
52
|
+
currentCleanup?.();
|
|
53
|
+
observer.disconnect();
|
|
33
54
|
};
|
|
34
|
-
}, [expanded,
|
|
55
|
+
}, [expanded, targetId, actualOnClick]);
|
|
35
56
|
// HINT: 念の為spanに対して外部からstyleを当てられるようにしておく。
|
|
36
57
|
// Fragmentにrefが渡せるようになったタイミングでclassNameも不要になる
|
|
37
58
|
// TODO: 将来的にspan -> Fragmentに変更する
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DisclosureTrigger.js","sources":["../../../src/components/Disclosure/DisclosureTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type ReactElement,
|
|
1
|
+
{"version":3,"file":"DisclosureTrigger.js","sources":["../../../src/components/Disclosure/DisclosureTrigger.tsx"],"sourcesContent":["'use client'\n\nimport { type FC, type ReactElement, useCallback, useEffect, useRef } from 'react'\n\nimport { useDisclosure } from './useDisclosure'\n\ntype DisclosureTriggerNodeChildren = Omit<\n ReactElement,\n 'onClick' | 'aria-expanded' | 'aria-controls'\n>\ntype DisclosureTriggerFuncChildren = (args: { expanded: boolean }) => DisclosureTriggerNodeChildren\n\ntype DisclosureTriggerProps = {\n /** DisclosureContentのidと紐づける文字列 */\n targetId: string\n /** 開閉時のハンドラー */\n onClick?: (open: () => void, e: MouseEvent) => void\n children: DisclosureTriggerNodeChildren | DisclosureTriggerFuncChildren\n}\n\nexport const DisclosureTrigger: FC<DisclosureTriggerProps> = ({ targetId, children, onClick }) => {\n const [expanded, setExpanded] = useDisclosure(targetId)\n const ref = useRef<HTMLSpanElement | null>(null)\n\n const unstableRef = useRef({ onClick, setExpanded })\n unstableRef.current = { onClick, setExpanded }\n\n const actualOnClick = useCallback((e: MouseEvent) => {\n const toggleExpanded = () => {\n unstableRef.current.setExpanded((current) => !current)\n }\n\n if (unstableRef.current.onClick) {\n unstableRef.current.onClick(toggleExpanded, e)\n } else {\n toggleExpanded()\n }\n }, [])\n\n useEffect(() => {\n const wrapper = ref.current\n if (!wrapper) {\n return\n }\n\n let currentCleanup: (() => void) | undefined\n\n const setupButton = () => {\n currentCleanup?.()\n currentCleanup = undefined\n\n const button = wrapper.querySelector('button')\n\n if (!button) {\n throw new Error('DisclosureTriggerのchildrenにbutton要素を設置してください')\n }\n\n button.setAttribute('aria-expanded', expanded.toString())\n button.setAttribute('aria-controls', targetId)\n button.addEventListener('click', actualOnClick)\n\n currentCleanup = () => {\n button.removeEventListener('click', actualOnClick)\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n observer.observe(wrapper, {\n childList: true,\n subtree: true,\n // button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す\n attributes: true,\n attributeFilter: ['disabled', 'aria-disabled'],\n })\n\n return () => {\n currentCleanup?.()\n observer.disconnect()\n }\n }, [expanded, targetId, actualOnClick])\n\n // HINT: 念の為spanに対して外部からstyleを当てられるようにしておく。\n // Fragmentにrefが渡せるようになったタイミングでclassNameも不要になる\n // TODO: 将来的にspan -> Fragmentに変更する\n return (\n <span className=\"smarthr-ui-DisclosureTriggerWrapper shr-contents\" ref={ref}>\n {children instanceof Function ? children({ expanded }) : children}\n </span>\n )\n}\n"],"names":[],"mappings":";;;;;AAoBO;;AAEL;;;AAKA;;AAEI;AACF;AAEA;;;;AAGE;;;;AAKF;;;;AAKA;;;;;;AASI;;;AAIF;AACA;;AAGE;AACF;AACF;AAEA;AAEA;AACA;AACE;AACA;;AAEA;AACA;AACD;AAED;;;AAGA;;;;;AAMF;AAKF;;"}
|
|
@@ -13,29 +13,23 @@ const useDisclosure = (id) => {
|
|
|
13
13
|
detail: { id, expanded },
|
|
14
14
|
}));
|
|
15
15
|
}, [expanded, id]);
|
|
16
|
-
const handleDisclosureChange = React.useCallback((e) => {
|
|
17
|
-
if (id === e.detail.id) {
|
|
18
|
-
setExpanded(e.detail.expanded);
|
|
19
|
-
}
|
|
20
|
-
}, [id]);
|
|
21
16
|
React.useEffect(() => {
|
|
17
|
+
const handleDisclosureChange = (e) => {
|
|
18
|
+
if (id === e.detail.id) {
|
|
19
|
+
setExpanded(e.detail.expanded);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
22
|
document.addEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange);
|
|
23
23
|
return () => {
|
|
24
24
|
document.removeEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange);
|
|
25
25
|
};
|
|
26
|
-
}, [id
|
|
26
|
+
}, [id]);
|
|
27
27
|
const safeSetExpanded = React.useCallback((value) => {
|
|
28
28
|
// DisclosureTrigger と DisclosureContent のレンダリング順序に影響しないように animation frame を待ってから state を更新する
|
|
29
29
|
requestAnimationFrame(() => {
|
|
30
|
-
|
|
31
|
-
const newValue = value(expanded);
|
|
32
|
-
setExpanded(newValue);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
setExpanded(value);
|
|
36
|
-
}
|
|
30
|
+
setExpanded(value);
|
|
37
31
|
});
|
|
38
|
-
}, [
|
|
32
|
+
}, []);
|
|
39
33
|
return [expanded, safeSetExpanded];
|
|
40
34
|
};
|
|
41
35
|
|