smarthr-ui 96.1.3 → 97.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/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/useOptions.cjs +21 -17
- package/lib/components/Combobox/useOptions.cjs.map +1 -1
- package/lib/components/Combobox/useOptions.js +21 -17
- package/lib/components/Combobox/useOptions.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/ModelessDialog/ModelessDialog.cjs +13 -11
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.cjs.map +1 -1
- package/lib/components/Dialog/ModelessDialog/ModelessDialog.js +13 -11
- 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/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs +4 -2
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.cjs.map +1 -1
- package/lib/components/Dropdown/DropdownMenuButton/DropdownMenuButton.js +4 -2
- 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/InputFile/InputFileMultiplyAppendable.cjs +12 -17
- package/lib/components/InputFile/InputFileMultiplyAppendable.cjs.map +1 -1
- package/lib/components/InputFile/InputFileMultiplyAppendable.js +12 -17
- package/lib/components/InputFile/InputFileMultiplyAppendable.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/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/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/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/intl/Localizer.d.ts +3 -3
- 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/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":"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
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDisclosure.cjs","sources":["../../../src/components/Disclosure/useDisclosure.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react'\n\nconst DISCLOSURE_CHANGE_EVENT = 'smarthr-ui:disclosure-change'\ntype DisclosureChangeEventDetail = { id: string; expanded: boolean }\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface DocumentEventMap {\n [DISCLOSURE_CHANGE_EVENT]: CustomEvent<DisclosureChangeEventDetail>\n }\n}\n\ntype Setter = (value: boolean | ((prev: boolean) => boolean)) => void\n\ntype UseDisclosureResult = [expanded: boolean, setExpanded: Setter]\n\n/**\n * 同じ `id` で呼ぶとイベント経由で状態が同期される custom hook\n */\nexport const useDisclosure = (id: string): UseDisclosureResult => {\n const [expanded, setExpanded] = useState(false)\n\n useEffect(() => {\n document.dispatchEvent(\n new CustomEvent<DisclosureChangeEventDetail>(DISCLOSURE_CHANGE_EVENT, {\n detail: { id, expanded },\n }),\n )\n }, [expanded, id])\n\n const handleDisclosureChange =
|
|
1
|
+
{"version":3,"file":"useDisclosure.cjs","sources":["../../../src/components/Disclosure/useDisclosure.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react'\n\nconst DISCLOSURE_CHANGE_EVENT = 'smarthr-ui:disclosure-change'\ntype DisclosureChangeEventDetail = { id: string; expanded: boolean }\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface DocumentEventMap {\n [DISCLOSURE_CHANGE_EVENT]: CustomEvent<DisclosureChangeEventDetail>\n }\n}\n\ntype Setter = (value: boolean | ((prev: boolean) => boolean)) => void\n\ntype UseDisclosureResult = [expanded: boolean, setExpanded: Setter]\n\n/**\n * 同じ `id` で呼ぶとイベント経由で状態が同期される custom hook\n */\nexport const useDisclosure = (id: string): UseDisclosureResult => {\n const [expanded, setExpanded] = useState(false)\n\n useEffect(() => {\n document.dispatchEvent(\n new CustomEvent<DisclosureChangeEventDetail>(DISCLOSURE_CHANGE_EVENT, {\n detail: { id, expanded },\n }),\n )\n }, [expanded, id])\n\n useEffect(() => {\n const handleDisclosureChange = (e: CustomEvent<DisclosureChangeEventDetail>) => {\n if (id === e.detail.id) {\n setExpanded(e.detail.expanded)\n }\n }\n\n document.addEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange)\n return () => {\n document.removeEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange)\n }\n }, [id])\n\n const safeSetExpanded: Setter = useCallback((value) => {\n // DisclosureTrigger と DisclosureContent のレンダリング順序に影響しないように animation frame を待ってから state を更新する\n requestAnimationFrame(() => {\n setExpanded(value)\n })\n }, [])\n\n return [expanded, safeSetExpanded]\n}\n"],"names":["useState","useEffect","useCallback"],"mappings":";;;;AAEA,MAAM,uBAAuB,GAAG,8BAA8B;AAc9D;;AAEG;AACI,MAAM,aAAa,GAAG,CAAC,EAAU,KAAyB;IAC/D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAGA,cAAQ,CAAC,KAAK,CAAC;IAE/CC,eAAS,CAAC,MAAK;AACb,QAAA,QAAQ,CAAC,aAAa,CACpB,IAAI,WAAW,CAA8B,uBAAuB,EAAE;AACpE,YAAA,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;AACzB,SAAA,CAAC,CACH;AACH,IAAA,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAElBA,eAAS,CAAC,MAAK;AACb,QAAA,MAAM,sBAAsB,GAAG,CAAC,CAA2C,KAAI;YAC7E,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE;AACtB,gBAAA,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChC;AACF,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,gBAAgB,CAAC,uBAAuB,EAAE,sBAAsB,CAAC;AAC1E,QAAA,OAAO,MAAK;AACV,YAAA,QAAQ,CAAC,mBAAmB,CAAC,uBAAuB,EAAE,sBAAsB,CAAC;AAC/E,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAER,IAAA,MAAM,eAAe,GAAWC,iBAAW,CAAC,CAAC,KAAK,KAAI;;QAEpD,qBAAqB,CAAC,MAAK;YACzB,WAAW,CAAC,KAAK,CAAC;AACpB,QAAA,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,OAAO,CAAC,QAAQ,EAAE,eAAe,CAAC;AACpC;;;;"}
|
|
@@ -11,29 +11,23 @@ const useDisclosure = (id) => {
|
|
|
11
11
|
detail: { id, expanded },
|
|
12
12
|
}));
|
|
13
13
|
}, [expanded, id]);
|
|
14
|
-
const handleDisclosureChange = useCallback((e) => {
|
|
15
|
-
if (id === e.detail.id) {
|
|
16
|
-
setExpanded(e.detail.expanded);
|
|
17
|
-
}
|
|
18
|
-
}, [id]);
|
|
19
14
|
useEffect(() => {
|
|
15
|
+
const handleDisclosureChange = (e) => {
|
|
16
|
+
if (id === e.detail.id) {
|
|
17
|
+
setExpanded(e.detail.expanded);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
20
|
document.addEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange);
|
|
21
21
|
return () => {
|
|
22
22
|
document.removeEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange);
|
|
23
23
|
};
|
|
24
|
-
}, [id
|
|
24
|
+
}, [id]);
|
|
25
25
|
const safeSetExpanded = useCallback((value) => {
|
|
26
26
|
// DisclosureTrigger と DisclosureContent のレンダリング順序に影響しないように animation frame を待ってから state を更新する
|
|
27
27
|
requestAnimationFrame(() => {
|
|
28
|
-
|
|
29
|
-
const newValue = value(expanded);
|
|
30
|
-
setExpanded(newValue);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
setExpanded(value);
|
|
34
|
-
}
|
|
28
|
+
setExpanded(value);
|
|
35
29
|
});
|
|
36
|
-
}, [
|
|
30
|
+
}, []);
|
|
37
31
|
return [expanded, safeSetExpanded];
|
|
38
32
|
};
|
|
39
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDisclosure.js","sources":["../../../src/components/Disclosure/useDisclosure.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react'\n\nconst DISCLOSURE_CHANGE_EVENT = 'smarthr-ui:disclosure-change'\ntype DisclosureChangeEventDetail = { id: string; expanded: boolean }\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface DocumentEventMap {\n [DISCLOSURE_CHANGE_EVENT]: CustomEvent<DisclosureChangeEventDetail>\n }\n}\n\ntype Setter = (value: boolean | ((prev: boolean) => boolean)) => void\n\ntype UseDisclosureResult = [expanded: boolean, setExpanded: Setter]\n\n/**\n * 同じ `id` で呼ぶとイベント経由で状態が同期される custom hook\n */\nexport const useDisclosure = (id: string): UseDisclosureResult => {\n const [expanded, setExpanded] = useState(false)\n\n useEffect(() => {\n document.dispatchEvent(\n new CustomEvent<DisclosureChangeEventDetail>(DISCLOSURE_CHANGE_EVENT, {\n detail: { id, expanded },\n }),\n )\n }, [expanded, id])\n\n const handleDisclosureChange =
|
|
1
|
+
{"version":3,"file":"useDisclosure.js","sources":["../../../src/components/Disclosure/useDisclosure.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react'\n\nconst DISCLOSURE_CHANGE_EVENT = 'smarthr-ui:disclosure-change'\ntype DisclosureChangeEventDetail = { id: string; expanded: boolean }\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface DocumentEventMap {\n [DISCLOSURE_CHANGE_EVENT]: CustomEvent<DisclosureChangeEventDetail>\n }\n}\n\ntype Setter = (value: boolean | ((prev: boolean) => boolean)) => void\n\ntype UseDisclosureResult = [expanded: boolean, setExpanded: Setter]\n\n/**\n * 同じ `id` で呼ぶとイベント経由で状態が同期される custom hook\n */\nexport const useDisclosure = (id: string): UseDisclosureResult => {\n const [expanded, setExpanded] = useState(false)\n\n useEffect(() => {\n document.dispatchEvent(\n new CustomEvent<DisclosureChangeEventDetail>(DISCLOSURE_CHANGE_EVENT, {\n detail: { id, expanded },\n }),\n )\n }, [expanded, id])\n\n useEffect(() => {\n const handleDisclosureChange = (e: CustomEvent<DisclosureChangeEventDetail>) => {\n if (id === e.detail.id) {\n setExpanded(e.detail.expanded)\n }\n }\n\n document.addEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange)\n return () => {\n document.removeEventListener(DISCLOSURE_CHANGE_EVENT, handleDisclosureChange)\n }\n }, [id])\n\n const safeSetExpanded: Setter = useCallback((value) => {\n // DisclosureTrigger と DisclosureContent のレンダリング順序に影響しないように animation frame を待ってから state を更新する\n requestAnimationFrame(() => {\n setExpanded(value)\n })\n }, [])\n\n return [expanded, safeSetExpanded]\n}\n"],"names":[],"mappings":";;AAEA,MAAM,uBAAuB,GAAG,8BAA8B;AAc9D;;AAEG;AACI,MAAM,aAAa,GAAG,CAAC,EAAU,KAAyB;IAC/D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAE/C,SAAS,CAAC,MAAK;AACb,QAAA,QAAQ,CAAC,aAAa,CACpB,IAAI,WAAW,CAA8B,uBAAuB,EAAE;AACpE,YAAA,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;AACzB,SAAA,CAAC,CACH;AACH,IAAA,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAElB,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,sBAAsB,GAAG,CAAC,CAA2C,KAAI;YAC7E,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE;AACtB,gBAAA,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YAChC;AACF,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,gBAAgB,CAAC,uBAAuB,EAAE,sBAAsB,CAAC;AAC1E,QAAA,OAAO,MAAK;AACV,YAAA,QAAQ,CAAC,mBAAmB,CAAC,uBAAuB,EAAE,sBAAsB,CAAC;AAC/E,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAER,IAAA,MAAM,eAAe,GAAW,WAAW,CAAC,CAAC,KAAK,KAAI;;QAEpD,qBAAqB,CAAC,MAAK;YACzB,WAAW,CAAC,KAAK,CAAC;AACpB,QAAA,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,OAAO,CAAC,QAAQ,EAAE,eAAe,CAAC;AACpC;;;;"}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var React = require('react');
|
|
6
|
-
var index$1 = require('../../../_virtual/index3.cjs');
|
|
7
6
|
var index = require('./../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.cjs');
|
|
8
7
|
var hooks_useObjectAttributes = require('../../../hooks/useObjectAttributes.cjs');
|
|
9
8
|
require('../../../intl/IntlProvider.cjs');
|
|
@@ -78,7 +77,7 @@ const TriggerLabelText = React.memo(({ children, onlyIconTrigger }) => {
|
|
|
78
77
|
return children;
|
|
79
78
|
}
|
|
80
79
|
const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || components_Icon_FaIcon.FaEllipsisIcon;
|
|
81
|
-
return jsxRuntime.jsx(Icon, { alt:
|
|
80
|
+
return jsxRuntime.jsx(Icon, { alt: children });
|
|
82
81
|
});
|
|
83
82
|
const renderButtonList = (children) => React.Children.map(children, (item) => {
|
|
84
83
|
if (!item || !React.isValidElement(item)) {
|
|
@@ -111,6 +110,9 @@ const ButtonListItem = ({ children }) => {
|
|
|
111
110
|
observer.observe(listItem, {
|
|
112
111
|
childList: true,
|
|
113
112
|
subtree: true,
|
|
113
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
114
|
+
attributes: true,
|
|
115
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
114
116
|
});
|
|
115
117
|
return () => {
|
|
116
118
|
observer.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenuButton.cjs","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport
|
|
1
|
+
{"version":3,"file":"DropdownMenuButton.cjs","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../../hooks/useObjectAttributes'\nimport { useIntl } from '../../../intl'\nimport { type AnchorButton, Button, type AbstractProps as ButtonProps } from '../../Button'\nimport { FaCaretDownIcon, FaEllipsisIcon } from '../../Icon'\nimport { Dropdown, DropdownContext } from '../Dropdown'\nimport { DropdownCloser } from '../DropdownCloser'\nimport { DropdownContent } from '../DropdownContent'\nimport { DropdownTrigger } from '../DropdownTrigger'\n\nimport { DropdownMenuGroup } from './DropdownMenuGroup'\nimport useKeyboardNavigation from './useKeyboardNavigation'\n\nimport type { RemoteDialogTrigger } from '../../Dialog'\n\ntype Actions = ActionItem | ActionItem[]\n\n// これでコンポーネントを絞れるわけではないが Button[variant=text] を使ってほしいんだよ! という気持ち\ntype ActionItem =\n | ReactElement<ComponentProps<typeof Button>>\n | ReactElement<ComponentProps<typeof AnchorButton>>\n | ReactElement<ComponentProps<typeof RemoteDialogTrigger>>\n | ReactNode\n\ntype ObjectTriggerType = {\n /** 引き金となるボタンラベル */\n children: ReactNode\n /** 引き金となるボタンの大きさ */\n size?: ButtonProps['size']\n /** 引き金となるボタンをアイコンのみとするかどうか */\n onlyIcon?:\n | boolean\n | {\n /** 引き金となるアイコンを差し替えたい場合(onlyIcon=true の場合のみ有効) */\n component?: ComponentType<ComponentProps<typeof FaCaretDownIcon>>\n }\n}\ntype AbstractProps = {\n /** 引き金となるボタン */\n trigger: ReactNode | ObjectTriggerType\n /** 操作群 */\n children: Actions\n /** ドロップダウンメニューが開かれた際のイベント */\n onOpen?: () => void\n /** ドロップダウンメニューが閉じられた際のイベント */\n onClose?: () => void\n}\ntype ElementProps = Omit<ComponentPropsWithRef<'button'>, keyof AbstractProps>\ntype Props = AbstractProps & ElementProps\n\nconst triggerObjectConverter = (trigger: ReactNode) => ({\n children: trigger,\n})\n\nconst classNameGenerator = tv({\n slots: {\n triggerWrapper: 'smarthr-ui-DropdownMenuButton',\n triggerButton:\n 'smarthr-ui-DropdownMenuButton-trigger [&[aria-expanded=\"true\"]_.smarthr-ui-Icon:last-child]:shr-rotate-180',\n actionList: [\n 'smarthr-ui-DropdownMenuButton-panel',\n 'shr-list-none shr-py-0.5',\n [\n /* unset した Button の右 padding 分 */\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-pe-1',\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-gap-x-0.5',\n '[&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-w-[unset] [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-bg-transparent [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-pe-[unset]',\n ],\n ],\n actionListItemButton: [\n // HINT: 実際にレンダリングされた要素のclassに対して追加されるため、優先度を上げる必要がある\n '[&&]:shr-w-full [&&]:shr-justify-start [&&]:shr-rounded-none [&&]:shr-border-none [&&]:shr-py-0.5 [&&]:shr-font-normal',\n '[&&]:focus-visible:shr-focus-indicator',\n ],\n },\n})\n\nconst { triggerWrapper, triggerButton, actionList, actionListItemButton } = classNameGenerator()\n\nexport const DropdownMenuButton: FC<Props> = ({\n trigger,\n children,\n onOpen,\n onClose,\n className,\n ...rest\n}) => {\n const {\n children: triggerChildren,\n size: triggerSize,\n onlyIcon: onlyIconTrigger,\n } = useObjectAttributes<ReactNode | ObjectTriggerType, ObjectTriggerType>(\n trigger,\n triggerObjectConverter,\n )\n\n const containerRef = useRef<HTMLUListElement>(null)\n\n useKeyboardNavigation(containerRef)\n\n const classNames = useMemo(\n () => ({\n triggerWrapper: triggerWrapper({ className }),\n triggerButton: triggerButton(),\n actionList: actionList(),\n }),\n [className],\n )\n\n return (\n <Dropdown onOpen={onOpen} onClose={onClose}>\n <MemoizedTriggerButton\n {...rest}\n onlyIconTrigger={onlyIconTrigger}\n triggerSize={triggerSize}\n classNames={classNames}\n >\n {triggerChildren}\n </MemoizedTriggerButton>\n <DropdownContent controllable={true}>\n <menu ref={containerRef} role=\"menu\" className={classNames.actionList}>\n {renderButtonList(children)}\n </menu>\n </DropdownContent>\n </Dropdown>\n )\n}\n\nconst MemoizedTriggerButton = memo<\n ElementProps & {\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n triggerSize: ObjectTriggerType['size']\n children: ObjectTriggerType['children']\n classNames: {\n triggerWrapper: string\n triggerButton: string\n }\n }\n>(({ onlyIconTrigger, triggerSize, children, classNames, ...rest }) => {\n const { localize } = useIntl()\n\n const { active } = useContext(DropdownContext)\n\n return (\n <DropdownTrigger\n className={classNames.triggerWrapper}\n tooltip={{ show: !!onlyIconTrigger, message: children }}\n >\n <Button\n {...rest}\n suffix={\n !onlyIconTrigger && (\n <FaCaretDownIcon\n alt={\n active\n ? localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerActive',\n defaultText: '候補を閉じる',\n })\n : localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerInactive',\n defaultText: '候補を開く',\n })\n }\n />\n )\n }\n size={triggerSize}\n className={classNames.triggerButton}\n >\n <TriggerLabelText onlyIconTrigger={onlyIconTrigger}>{children}</TriggerLabelText>\n </Button>\n </DropdownTrigger>\n )\n})\n\nconst TriggerLabelText = memo<{\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n children: ObjectTriggerType['children']\n}>(({ children, onlyIconTrigger }) => {\n if (!onlyIconTrigger) {\n return children\n }\n\n const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || FaEllipsisIcon\n\n return <Icon alt={children} />\n})\n\nexport const renderButtonList = (children: Actions) =>\n Children.map(children, (item): ReactNode => {\n if (!item || !isValidElement(item)) {\n return null\n }\n\n switch (item.type) {\n case Fragment:\n return renderButtonList(item.props.children)\n case DropdownMenuGroup:\n return item\n }\n\n return <ButtonListItem>{item}</ButtonListItem>\n })\n\nconst ButtonListItem: FC<{ children: ReactElement }> = ({ children }) => {\n const ref = useRef<HTMLLIElement>(null)\n\n useEffect(() => {\n const listItem = ref.current\n if (!listItem) {\n return\n }\n\n const setupButton = () => {\n const button = listItem.querySelector('button,a')\n\n if (button) {\n button.setAttribute('role', 'menuitem')\n button.setAttribute(\n 'class',\n actionListItemButton({ className: button.getAttribute('class') }),\n )\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n observer.observe(listItem, {\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 }\n }, [])\n\n return (\n <li role=\"presentation\" ref={ref}>\n <DropdownCloser>{children}</DropdownCloser>\n </li>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAqEA;AACE;AACD;AAED;AACE;AACE;AACA;AAEA;;;AAGE;;;;;AAKC;AACF;AACD;;;;AAIC;AACF;AACF;AAED;;;AAmBE;;AAIA;AAEI;;;AAGD;AAIH;AAiBF;AAEA;AAWE;;AAIA;;AAaoB;AACA;;;AAGA;AACA;;AAatB;AAEA;;AAKI;;AAGF;AAEA;AACF;AAEO;;AAGD;;AAGF;AACE;;AAEA;AACE;;AAGJ;AACF;AAEF;AACE;;AAGE;;;;;;;AASI;AACA;;AAKJ;AAEA;AAEA;AACA;AACE;AACA;;AAEA;AACA;AACD;AAED;;AAEA;;AAGF;AAKF;;;"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
3
|
import { memo, useContext, useRef, useMemo, Children, isValidElement, Fragment, useEffect } from 'react';
|
|
4
|
-
import innerText from '../../../_virtual/index3.js';
|
|
5
4
|
import { tv as ce } from './../../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
|
|
6
5
|
import { useObjectAttributes } from '../../../hooks/useObjectAttributes.js';
|
|
7
6
|
import '../../../intl/IntlProvider.js';
|
|
@@ -76,7 +75,7 @@ const TriggerLabelText = memo(({ children, onlyIconTrigger }) => {
|
|
|
76
75
|
return children;
|
|
77
76
|
}
|
|
78
77
|
const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || FaEllipsisIcon;
|
|
79
|
-
return jsx(Icon, { alt:
|
|
78
|
+
return jsx(Icon, { alt: children });
|
|
80
79
|
});
|
|
81
80
|
const renderButtonList = (children) => Children.map(children, (item) => {
|
|
82
81
|
if (!item || !isValidElement(item)) {
|
|
@@ -109,6 +108,9 @@ const ButtonListItem = ({ children }) => {
|
|
|
109
108
|
observer.observe(listItem, {
|
|
110
109
|
childList: true,
|
|
111
110
|
subtree: true,
|
|
111
|
+
// button要素の disabled / aria-disabled が動的に変化した場合も検知してリスナーを貼り直す
|
|
112
|
+
attributes: true,
|
|
113
|
+
attributeFilter: ['disabled', 'aria-disabled'],
|
|
112
114
|
});
|
|
113
115
|
return () => {
|
|
114
116
|
observer.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenuButton.js","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport
|
|
1
|
+
{"version":3,"file":"DropdownMenuButton.js","sources":["../../../../src/components/Dropdown/DropdownMenuButton/DropdownMenuButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Children,\n type ComponentProps,\n type ComponentPropsWithRef,\n type ComponentType,\n type FC,\n Fragment,\n type ReactElement,\n type ReactNode,\n isValidElement,\n memo,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useObjectAttributes } from '../../../hooks/useObjectAttributes'\nimport { useIntl } from '../../../intl'\nimport { type AnchorButton, Button, type AbstractProps as ButtonProps } from '../../Button'\nimport { FaCaretDownIcon, FaEllipsisIcon } from '../../Icon'\nimport { Dropdown, DropdownContext } from '../Dropdown'\nimport { DropdownCloser } from '../DropdownCloser'\nimport { DropdownContent } from '../DropdownContent'\nimport { DropdownTrigger } from '../DropdownTrigger'\n\nimport { DropdownMenuGroup } from './DropdownMenuGroup'\nimport useKeyboardNavigation from './useKeyboardNavigation'\n\nimport type { RemoteDialogTrigger } from '../../Dialog'\n\ntype Actions = ActionItem | ActionItem[]\n\n// これでコンポーネントを絞れるわけではないが Button[variant=text] を使ってほしいんだよ! という気持ち\ntype ActionItem =\n | ReactElement<ComponentProps<typeof Button>>\n | ReactElement<ComponentProps<typeof AnchorButton>>\n | ReactElement<ComponentProps<typeof RemoteDialogTrigger>>\n | ReactNode\n\ntype ObjectTriggerType = {\n /** 引き金となるボタンラベル */\n children: ReactNode\n /** 引き金となるボタンの大きさ */\n size?: ButtonProps['size']\n /** 引き金となるボタンをアイコンのみとするかどうか */\n onlyIcon?:\n | boolean\n | {\n /** 引き金となるアイコンを差し替えたい場合(onlyIcon=true の場合のみ有効) */\n component?: ComponentType<ComponentProps<typeof FaCaretDownIcon>>\n }\n}\ntype AbstractProps = {\n /** 引き金となるボタン */\n trigger: ReactNode | ObjectTriggerType\n /** 操作群 */\n children: Actions\n /** ドロップダウンメニューが開かれた際のイベント */\n onOpen?: () => void\n /** ドロップダウンメニューが閉じられた際のイベント */\n onClose?: () => void\n}\ntype ElementProps = Omit<ComponentPropsWithRef<'button'>, keyof AbstractProps>\ntype Props = AbstractProps & ElementProps\n\nconst triggerObjectConverter = (trigger: ReactNode) => ({\n children: trigger,\n})\n\nconst classNameGenerator = tv({\n slots: {\n triggerWrapper: 'smarthr-ui-DropdownMenuButton',\n triggerButton:\n 'smarthr-ui-DropdownMenuButton-trigger [&[aria-expanded=\"true\"]_.smarthr-ui-Icon:last-child]:shr-rotate-180',\n actionList: [\n 'smarthr-ui-DropdownMenuButton-panel',\n 'shr-list-none shr-py-0.5',\n [\n /* unset した Button の右 padding 分 */\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-pe-1',\n '[&_.smarthr-ui-Button-disabledWrapper]:shr-gap-x-0.5',\n '[&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-w-[unset] [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-bg-transparent [&_.smarthr-ui-Button-disabledWrapper_>_.smarthr-ui-Button]:shr-pe-[unset]',\n ],\n ],\n actionListItemButton: [\n // HINT: 実際にレンダリングされた要素のclassに対して追加されるため、優先度を上げる必要がある\n '[&&]:shr-w-full [&&]:shr-justify-start [&&]:shr-rounded-none [&&]:shr-border-none [&&]:shr-py-0.5 [&&]:shr-font-normal',\n '[&&]:focus-visible:shr-focus-indicator',\n ],\n },\n})\n\nconst { triggerWrapper, triggerButton, actionList, actionListItemButton } = classNameGenerator()\n\nexport const DropdownMenuButton: FC<Props> = ({\n trigger,\n children,\n onOpen,\n onClose,\n className,\n ...rest\n}) => {\n const {\n children: triggerChildren,\n size: triggerSize,\n onlyIcon: onlyIconTrigger,\n } = useObjectAttributes<ReactNode | ObjectTriggerType, ObjectTriggerType>(\n trigger,\n triggerObjectConverter,\n )\n\n const containerRef = useRef<HTMLUListElement>(null)\n\n useKeyboardNavigation(containerRef)\n\n const classNames = useMemo(\n () => ({\n triggerWrapper: triggerWrapper({ className }),\n triggerButton: triggerButton(),\n actionList: actionList(),\n }),\n [className],\n )\n\n return (\n <Dropdown onOpen={onOpen} onClose={onClose}>\n <MemoizedTriggerButton\n {...rest}\n onlyIconTrigger={onlyIconTrigger}\n triggerSize={triggerSize}\n classNames={classNames}\n >\n {triggerChildren}\n </MemoizedTriggerButton>\n <DropdownContent controllable={true}>\n <menu ref={containerRef} role=\"menu\" className={classNames.actionList}>\n {renderButtonList(children)}\n </menu>\n </DropdownContent>\n </Dropdown>\n )\n}\n\nconst MemoizedTriggerButton = memo<\n ElementProps & {\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n triggerSize: ObjectTriggerType['size']\n children: ObjectTriggerType['children']\n classNames: {\n triggerWrapper: string\n triggerButton: string\n }\n }\n>(({ onlyIconTrigger, triggerSize, children, classNames, ...rest }) => {\n const { localize } = useIntl()\n\n const { active } = useContext(DropdownContext)\n\n return (\n <DropdownTrigger\n className={classNames.triggerWrapper}\n tooltip={{ show: !!onlyIconTrigger, message: children }}\n >\n <Button\n {...rest}\n suffix={\n !onlyIconTrigger && (\n <FaCaretDownIcon\n alt={\n active\n ? localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerActive',\n defaultText: '候補を閉じる',\n })\n : localize({\n id: 'smarthr-ui/DropdownMenuButton/triggerInactive',\n defaultText: '候補を開く',\n })\n }\n />\n )\n }\n size={triggerSize}\n className={classNames.triggerButton}\n >\n <TriggerLabelText onlyIconTrigger={onlyIconTrigger}>{children}</TriggerLabelText>\n </Button>\n </DropdownTrigger>\n )\n})\n\nconst TriggerLabelText = memo<{\n onlyIconTrigger: ObjectTriggerType['onlyIcon']\n children: ObjectTriggerType['children']\n}>(({ children, onlyIconTrigger }) => {\n if (!onlyIconTrigger) {\n return children\n }\n\n const Icon = (typeof onlyIconTrigger === 'object' && onlyIconTrigger.component) || FaEllipsisIcon\n\n return <Icon alt={children} />\n})\n\nexport const renderButtonList = (children: Actions) =>\n Children.map(children, (item): ReactNode => {\n if (!item || !isValidElement(item)) {\n return null\n }\n\n switch (item.type) {\n case Fragment:\n return renderButtonList(item.props.children)\n case DropdownMenuGroup:\n return item\n }\n\n return <ButtonListItem>{item}</ButtonListItem>\n })\n\nconst ButtonListItem: FC<{ children: ReactElement }> = ({ children }) => {\n const ref = useRef<HTMLLIElement>(null)\n\n useEffect(() => {\n const listItem = ref.current\n if (!listItem) {\n return\n }\n\n const setupButton = () => {\n const button = listItem.querySelector('button,a')\n\n if (button) {\n button.setAttribute('role', 'menuitem')\n button.setAttribute(\n 'class',\n actionListItemButton({ className: button.getAttribute('class') }),\n )\n }\n }\n\n setupButton()\n\n const observer = new MutationObserver(setupButton)\n observer.observe(listItem, {\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 }\n }, [])\n\n return (\n <li role=\"presentation\" ref={ref}>\n <DropdownCloser>{children}</DropdownCloser>\n </li>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqEA;AACE;AACD;AAED;AACE;AACE;AACA;AAEA;;;AAGE;;;;;AAKC;AACF;AACD;;;;AAIC;AACF;AACF;AAED;;;AAmBE;;AAIA;AAEI;;;AAGD;AAIH;AAiBF;AAEA;AAWE;;AAIA;;AAaoB;AACA;;;AAGA;AACA;;AAatB;AAEA;;AAKI;;AAGF;AAEA;AACF;AAEO;;AAGD;;AAGF;AACE;;AAEA;AACE;;AAGJ;AACF;AAEF;AACE;;AAGE;;;;;;;AASI;AACA;;AAKJ;AAEA;AAEA;AACA;AACE;AACA;;AAEA;AACA;AACD;AAED;;AAEA;;AAGF;AAKF;;"}
|