@contember/react-utils 1.2.0-rc.16 → 1.2.0-rc.17

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.
Files changed (42) hide show
  1. package/dist/development/hooks/useAutoHeightTextArea.js +41 -0
  2. package/dist/development/hooks/useAutoHeightTextArea.js.map +1 -0
  3. package/dist/development/hooks/useOnElementClickOutsideCallback.js +20 -0
  4. package/dist/development/hooks/useOnElementClickOutsideCallback.js.map +1 -0
  5. package/dist/development/hooks/useOnElementMouseEnterDelayedCallback.js +33 -0
  6. package/dist/development/hooks/useOnElementMouseEnterDelayedCallback.js.map +1 -0
  7. package/dist/development/hooks/useOnElementResize.js +28 -26
  8. package/dist/development/hooks/useOnElementResize.js.map +1 -1
  9. package/dist/development/hooks/useOnScrollWithin.js +33 -31
  10. package/dist/development/hooks/useOnScrollWithin.js.map +1 -1
  11. package/dist/development/index.js +6 -0
  12. package/dist/development/index.js.map +1 -1
  13. package/dist/production/hooks/useAutoHeightTextArea.js +41 -0
  14. package/dist/production/hooks/useAutoHeightTextArea.js.map +1 -0
  15. package/dist/production/hooks/useOnElementClickOutsideCallback.js +20 -0
  16. package/dist/production/hooks/useOnElementClickOutsideCallback.js.map +1 -0
  17. package/dist/production/hooks/useOnElementMouseEnterDelayedCallback.js +33 -0
  18. package/dist/production/hooks/useOnElementMouseEnterDelayedCallback.js.map +1 -0
  19. package/dist/production/hooks/useOnElementResize.js +28 -26
  20. package/dist/production/hooks/useOnElementResize.js.map +1 -1
  21. package/dist/production/hooks/useOnScrollWithin.js +33 -31
  22. package/dist/production/hooks/useOnScrollWithin.js.map +1 -1
  23. package/dist/production/index.js +6 -0
  24. package/dist/production/index.js.map +1 -1
  25. package/dist/types/hooks/index.d.ts +3 -0
  26. package/dist/types/hooks/index.d.ts.map +1 -1
  27. package/dist/types/hooks/useAutoHeightTextArea.d.ts +3 -0
  28. package/dist/types/hooks/useAutoHeightTextArea.d.ts.map +1 -0
  29. package/dist/types/hooks/useOnElementClickOutsideCallback.d.ts +3 -0
  30. package/dist/types/hooks/useOnElementClickOutsideCallback.d.ts.map +1 -0
  31. package/dist/types/hooks/useOnElementMouseEnterDelayedCallback.d.ts +14 -0
  32. package/dist/types/hooks/useOnElementMouseEnterDelayedCallback.d.ts.map +1 -0
  33. package/dist/types/hooks/useOnElementResize.d.ts.map +1 -1
  34. package/dist/types/hooks/useOnScrollWithin.d.ts.map +1 -1
  35. package/dist/types/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +2 -2
  37. package/src/hooks/index.ts +3 -0
  38. package/src/hooks/useAutoHeightTextArea.ts +51 -0
  39. package/src/hooks/useOnElementClickOutsideCallback.ts +23 -0
  40. package/src/hooks/useOnElementMouseEnterDelayedCallback.ts +54 -0
  41. package/src/hooks/useOnElementResize.ts +34 -31
  42. package/src/hooks/useOnScrollWithin.ts +32 -31
@@ -0,0 +1,51 @@
1
+ import { px } from '@contember/utilities'
2
+ import { useCallback, useLayoutEffect } from 'react'
3
+ import { RefObjectOrElement, unwrapRefValue } from './unwrapRefValue'
4
+ import { useOnElementResize } from './useOnElementResize'
5
+
6
+ export const useAutoHeightTextArea = (
7
+ textAreaRef: RefObjectOrElement<HTMLTextAreaElement>,
8
+ value: string,
9
+ minRows: number,
10
+ maxRows: number,
11
+ ) => {
12
+ const measure = useCallback((ref: HTMLTextAreaElement | null, minRows: number, maxRows: number, value: string) => {
13
+ if (ref) {
14
+ const rowsAttribute = ref.rows
15
+
16
+ ref.style.height = ''
17
+ ref.style.minHeight = ''
18
+ ref.style.maxHeight = ''
19
+
20
+ if (minRows !== maxRows) {
21
+ const height = ref.scrollHeight
22
+ let minHeight: number
23
+ let maxHeight: number
24
+
25
+ ref.rows = minRows
26
+ minHeight = ref.offsetHeight
27
+
28
+ if (maxRows === Infinity) {
29
+ maxHeight = Infinity
30
+ } else {
31
+ ref.rows = maxRows
32
+ maxHeight = ref.offsetHeight
33
+ }
34
+
35
+ ref.style.height = px(height)
36
+ ref.style.minHeight = px(minHeight)
37
+ ref.style.maxHeight = px(maxHeight)
38
+ }
39
+
40
+ ref.rows = rowsAttribute
41
+ }
42
+ }, [])
43
+
44
+ useOnElementResize(textAreaRef, () => {
45
+ measure(unwrapRefValue(textAreaRef), minRows, maxRows, value)
46
+ })
47
+
48
+ useLayoutEffect(() => {
49
+ measure(unwrapRefValue(textAreaRef), minRows, maxRows, value)
50
+ }, [maxRows, measure, minRows, textAreaRef, value])
51
+ }
@@ -0,0 +1,23 @@
1
+ import { useEffect } from 'react'
2
+ import { RefObjectOrElement, unwrapRefValue } from './unwrapRefValue'
3
+
4
+ export function useOnElementClickOutsideCallback(
5
+ refOrElement: RefObjectOrElement<HTMLElement>,
6
+ callback: (event: MouseEvent) => void,
7
+ ): void {
8
+ useEffect(() => {
9
+ const refValue = unwrapRefValue(refOrElement)
10
+
11
+ const handleClickOutside = (event: MouseEvent) => {
12
+ if (refValue && !refValue.contains(event.target as Node)) {
13
+ callback(event)
14
+ }
15
+ }
16
+
17
+ document.addEventListener('mousedown', handleClickOutside)
18
+
19
+ return () => {
20
+ document.removeEventListener('mousedown', handleClickOutside)
21
+ }
22
+ }, [callback, refOrElement])
23
+ }
@@ -0,0 +1,54 @@
1
+ import { useEffect } from 'react'
2
+ import { RefObjectOrElement, unwrapRefValue } from './unwrapRefValue'
3
+
4
+ /**
5
+ * Calls the callback when the mouse enters the element, but only after a timeout, e.g. for tooltips or dropdown menus.
6
+ *
7
+ * Calls the callback immediately if the mouse is pressed down with `event.type` of `mousedown`. This is useful for
8
+ * dropdown menus, where the user may click on the menu item before the callback is called resulting in the menu
9
+ * opening and closing immediately.
10
+ *
11
+ * @param refOrElement - The element or ref to the element to attach the event listener to.
12
+ * @param callback - The callback to call when the mouse enters the element.
13
+ * @param timeoutMs - The timeout in milliseconds to wait before calling the callback.
14
+ */
15
+ export function useOnElementMouseEnterDelayedCallback(
16
+ refOrElement: RefObjectOrElement<HTMLElement>,
17
+ callback: (event: MouseEvent) => void,
18
+ timeoutMs: number = 300,
19
+ ): void {
20
+ useEffect(() => {
21
+ const refValue = unwrapRefValue(refOrElement)
22
+
23
+ if (refValue) {
24
+ let timeoutID: ReturnType<typeof setTimeout> | undefined = undefined
25
+
26
+ function handleMouseEnter(event: MouseEvent) {
27
+ clearTimeout(timeoutID)
28
+
29
+ timeoutID = setTimeout(() => {
30
+ callback(event)
31
+ }, timeoutMs)
32
+ }
33
+
34
+ function handleMouseLeave() {
35
+ clearTimeout(timeoutID)
36
+ }
37
+
38
+ function handleMouseDown(event: MouseEvent) {
39
+ clearTimeout(timeoutID)
40
+ callback(event)
41
+ }
42
+
43
+ refValue.addEventListener('mouseenter', handleMouseEnter)
44
+ refValue.addEventListener('mouseleave', handleMouseLeave)
45
+ refValue.addEventListener('mousedown', handleMouseDown)
46
+
47
+ return () => {
48
+ refValue.removeEventListener('mouseover', handleMouseEnter)
49
+ refValue.removeEventListener('mouseleave', handleMouseLeave)
50
+ refValue.removeEventListener('mousedown', handleMouseDown)
51
+ }
52
+ }
53
+ }, [callback, refOrElement, timeoutMs])
54
+ }
@@ -11,42 +11,45 @@ export function useOnElementResize(
11
11
  const scopedConsoleRef = useScopedConsoleRef('useOnElementResize')
12
12
 
13
13
  const { box = 'border-box' } = options
14
- const element = unwrapRefValue(refOrElement)
15
14
  const callbackRef = useRef(callback); callbackRef.current = callback
16
15
  const lastTimeStamp = useRef<number>(0)
17
16
 
18
17
  useLayoutEffect(() => {
19
- let timeoutID: number | undefined = undefined
20
-
21
- function debouncedOnChange([entry]: ResizeObserverEntry[]) {
22
- const timeStamp = Date.now()
23
- const delta = timeStamp - lastTimeStamp.current
24
-
25
- if (delta > timeout) {
26
- scopedConsoleRef.current.warned('element.resize:immediate', null)
27
- callbackRef.current(entry)
28
- lastTimeStamp.current = timeStamp
18
+ const element = unwrapRefValue(refOrElement)
19
+
20
+ if (element) {
21
+ if (element instanceof HTMLElement) {
22
+ let timeoutID: number | undefined = undefined
23
+
24
+ function debouncedOnChange([entry]: ResizeObserverEntry[]) {
25
+ const timeStamp = Date.now()
26
+ const delta = timeStamp - lastTimeStamp.current
27
+
28
+ if (delta > timeout) {
29
+ scopedConsoleRef.current.warned('element.resize:immediate', null)
30
+ callbackRef.current(entry)
31
+ lastTimeStamp.current = timeStamp
32
+ } else {
33
+ clearTimeout(timeoutID)
34
+ timeoutID = setTimeout(() => {
35
+ scopedConsoleRef.current.warned('element.resize:debounced', null)
36
+ callbackRef.current(entry)
37
+ lastTimeStamp.current = timeStamp
38
+ }, timeout)
39
+ }
40
+ }
41
+
42
+ const resizeObserver = new ResizeObserver(debouncedOnChange)
43
+
44
+ resizeObserver.observe(element, { box })
45
+
46
+ return () => {
47
+ clearTimeout(timeoutID)
48
+ resizeObserver.unobserve(element)
49
+ }
29
50
  } else {
30
- clearTimeout(timeoutID)
31
- timeoutID = setTimeout(() => {
32
- scopedConsoleRef.current.warned('element.resize:debounced', null)
33
- callbackRef.current(entry)
34
- lastTimeStamp.current = timeStamp
35
- }, timeout)
36
- }
37
- }
38
-
39
- if (element instanceof HTMLElement) {
40
- const resizeObserver = new ResizeObserver(debouncedOnChange)
41
-
42
- resizeObserver.observe(element, { box })
43
-
44
- return () => {
45
- clearTimeout(timeoutID)
46
- resizeObserver.unobserve(element)
51
+ throw new Error('Exhaustive error: Expecting element to be instance of HTMLElement')
47
52
  }
48
- } else if (element) {
49
- throw new Error('Exhaustive error: Expecting element to be instance of HTMLElement')
50
53
  }
51
- }, [box, element, scopedConsoleRef, timeout])
54
+ }, [box, refOrElement, scopedConsoleRef, timeout])
52
55
  }
@@ -19,49 +19,50 @@ export function useOnScrollWithin(
19
19
  const callbackRef = useRef(callback); callbackRef.current = callback
20
20
  const lastTimeStamp = useRef<number>(0)
21
21
 
22
- const element = unwrapRefValue(refOrElement)
23
-
24
22
  useLayoutEffect(() => {
23
+ const element = unwrapRefValue(refOrElement)
24
+
25
25
  let timeoutID: number | undefined = undefined
26
26
 
27
- function debouncedHandler(event: Event) {
28
- clearTimeout(timeoutID)
27
+ if (element) {
28
+ function debouncedHandler(event: Event) {
29
+ clearTimeout(timeoutID)
29
30
 
30
- const delta = event.timeStamp - lastTimeStamp.current
31
- scopedConsoleRef.current.logged('event:element.scroll:delta', delta)
31
+ const delta = event.timeStamp - lastTimeStamp.current
32
+ scopedConsoleRef.current.logged('event:element.scroll:delta', delta)
32
33
 
33
- if (delta > interval) {
34
- scopedConsoleRef.current.warned('event:element.scroll:immediately', event)
35
- callbackRef.current(event)
36
- lastTimeStamp.current = event.timeStamp
37
- } else {
38
- timeoutID = setTimeout(() => {
39
- scopedConsoleRef.current.warned('event:element.scroll:debounced', event)
40
- lastTimeStamp.current = event.timeStamp
34
+ if (delta > interval) {
35
+ scopedConsoleRef.current.warned('event:element.scroll:immediately', event)
41
36
  callbackRef.current(event)
42
- }, interval)
37
+ lastTimeStamp.current = event.timeStamp
38
+ } else {
39
+ timeoutID = setTimeout(() => {
40
+ scopedConsoleRef.current.warned('event:element.scroll:debounced', event)
41
+ lastTimeStamp.current = event.timeStamp
42
+ callbackRef.current(event)
43
+ }, interval)
44
+ }
43
45
  }
44
- }
45
46
 
46
- if (element && element instanceof HTMLElement) {
47
- if (element instanceof HTMLBodyElement || element instanceof HTMLHtmlElement) {
48
- window.addEventListener('scroll', debouncedHandler, { capture: true, passive: true })
49
- window.addEventListener('resize', debouncedHandler)
47
+ if (element instanceof HTMLElement) {
48
+ if (element instanceof HTMLBodyElement || element instanceof HTMLHtmlElement) {
49
+ window.addEventListener('scroll', debouncedHandler, { capture: true, passive: true })
50
+ window.addEventListener('resize', debouncedHandler)
50
51
 
51
- return () => {
52
- window.removeEventListener('scroll', debouncedHandler)
53
- window.removeEventListener('resize', debouncedHandler)
54
- }
55
- } else {
56
- element.addEventListener('scroll', debouncedHandler, { passive: true, capture: true })
52
+ return () => {
53
+ window.removeEventListener('scroll', debouncedHandler)
54
+ window.removeEventListener('resize', debouncedHandler)
55
+ }
56
+ } else {
57
+ element.addEventListener('scroll', debouncedHandler, { passive: true, capture: true })
57
58
 
58
- return () => {
59
- element.removeEventListener('scroll', debouncedHandler)
59
+ return () => {
60
+ element.removeEventListener('scroll', debouncedHandler)
61
+ }
60
62
  }
61
63
  }
62
- } else if (element) {
64
+ } else {
63
65
  throw new Error('Exhaustive error: Expecting element to be instance of HTMLElement')
64
66
  }
65
-
66
- }, [element, interval, scopedConsoleRef])
67
+ }, [interval, refOrElement, scopedConsoleRef])
67
68
  }