Skip to content

Commit

Permalink
style: fix the UI/UX of ContextPanel and DOMInspectAgent
Browse files Browse the repository at this point in the history
- stop click event propagation in the ContextPanel to avoid closing other popover components
- darken the box-shadow color of ContextPanel
- refactor the exports of inspect and fiber utils
- fix the type-infer of InspectAgents in Inspector parameters
  • Loading branch information
zthxxx committed Apr 27, 2024
1 parent d1e3d5a commit 7de232a
Show file tree
Hide file tree
Showing 37 changed files with 482 additions and 284 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ build

**/.stories/components/
**/components/logos/
**/components/icons/
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.next
next-env.d.ts
/out
2 changes: 2 additions & 0 deletions packages/inspector/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/.stories/
src/**/*.stories.*
55 changes: 42 additions & 13 deletions packages/inspector/src/Inspector/DOMInspectAgent/DOMInspectAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,37 @@ import type {

export type DOMElement = HTMLElement | SVGElement

export interface DOMInspectAgentOptions {
/**
* events that need be prevent and stop propagation on capture,
* default is {@link defaultPreventEvents},
*
* adjust if you find some interaction conflict.
*
* > DO NOT set 'click' / 'mousedown' / 'pointerdown' / 'pointerover'
* > which are always handle by DOMInspectAgent internal
*/
preventEvents?: (keyof GlobalEventHandlersEventMap)[];
}

const defaultPreventEvents = [
'mouseup',
'pointerup',
'mouseover',
'mouseout',
'pointerout',
] satisfies (keyof GlobalEventHandlersEventMap)[]

export class DOMInspectAgent implements InspectAgent<DOMElement> {
protected overlay?: Overlay
protected unsubscribeListener?: () => void
#overlay?: Overlay
#unsubscribeListener?: () => void
#preventEvents: (keyof GlobalEventHandlersEventMap)[]

constructor({
preventEvents = defaultPreventEvents,
}: DOMInspectAgentOptions = {}) {
this.#preventEvents = preventEvents
}

public activate = ({
onHover,
Expand All @@ -38,43 +66,44 @@ export class DOMInspectAgent implements InspectAgent<DOMElement> {
onClick: (params: { element?: DOMElement; pointer: PointerEvent }) => void;
}) => {
this.deactivate()
this.overlay = new Overlay()
this.#overlay = new Overlay()

this.unsubscribeListener = setupPointerListener({
this.#unsubscribeListener = setupPointerListener({
onPointerOver: onHover,
onPointerDown,
onClick,
preventEvents: this.#preventEvents,
})
}

public deactivate = () => {
this.overlay?.remove()
this.overlay = undefined
this.#overlay?.remove()
this.#overlay = undefined

this.unsubscribeListener?.()
this.unsubscribeListener = undefined
this.#unsubscribeListener?.()
this.#unsubscribeListener = undefined
}

public indicate = ({ element, codeInfo, title }: {
element: DOMElement;
title?: string;
codeInfo?: CodeInfo;
}) => {
if (!this.overlay) {
this.overlay = new Overlay()
if (!this.#overlay) {
this.#overlay = new Overlay()
}

codeInfo ??= this.findCodeInfo(element)

this.overlay.inspect({
this.#overlay.inspect({
element,
title,
info: getPathWithLineNumber(codeInfo),
})
}

public removeIndicate = () => {
this.overlay?.hide()
this.#overlay?.hide()
}

public getTopElementFromPointer = (pointer: Pointer): DOMElement | undefined | null => {
Expand Down Expand Up @@ -180,7 +209,7 @@ export class DOMInspectAgent implements InspectAgent<DOMElement> {
}


export const domInspectAgent = new DOMInspectAgent()
export const domInspectAgent: InspectAgent<DOMElement> = new DOMInspectAgent()


export const getDOMElementTags = (element: unknown): TagItem[] => {
Expand Down
35 changes: 27 additions & 8 deletions packages/inspector/src/Inspector/Inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ import {
} from '@react-dev-inspector/launch-editor-endpoint'
import {
gotoServerEditor,
elementsChainGenerator,
} from './utils'
import {
elementsChainGenerator,
} from './services'
import {
useHotkeyToggle,
useEffectEvent,
useRecordPointer,
useControlledActive,
} from './hooks'
import { domInspectAgent, type DOMElement } from './DOMInspectAgent'
import {
type DOMElement,
domInspectAgent,
} from './DOMInspectAgent'
import type {
CodeInfo,
InspectAgent,
Expand All @@ -38,7 +43,7 @@ const defaultInspectAgents: InspectAgent<any>[] = [
/**
* the inspect meta info that is sent to the callback when an element is hovered over or clicked.
*/
export interface InspectParams<Element = DOMElement> {
export interface InspectParams<Element> {
/** hover / click event target dom element */
element: Element;
/** nearest named react component fiber for dom element */
Expand All @@ -59,7 +64,10 @@ export type OnInspectElementParams<Element> =
& Omit<Required<InspectParams<Element>>, 'editor'>
& Pick<InspectParams<Element>, 'editor'>

export interface InspectorProps<Element> {
export interface InspectorProps<
InspectAgents extends InspectAgent<any>[],
Element extends ElementInInspectAgents<InspectAgents> = ElementInInspectAgents<InspectAgents>,
> {
/**
* Inspector Component toggle hotkeys,
*
Expand Down Expand Up @@ -105,11 +113,12 @@ export interface InspectorProps<Element> {

/**
* Agent for get inspection info in different React renderer with user interaction
* @default [domInspectAgent]
*
* Default: {@link domInspectAgent}
*
* > add in version `v2.1.0`
*/
inspectAgents?: InspectAgent<Element>[];
inspectAgents?: InspectAgents;

/**
* Callback when left-clicking on an element, with ensuring the source code info is found.
Expand Down Expand Up @@ -159,15 +168,19 @@ export interface InspectorProps<Element> {
disableLaunchEditor?: boolean;
}

export const Inspector = function<Element = any>(props: InspectorProps<Element>) {
export const Inspector = function<
InspectAgents extends InspectAgent<any>[] = InspectAgent<DOMElement>[],
>(props: InspectorProps<InspectAgents>) {
type Element = ElementInInspectAgents<InspectAgents>

const {
keys,
onHoverElement,
onClickElement,
onInspectElement,
active: controlledActive,
onActiveChange,
inspectAgents = defaultInspectAgents as InspectAgent<Element>[],
inspectAgents = defaultInspectAgents,
disableLaunchEditor,
disable = (process.env.NODE_ENV !== 'development'),
ContextPanel = InspectContextPanel,
Expand Down Expand Up @@ -502,6 +515,12 @@ interface InspectElementItem<Element = any> extends ElementItemInfo {
element?: Element | null;
}

type ElementInInspectAgents<Agents> = Agents extends (infer Agent)[]
? Agent extends InspectAgent<infer Element>
? Element
: unknown
: unknown

const contextPanelSizeLimit: InspectContextPanelShowParams['sizeLimit'] = {
minWidth: 160,
minHeight: 160,
Expand Down
9 changes: 3 additions & 6 deletions packages/inspector/src/Inspector/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@ export {
gotoServerEditor,
/** @deprecated Use `gotoServerEditor` instead. */
gotoServerEditor as gotoEditor,

getCodeInfoFromFiber,
getElementCodeInfo,
getNamedFiber,
genInspectChainFromFibers,
} from './utils'

export * from './utils/fiber'
export * as inspectUtils from './utils/inspect'
export * as fiberUtils from './utils/fiber'

export {
defaultHotkeys,
} from './hooks'

export {
type DOMElement,
DOMInspectAgent,
domInspectAgent,
} from './DOMInspectAgent'
Expand Down
1 change: 1 addition & 0 deletions packages/inspector/src/Inspector/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './inspect-agent'
45 changes: 45 additions & 0 deletions packages/inspector/src/Inspector/services/inspect-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type {
InspectAgent,
InspectChainGenerator,
} from '../types'


export function * elementsChainGenerator<Element = unknown>({
agent,
agents,
element,
generateElement,
}: {
agent: InspectAgent<Element>;
agents: InspectAgent<Element>[];
element: Element;
generateElement: <Element = unknown>(agent: InspectAgent<Element>, element: Element) => InspectChainGenerator<Element>;
}): InspectChainGenerator<Element> {
const generator = generateElement(agent, element)
while (true) {
const next = generator.next()
if (!next.done) {
yield next.value
continue
}

if (!next.value) {
return
}

for (const agent of agents) {
if (!agent.isAgentElement(next.value)) {
continue
}
yield * elementsChainGenerator({
agent,
agents,
element: next.value,
generateElement,
})
return
}

return
}
}
72 changes: 69 additions & 3 deletions packages/inspector/src/Inspector/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface CodeDataAttribute {

/**
* Pointer(mouse/touch) position
* use in {@link InspectAgent.getTopElementFromPointer}
*/
export interface Pointer {
/**
Expand Down Expand Up @@ -130,7 +131,42 @@ export interface InspectAgent<Element> {
* - so the returned element should not be {@link Element}, but the outside parent of the root element.
* - if agent's renderer is the top root of whole app, just return `undefined | null`.
*
* @TODO chain list will show in the Inspector's context-menu when right-click on the element.
*
* e.g. if fetching source-chain from `<Child>'s` element with this source code:
*
* ```tsx
* const Root = () => (
* <div id=1 >
* <Parent>
* <p id=2 >
* <Child />
* </p>
* </Parent>
* </div>
* )
*
* const Parent = (children) => (
* <div id=3 >
* {children}
* </div>
* )
*
* const Child = () => <div id=child />
* ```
*
* will expect to get chain:
*
* ```tsx
* [
* <div id=child >,
* <Child>,
* <p id=2 >,
* <div id=3 >,
* <Parent>,
* <div id=1 >,
* <Root>,
* ]
* ```
*/
getRenderChain(element: Element): InspectChainGenerator<Element>;

Expand All @@ -139,7 +175,37 @@ export interface InspectAgent<Element> {
* - but base on source-code structure order rather than render structure order,
* - and only yield valid elements which considered have a valid name, source code position, or you want show it in the inspected list.
*
* @TODO chain list will show in the Inspector's context-menu when right-click on the element.
* e.g. if fetching source-chain from `<Child>'s` element with this source code:
*
* ```tsx
* const Root = () => (
* <div id=1 >
* <Parent>
* <p id=2 >
* <Child />
* </p>
* </Parent>
* </div>
* )
*
* const Parent = (children) => (
* <div id=3 >
* {children}
* </div>
* )
*
* const Child = () => <div id=child />
* ```
*
* will expect to get chain:
*
* ```tsx
* [
* <div id=child >,
* <Child>,
* <Root>,
* ]
* ```
*/
getSourceChain(element: Element): InspectChainGenerator<Element>;

Expand Down Expand Up @@ -187,7 +253,7 @@ export type InspectChainGenerator<Element> = Generator<InspectChainItem<Element>
type UpperRootElement = any

/**
* chain item data in {@link InspectAgent.getRenderChain} / {@link InspectAgent.getSourceChain}
* chain item data structure of {@link InspectAgent.getRenderChain} / {@link InspectAgent.getSourceChain}
*/
export interface InspectChainItem<Element> {
agent: InspectAgent<Element>;
Expand Down
Loading

0 comments on commit 7de232a

Please sign in to comment.