1- import { last } from 'lodash-es'
2- import { useCallback , useEffect , useMemo , useState } from 'react'
1+ import { useCallback , useEffect , useState } from 'react'
32
4- import { ElementSelector } from '@/schemas/recording'
5- import { uuid } from '@/utils/uuid'
6- import { ElementRole , getElementRoles } from 'extension/src/utils/aria'
7-
8- import { generateSelectors } from '../../../selectors'
93import { useGlobalClass } from '../GlobalStyles'
104import { useHighlightDebounce } from '../hooks/useHighlightDebounce'
115import { usePreventClick } from '../hooks/usePreventClick'
126import { Bounds , Position } from '../types'
13- import { getElementBounds } from '../utils'
14-
15- function * getAncestors ( element : Element ) {
16- let current : Element | null = element . parentElement
17-
18- while ( current !== null ) {
19- yield current
20-
21- if ( current === document . documentElement ) {
22- break
23- }
24-
25- current = current . parentElement
26- }
27- }
28-
29- function findLabelFor ( label : HTMLLabelElement ) : Element | null {
30- const id = label . getAttribute ( 'for' )
31-
32- if ( id === null ) {
33- return null
34- }
35-
36- return document . getElementById ( id )
37- }
387
39- function findInChildren ( label : HTMLLabelElement ) : Element | null {
40- return label . querySelector ( 'input, select, textarea' )
41- }
42-
43- function findLabelledBy ( label : HTMLLabelElement ) : Element | null {
44- if ( label . id === '' ) {
45- return null
46- }
47-
48- return document . querySelector ( `[aria-labelledby="${ label . id } "]` )
49- }
50-
51- export function findRelatedInput ( element : Element ) : TrackedElement | null {
52- const label = [ ...getAncestors ( element ) ] . find (
53- ( ancestor ) => ancestor instanceof HTMLLabelElement
54- )
55-
56- if ( label === undefined ) {
57- return null
58- }
59-
60- const input =
61- findLabelFor ( label ) ?? findInChildren ( label ) ?? findLabelledBy ( label )
62-
63- if ( input === null ) {
64- return null
65- }
66-
67- return toTrackedElement ( input )
68- }
69-
70- export interface TrackedElement {
71- id : string
72- roles : ElementRole [ ]
73- selector : ElementSelector
74- target : Element
75- bounds : Bounds
76- }
77-
78- function toTrackedElement ( element : Element ) : TrackedElement {
79- const roles = getElementRoles ( element )
80-
81- return {
82- id : uuid ( ) ,
83- roles : [ ...roles ] ,
84- selector : generateSelectors ( element ) ,
85- target : element ,
86- bounds : getElementBounds ( element ) ,
87- }
88- }
8+ import { usePinnedElement } from './hooks'
9+ import { toTrackedElement , TrackedElement } from './utils'
8910
9011function isInsideBounds (
9112 position : Position ,
@@ -118,9 +39,10 @@ export function useInspectedElement() {
11839 * the selection and removed when the user contracts the selection. If the
11940 * stack is empty, then no element is pinned.
12041 */
121- const [ pinnedEl , setPinnedElement ] = useState < TrackedElement [ ] > ( [ ] )
12242 const [ hoveredEl , setHoveredEl ] = useState < TrackedElement | null > ( null )
12343
44+ const { selected, pinned, pin, unpin, expand, contract } = usePinnedElement ( )
45+
12446 useGlobalClass ( 'inspecting' )
12547
12648 useEffect ( ( ) => {
@@ -155,23 +77,23 @@ export function useInspectedElement() {
15577 }
15678 } , [ ] )
15779
158- const unpin = useCallback ( ( ) => {
80+ const reset = useCallback ( ( ) => {
15981 setMousePosition ( {
16082 top : 0 ,
16183 left : 0 ,
16284 } )
16385
164- setPinnedElement ( [ ] )
165- } , [ ] )
86+ unpin ( )
87+ } , [ unpin ] )
16688
16789 usePreventClick ( {
16890 callback : ( ev ) => {
16991 if ( hoveredEl === null ) {
17092 return
17193 }
17294
173- if ( pinnedEl . length > 0 ) {
174- unpin ( )
95+ if ( pinned !== null ) {
96+ reset ( )
17597
17698 return
17799 }
@@ -191,53 +113,18 @@ export function useInspectedElement() {
191113 }
192114
193115 setMousePosition ( position )
194- setPinnedElement ( [ hoveredEl ] )
116+ pin ( hoveredEl )
195117 } ,
196- dependencies : [ pinnedEl , hoveredEl , unpin ] ,
118+ dependencies : [ pinned , hoveredEl , unpin ] ,
197119 } )
198120
199- const expand = useMemo ( ( ) => {
200- const [ head ] = pinnedEl
201-
202- if ( head === undefined ) {
203- return undefined
204- }
205-
206- const parent = head . target . parentElement
207-
208- if ( parent === null || parent === document . documentElement ) {
209- return undefined
210- }
211-
212- return ( ) => {
213- setPinnedElement ( ( pinned ) => {
214- return [ toTrackedElement ( parent ) , ...pinned ]
215- } )
216- }
217- } , [ pinnedEl ] )
218-
219- const contract = useMemo ( ( ) => {
220- const [ head , ...tail ] = pinnedEl
221-
222- // If head is undefined, that means no element is pinned. If tail is
223- // empty, that means we're back at the intial element. In either case
224- // we can't decrease the selection any further.
225- if ( head === undefined || tail . length === 0 ) {
226- return undefined
227- }
228-
229- return ( ) => {
230- setPinnedElement ( tail )
231- }
232- } , [ pinnedEl ] )
233-
234121 const highlightedEl = useHighlightDebounce ( hoveredEl )
235122
236123 return {
237- pinned : last ( pinnedEl ) ?? null ,
238- element : pinnedEl [ 0 ] ?? highlightedEl ,
124+ pinned,
125+ element : selected ?? highlightedEl ,
239126 mousePosition,
240- unpin ,
127+ reset ,
241128 expand,
242129 contract,
243130 }
0 commit comments