Skip to content

Commit 54ae5a7

Browse files
authored
fix: correct intersection logic (#36)
1 parent 48cf7d8 commit 54ae5a7

File tree

2 files changed

+73
-77
lines changed

2 files changed

+73
-77
lines changed

src/ink-mouse/isIntersecting.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import type { MousePosition } from './MouseContext';
22

3-
// const LEFT_ADJUSTMENT = 13;
4-
// const TOP_ADJUSTMENT = 2;
5-
63
/**
74
* Determines if the event is intersecting with the elements layout.
85
*
9-
* TODO: This currently seems to have an off by 13 and 2 issue.
106
*/
117
function isIntersecting({
128
mouse: { x, y },
@@ -15,16 +11,14 @@ function isIntersecting({
1511
mouse: MousePosition;
1612
element: { left: number; top: number; width: number; height: number };
1713
}) {
18-
/**
19-
* for some reason the position is off by 13 and 2
20-
*/
2114
const left = element.left;
2215
const top = element.top;
2316
const width = element.width;
2417
const height = element.height;
25-
const isOutsideHorizontally = x < left || x > left + width;
26-
const isOutsideVertically = y < top || y > top + height;
18+
const isOutsideHorizontally = x < left || x >= left + width;
19+
const isOutsideVertically = y < top || y >= top + height;
2720

2821
return !isOutsideHorizontally && !isOutsideVertically;
2922
}
23+
3024
export { isIntersecting };
Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import type { DOMElement } from 'ink';
2-
import { useEffect, useState, type RefObject } from 'react';
2+
import { type RefObject, useEffect, useState } from 'react';
33

44
/**
55
* Statefule hook to provide the position of the referenced element.
66
*
77
* @param ref - The reference to the element.
8+
* @param deps - Dependencies to recompute the position.
89
* @returns The position of the element.
910
*/
10-
function useElementPosition(ref: RefObject<DOMElement | null>, deps: unknown[] = []) {
11+
function useElementPosition(
12+
ref: RefObject<DOMElement | null>,
13+
deps: unknown[] = [],
14+
) {
1115
const [position, setPosition] = useState<{
1216
left: number;
1317
top: number;
@@ -27,41 +31,40 @@ function useElementPosition(ref: RefObject<DOMElement | null>, deps: unknown[] =
2731
return position;
2832
}
2933

30-
function useElementDimensions(ref: RefObject<DOMElement | null>, deps: unknown[] = []) {
31-
const [dimensions, setDimensions] = useState<{
32-
width: number;
33-
height: number;
34-
}>({
35-
width: 0,
36-
height: 0
37-
});
38-
39-
useEffect(function UpdateDimensions() {
40-
const dimensions = getElementDimensions(ref.current);
41-
if (!dimensions) {
42-
return;
43-
}
44-
setDimensions(dimensions);
45-
}, deps);
46-
47-
return dimensions;
48-
}
34+
function useElementDimensions(
35+
ref: RefObject<DOMElement | null>,
36+
deps: unknown[] = [],
37+
) {
38+
const [dimensions, setDimensions] = useState<{
39+
width: number;
40+
height: number;
41+
}>({
42+
width: 0,
43+
height: 0,
44+
});
4945

50-
function getElementDimensions(node: DOMElement | null) {
51-
if (!node) {
52-
return;
46+
useEffect(function UpdateDimensions() {
47+
const dimensions = getElementDimensions(ref.current);
48+
if (!dimensions) {
49+
return;
5350
}
51+
setDimensions(dimensions);
52+
}, deps);
5453

55-
if (!node.yogaNode) {
56-
return;
57-
}
54+
return dimensions;
55+
}
5856

59-
const elementLayout = node.yogaNode.getComputedLayout();
57+
function getElementDimensions(node: DOMElement | null) {
58+
const elementLayout = node?.yogaNode?.getComputedLayout();
6059

61-
return {
62-
width: elementLayout.width,
63-
height: elementLayout.height
64-
}
60+
if (!elementLayout) {
61+
return;
62+
}
63+
64+
return {
65+
width: elementLayout.width,
66+
height: elementLayout.height,
67+
};
6568
}
6669

6770
/**
@@ -70,57 +73,56 @@ function getElementDimensions(node: DOMElement | null) {
7073
*/
7174
function getElementPosition(node: DOMElement | null) {
7275
if (!node) {
73-
return null;
74-
}
75-
76-
if (!node.yogaNode) {
77-
return null;
76+
return;
7877
}
79-
const elementLayout = node.yogaNode.getComputedLayout();
80-
81-
const parent = walkParentPosition(node);
78+
const { left, top } = walkNodePosition(node);
8279

83-
const position = {
84-
left: elementLayout.left + parent.x,
85-
top: elementLayout.top + parent.y,
80+
return {
81+
left,
82+
top,
8683
};
87-
88-
return position;
8984
}
9085

9186
/**
92-
* Walk the parent ancestory to get the position of the element.
87+
* Walks the node's ancestry to calculate its absolute position.
88+
*
89+
* This function traverses up the parent chain of a DOMElement, accumulating
90+
* the `left` and `top` layout values to determine the element's final
91+
* absolute position within the Ink rendering context.
92+
*
93+
* Note: The initial `left` and `top` values are set to 1 because terminal
94+
* coordinates are 1-indexed. Relative coordinates of each element, however,
95+
* start from 0.
9396
*
9497
* Since InkNodes are relative by default and because Ink does not
9598
* provide precomputed x and y values, we need to walk the parent and
9699
* accumulate the x and y values.
97100
*
98-
* I only discovered this by debugging the getElementPosition before
99-
* and after wrapping the element in a Box with padding:
100-
*
101-
* - before padding: { left: 0, top: 0, width: 10, height: 1 }
102-
* - after padding: { left: 2, top: 0, width: 10, height: 1 }
103-
*
104-
* It's still a mystery why padding on a parent results in the child
105-
* having a different top value. `#todo`
101+
* @param node - The DOMElement for which to calculate the position.
102+
* @returns An object containing the calculated `left` and `top` absolute coordinates.
106103
*/
107-
function walkParentPosition(node: DOMElement) {
108-
let parent = node.parentNode;
109-
let x = 0;
110-
let y = 0;
111-
112-
while (parent) {
113-
if (!parent.yogaNode) {
114-
return { x, y };
104+
function walkNodePosition(node: DOMElement) {
105+
let current: DOMElement | undefined = node;
106+
let left = 1;
107+
let top = 1;
108+
109+
while (current) {
110+
if (!current.yogaNode) {
111+
return { left, top };
115112
}
116113

117-
const layout = parent.yogaNode.getComputedLayout();
118-
x += layout.left;
119-
y += layout.top;
114+
const layout = current.yogaNode.getComputedLayout();
115+
left += layout.left;
116+
top += layout.top;
120117

121-
parent = parent.parentNode;
118+
current = current.parentNode;
122119
}
123-
return { x, y };
120+
return { left, top };
124121
}
125122

126-
export { useElementPosition, getElementPosition, getElementDimensions, useElementDimensions };
123+
export {
124+
useElementPosition,
125+
getElementPosition,
126+
getElementDimensions,
127+
useElementDimensions,
128+
};

0 commit comments

Comments
 (0)