use-resize.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { useState, useEffect, useRef } from 'react'
  2. import usePersistedState from './use-persisted-state'
  3. import { Nullable } from '../../../../types/utils'
  4. type Pos = Nullable<{
  5. x: number
  6. }>
  7. function useResizeBase(
  8. state: [Pos, React.Dispatch<React.SetStateAction<Pos>>]
  9. ) {
  10. const [mousePos, setMousePos] = state
  11. const isResizingRef = useRef(false)
  12. const handleRef = useRef<HTMLElement | null>(null)
  13. const defaultHandleStyles = useRef<React.CSSProperties>({
  14. cursor: 'col-resize',
  15. userSelect: 'none',
  16. })
  17. useEffect(() => {
  18. const handleMouseDown = function (e: MouseEvent) {
  19. if (e.button !== 0) {
  20. return
  21. }
  22. if (defaultHandleStyles.current.cursor) {
  23. document.body.style.cursor = defaultHandleStyles.current.cursor
  24. }
  25. isResizingRef.current = true
  26. }
  27. const handle = handleRef.current
  28. handle?.addEventListener('mousedown', handleMouseDown)
  29. return () => {
  30. handle?.removeEventListener('mousedown', handleMouseDown)
  31. }
  32. }, [])
  33. useEffect(() => {
  34. const handleMouseUp = function () {
  35. document.body.style.cursor = 'default'
  36. isResizingRef.current = false
  37. }
  38. document.addEventListener('mouseup', handleMouseUp)
  39. return () => {
  40. document.removeEventListener('mouseup', handleMouseUp)
  41. }
  42. }, [])
  43. useEffect(() => {
  44. const handleMouseMove = function (e: MouseEvent) {
  45. if (isResizingRef.current) {
  46. setMousePos({ x: e.clientX })
  47. }
  48. }
  49. document.addEventListener('mousemove', handleMouseMove)
  50. return () => {
  51. document.removeEventListener('mousemove', handleMouseMove)
  52. }
  53. }, [setMousePos])
  54. const getTargetProps = ({ style }: { style?: React.CSSProperties } = {}) => {
  55. return {
  56. style: {
  57. ...style,
  58. },
  59. }
  60. }
  61. const setHandleRef = (node: HTMLElement | null) => {
  62. handleRef.current = node
  63. }
  64. const getHandleProps = ({ style }: { style?: React.CSSProperties } = {}) => {
  65. if (style?.cursor) {
  66. defaultHandleStyles.current.cursor = style.cursor
  67. }
  68. return {
  69. style: {
  70. ...defaultHandleStyles.current,
  71. ...style,
  72. },
  73. ref: setHandleRef,
  74. }
  75. }
  76. return <const>{
  77. mousePos,
  78. getHandleProps,
  79. getTargetProps,
  80. }
  81. }
  82. function useResize() {
  83. const state = useState<Pos>(null)
  84. return useResizeBase(state)
  85. }
  86. function usePersistedResize({ name }: { name: string }) {
  87. const state = usePersistedState<Pos>(`resizeable-${name}`, null)
  88. return useResizeBase(state)
  89. }
  90. export { useResize, usePersistedResize }