@koine/react 2.0.0-beta.72 → 2.0.0-beta.74
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/FaviconTags.d.ts +9 -0
- package/FaviconTags.js +5 -0
- package/Meta.d.ts +5 -0
- package/Meta.js +5 -0
- package/NoJs.d.ts +3 -0
- package/NoJs.js +7 -0
- package/Polymorphic.d.ts +26 -0
- package/Polymorphic.js +1 -0
- package/calendar/CalendarDaygridCell.js +42 -0
- package/calendar/CalendarDaygridNav.js +22 -0
- package/calendar/CalendarDaygridTable.js +50 -0
- package/calendar/CalendarLegend.js +4 -0
- package/calendar/calendar-api-google.js +90 -0
- package/calendar/types.js +1 -0
- package/calendar/useCalendar.js +175 -0
- package/calendar/useDateLocale.js +16 -0
- package/calendar/utils.js +172 -0
- package/calendar.js +7 -0
- package/classed.d.ts +8 -0
- package/classed.js +41 -0
- package/components/FaviconTags.js +4 -0
- package/components/Meta.js +4 -0
- package/components/NoJs.js +6 -0
- package/createUseMediaQueryWidth.d.ts +6 -0
- package/createUseMediaQueryWidth.js +38 -0
- package/extendComponent.d.ts +16 -0
- package/extendComponent.js +9 -0
- package/forms/antispam.js +29 -0
- package/forms.js +1 -0
- package/hooks/index.js +19 -0
- package/hooks/useAsyncFn.js +25 -0
- package/hooks/useFirstMountState.js +9 -0
- package/hooks/useFixedOffset.js +41 -0
- package/hooks/useFocus.js +8 -0
- package/hooks/useInterval.js +19 -0
- package/hooks/useIsomorphicLayoutEffect.js +3 -0
- package/hooks/useKeyUp.js +15 -0
- package/hooks/useMeasure.js +118 -0
- package/hooks/useMountedState.js +12 -0
- package/hooks/useNavigateAway.js +24 -0
- package/hooks/usePrevious.js +8 -0
- package/hooks/usePreviousRef.js +8 -0
- package/hooks/useReveal.js +41 -0
- package/hooks/useScrollPosition.js +57 -0
- package/hooks/useScrollThreshold.js +25 -0
- package/hooks/useScrollTo.js +17 -0
- package/hooks/useSmoothScroll.js +31 -0
- package/hooks/useSpinDelay.js +35 -0
- package/hooks/useTraceUpdate.js +16 -0
- package/hooks/useUpdateEffect.js +10 -0
- package/hooks/useWindowSize.js +19 -0
- package/index.cjs.js +33 -23
- package/index.d.ts +28 -3
- package/index.esm.js +33 -26
- package/index.js +26 -0
- package/mergeRefs.d.ts +2 -0
- package/mergeRefs.js +13 -0
- package/package.json +3 -3
- package/types.js +1 -0
- package/useAsyncFn.d.ts +24 -0
- package/useAsyncFn.js +26 -0
- package/useFirstMountState.d.ts +2 -0
- package/useFirstMountState.js +10 -0
- package/useFixedOffset.d.ts +2 -0
- package/useFixedOffset.js +42 -0
- package/useFocus.d.ts +2 -0
- package/useFocus.js +9 -0
- package/useInterval.d.ts +2 -0
- package/useInterval.js +20 -0
- package/useIsomorphicLayoutEffect.d.ts +3 -0
- package/useIsomorphicLayoutEffect.js +4 -0
- package/useKeyUp.d.ts +2 -0
- package/useKeyUp.js +16 -0
- package/useMeasure.d.ts +22 -0
- package/useMeasure.js +119 -0
- package/useMountedState.d.ts +2 -0
- package/useMountedState.js +13 -0
- package/useNavigateAway.d.ts +3 -0
- package/useNavigateAway.js +25 -0
- package/usePrevious.d.ts +2 -0
- package/usePrevious.js +9 -0
- package/usePreviousRef.d.ts +2 -0
- package/usePreviousRef.js +9 -0
- package/useReveal.d.ts +13 -0
- package/useReveal.js +42 -0
- package/useScrollPosition.d.ts +7 -0
- package/useScrollPosition.js +58 -0
- package/useScrollThreshold.d.ts +2 -0
- package/useScrollThreshold.js +26 -0
- package/useScrollTo.d.ts +2 -0
- package/useScrollTo.js +18 -0
- package/useSmoothScroll.d.ts +2 -0
- package/useSmoothScroll.js +32 -0
- package/useSpinDelay.d.ts +2 -0
- package/useSpinDelay.js +36 -0
- package/useTraceUpdate.d.ts +2 -0
- package/useTraceUpdate.js +17 -0
- package/useUpdateEffect.d.ts +3 -0
- package/useUpdateEffect.js +11 -0
- package/useWindowSize.d.ts +3 -0
- package/useWindowSize.js +20 -0
- package/utils/Polymorphic.js +1 -0
- package/utils/classed.js +40 -0
- package/utils/createUseMediaQueryWidth.js +37 -0
- package/utils/extendComponent.js +8 -0
- package/utils/index.js +4 -0
- package/utils/mergeRefs.js +12 -0
package/useInterval.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { noop } from "@koine/utils";
|
|
3
|
+
export let useInterval = (callback, delay, deps = []) => {
|
|
4
|
+
const savedCallback = useRef();
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
savedCallback.current = callback;
|
|
7
|
+
}, [callback, ...deps]);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
function tick() {
|
|
10
|
+
if (savedCallback.current)
|
|
11
|
+
savedCallback.current();
|
|
12
|
+
}
|
|
13
|
+
if (delay !== null) {
|
|
14
|
+
const id = setInterval(tick, delay);
|
|
15
|
+
return () => clearInterval(id);
|
|
16
|
+
}
|
|
17
|
+
return noop;
|
|
18
|
+
}, [delay]);
|
|
19
|
+
};
|
|
20
|
+
export default useInterval;
|
package/useKeyUp.d.ts
ADDED
package/useKeyUp.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { on } from "@koine/dom";
|
|
3
|
+
export let useKeyUp = (callback, deps = []) => {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const listener = on(window, "keyup", (event) => {
|
|
6
|
+
if (!event.ctrlKey &&
|
|
7
|
+
!event.altKey &&
|
|
8
|
+
!event.shiftKey &&
|
|
9
|
+
!event.metaKey) {
|
|
10
|
+
callback(event);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return listener;
|
|
14
|
+
}, [callback, ...deps]);
|
|
15
|
+
};
|
|
16
|
+
export default useKeyUp;
|
package/useMeasure.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface RectReadOnly {
|
|
2
|
+
readonly x: number;
|
|
3
|
+
readonly y: number;
|
|
4
|
+
readonly width: number;
|
|
5
|
+
readonly height: number;
|
|
6
|
+
readonly top: number;
|
|
7
|
+
readonly right: number;
|
|
8
|
+
readonly bottom: number;
|
|
9
|
+
readonly left: number;
|
|
10
|
+
[key: string]: number;
|
|
11
|
+
}
|
|
12
|
+
type HTMLOrSVGElement = HTMLElement | SVGElement;
|
|
13
|
+
export type UseMeasureOptions = {
|
|
14
|
+
scroll?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type UseMeasureReturn = [
|
|
17
|
+
(element: HTMLOrSVGElement | null) => void,
|
|
18
|
+
RectReadOnly,
|
|
19
|
+
() => void
|
|
20
|
+
];
|
|
21
|
+
export declare let useMeasure: (options?: UseMeasureOptions) => UseMeasureReturn;
|
|
22
|
+
export default useMeasure;
|
package/useMeasure.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { debounce, noop } from "@koine/utils";
|
|
3
|
+
import { listenResizeDebounced, listenScrollDebounced, off, on, } from "@koine/dom";
|
|
4
|
+
let observer;
|
|
5
|
+
let findScrollContainers = (element) => {
|
|
6
|
+
const result = [];
|
|
7
|
+
if (!element || element === document.body)
|
|
8
|
+
return result;
|
|
9
|
+
const { overflow, overflowX, overflowY } = window.getComputedStyle(element);
|
|
10
|
+
if ([overflow, overflowX, overflowY].some((prop) => prop === "auto" || prop === "scroll"))
|
|
11
|
+
result.push(element);
|
|
12
|
+
return [...result, ...findScrollContainers(element.parentElement)];
|
|
13
|
+
};
|
|
14
|
+
const keys = [
|
|
15
|
+
"x",
|
|
16
|
+
"y",
|
|
17
|
+
"top",
|
|
18
|
+
"bottom",
|
|
19
|
+
"left",
|
|
20
|
+
"right",
|
|
21
|
+
"width",
|
|
22
|
+
"height",
|
|
23
|
+
];
|
|
24
|
+
const areBoundsEqual = (a, b) => keys.every((key) => a[key] === b[key]);
|
|
25
|
+
export let useMeasure = (options) => {
|
|
26
|
+
const { scroll = false } = options || {};
|
|
27
|
+
const [bounds, setBounds] = useState({
|
|
28
|
+
left: 0,
|
|
29
|
+
top: 0,
|
|
30
|
+
width: 0,
|
|
31
|
+
height: 0,
|
|
32
|
+
bottom: 0,
|
|
33
|
+
right: 0,
|
|
34
|
+
x: 0,
|
|
35
|
+
y: 0,
|
|
36
|
+
});
|
|
37
|
+
const state = useRef([
|
|
38
|
+
null,
|
|
39
|
+
null,
|
|
40
|
+
null,
|
|
41
|
+
bounds,
|
|
42
|
+
]);
|
|
43
|
+
const mounted = useRef(false);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
mounted.current = true;
|
|
46
|
+
return () => void (mounted.current = false);
|
|
47
|
+
}, []);
|
|
48
|
+
const [forceRefresh, , scrollChange] = useMemo(() => {
|
|
49
|
+
const callback = (..._args) => {
|
|
50
|
+
const [element, , , lastBounds] = state.current;
|
|
51
|
+
if (!element)
|
|
52
|
+
return;
|
|
53
|
+
const size = element.getBoundingClientRect();
|
|
54
|
+
Object.freeze(size);
|
|
55
|
+
if (mounted.current && !areBoundsEqual(lastBounds, size)) {
|
|
56
|
+
state.current[3] = size;
|
|
57
|
+
setBounds(size);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const debouncedCallback = debounce(callback);
|
|
61
|
+
return [callback, debouncedCallback, debouncedCallback];
|
|
62
|
+
}, [setBounds]);
|
|
63
|
+
function removeListeners() {
|
|
64
|
+
const [, scrollContainers, resizeObserver] = state.current;
|
|
65
|
+
if (scrollContainers) {
|
|
66
|
+
scrollContainers.forEach((element) => off(element, "scroll", scrollChange));
|
|
67
|
+
state.current[1] = null;
|
|
68
|
+
}
|
|
69
|
+
if (resizeObserver) {
|
|
70
|
+
resizeObserver.disconnect();
|
|
71
|
+
state.current[2] = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function addListeners() {
|
|
75
|
+
const [element, scrollContainers] = state.current;
|
|
76
|
+
if (!element)
|
|
77
|
+
return;
|
|
78
|
+
if (!observer && ResizeObserver) {
|
|
79
|
+
observer = new ResizeObserver(scrollChange);
|
|
80
|
+
state.current[2] = observer;
|
|
81
|
+
observer.observe(element);
|
|
82
|
+
if (scroll && scrollContainers) {
|
|
83
|
+
scrollContainers.forEach((scrollContainer) => on(scrollContainer, "scroll", scrollChange, {
|
|
84
|
+
capture: true,
|
|
85
|
+
passive: true,
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const ref = (node) => {
|
|
91
|
+
if (!node || node === state.current[0])
|
|
92
|
+
return;
|
|
93
|
+
removeListeners();
|
|
94
|
+
state.current[0] = node;
|
|
95
|
+
state.current[1] = findScrollContainers(node);
|
|
96
|
+
addListeners();
|
|
97
|
+
};
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (scroll) {
|
|
100
|
+
const listener = listenScrollDebounced(0, forceRefresh, 100);
|
|
101
|
+
return listener;
|
|
102
|
+
}
|
|
103
|
+
return noop;
|
|
104
|
+
}, [scroll, forceRefresh]);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const listener = listenResizeDebounced(0, forceRefresh, 100);
|
|
107
|
+
return listener;
|
|
108
|
+
}, [forceRefresh]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
removeListeners();
|
|
111
|
+
addListeners();
|
|
112
|
+
}, [scroll]);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
forceRefresh();
|
|
115
|
+
return removeListeners;
|
|
116
|
+
}, []);
|
|
117
|
+
return [ref, bounds, forceRefresh];
|
|
118
|
+
};
|
|
119
|
+
export default useMeasure;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
export let useMountedState = () => {
|
|
3
|
+
const mountedRef = useRef(false);
|
|
4
|
+
const get = useCallback(() => mountedRef.current, []);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
mountedRef.current = true;
|
|
7
|
+
return () => {
|
|
8
|
+
mountedRef.current = false;
|
|
9
|
+
};
|
|
10
|
+
}, []);
|
|
11
|
+
return get;
|
|
12
|
+
};
|
|
13
|
+
export default useMountedState;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { on } from "@koine/dom";
|
|
3
|
+
export let useNavigateAway = (handler) => {
|
|
4
|
+
const beforeUnloadHandlerRef = useRef();
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
beforeUnloadHandlerRef.current = (event) => {
|
|
7
|
+
const customMessageOrCondition = handler(event);
|
|
8
|
+
if (customMessageOrCondition) {
|
|
9
|
+
event.preventDefault();
|
|
10
|
+
}
|
|
11
|
+
if (typeof customMessageOrCondition === "string") {
|
|
12
|
+
return (event.returnValue = customMessageOrCondition);
|
|
13
|
+
}
|
|
14
|
+
if (event.defaultPrevented) {
|
|
15
|
+
return (event.returnValue = "");
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
};
|
|
19
|
+
}, [handler]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const listenerBeforeunload = on(window, "beforeunload", (event) => beforeUnloadHandlerRef.current?.(event));
|
|
22
|
+
return listenerBeforeunload;
|
|
23
|
+
}, []);
|
|
24
|
+
};
|
|
25
|
+
export default useNavigateAway;
|
package/usePrevious.d.ts
ADDED
package/usePrevious.js
ADDED
package/useReveal.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type UseRevealOptions = {
|
|
2
|
+
direction?: "left" | "right";
|
|
3
|
+
offsetStartY?: number;
|
|
4
|
+
offsetEndY?: number;
|
|
5
|
+
offsetStartX?: number | "all";
|
|
6
|
+
};
|
|
7
|
+
export declare let useReveal: <T extends HTMLElement = HTMLDivElement>({ direction, offsetStartY, offsetEndY, offsetStartX, }: UseRevealOptions) => {
|
|
8
|
+
ref: import("react").RefObject<T>;
|
|
9
|
+
startY: number;
|
|
10
|
+
endY: number;
|
|
11
|
+
startX: number;
|
|
12
|
+
};
|
|
13
|
+
export default useReveal;
|
package/useReveal.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
export let useReveal = ({ direction = "left", offsetStartY = -2, offsetEndY = 0, offsetStartX = "all", }) => {
|
|
3
|
+
const ref = useRef(null);
|
|
4
|
+
const [startY, setStartY] = useState(0);
|
|
5
|
+
const [endY, setEndY] = useState(0);
|
|
6
|
+
const [startX, setStartX] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!ref.current) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const rect = ref.current.getBoundingClientRect();
|
|
12
|
+
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
13
|
+
const elementHeight = rect.height;
|
|
14
|
+
const elementTop = rect.top;
|
|
15
|
+
const distanceTop = elementTop + scrollTop;
|
|
16
|
+
const offsetTop = offsetStartY ? elementHeight * offsetStartY : 0;
|
|
17
|
+
const offsetBottom = offsetEndY ? elementHeight * offsetEndY : offsetTop;
|
|
18
|
+
const startY = (distanceTop + offsetTop) / document.body.clientHeight;
|
|
19
|
+
const endY = (distanceTop + elementHeight + offsetBottom) / document.body.clientHeight;
|
|
20
|
+
let startX;
|
|
21
|
+
if (offsetStartX === "all") {
|
|
22
|
+
startX = direction === "left" ? -rect.right : rect.left;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
startX = rect.width * offsetStartX;
|
|
26
|
+
startX = direction === "left" ? -startX : startX;
|
|
27
|
+
}
|
|
28
|
+
setStartY(startY);
|
|
29
|
+
setEndY(endY);
|
|
30
|
+
setStartX(startX);
|
|
31
|
+
}, [
|
|
32
|
+
setStartY,
|
|
33
|
+
setEndY,
|
|
34
|
+
setStartX,
|
|
35
|
+
offsetStartY,
|
|
36
|
+
offsetEndY,
|
|
37
|
+
offsetStartX,
|
|
38
|
+
direction,
|
|
39
|
+
]);
|
|
40
|
+
return { ref, startY, endY, startX };
|
|
41
|
+
};
|
|
42
|
+
export default useReveal;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type Position = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
type ElementRef = React.MutableRefObject<HTMLElement | undefined>;
|
|
6
|
+
export declare let useScrollPosition: (effect: (currentPosition: Position, prevPosition: Position) => void, deps?: import("react").DependencyList, element?: ElementRef, boundingElement?: ElementRef, wait?: number) => void;
|
|
7
|
+
export default useScrollPosition;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { isBrowser } from "@koine/utils";
|
|
3
|
+
import { listenScroll } from "@koine/dom";
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
|
|
5
|
+
const zeroPosition = { x: 0, y: 0 };
|
|
6
|
+
const getClientRect = (element) => element?.getBoundingClientRect();
|
|
7
|
+
const getScrollPosition = (element, boundingElement) => {
|
|
8
|
+
if (!isBrowser) {
|
|
9
|
+
return zeroPosition;
|
|
10
|
+
}
|
|
11
|
+
if (!boundingElement) {
|
|
12
|
+
return { x: window.scrollX, y: window.scrollY };
|
|
13
|
+
}
|
|
14
|
+
const targetPosition = getClientRect(element?.current || document.body);
|
|
15
|
+
const containerPosition = getClientRect(boundingElement.current);
|
|
16
|
+
if (!targetPosition) {
|
|
17
|
+
return zeroPosition;
|
|
18
|
+
}
|
|
19
|
+
return containerPosition
|
|
20
|
+
? {
|
|
21
|
+
x: (containerPosition.x || 0) - (targetPosition.x || 0),
|
|
22
|
+
y: (containerPosition.y || 0) - (targetPosition.y || 0),
|
|
23
|
+
}
|
|
24
|
+
: { x: targetPosition.left, y: targetPosition.top };
|
|
25
|
+
};
|
|
26
|
+
export let useScrollPosition = (effect, deps = [], element, boundingElement, wait) => {
|
|
27
|
+
const position = useRef(getScrollPosition(null, boundingElement));
|
|
28
|
+
let throttleTimeout = null;
|
|
29
|
+
const callBack = () => {
|
|
30
|
+
const current = getScrollPosition(element, boundingElement);
|
|
31
|
+
effect(current, position.current);
|
|
32
|
+
position.current = current;
|
|
33
|
+
throttleTimeout = null;
|
|
34
|
+
};
|
|
35
|
+
useIsomorphicLayoutEffect(() => {
|
|
36
|
+
if (!isBrowser) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const handleScroll = () => {
|
|
40
|
+
if (wait) {
|
|
41
|
+
if (throttleTimeout === null) {
|
|
42
|
+
throttleTimeout = window.setTimeout(callBack, wait);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
callBack();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const listener = listenScroll(handleScroll, boundingElement?.current);
|
|
50
|
+
return () => {
|
|
51
|
+
listener();
|
|
52
|
+
if (throttleTimeout) {
|
|
53
|
+
clearTimeout(throttleTimeout);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, deps);
|
|
57
|
+
};
|
|
58
|
+
export default useScrollPosition;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { noop } from "@koine/utils";
|
|
3
|
+
import { listenScroll } from "@koine/dom";
|
|
4
|
+
export let useScrollThreshold = (threshold, callback) => {
|
|
5
|
+
const [isBelow, setIsBelow] = useState(false);
|
|
6
|
+
const handler = useCallback(() => {
|
|
7
|
+
if (threshold) {
|
|
8
|
+
const posY = window.scrollY;
|
|
9
|
+
const isAbove = posY < threshold;
|
|
10
|
+
const isBelow = posY > threshold;
|
|
11
|
+
setIsBelow(isBelow);
|
|
12
|
+
if (callback)
|
|
13
|
+
callback(isAbove, isBelow);
|
|
14
|
+
}
|
|
15
|
+
}, [threshold, callback]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (threshold) {
|
|
18
|
+
const listener = listenScroll(handler);
|
|
19
|
+
handler();
|
|
20
|
+
return listener;
|
|
21
|
+
}
|
|
22
|
+
return noop;
|
|
23
|
+
}, [threshold, handler]);
|
|
24
|
+
return isBelow;
|
|
25
|
+
};
|
|
26
|
+
export default useScrollThreshold;
|
package/useScrollTo.d.ts
ADDED
package/useScrollTo.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isBrowser } from "@koine/utils";
|
|
2
|
+
export let useScrollTo = (id = "", offset = 0) => {
|
|
3
|
+
if (!isBrowser) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const headerOffset = 0;
|
|
7
|
+
let element = document.getElementById(id);
|
|
8
|
+
let top = 0;
|
|
9
|
+
if (element && element.offsetParent) {
|
|
10
|
+
do {
|
|
11
|
+
top += element.offsetTop;
|
|
12
|
+
} while ((element = element.offsetParent));
|
|
13
|
+
}
|
|
14
|
+
top -= offset;
|
|
15
|
+
top -= headerOffset;
|
|
16
|
+
window.scroll(0, top);
|
|
17
|
+
};
|
|
18
|
+
export default useScrollTo;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { isNumber } from "@koine/utils";
|
|
3
|
+
import { getOffsetTopSlim, scrollTo } from "@koine/dom";
|
|
4
|
+
import { useFixedOffset } from "./useFixedOffset";
|
|
5
|
+
export let useSmoothScroll = (disregardAutomaticFixedOffset) => {
|
|
6
|
+
const fixedOffset = useFixedOffset();
|
|
7
|
+
const scroll = useCallback((to, customOffset, callback, fallbackTimeout, behavior) => {
|
|
8
|
+
let top = undefined;
|
|
9
|
+
let toIsElement = false;
|
|
10
|
+
if (isNumber(to)) {
|
|
11
|
+
top = to;
|
|
12
|
+
}
|
|
13
|
+
else if (to) {
|
|
14
|
+
const el = document.getElementById(to);
|
|
15
|
+
if (el) {
|
|
16
|
+
top = getOffsetTopSlim(el) - fixedOffset.current;
|
|
17
|
+
toIsElement = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (isNumber(top)) {
|
|
21
|
+
top =
|
|
22
|
+
top +
|
|
23
|
+
(customOffset || 0) +
|
|
24
|
+
(disregardAutomaticFixedOffset || toIsElement
|
|
25
|
+
? 0
|
|
26
|
+
: fixedOffset.current);
|
|
27
|
+
scrollTo(top, callback, fallbackTimeout, behavior);
|
|
28
|
+
}
|
|
29
|
+
}, [disregardAutomaticFixedOffset, fixedOffset]);
|
|
30
|
+
return scroll;
|
|
31
|
+
};
|
|
32
|
+
export default useSmoothScroll;
|
package/useSpinDelay.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
var State;
|
|
3
|
+
(function (State) {
|
|
4
|
+
State[State["IDLE"] = 0] = "IDLE";
|
|
5
|
+
State[State["DELAY"] = 1] = "DELAY";
|
|
6
|
+
State[State["DISPLAY"] = 2] = "DISPLAY";
|
|
7
|
+
State[State["EXPIRE"] = 3] = "EXPIRE";
|
|
8
|
+
})(State || (State = {}));
|
|
9
|
+
export let useSpinDelay = (loading, delay = 500, minDuration = 200) => {
|
|
10
|
+
const [state, setState] = useState(State.IDLE);
|
|
11
|
+
const timeout = useRef();
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (loading && state === State.IDLE) {
|
|
14
|
+
clearTimeout(timeout.current);
|
|
15
|
+
timeout.current = setTimeout(() => {
|
|
16
|
+
if (!loading) {
|
|
17
|
+
return setState(State.IDLE);
|
|
18
|
+
}
|
|
19
|
+
timeout.current = setTimeout(() => {
|
|
20
|
+
setState(State.EXPIRE);
|
|
21
|
+
}, minDuration);
|
|
22
|
+
setState(State.DISPLAY);
|
|
23
|
+
}, delay);
|
|
24
|
+
setState(State.DELAY);
|
|
25
|
+
}
|
|
26
|
+
if (!loading && state !== State.DISPLAY) {
|
|
27
|
+
clearTimeout(timeout.current);
|
|
28
|
+
setState(State.IDLE);
|
|
29
|
+
}
|
|
30
|
+
}, [loading, state, delay, minDuration]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
return () => clearTimeout(timeout.current);
|
|
33
|
+
}, []);
|
|
34
|
+
return state === State.DISPLAY || state === State.EXPIRE;
|
|
35
|
+
};
|
|
36
|
+
export default useSpinDelay;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
export let useTraceUpdate = (props) => {
|
|
3
|
+
const prev = useRef(props);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
|
|
6
|
+
if (prev.current[k] !== v) {
|
|
7
|
+
ps[k] = [prev.current[k], v];
|
|
8
|
+
}
|
|
9
|
+
return ps;
|
|
10
|
+
}, {});
|
|
11
|
+
if (Object.keys(changedProps).length > 0) {
|
|
12
|
+
console.info("[@koine/react:useTraceUpdate] changed props:", changedProps);
|
|
13
|
+
}
|
|
14
|
+
prev.current = props;
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
export default useTraceUpdate;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useFirstMountState } from "./useFirstMountState";
|
|
3
|
+
export let useUpdateEffect = (effect, deps) => {
|
|
4
|
+
const isFirstMount = useFirstMountState();
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (!isFirstMount) {
|
|
7
|
+
return effect();
|
|
8
|
+
}
|
|
9
|
+
}, deps);
|
|
10
|
+
};
|
|
11
|
+
export default useUpdateEffect;
|
package/useWindowSize.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { debounce } from "@koine/utils";
|
|
3
|
+
import { listenResize, listenResizeDebounced } from "@koine/dom";
|
|
4
|
+
export let useWindowSize = (wait, immediate) => {
|
|
5
|
+
const [width, widthSet] = useState(0);
|
|
6
|
+
const [height, heightSet] = useState(0);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const updateSize = () => {
|
|
9
|
+
widthSet(window.innerWidth);
|
|
10
|
+
heightSet(window.innerHeight);
|
|
11
|
+
};
|
|
12
|
+
const listener = wait
|
|
13
|
+
? listenResizeDebounced(0, updateSize, wait, immediate)
|
|
14
|
+
: listenResize(updateSize);
|
|
15
|
+
updateSize();
|
|
16
|
+
return listener;
|
|
17
|
+
}, [wait, immediate]);
|
|
18
|
+
return [width, height];
|
|
19
|
+
};
|
|
20
|
+
export default useWindowSize;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/utils/classed.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { createElement, forwardRef } from "react";
|
|
2
|
+
export let classed = (component) => {
|
|
3
|
+
const type = component.type || component;
|
|
4
|
+
return function (strings, ...args) {
|
|
5
|
+
const WrappedComponent = forwardRef(function (props, ref) {
|
|
6
|
+
const argResolved = args
|
|
7
|
+
.map((arg, index) => {
|
|
8
|
+
let result = "";
|
|
9
|
+
if (typeof arg === "function") {
|
|
10
|
+
result = arg(props);
|
|
11
|
+
}
|
|
12
|
+
else if (typeof arg !== "undefined") {
|
|
13
|
+
result = arg.toString();
|
|
14
|
+
}
|
|
15
|
+
return strings[index] + result;
|
|
16
|
+
})
|
|
17
|
+
.join("");
|
|
18
|
+
const isNativeHtmlElement = typeof type === "string";
|
|
19
|
+
const propsToForward = isNativeHtmlElement
|
|
20
|
+
? {}
|
|
21
|
+
: props;
|
|
22
|
+
if (isNativeHtmlElement) {
|
|
23
|
+
for (const key in props) {
|
|
24
|
+
if (!key.startsWith("$")) {
|
|
25
|
+
propsToForward[key] = props[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
let className = argResolved || strings[0];
|
|
30
|
+
className = className.match(/class="([^"]*)/)?.[1] || className;
|
|
31
|
+
className += props?.className ? " " + props?.className : "";
|
|
32
|
+
return createElement(type, {
|
|
33
|
+
...propsToForward,
|
|
34
|
+
className: className || undefined,
|
|
35
|
+
ref,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return WrappedComponent;
|
|
39
|
+
};
|
|
40
|
+
};
|