@yamada-ui/use-clickable
Version:
Yamada UI useClickable custom hook
1 lines • 10.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { UIProps } from \"@yamada-ui/core\"\nimport type { HTMLAttributes, KeyboardEvent, MouseEvent, Ref } from \"react\"\nimport { useEventListeners } from \"@yamada-ui/use-event-listener\"\nimport { dataAttr, isTouchDevice, mergeRefs } from \"@yamada-ui/utils\"\nimport { useCallback, useState } from \"react\"\n\ntype Props<Y extends HTMLElement = HTMLElement> = Omit<\n HTMLAttributes<Y>,\n \"ref\" | \"size\" | keyof UIProps\n>\n\nexport type UseClickableProps<\n Y extends HTMLElement = HTMLElement,\n M extends Props<Y> = Props<Y>,\n> = {\n /**\n * The ref for the element.\n */\n ref?: Ref<Y>\n /**\n * Whether or not trigger click on pressing `Enter`.\n *\n * @default true\n */\n clickOnEnter?: boolean\n /**\n * Whether or not trigger click on pressing `Space`.\n *\n * @default true\n */\n clickOnSpace?: boolean\n /**\n * If `true`, the element will be disabled. It will set the `disabled` HTML attribute.\n *\n * @default false\n */\n disabled?: boolean\n /**\n * Disable the touch device behavior.\n *\n * @default true\n */\n disableTouchBehavior?: boolean\n /**\n * If `true` and isDisabled, the element will have only `aria-disabled` set to `true`.\n *\n * @default false\n */\n focusable?: boolean\n /**\n * Whether or not to focus the element when it is clicked.\n * If `true`, the element will receive focus upon click.\n *\n * @default true\n */\n focusOnClick?: boolean\n /**\n * If `true`, the element will be disabled. It will set the `disabled` HTML attribute.\n *\n * @default false\n *\n * @deprecated Use `disabled` instead.\n */\n isDisabled?: boolean\n /**\n * If `true` and isDisabled, the element will have only `aria-disabled` set to `true`.\n *\n * @default false\n *\n * @deprecated Use `focusable` instead.\n */\n isFocusable?: boolean\n} & M\n\nconst isValidElement = (\n ev: KeyboardEvent | KeyboardEvent[\"nativeEvent\"],\n): boolean => {\n const { isContentEditable, tagName } = ev.target as HTMLElement\n\n return tagName !== \"INPUT\" && tagName !== \"TEXTAREA\" && !isContentEditable\n}\n\nexport const useClickable = <\n Y extends HTMLElement = HTMLElement,\n M extends Props<Y> = Props<Y>,\n>(\n {\n ref,\n clickOnEnter = true,\n clickOnSpace = true,\n isDisabled,\n disabled = isDisabled,\n disableTouchBehavior = true,\n isFocusable,\n focusable = isFocusable,\n focusOnClick = true,\n tabIndex: _tabIndex,\n onClick,\n onKeyDown,\n onKeyUp,\n onMouseDown,\n onMouseLeave,\n onMouseOver,\n onMouseUp,\n ...props\n }: UseClickableProps<Y, M> = {} as UseClickableProps<Y, M>,\n) => {\n const [button, setButton] = useState<boolean>(true)\n const [pressed, setPressed] = useState<boolean>(false)\n\n const listeners = useEventListeners()\n\n const tabIndex = button ? _tabIndex : _tabIndex || 0\n const trulyDisabled = disabled && !focusable\n\n const refCb = (node: any) => {\n if (!node) return\n\n if (node.tagName !== \"BUTTON\") setButton(false)\n }\n\n const handleClick = useCallback(\n (ev: MouseEvent<Y>) => {\n if (disabled) {\n ev.stopPropagation()\n ev.preventDefault()\n\n return\n }\n\n if (focusOnClick) ev.currentTarget.focus()\n onClick?.(ev)\n },\n [disabled, focusOnClick, onClick],\n )\n\n const onDocumentKeyUp = useCallback(\n (ev: KeyboardEvent<Y>) => {\n if (pressed && isValidElement(ev)) {\n ev.preventDefault()\n ev.stopPropagation()\n\n setPressed(false)\n\n listeners.remove(document, \"keyup\", onDocumentKeyUp, false)\n }\n },\n [pressed, listeners],\n )\n\n const handleKeyDown = useCallback(\n (ev: KeyboardEvent<Y>) => {\n onKeyDown?.(ev)\n\n if (disabled || ev.defaultPrevented || ev.metaKey) return\n\n if (!isValidElement(ev.nativeEvent) || button) return\n\n if (clickOnSpace && ev.key === \" \") {\n ev.preventDefault()\n setPressed(true)\n }\n\n if (clickOnEnter && ev.key === \"Enter\") {\n ev.preventDefault()\n ev.currentTarget.click()\n }\n\n listeners.add(document, \"keyup\", onDocumentKeyUp, false)\n },\n [\n disabled,\n button,\n onKeyDown,\n clickOnEnter,\n clickOnSpace,\n listeners,\n onDocumentKeyUp,\n ],\n )\n\n const handleKeyUp = useCallback(\n (ev: KeyboardEvent<Y>) => {\n onKeyUp?.(ev)\n\n if (disabled || ev.defaultPrevented || ev.metaKey) return\n\n if (!isValidElement(ev.nativeEvent) || button) return\n\n if (clickOnSpace && ev.key === \" \") {\n ev.preventDefault()\n setPressed(false)\n\n ev.currentTarget.click()\n }\n },\n [clickOnSpace, button, disabled, onKeyUp],\n )\n\n const onDocumentMouseUp = useCallback(\n (ev: MouseEvent<Y>) => {\n if (ev.button !== 0) return\n\n setPressed(false)\n\n listeners.remove(document, \"mouseup\", onDocumentMouseUp, false)\n },\n [listeners],\n )\n\n const handleMouseDown = useCallback(\n (ev: MouseEvent<Y>) => {\n if (ev.button !== 0) return\n\n if (disabled) {\n ev.stopPropagation()\n ev.preventDefault()\n\n return\n }\n\n if (!button) setPressed(true)\n\n if (focusOnClick) ev.currentTarget.focus({ preventScroll: true })\n\n listeners.add(document, \"mouseup\", onDocumentMouseUp, false)\n\n onMouseDown?.(ev)\n },\n [disabled, button, focusOnClick, onMouseDown, listeners, onDocumentMouseUp],\n )\n\n const handleMouseUp = useCallback(\n (ev: MouseEvent<Y>) => {\n if (ev.button !== 0) return\n\n if (!button) setPressed(false)\n\n onMouseUp?.(ev)\n },\n [onMouseUp, button],\n )\n\n const handleMouseOver = useCallback(\n (ev: MouseEvent<Y>) => {\n if (disabled) {\n ev.preventDefault()\n\n return\n }\n\n if (disableTouchBehavior && isTouchDevice()) return\n\n onMouseOver?.(ev)\n },\n [disabled, onMouseOver, disableTouchBehavior],\n )\n\n const handleMouseLeave = useCallback(\n (ev: MouseEvent<Y>) => {\n if (pressed) {\n ev.preventDefault()\n\n setPressed(false)\n }\n\n if (disableTouchBehavior && isTouchDevice()) return\n\n onMouseLeave?.(ev)\n },\n [pressed, onMouseLeave, disableTouchBehavior],\n )\n\n if (button) {\n return {\n ...props,\n ref: mergeRefs(ref, refCb),\n type: \"button\",\n \"aria-disabled\": trulyDisabled ? undefined : disabled,\n disabled: trulyDisabled,\n onClick: handleClick,\n onKeyDown,\n onKeyUp,\n onMouseDown,\n onMouseLeave,\n onMouseOver,\n onMouseUp,\n }\n } else {\n return {\n ...props,\n ref: mergeRefs(ref, refCb),\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"data-active\": dataAttr(pressed),\n role: \"button\",\n tabIndex: trulyDisabled ? undefined : tabIndex,\n onClick: handleClick,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n onMouseDown: handleMouseDown,\n onMouseLeave: handleMouseLeave,\n onMouseOver: handleMouseOver,\n onMouseUp: handleMouseUp,\n }\n }\n}\n\nexport type UseClickableReturn = ReturnType<typeof useClickable>\n"],"mappings":"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","names":[]}