Skip to content

Commit d944b84

Browse files
committed
main 🧊 rework use mouse, use scroll
1 parent dc04e4f commit d944b84

File tree

14 files changed

+746
-181
lines changed

14 files changed

+746
-181
lines changed

‎packages/core/src/bundle/hooks/useBatchedCallback/useBatchedCallback.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ import { useMemo, useRef } from 'react';
1414
* const batched = useBatchedCallback((batch) => console.log(batch), 5);
1515
*/
1616
export const useBatchedCallback = (callback, size) => {
17-
const callbackRef = useRef(callback);
17+
const internalCallbackRef = useRef(callback);
1818
const sizeRef = useRef(size);
1919
const queueRef = useRef([]);
20-
callbackRef.current = callback;
20+
internalCallbackRef.current = callback;
2121
sizeRef.current = size;
2222
const flush = () => {
2323
if (!queueRef.current.length) return;
2424
const batch = queueRef.current;
2525
queueRef.current = [];
26-
callbackRef.current(batch);
26+
internalCallbackRef.current(batch);
2727
};
2828
const batched = useMemo(() => {
2929
const batchedCallback = (...args) => {

‎packages/core/src/bundle/hooks/useField/useField.js‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ import { useRerender } from '../useRerender/useRerender';
1919
* @example
2020
* const { register, getValue, setValue, reset, dirty, error, setError, clearError, touched, focus, watch } = useField();
2121
*/
22-
export const useField = (params) => {
23-
const initialValue = params?.initialValue ?? '';
22+
export const useField = (initialValue, options) => {
2423
const inputRef = useRef(null);
2524
const watchingRef = useRef(false);
2625
const rerender = useRerender();
2726
const [dirty, setDirty] = useState(false);
28-
const [touched, setTouched] = useState(params?.initialTouched ?? false);
27+
const [touched, setTouched] = useState(options?.initialTouched ?? false);
2928
const [error, setError] = useState(undefined);
3029
const getValue = () => {
3130
if (inputRef.current?.type === 'radio' || inputRef.current?.type === 'checkbox')
@@ -76,29 +75,29 @@ export const useField = (params) => {
7675
const register = (registerParams) => ({
7776
ref: (node) => {
7877
if (!inputRef.current && node) {
79-
if (params?.autoFocus) node.focus();
78+
if (options?.autoFocus) node.focus();
8079
inputRef.current = node;
8180
if (inputRef.current.type === 'radio') {
82-
inputRef.current.defaultChecked = params?.initialValue === node.value;
81+
inputRef.current.defaultChecked = initialValue === node.value;
8382
return;
8483
}
8584
if (inputRef.current.type === 'checkbox') {
86-
inputRef.current.defaultChecked = !!params?.initialValue;
85+
inputRef.current.defaultChecked = !!initialValue;
8786
return;
8887
}
8988
inputRef.current.defaultValue = String(initialValue);
90-
if (registerParams && params?.validateOnMount) validate(registerParams);
89+
if (registerParams && options?.validateOnMount) validate(registerParams);
9190
}
9291
},
9392
onChange: async () => {
9493
if (watchingRef.current) return rerender();
9594
if (inputRef.current.value !== initialValue) setDirty(true);
9695
if (dirty && inputRef.current.value === initialValue) setDirty(false);
97-
if (registerParams && params?.validateOnChange) await validate(registerParams);
98-
if (registerParams && params?.validateOnBlur) setError(undefined);
96+
if (registerParams && options?.validateOnChange) await validate(registerParams);
97+
if (registerParams && options?.validateOnBlur) setError(undefined);
9998
},
10099
onBlur: async () => {
101-
if (registerParams && params?.validateOnBlur) await validate(registerParams);
100+
if (registerParams && options?.validateOnBlur) await validate(registerParams);
102101
setTouched(true);
103102
}
104103
});
Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { useEffect, useState } from 'react';
1+
import { useEffect, useRef } from 'react';
22
import { isTarget } from '@/utils/helpers';
33
import { useRefState } from '../useRefState/useRefState';
4+
import { useRerender } from '../useRerender/useRerender';
45
/**
56
* @name useMouse
67
* @description - Hook that manages a mouse position
@@ -9,21 +10,24 @@ import { useRefState } from '../useRefState/useRefState';
910
*
1011
* @overload
1112
* @param {HookTarget} [target=window] The target element to manage the mouse position for
12-
* @returns {UseMouseReturn} An object with the current mouse position
13+
* @param {(value: UseMouseValue, event: Event) => void} [callback] The callback to invoke on mouse updates
14+
* @returns {UseMouseReturn} An object with mouse value controls
1315
*
1416
* @example
15-
* const { x, y, clientX, clientY, elementX, elementY, elementPositionX, elementPositionY } = useMouse(ref);
17+
* const mouse = useMouse(ref);
1618
*
1719
* @overload
1820
* @template Target The target element
19-
* @returns {UseMouseReturn & { ref: StateRef<Target> }} An object with the current mouse position and a ref
21+
* @param {(value: UseMouseValue, event: Event) => void} [callback] The callback to invoke on mouse updates
22+
* @returns {UseMouseReturn & { ref: StateRef<Target> }} An object with mouse value controls and a ref
2023
*
2124
* @example
22-
* const { ref, x, y, clientX, clientY, elementX, elementY, elementPositionX, elementPositionY } = useMouse();
25+
* const mouse = useMouse<HTMLDivElement>();
2326
*/
2427
export const useMouse = (...params) => {
2528
const target = isTarget(params[0]) ? params[0] : undefined;
26-
const [value, setValue] = useState({
29+
const callback = target ? params[1] : params[0];
30+
const snapshotRef = useRef({
2731
x: 0,
2832
y: 0,
2933
elementX: 0,
@@ -33,15 +37,32 @@ export const useMouse = (...params) => {
3337
clientX: 0,
3438
clientY: 0
3539
});
40+
const internalCallbackRef = useRef(callback);
41+
internalCallbackRef.current = callback;
42+
const watchingRef = useRef(false);
43+
const rerender = useRerender();
3644
const internalRef = useRefState();
45+
const updateValue = (nextValue, event) => {
46+
snapshotRef.current = nextValue;
47+
internalCallbackRef.current?.(nextValue, event);
48+
if (watchingRef.current) rerender();
49+
};
50+
const watch = () => {
51+
watchingRef.current = true;
52+
return snapshotRef.current;
53+
};
3754
useEffect(() => {
3855
const onMouseMove = (event) => {
3956
const element = target ? isTarget.getElement(target) : internalRef.current;
4057
const updatedValue = {
4158
x: event.pageX,
4259
y: event.pageY,
4360
clientX: event.clientX,
44-
clientY: event.clientY
61+
clientY: event.clientY,
62+
elementX: event.pageX,
63+
elementY: event.pageY,
64+
elementPositionX: 0,
65+
elementPositionY: 0
4566
};
4667
if (element) {
4768
const { left, top } = element.getBoundingClientRect();
@@ -53,29 +74,18 @@ export const useMouse = (...params) => {
5374
updatedValue.elementY = elementY;
5475
updatedValue.elementPositionX = elementPositionX;
5576
updatedValue.elementPositionY = elementPositionY;
56-
setValue((prevValue) => ({
57-
...prevValue,
58-
...updatedValue
59-
}));
60-
} else {
61-
updatedValue.elementX = event.pageX;
62-
updatedValue.elementY = event.pageY;
63-
updatedValue.elementPositionX = 0;
64-
updatedValue.elementPositionY = 0;
65-
setValue((prevValue) => ({
66-
...prevValue,
67-
...updatedValue
68-
}));
6977
}
78+
updateValue(updatedValue, event);
7079
};
71-
const onScroll = () => {
72-
setValue((prevValue) => ({
73-
...prevValue,
74-
x: prevValue.x + window.scrollX - prevValue.elementPositionX,
75-
y: prevValue.y + window.scrollY - prevValue.elementPositionY,
80+
const onScroll = (event) => {
81+
const updatedValue = {
82+
...snapshotRef.current,
83+
x: snapshotRef.current.x + window.scrollX - snapshotRef.current.elementPositionX,
84+
y: snapshotRef.current.y + window.scrollY - snapshotRef.current.elementPositionY,
7685
elementPositionX: window.scrollX,
7786
elementPositionY: window.scrollY
78-
}));
87+
};
88+
updateValue(updatedValue, event);
7989
};
8090
document.addEventListener('scroll', onScroll, { passive: true });
8191
document.addEventListener('mousemove', onMouseMove);
@@ -84,9 +94,10 @@ export const useMouse = (...params) => {
8494
document.removeEventListener('mousemove', onMouseMove);
8595
};
8696
}, [internalRef.state, target && isTarget.getRawElement(target)]);
87-
if (target) return value;
97+
if (target) return { snapshot: snapshotRef.current, watch };
8898
return {
8999
ref: internalRef,
90-
...value
100+
snapshot: snapshotRef.current,
101+
watch
91102
};
92103
};

‎packages/core/src/bundle/hooks/useScroll/useScroll.js‎

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { useEffect, useRef, useState } from 'react';
1+
import { useEffect, useRef } from 'react';
22
import { isTarget } from '@/utils/helpers';
33
import { useRefState } from '../useRefState/useRefState';
4+
import { useRerender } from '../useRerender/useRerender';
45
const ARRIVED_STATE_THRESHOLD_PIXELS = 1;
56
/**
67
* @name useScroll
@@ -57,28 +58,52 @@ const ARRIVED_STATE_THRESHOLD_PIXELS = 1;
5758
export const useScroll = (...params) => {
5859
const target = isTarget(params[0]) ? params[0] : undefined;
5960
const options = target
60-
? typeof params[1] === 'object'
61-
? params[1]
62-
: { onScroll: params[1] }
61+
? typeof params[1] === 'function'
62+
? { ...params[2], onScroll: params[1] }
63+
: params[1]
6364
: typeof params[0] === 'object'
6465
? params[0]
65-
: { onScroll: params[0] };
66+
: typeof params[0] === 'function'
67+
? { ...params[1], onScroll: params[0] }
68+
: undefined;
6669
const internalRef = useRefState();
6770
const internalOptionsRef = useRef(options);
6871
const elementRef = useRef(null);
72+
const snapshotRef = useRef({
73+
x: 0,
74+
y: 0,
75+
directions: {
76+
left: false,
77+
right: false,
78+
top: false,
79+
bottom: false
80+
},
81+
arrived: {
82+
left: true,
83+
right: false,
84+
top: true,
85+
bottom: false
86+
}
87+
});
88+
const watchingRef = useRef(false);
89+
const rerender = useRerender();
6990
internalOptionsRef.current = options;
70-
const [scrolling, setScrolling] = useState(false);
71-
const scrollPositionRef = useRef({ x: 0, y: 0 });
91+
const watch = () => {
92+
watchingRef.current = true;
93+
return snapshotRef.current;
94+
};
95+
const updateValue = (value) => {
96+
snapshotRef.current = value;
97+
if (watchingRef.current) rerender();
98+
};
7299
useEffect(() => {
73100
if (!target && !internalRef.state) return;
74101
const element = (target ? isTarget.getElement(target) : internalRef.current) ?? window;
75102
elementRef.current = element;
76103
const onScrollEnd = (event) => {
77-
setScrolling(false);
78-
options?.onStop?.(event);
104+
internalOptionsRef.current?.onStop?.(event);
79105
};
80106
const onScroll = (event) => {
81-
setScrolling(true);
82107
const target = event.target === document ? event.target.documentElement : event.target;
83108
const { display, flexDirection, direction } = target.style;
84109
const directionMultiplier = direction === 'rtl' ? -1 : 1;
@@ -95,15 +120,15 @@ export const useScroll = (...params) => {
95120
scrollTop + target.clientHeight >=
96121
target.scrollHeight - (offset?.bottom ?? 0) - ARRIVED_STATE_THRESHOLD_PIXELS;
97122
const isColumnReverse = display === 'flex' && flexDirection === 'column-reverse';
98-
const isRowReverse = display === 'flex' && flexDirection === 'column-reverse';
99-
const params = {
123+
const isRowReverse = display === 'flex' && flexDirection === 'row-reverse';
124+
const updatedValue = {
100125
x: scrollLeft,
101126
y: scrollTop,
102127
directions: {
103-
left: scrollLeft < scrollPositionRef.current.x,
104-
right: scrollLeft > scrollPositionRef.current.x,
105-
top: scrollTop < scrollPositionRef.current.y,
106-
bottom: scrollTop > scrollPositionRef.current.y
128+
left: scrollLeft < snapshotRef.current.x,
129+
right: scrollLeft > snapshotRef.current.x,
130+
top: scrollTop < snapshotRef.current.y,
131+
bottom: scrollTop > snapshotRef.current.y
107132
},
108133
arrived: {
109134
left: isRowReverse ? right : left,
@@ -112,8 +137,8 @@ export const useScroll = (...params) => {
112137
bottom: isColumnReverse ? top : bottom
113138
}
114139
};
115-
scrollPositionRef.current = { x: scrollLeft, y: scrollTop };
116-
internalOptionsRef.current?.onScroll?.(params, event);
140+
updateValue(updatedValue);
141+
internalOptionsRef.current?.onScroll?.(updatedValue, event);
117142
};
118143
element.addEventListener('scroll', onScroll);
119144
element.addEventListener('scrollend', onScrollEnd);
@@ -136,10 +161,11 @@ export const useScroll = (...params) => {
136161
const { x, y, behavior } = params ?? {};
137162
elementRef.current.scrollTo({ left: x, top: y, behavior });
138163
};
139-
if (target) return { scrollIntoView, scrollTo, scrolling };
164+
if (target) return { scrollIntoView, scrollTo, snapshot: snapshotRef.current, watch };
140165
return {
141166
ref: internalRef,
142-
scrolling,
167+
snapshot: snapshotRef.current,
168+
watch,
143169
scrollIntoView,
144170
scrollTo
145171
};

‎packages/core/src/hooks/useBatchedCallback/useBatchedCallback.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ export const useBatchedCallback = <Params extends unknown[]>(
2323
callback: (batch: Params[]) => void,
2424
size: number
2525
): BatchedCallback<Params> => {
26-
const callbackRef = useRef(callback);
26+
const internalCallbackRef = useRef(callback);
2727
const sizeRef = useRef(size);
2828
const queueRef = useRef<Params[]>([]);
2929

30-
callbackRef.current = callback;
30+
internalCallbackRef.current = callback;
3131
sizeRef.current = size;
3232

3333
const flush = () => {
3434
if (!queueRef.current.length) return;
3535
const batch = queueRef.current;
3636
queueRef.current = [];
37-
callbackRef.current(batch);
37+
internalCallbackRef.current(batch);
3838
};
3939

4040
const batched = useMemo(() => {

‎packages/core/src/hooks/useField/useField.demo.tsx‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ import { useField, useRenderCount } from '@siberiacancode/reactuse';
22

33
const Demo = () => {
44
const renderCount = useRenderCount();
5-
const nameInput = useField({
6-
initialValue: 'Dmtitry',
7-
validateOnChange: true
8-
});
9-
const sexSelect = useField({ initialValue: 'Male' });
5+
const nameInput = useField('Dmtitry', { validateOnChange: true });
6+
const sexSelect = useField('Male');
107
const aboutTextArea = useField();
118
const rememberThisComputerCheckbox = useField();
129

@@ -47,8 +44,8 @@ const Demo = () => {
4744

4845
<div>
4946
<input
50-
type='text'
5147
placeholder='Name'
48+
type='text'
5249
{...nameInput.register({
5350
required: 'field is required',
5451
minLength: {

0 commit comments

Comments
 (0)