@lynx-example/design-guide 0.3.2 → 0.4.0
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/dist/force_field.lynx.bundle +0 -0
- package/dist/force_field.web.bundle +1 -1
- package/dist/gooey_effect.lynx.bundle +0 -0
- package/dist/gooey_effect.web.bundle +1 -1
- package/dist/soft_glow.lynx.bundle +0 -0
- package/lynx.config.mjs +5 -2
- package/package.json +3 -2
- package/src/force_field/field-force.ts +5 -0
- package/src/force_field/field.tsx +8 -13
- package/src/force_field/field.types.ts +5 -2
- package/src/force_field/index.tsx +4 -10
- package/src/gooey_effect/index.css +27 -17
- package/src/gooey_effect/index.tsx +1 -1
- package/src/shared/hooks/use-element-frame/index.ts +70 -0
- package/src/shared/hooks/use-pointer-axis/index.ts +90 -0
- package/src/shared/hooks/use-pointer-axis/types.ts +37 -0
- package/src/shared/hooks/use-pointer-point/index.ts +112 -0
- package/src/shared/hooks/use-pointer-field-point/index.ts +0 -85
- package/src/shared/hooks/use-pointer-interaction/index.ts +0 -100
- package/src/shared/hooks/use-pointer-interaction/types.ts +0 -30
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Gooey Effect (blur + contrast)
|
|
3
|
+
*
|
|
4
|
+
* Visual output depends on each platform’s graphics pipeline:
|
|
5
|
+
* - Web renders a precise, crisp Gaussian blur.
|
|
6
|
+
* - iOS appears softer and more diffused.
|
|
7
|
+
* - Android may exhibit minor geometric artifacts on small surfaces
|
|
8
|
+
* (e.g. a diamond-shaped silhouette) due to blur and downsampling.
|
|
9
|
+
*
|
|
10
|
+
* Blur is applied at the container level to reduce per-element artifacts.
|
|
11
|
+
*/
|
|
12
|
+
|
|
1
13
|
.stage {
|
|
2
|
-
filter: contrast(
|
|
3
|
-
/*
|
|
14
|
+
filter: contrast(5);
|
|
15
|
+
/* Background color must be explicit for contrast-based thresholding */
|
|
4
16
|
background-color: black;
|
|
5
|
-
|
|
6
17
|
width: 100%;
|
|
7
18
|
height: 200px;
|
|
8
19
|
}
|
|
9
20
|
|
|
10
|
-
.
|
|
11
|
-
/*
|
|
12
|
-
filter: blur(
|
|
13
|
-
|
|
21
|
+
.dots-container {
|
|
22
|
+
/* Apply blur at the group level to reduce per-element blur artifacts on some platforms */
|
|
23
|
+
filter: blur(12px);
|
|
14
24
|
width: 100%;
|
|
15
25
|
height: 100%;
|
|
16
26
|
display: flex;
|
|
@@ -20,11 +30,11 @@
|
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
.dot {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
/* Keep dots crisp; container-level blur produces the gooey merge */
|
|
34
|
+
width: 54px;
|
|
35
|
+
height: 54px;
|
|
36
|
+
border-radius: 999px;
|
|
26
37
|
background: white;
|
|
27
|
-
|
|
28
38
|
animation: blob 2200ms ease-in-out infinite;
|
|
29
39
|
}
|
|
30
40
|
|
|
@@ -44,19 +54,19 @@
|
|
|
44
54
|
|
|
45
55
|
@keyframes blob {
|
|
46
56
|
0% {
|
|
47
|
-
transform: translateX(-
|
|
57
|
+
transform: translateX(-18px) translateY(0px) scale(1.00);
|
|
48
58
|
}
|
|
49
59
|
25% {
|
|
50
|
-
transform: translateX(
|
|
60
|
+
transform: translateX(7.5px) translateY(-4.5px) scale(1.06);
|
|
51
61
|
}
|
|
52
62
|
50% {
|
|
53
|
-
transform: translateX(
|
|
63
|
+
transform: translateX(21px) translateY(0px) scale(1.00);
|
|
54
64
|
}
|
|
55
65
|
75% {
|
|
56
|
-
transform: translateX(
|
|
66
|
+
transform: translateX(4.5px) translateY(4.5px) scale(0.98);
|
|
57
67
|
}
|
|
58
68
|
100% {
|
|
59
|
-
transform: translateX(-
|
|
69
|
+
transform: translateX(-18px) translateY(0px) scale(1.00);
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
|
|
@@ -73,7 +83,7 @@
|
|
|
73
83
|
justify-content: center;
|
|
74
84
|
align-items: center;
|
|
75
85
|
background-color: black;
|
|
76
|
-
gap:
|
|
86
|
+
gap: 40px;
|
|
77
87
|
|
|
78
88
|
padding-left: 0;
|
|
79
89
|
padding-right: 0;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useRef } from "@lynx-js/react";
|
|
2
|
+
import type { MutableRefObject } from "@lynx-js/react";
|
|
3
|
+
import type { LayoutChangeEvent } from "@lynx-js/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Element measurement frame.
|
|
7
|
+
*
|
|
8
|
+
* Maintains the element's screen-based bounding frame:
|
|
9
|
+
* - left / top (from boundingClientRect)
|
|
10
|
+
* - width / height (from layout change)
|
|
11
|
+
*
|
|
12
|
+
* Designed to be shared by multiple pointer axes
|
|
13
|
+
* to avoid duplicate layout queries.
|
|
14
|
+
*/
|
|
15
|
+
export type ElementFrame = {
|
|
16
|
+
/** Screen-based left offset */
|
|
17
|
+
leftRef: MutableRefObject<number | null>;
|
|
18
|
+
|
|
19
|
+
/** Screen-based top offset */
|
|
20
|
+
topRef: MutableRefObject<number | null>;
|
|
21
|
+
|
|
22
|
+
/** Element width (px) */
|
|
23
|
+
widthRef: MutableRefObject<number>;
|
|
24
|
+
|
|
25
|
+
/** Element height (px) */
|
|
26
|
+
heightRef: MutableRefObject<number>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type UseElementFrameReturnValue = ElementFrame & {
|
|
30
|
+
/** Bind to <view bindlayoutchange={...} /> */
|
|
31
|
+
handleLayoutChange: (e: LayoutChangeEvent) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function useElementFrame(): UseElementFrameReturnValue {
|
|
35
|
+
const leftRef = useRef<number | null>(null);
|
|
36
|
+
const topRef = useRef<number | null>(null);
|
|
37
|
+
const widthRef = useRef(0);
|
|
38
|
+
const heightRef = useRef(0);
|
|
39
|
+
|
|
40
|
+
const handleLayoutChange = (e: LayoutChangeEvent) => {
|
|
41
|
+
// Sync element size from layout event
|
|
42
|
+
widthRef.current = e.detail.width;
|
|
43
|
+
heightRef.current = e.detail.height;
|
|
44
|
+
|
|
45
|
+
// Query screen-based bounding rect
|
|
46
|
+
const currentTarget = lynx
|
|
47
|
+
.createSelectorQuery()
|
|
48
|
+
// @ts-expect-error Lynx internal UID typing
|
|
49
|
+
.selectUniqueID(e.currentTarget.uid ?? e.currentTarget.uniqueId);
|
|
50
|
+
|
|
51
|
+
currentTarget
|
|
52
|
+
?.invoke({
|
|
53
|
+
method: "boundingClientRect",
|
|
54
|
+
params: { relativeTo: "screen" },
|
|
55
|
+
success: (res: { left: number; top: number }) => {
|
|
56
|
+
leftRef.current = res.left;
|
|
57
|
+
topRef.current = res.top;
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
.exec();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
leftRef,
|
|
65
|
+
topRef,
|
|
66
|
+
widthRef,
|
|
67
|
+
heightRef,
|
|
68
|
+
handleLayoutChange,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useRef } from "@lynx-js/react";
|
|
2
|
+
import type { LayoutChangeEvent, TouchEvent } from "@lynx-js/types";
|
|
3
|
+
import { useElementFrame } from "../use-element-frame/index.js";
|
|
4
|
+
import type { PointerAxisPosition, UsePointerAxisProps, UsePointerAxisReturnValueBase } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pointer → element-local coordinates adapter (single axis, X).
|
|
8
|
+
*
|
|
9
|
+
* - The container usually hosts touch/pointer events (recommended for larger hit area).
|
|
10
|
+
* - The element provides the measurement frame (layout + bounding rect).
|
|
11
|
+
* - If you don't need a separate container, you may bind all handlers on the element
|
|
12
|
+
* itself (container === element).
|
|
13
|
+
*
|
|
14
|
+
* No clamping/step logic is applied here.
|
|
15
|
+
*/
|
|
16
|
+
function usePointerAxis({
|
|
17
|
+
axis = "x",
|
|
18
|
+
onUpdate,
|
|
19
|
+
onCommit,
|
|
20
|
+
frame: externalFrame,
|
|
21
|
+
}: UsePointerAxisProps = {}): UsePointerAxisReturnValue {
|
|
22
|
+
/** Element (coordinate frame) & metrics */
|
|
23
|
+
|
|
24
|
+
const internalFrameRef = useElementFrame();
|
|
25
|
+
const frame = externalFrame ?? internalFrameRef;
|
|
26
|
+
|
|
27
|
+
const posRef = useRef<PointerAxisPosition | null>(null);
|
|
28
|
+
const draggingRef = useRef(false);
|
|
29
|
+
|
|
30
|
+
const pickCoord = (e: TouchEvent) => (axis === "x" ? e.detail.x : e.detail.y);
|
|
31
|
+
|
|
32
|
+
const startRef = axis === "x" ? frame.leftRef : frame.topRef;
|
|
33
|
+
const lengthRef = axis === "x" ? frame.widthRef : frame.heightRef;
|
|
34
|
+
|
|
35
|
+
const buildPosition = (coord: number): PointerAxisPosition | null => {
|
|
36
|
+
const length = lengthRef.current;
|
|
37
|
+
const start = startRef.current;
|
|
38
|
+
|
|
39
|
+
if (length > 0 && start != null) {
|
|
40
|
+
const offset = coord - start;
|
|
41
|
+
const offsetRatio = offset / length;
|
|
42
|
+
const pos = { offset, offsetRatio, elementLength: length };
|
|
43
|
+
posRef.current = pos;
|
|
44
|
+
return pos;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handlePointerDown = (e: TouchEvent) => {
|
|
50
|
+
draggingRef.current = true;
|
|
51
|
+
buildPosition(pickCoord(e));
|
|
52
|
+
if (posRef.current) onUpdate?.(posRef.current);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handlePointerMove = (e: TouchEvent) => {
|
|
56
|
+
if (!draggingRef.current) return;
|
|
57
|
+
buildPosition(pickCoord(e));
|
|
58
|
+
if (posRef.current) onUpdate?.(posRef.current);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handlePointerUp = (e: TouchEvent) => {
|
|
62
|
+
draggingRef.current = false;
|
|
63
|
+
buildPosition(pickCoord(e));
|
|
64
|
+
if (posRef.current) {
|
|
65
|
+
onUpdate?.(posRef.current);
|
|
66
|
+
onCommit?.(posRef.current);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleElementLayoutChange = (e: LayoutChangeEvent) => {
|
|
71
|
+
// If using injected frame, measurement is handled elsewhere.
|
|
72
|
+
if (externalFrame != null) return;
|
|
73
|
+
internalFrameRef.handleLayoutChange(e);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
handlePointerDown,
|
|
78
|
+
handlePointerMove,
|
|
79
|
+
handlePointerUp,
|
|
80
|
+
handleElementLayoutChange,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type UsePointerAxisReturnValue = UsePointerAxisReturnValueBase<
|
|
85
|
+
TouchEvent,
|
|
86
|
+
LayoutChangeEvent
|
|
87
|
+
>;
|
|
88
|
+
|
|
89
|
+
export { usePointerAxis };
|
|
90
|
+
export type { PointerAxisPosition, UsePointerAxisProps, UsePointerAxisReturnValue };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ElementFrame } from "../use-element-frame/index.js";
|
|
2
|
+
|
|
3
|
+
/** Pointer position in the element’s local frame. */
|
|
4
|
+
type PointerAxisPosition = {
|
|
5
|
+
/** Horizontal offset from element's left edge (px). Can be <0 or >length. */
|
|
6
|
+
offset: number;
|
|
7
|
+
/** offset / elementLength. Can be <0 or >1. */
|
|
8
|
+
offsetRatio: number;
|
|
9
|
+
/** Measured length of the element along this axis (px). */
|
|
10
|
+
elementLength: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Interaction callbacks. */
|
|
14
|
+
type UsePointerAxisProps = {
|
|
15
|
+
axis?: PointerAxis;
|
|
16
|
+
/** Injected measurement frame */
|
|
17
|
+
frame?: ElementFrame;
|
|
18
|
+
/** Fires during drag/move. */
|
|
19
|
+
onUpdate?: (pos: PointerAxisPosition) => void;
|
|
20
|
+
/** Fires on pointer up (final value). */
|
|
21
|
+
onCommit?: (pos: PointerAxisPosition) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type UsePointerAxisReturnValueBase<TPointer, TLayout> = {
|
|
25
|
+
/** Bind on CONTAINER (or ELEMENT if container === element): <view bindtouchstart|bindmousedown={handlePointerDown} /> */
|
|
26
|
+
handlePointerDown: (e: TPointer) => void;
|
|
27
|
+
/** Bind on CONTAINER (or ELEMENT if container === element): <view bindtouchmove|bindmousemove={handlePointerMove} /> */
|
|
28
|
+
handlePointerMove: (e: TPointer) => void;
|
|
29
|
+
/** Bind on CONTAINER (or ELEMENT if container===element): <view bindtouchend|bindtouchcancel|bindmousedown|bindmousecancel={handlePointerUp} /> */
|
|
30
|
+
handlePointerUp: (e: TPointer) => void;
|
|
31
|
+
/** Bind on ELEMENT: <view bindlayoutchange={handleElementLayoutChange} /> */
|
|
32
|
+
handleElementLayoutChange: (e: TLayout) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type PointerAxis = "x" | "y";
|
|
36
|
+
|
|
37
|
+
export type { PointerAxis, PointerAxisPosition, UsePointerAxisProps, UsePointerAxisReturnValueBase };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useRef, useState } from "@lynx-js/react";
|
|
2
|
+
import { useTouchEmulation } from "@lynx-js/react-use";
|
|
3
|
+
import type { LayoutChangeEvent, MouseEvent, TouchEvent } from "@lynx-js/types";
|
|
4
|
+
import { useElementFrame } from "../use-element-frame/index.js";
|
|
5
|
+
import { usePointerAxis } from "../use-pointer-axis/index.js";
|
|
6
|
+
|
|
7
|
+
export type PointerPoint = { x: number; y: number };
|
|
8
|
+
|
|
9
|
+
export type UsePointerPointProps = {
|
|
10
|
+
x0?: number;
|
|
11
|
+
y0?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PointerBindProps = {
|
|
15
|
+
/** Pointer (touch/mouse) handlers bound at the field level. */
|
|
16
|
+
bindtouchstart?: (e: TouchEvent) => void;
|
|
17
|
+
bindtouchmove?: (e: TouchEvent) => void;
|
|
18
|
+
bindtouchend?: (e: TouchEvent) => void;
|
|
19
|
+
bindtouchcancel?: (e: TouchEvent) => void;
|
|
20
|
+
|
|
21
|
+
bindmousedown?: (e: MouseEvent) => void;
|
|
22
|
+
bindmousemove?: (e: MouseEvent) => void;
|
|
23
|
+
bindmouseup?: (e: MouseEvent) => void;
|
|
24
|
+
|
|
25
|
+
/** Layout measurement handler for field coordinate mapping. */
|
|
26
|
+
bindlayoutchange?: (e: LayoutChangeEvent) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type UsePointerPointReturnValue = {
|
|
30
|
+
p: PointerPoint;
|
|
31
|
+
bind: PointerBindProps;
|
|
32
|
+
handlePointerDown: (e: TouchEvent) => void;
|
|
33
|
+
handlePointerMove: (e: TouchEvent) => void;
|
|
34
|
+
handlePointerUp: (e: TouchEvent) => void;
|
|
35
|
+
handleElementLayoutChange: (e: LayoutChangeEvent) => void;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function usePointerPoint({
|
|
39
|
+
x0 = 0.5,
|
|
40
|
+
y0 = 0.5,
|
|
41
|
+
}: UsePointerPointProps = {}): UsePointerPointReturnValue {
|
|
42
|
+
const [p, setP] = useState<PointerPoint>({ x: x0, y: y0 });
|
|
43
|
+
|
|
44
|
+
const lastXRef = useRef(x0);
|
|
45
|
+
const lastYRef = useRef(y0);
|
|
46
|
+
|
|
47
|
+
const frameRef = useElementFrame();
|
|
48
|
+
|
|
49
|
+
const axisX = usePointerAxis({
|
|
50
|
+
axis: "x",
|
|
51
|
+
frame: frameRef,
|
|
52
|
+
onUpdate(pos) {
|
|
53
|
+
lastXRef.current = pos.offsetRatio;
|
|
54
|
+
setP({ x: lastXRef.current, y: lastYRef.current });
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const axisY = usePointerAxis({
|
|
59
|
+
axis: "y",
|
|
60
|
+
frame: frameRef,
|
|
61
|
+
onUpdate(pos) {
|
|
62
|
+
lastYRef.current = pos.offsetRatio;
|
|
63
|
+
setP({ x: lastXRef.current, y: lastYRef.current });
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const handlePointerDown = (e: TouchEvent) => {
|
|
68
|
+
axisY.handlePointerDown(e);
|
|
69
|
+
axisX.handlePointerDown(e);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handlePointerMove = (e: TouchEvent) => {
|
|
73
|
+
axisY.handlePointerMove(e);
|
|
74
|
+
axisX.handlePointerMove(e);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handlePointerUp = (e: TouchEvent) => {
|
|
78
|
+
axisY.handlePointerUp(e);
|
|
79
|
+
axisX.handlePointerUp(e);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleElementLayoutChange = (e: LayoutChangeEvent) => {
|
|
83
|
+
frameRef.handleLayoutChange(e);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// One emulation at the boundary (touch + mouse)
|
|
87
|
+
const bindPointer = useTouchEmulation({
|
|
88
|
+
onTouchStart: handlePointerDown,
|
|
89
|
+
onTouchMove: handlePointerMove,
|
|
90
|
+
onTouchEnd: handlePointerUp,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
p,
|
|
95
|
+
handlePointerDown,
|
|
96
|
+
handlePointerMove,
|
|
97
|
+
handlePointerUp,
|
|
98
|
+
handleElementLayoutChange,
|
|
99
|
+
bind: {
|
|
100
|
+
bindtouchstart: bindPointer.bindtouchstart,
|
|
101
|
+
bindtouchmove: bindPointer.bindtouchmove,
|
|
102
|
+
bindtouchend: bindPointer.bindtouchend,
|
|
103
|
+
bindtouchcancel: bindPointer.bindtouchcancel,
|
|
104
|
+
bindmousedown: bindPointer.bindmousedown,
|
|
105
|
+
bindmousemove: bindPointer.bindmousemove,
|
|
106
|
+
bindmouseup: bindPointer.bindmouseup,
|
|
107
|
+
bindlayoutchange: handleElementLayoutChange,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { usePointerPoint };
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { useRef, useState } from "@lynx-js/react";
|
|
2
|
-
import type { LayoutChangeEvent, TouchEvent } from "@lynx-js/types";
|
|
3
|
-
import { usePointerInteraction } from "../use-pointer-interaction/index.js";
|
|
4
|
-
|
|
5
|
-
type FieldPoint = {
|
|
6
|
-
/** Normalized x in [0, 1] (not clamped). */
|
|
7
|
-
x: number;
|
|
8
|
-
/** Normalized y in [0, 1] (not clamped). */
|
|
9
|
-
y: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type UsePointerFieldPointProps = {
|
|
13
|
-
/** Initial normalized x, defaults to 0.5*/
|
|
14
|
-
x0?: number;
|
|
15
|
-
y0?: number;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function usePointerFieldPoint({ x0 = 0.5, y0 = 0.5 }: UsePointerFieldPointProps = {}) {
|
|
19
|
-
const [p, setP] = useState<FieldPoint>({ x: x0, y: y0 });
|
|
20
|
-
|
|
21
|
-
const eleTopRef = useRef<number | null>(null);
|
|
22
|
-
const eleHeightRef = useRef(0);
|
|
23
|
-
|
|
24
|
-
const lastYRatioRef = useRef(y0);
|
|
25
|
-
|
|
26
|
-
const pointer = usePointerInteraction({
|
|
27
|
-
onUpdate(pos) {
|
|
28
|
-
setP({
|
|
29
|
-
x: pos.offsetRatio,
|
|
30
|
-
y: lastYRatioRef.current,
|
|
31
|
-
});
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const updateYFromEvent = (e: TouchEvent) => {
|
|
36
|
-
const top = eleTopRef.current;
|
|
37
|
-
const height = eleHeightRef.current;
|
|
38
|
-
if (top != null && height > 0) {
|
|
39
|
-
lastYRatioRef.current = (e.detail.y - top) / height;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const handlePointerDown2D = (e: TouchEvent) => {
|
|
44
|
-
updateYFromEvent(e);
|
|
45
|
-
pointer.handlePointerDown(e);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const handlePointerMove2D = (e: TouchEvent) => {
|
|
49
|
-
updateYFromEvent(e);
|
|
50
|
-
// X handled by existing hook
|
|
51
|
-
pointer.handlePointerMove(e);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const handleLayoutChange2D = (e: LayoutChangeEvent) => {
|
|
55
|
-
eleHeightRef.current = e.detail.height;
|
|
56
|
-
|
|
57
|
-
const currentTarget = lynx
|
|
58
|
-
.createSelectorQuery()
|
|
59
|
-
// @ts-expect-error
|
|
60
|
-
.selectUniqueID(e.currentTarget.uid);
|
|
61
|
-
|
|
62
|
-
currentTarget
|
|
63
|
-
?.invoke({
|
|
64
|
-
method: "boundingClientRect",
|
|
65
|
-
params: { relativeTo: "screen" },
|
|
66
|
-
success: (res: { top: number }) => {
|
|
67
|
-
eleTopRef.current = res.top;
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
.exec();
|
|
71
|
-
|
|
72
|
-
pointer.handleElementLayoutChange(e);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
p,
|
|
77
|
-
handlePointerDown: handlePointerDown2D,
|
|
78
|
-
handlePointerMove: handlePointerMove2D,
|
|
79
|
-
handlePointerUp: pointer.handlePointerUp,
|
|
80
|
-
handleElementLayoutChange: handleLayoutChange2D,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export { usePointerFieldPoint };
|
|
85
|
-
export type { FieldPoint };
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { useRef } from "@lynx-js/react";
|
|
2
|
-
import type { LayoutChangeEvent, TouchEvent } from "@lynx-js/types";
|
|
3
|
-
import type { PointerPosition, UsePointerInteractionProps, UsePointerInteractionReturnValueBase } from "./types.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Pointer → element-local coordinates adapter.
|
|
7
|
-
*
|
|
8
|
-
* - The container usually hosts touch/pointer events (recommended for larger hit area).
|
|
9
|
-
* - The element provides the measurement frame (layout + bounding rect).
|
|
10
|
-
* - If you don't need a separate container, you may bind all handlers on the element
|
|
11
|
-
* itself (container === element).
|
|
12
|
-
*
|
|
13
|
-
* No clamping/step logic is applied here.
|
|
14
|
-
*/
|
|
15
|
-
function usePointerInteraction({
|
|
16
|
-
onUpdate,
|
|
17
|
-
onCommit,
|
|
18
|
-
}: UsePointerInteractionProps = {}): UsePointerInteractionReturnValue {
|
|
19
|
-
/** Element (coordinate frame) & metrics */
|
|
20
|
-
const eleLeftRef = useRef<number | null>(null);
|
|
21
|
-
const eleWidthRef = useRef(0);
|
|
22
|
-
|
|
23
|
-
/** Last computed pointer position snapshot */
|
|
24
|
-
const posRef = useRef<PointerPosition | null>(null);
|
|
25
|
-
|
|
26
|
-
const draggingRef = useRef(false);
|
|
27
|
-
|
|
28
|
-
const buildPosition = (x: number): PointerPosition | null => {
|
|
29
|
-
const width = eleWidthRef.current;
|
|
30
|
-
const left = eleLeftRef.current;
|
|
31
|
-
|
|
32
|
-
if (width > 0 && left != null) {
|
|
33
|
-
const offset = x - left;
|
|
34
|
-
const offsetRatio = offset / width;
|
|
35
|
-
const pos = { offset, offsetRatio, elementWidth: width };
|
|
36
|
-
posRef.current = pos;
|
|
37
|
-
return pos;
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const handlePointerDown = (e: TouchEvent) => {
|
|
43
|
-
draggingRef.current = true;
|
|
44
|
-
buildPosition(e.detail.x);
|
|
45
|
-
if (posRef.current) {
|
|
46
|
-
onUpdate?.(posRef.current);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const handlePointerMove = (e: TouchEvent) => {
|
|
51
|
-
if (!draggingRef.current) return;
|
|
52
|
-
buildPosition(e.detail.x);
|
|
53
|
-
if (posRef.current) {
|
|
54
|
-
onUpdate?.(posRef.current);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const handlePointerUp = (e: TouchEvent) => {
|
|
59
|
-
draggingRef.current = false;
|
|
60
|
-
buildPosition(e.detail.x);
|
|
61
|
-
if (posRef.current) {
|
|
62
|
-
onUpdate?.(posRef.current);
|
|
63
|
-
onCommit?.(posRef.current);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const handleElementLayoutChange = (e: LayoutChangeEvent) => {
|
|
68
|
-
eleWidthRef.current = e.detail.width;
|
|
69
|
-
|
|
70
|
-
const currentTarget = lynx
|
|
71
|
-
.createSelectorQuery()
|
|
72
|
-
// @ts-expect-error
|
|
73
|
-
.selectUniqueID(e.currentTarget.uid);
|
|
74
|
-
|
|
75
|
-
currentTarget
|
|
76
|
-
?.invoke({
|
|
77
|
-
method: "boundingClientRect",
|
|
78
|
-
params: { relativeTo: "screen" }, // screen-based so it matches e.detail.x
|
|
79
|
-
success: (res: { left: number }) => {
|
|
80
|
-
eleLeftRef.current = res.left;
|
|
81
|
-
},
|
|
82
|
-
})
|
|
83
|
-
.exec();
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
handlePointerDown,
|
|
88
|
-
handlePointerMove,
|
|
89
|
-
handlePointerUp,
|
|
90
|
-
handleElementLayoutChange,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
type UsePointerInteractionReturnValue = UsePointerInteractionReturnValueBase<
|
|
95
|
-
TouchEvent,
|
|
96
|
-
LayoutChangeEvent
|
|
97
|
-
>;
|
|
98
|
-
|
|
99
|
-
export { usePointerInteraction };
|
|
100
|
-
export type { PointerPosition, UsePointerInteractionProps, UsePointerInteractionReturnValue };
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** Pointer position in the element’s local frame. */
|
|
2
|
-
interface PointerPosition {
|
|
3
|
-
/** Horizontal offset from element's left edge (px). Can be <0 or >width. */
|
|
4
|
-
offset: number;
|
|
5
|
-
/** offset / elementWidth. Can be <0 or >1. */
|
|
6
|
-
offsetRatio: number;
|
|
7
|
-
/** Measured width of the element (px). */
|
|
8
|
-
elementWidth: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Interaction callbacks. */
|
|
12
|
-
interface UsePointerInteractionProps {
|
|
13
|
-
/** Fires during drag/move. */
|
|
14
|
-
onUpdate?: (pos: PointerPosition) => void;
|
|
15
|
-
/** Fires on pointer up (final value). */
|
|
16
|
-
onCommit?: (pos: PointerPosition) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type UsePointerInteractionReturnValueBase<TTouch, TLayout> = {
|
|
20
|
-
/** Bind on CONTAINER (or ELEMENT if container === element): <view bindtouchstart={handlePointerDown} /> */
|
|
21
|
-
handlePointerDown: (e: TTouch) => void;
|
|
22
|
-
/** Bind on CONTAINER (or ELEMENT if container === element): <view bindtouchmove={handlePointerMove} /> */
|
|
23
|
-
handlePointerMove: (e: TTouch) => void;
|
|
24
|
-
/** Bind on CONTAINER (or ELEMENT if container===element): <view bindtouchend|bindtouchcancel={handlePointerUp} /> */
|
|
25
|
-
handlePointerUp: (e: TTouch) => void;
|
|
26
|
-
/** Bind on ELEMENT: <view bindlayoutchange={handleElementLayoutChange} /> */
|
|
27
|
-
handleElementLayoutChange: (e: TLayout) => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type { PointerPosition, UsePointerInteractionProps, UsePointerInteractionReturnValueBase };
|