11import 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 */
7174function 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