@@ -3,29 +3,41 @@ import {defer} from '@solid-primitives/utils'
33import { makeEventListener } from '@solid-primitives/event-listener'
44import { createKeyHold } from '@solid-primitives/keyboard'
55import { scheduleIdle } from '@solid-primitives/scheduled'
6- import { makeHoverElementListener } from '@solid-devtools/shared/primitives'
76import { msg , warn } from '@solid-devtools/shared/utils'
87import * as walker from '../structure/walker.ts'
98import { ObjectType , getObjectById } from '../main/id.ts'
109import SolidAPI from '../main/setup.ts'
11- import { type NodeID , type OutputEmit } from '../main/types.ts'
10+ import { type ElementInterface , type NodeID , type OutputEmit , type SourceLocation } from '../main/types.ts'
1211import { createElementsOverlay } from './element-overlay.tsx'
13- import {
14- type LocatorComponent ,
15- type SourceCodeData ,
16- type SourceLocation ,
17- type TargetIDE ,
18- type TargetURLFunction ,
19- getLocationAttr ,
20- getProjectPath ,
21- getSourceCodeData ,
22- openSourceCode ,
23- } from './find-components.ts'
24- import { type HighlightElementPayload , type LocatorOptions } from './types.ts'
25-
26- export { parseLocationString } from './find-components.ts'
27-
28- export * from './types.ts'
12+ import * as locator from './locator.ts'
13+ import { unwrap_append } from '../main/utils.ts'
14+
15+ export * from './locator.ts'
16+
17+ export type LocatorComponent < TEl extends object > = {
18+ id : NodeID
19+ name : string | undefined
20+ element : TEl
21+ location ?: SourceLocation | null
22+ }
23+
24+ function makeHoverElementListener < TEl extends object > (
25+ eli : ElementInterface < TEl > ,
26+ onHover : ( el : TEl | null ) => void ,
27+ ) : void {
28+ let last : TEl | null = null
29+ makeEventListener ( window , 'mouseover' , e => {
30+ let el = eli . getElementAt ( e )
31+ if ( el !== last ) {
32+ onHover ( last = el )
33+ }
34+ } )
35+ makeEventListener ( document , 'mouseleave' , ( ) => {
36+ if ( null !== last ) {
37+ onHover ( last = null )
38+ }
39+ } )
40+ }
2941
3042export function createLocator < TEl extends object > ( props : {
3143 locatorEnabled : s . Accessor < boolean > ,
@@ -37,55 +49,80 @@ export function createLocator<TEl extends object>(props: {
3749 const [ enabledByPressingSignal , setEnabledByPressingSignal ] = s . createSignal ( ( ) : boolean => false )
3850 props . setLocatorEnabledSignal ( s . createMemo ( ( ) => enabledByPressingSignal ( ) ( ) ) )
3951
40- const [ hoverTarget , setHoverTarget ] = s . createSignal < HTMLElement | null > ( null )
41- const [ devtoolsTarget , setDevtoolsTarget ] = s . createSignal < HighlightElementPayload > ( null )
42-
43- const [ highlightedComponents , setHighlightedComponents ] = s . createSignal < LocatorComponent [ ] > ( [ ] )
52+ const [ hoverTarget , setHoverTarget ] = s . createSignal < TEl | null > ( null )
53+ const [ devtoolsTarget , setDevtoolsTarget ] = s . createSignal < locator . HighlightElementPayload > ( null )
4454
45- const calcHighlightedComponents = (
46- target : HTMLElement | HighlightElementPayload ,
47- ) : LocatorComponent [ ] => {
48- if ( ! target ) return [ ]
55+ const [ highlightedComponents , setHighlightedComponents ] = s . createSignal < LocatorComponent < TEl > [ ] > ( [ ] )
4956
50- // target is an elementId
51- if ( 'type' in target && target . type === 'element' ) {
52- const element = getObjectById ( target . id , ObjectType . Element )
53- if ( ! ( element instanceof HTMLElement ) ) return [ ]
54- target = element
55- }
56-
57- // target is an element
58- if ( target instanceof HTMLElement ) {
59- const comp = walker . findComponent ( props . component_registry , target )
60- if ( ! comp ) return [ ]
61- return [
62- {
63- location : getLocationAttr ( target ) ,
64- element : target ,
65- id : comp . id ,
66- name : comp . name ,
67- } ,
68- ]
69- }
70-
71- // target is a component or an element of a component (in DOM walker mode)
72- const comp = walker . getComponent ( props . component_registry , target . id )
73- if ( ! comp ) return [ ]
74- return comp . elements . map ( element => ( {
75- element,
57+ function getLocatorComponentFromElement (
58+ el : TEl ,
59+ ) : LocatorComponent < TEl > | null {
60+ let comp = walker . findComponent ( props . component_registry , el )
61+ return comp && {
62+ location : props . component_registry . eli . getLocation ( el ) ,
63+ element : el ,
7664 id : comp . id ,
7765 name : comp . name ,
78- } ) )
66+ }
7967 }
8068
81- s . createEffect (
82- defer (
83- ( ) => hoverTarget ( ) ?? devtoolsTarget ( ) ,
84- scheduleIdle ( target =>
85- setHighlightedComponents ( ( ) => calcHighlightedComponents ( target ) ) ,
86- ) ,
87- ) ,
88- )
69+ const target = s . createMemo ( ( ) => {
70+ let hover = hoverTarget ( )
71+ return hover != null
72+ ? {
73+ type : 'hover' as const ,
74+ element : hover ,
75+ }
76+ : devtoolsTarget ( )
77+ } , undefined , {
78+ equals : ( a , b ) => {
79+ if ( a === b ) return true
80+ if ( a == null && b == null ) return true
81+ if ( a == null || b == null ) return false
82+ if ( a . type !== b . type ) return false
83+ switch ( a . type ) {
84+ case 'hover' : return a . element === ( b as any ) . element
85+ case 'node' : return a . id === ( b as any ) . id
86+ case 'element' : return a . id === ( b as any ) . id
87+ }
88+ } ,
89+ } )
90+
91+ s . createEffect ( defer ( target , scheduleIdle ( target => {
92+
93+ let locator_components : LocatorComponent < TEl > [ ] = [ ]
94+
95+ if ( target != null ) {
96+ switch ( target . type ) {
97+ case 'hover' : {
98+ unwrap_append ( locator_components , getLocatorComponentFromElement ( target . element ) )
99+ break
100+ }
101+ case 'element' : {
102+ let element = getObjectById ( target . id , ObjectType . Element ) as TEl | null
103+ if ( element != null ) {
104+ unwrap_append ( locator_components , getLocatorComponentFromElement ( element ) )
105+ }
106+ break
107+ }
108+ case 'node' : {
109+ // target is a component or an element of a component (in DOM walker mode)
110+ let comp = walker . getComponent ( props . component_registry , target . id )
111+ if ( comp != null ) {
112+ for ( let el of comp . elements ) {
113+ locator_components . push ( {
114+ element : el ,
115+ id : comp . id ,
116+ name : comp . name ,
117+ } )
118+ }
119+ }
120+ }
121+ }
122+ }
123+
124+ setHighlightedComponents ( locator_components )
125+ } ) ) )
89126
90127 createElementsOverlay ( highlightedComponents )
91128
@@ -103,39 +140,49 @@ export function createLocator<TEl extends object>(props: {
103140 }
104141 } )
105142
106- let targetIDE : TargetIDE | TargetURLFunction | undefined
143+ let target_ide : locator . TargetIDE | locator . TargetURLFunction | undefined
107144
108145 s . createEffect ( ( ) => {
109146 if ( ! props . locatorEnabled ( ) ) return
110147
111148 // set hovered element as target
112- makeHoverElementListener ( el => setHoverTarget ( el ) )
149+ makeHoverElementListener ( props . component_registry . eli , el => setHoverTarget ( ( ) => el ) )
113150 s . onCleanup ( ( ) => setHoverTarget ( null ) )
114151
115152 // go to selected component source code on click
116- makeEventListener (
117- window ,
118- 'click' ,
119- e => {
120- const { target} = e
121- if ( ! ( target instanceof HTMLElement ) ) return
122- const highlighted = highlightedComponents ( )
123- const comp =
124- highlighted . find ( ( { element} ) => target . contains ( element ) ) ?? highlighted [ 0 ]
125- if ( ! comp ) return
126- const sourceCodeData =
127- comp . location && getSourceCodeData ( comp . location , comp . element )
128-
129- // intercept on-page components clicks and send them to the devtools overlay
130- props . onComponentClick ( comp . id , ( ) => {
131- if ( ! targetIDE || ! sourceCodeData ) return
132- e . preventDefault ( )
133- e . stopPropagation ( )
134- openSourceCode ( targetIDE , sourceCodeData )
135- } )
136- } ,
137- true ,
138- )
153+ makeEventListener ( window , 'click' , e => {
154+
155+ let el = props . component_registry . eli . getElementAt ( e )
156+ if ( el == null ) {
157+ DEV: { warn ( "Locator: can't find element at click target (target=%o)" , e . target ) }
158+ return
159+ }
160+
161+ let comp = getLocatorComponentFromElement ( el )
162+ if ( comp == null ) {
163+ DEV: { warn ( "Locator: can't find component at click target (target=%o, el=%o)" , e . target , el ) }
164+ return
165+ }
166+
167+ if ( comp . location == null ) {
168+ DEV: { warn ( "Locator: can't find source location for component (comp=%o)" , comp ) }
169+ return
170+ }
171+
172+ let source_code_data = locator . getSourceCodeData ( comp . location , comp . element )
173+ if ( source_code_data == null ) {
174+ DEV: { warn ( "Locator: can't find source code data for component (comp=%o)" , comp ) }
175+ return
176+ }
177+
178+ // intercept on-page components clicks and send them to the devtools overlay
179+ props . onComponentClick ( comp . id , ( ) => {
180+ if ( target_ide == null ) return
181+ e . preventDefault ( )
182+ e . stopPropagation ( )
183+ locator . openSourceCode ( target_ide , source_code_data )
184+ } )
185+ } , true )
139186 } )
140187
141188 let locatorUsed = false
@@ -147,11 +194,11 @@ export function createLocator<TEl extends object>(props: {
147194 *
148195 * @param options {@link LocatorOptions } for the locator.
149196 */
150- function useLocator ( options : LocatorOptions ) : void {
197+ function useLocator ( options : locator . LocatorOptions ) : void {
151198 s . runWithOwner ( owner , ( ) => {
152199 if ( locatorUsed ) return warn ( 'useLocator can be called only once.' )
153200 locatorUsed = true
154- if ( options . targetIDE ) targetIDE = options . targetIDE
201+ if ( options . targetIDE ) target_ide = options . targetIDE
155202 if ( options . key !== false ) {
156203 const isHoldingKey = createKeyHold ( options . key ?? 'Alt' , { preventDefault : true } )
157204 setEnabledByPressingSignal ( ( ) => isHoldingKey )
@@ -167,14 +214,14 @@ export function createLocator<TEl extends object>(props: {
167214
168215 return {
169216 useLocator,
170- setDevtoolsHighlightTarget ( target : HighlightElementPayload ) {
217+ setDevtoolsHighlightTarget ( target : locator . HighlightElementPayload ) {
171218 setDevtoolsTarget ( target )
172219 } ,
173- openElementSourceCode ( location : SourceLocation , element : SourceCodeData [ 'element' ] ) {
174- if ( ! targetIDE ) return warn ( 'Please set `targetIDE` it in useLocator options.' )
175- const projectPath = getProjectPath ( )
220+ openElementSourceCode ( location : SourceLocation , element : locator . SourceCodeData [ 'element' ] ) {
221+ if ( ! target_ide ) return warn ( 'Please set `targetIDE` it in useLocator options.' )
222+ const projectPath = locator . getProjectPath ( )
176223 if ( ! projectPath ) return warn ( 'projectPath is not set.' )
177- openSourceCode ( targetIDE , {
224+ locator . openSourceCode ( target_ide , {
178225 ...location ,
179226 projectPath,
180227 element,
0 commit comments