-
Notifications
You must be signed in to change notification settings - Fork 156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Merge mouseleave detection logic #1374
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,8 +103,8 @@ export function parseCssVariable(value: string) { | |
* @param parent Parent node | ||
* @param descendant Node that is checked to be a descendant of the parent node | ||
*/ | ||
export function nodeContains(parent: Node | null, descendant: Node | null) { | ||
if (!parent || !descendant) { | ||
export function nodeContains(parent: Node | null, descendant: Node | EventTarget | null) { | ||
if (!parent || !descendant || !(descendant instanceof Node)) { | ||
Comment on lines
+106
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have many instances of |
||
return false; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -528,6 +528,29 @@ describe('Details popover', () => { | |
expect(detailPopover?.findHeader()?.getElement()).toHaveTextContent(defaultData[0].title); | ||
}); | ||
|
||
test('allow mouse to be move between segment and popover ', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: typo in "to be move" (could be either "to move" or "to be moved" instead) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. grammar is hard. Since this issue is kinda critical, I am going to merge and fix it next time |
||
const { wrapper } = renderPieChart(<PieChart data={defaultData} />); | ||
fireEvent.mouseOver(wrapper!.findSegments()[0].getElement()); | ||
|
||
expect(wrapper.findDetailPopover()).toBeTruthy(); | ||
|
||
fireEvent.mouseOut(wrapper.findDetailPopover()!.getElement(), { | ||
relatedTarget: wrapper!.findSegments()[0].getElement(), | ||
}); | ||
|
||
expect(wrapper.findDetailPopover()).toBeTruthy(); | ||
|
||
fireEvent.mouseOut(wrapper!.findSegments()[0].getElement(), { | ||
relatedTarget: wrapper.findDetailPopover()!.getElement(), | ||
}); | ||
|
||
expect(wrapper.findDetailPopover()).toBeTruthy(); | ||
|
||
fireEvent.mouseOut(wrapper!.findSegments()[0].getElement(), { relatedTarget: window }); | ||
|
||
expect(wrapper.findDetailPopover()).toBeFalsy(); | ||
}); | ||
|
||
test('close popover when mouse leaves it ', () => { | ||
const { wrapper } = renderPieChart(<PieChart data={defaultData} />); | ||
wrapper.findApplication()!.focus(); | ||
|
@@ -565,13 +588,24 @@ describe('Details popover', () => { | |
|
||
test('dismisses when clicking same segment', () => { | ||
const { wrapper } = renderPieChart(<PieChart data={defaultData} />); | ||
wrapper.findSegments()[0].click(); | ||
expect(wrapper.findDetailPopover()).toBeDefined(); | ||
|
||
wrapper.findSegments()[0].click(); | ||
fireEvent.mouseDown(wrapper.findSegments()[0].getElement()); | ||
expect(wrapper.findDetailPopover()).toBeTruthy(); | ||
|
||
fireEvent.mouseDown(wrapper.findSegments()[0].getElement()); | ||
expect(wrapper.findDetailPopover()).toBeNull(); | ||
}); | ||
|
||
test('stays open with mouseleave events when pinned', () => { | ||
const { wrapper } = renderPieChart(<PieChart data={defaultData} />); | ||
|
||
fireEvent.mouseDown(wrapper.findSegments()[0].getElement()); | ||
expect(wrapper.findDetailPopover()).toBeTruthy(); | ||
|
||
fireEvent.mouseOut(wrapper.findSegments()[0].getElement()); | ||
expect(wrapper.findDetailPopover()).toBeTruthy(); | ||
}); | ||
|
||
test('can be dismissed with click on the dismiss button', () => { | ||
const { wrapper } = renderPieChart(<PieChart data={defaultData} />); | ||
wrapper.findSegments()[1].click(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,8 +87,6 @@ const PieChart = function PieChart<T extends PieChartProps.Datum = PieChartProps | |
} | ||
); | ||
|
||
const [pinnedSegment, setPinnedSegment] = useState<T | null>(null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not used directly in this component. Moved it down where it is actually used to simplify |
||
|
||
const visibleData = useMemo( | ||
() => data.filter(d => visibleSegments?.indexOf(d.datum) !== -1), | ||
[data, visibleSegments] | ||
|
@@ -212,8 +210,6 @@ const PieChart = function PieChart<T extends PieChartProps.Datum = PieChartProps | |
onHighlightChange={onHighlightChange} | ||
highlightedSegment={highlightedSegment} | ||
legendSegment={legendSegment} | ||
pinnedSegment={pinnedSegment} | ||
setPinnedSegment={setPinnedSegment} | ||
detailPopoverSize={detailPopoverSize} | ||
pieData={pieData} | ||
dataSum={dataSum} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import { nodeBelongs } from '../internal/utils/node-belongs'; | |
import clsx from 'clsx'; | ||
import { useVisualRefresh } from '../internal/hooks/use-visual-mode'; | ||
import { useHeightMeasure } from '../internal/hooks/container-queries/use-height-measure'; | ||
import { nodeContains } from '../internal/utils/dom'; | ||
|
||
export interface InternalChartDatum<T> { | ||
index: number; | ||
|
@@ -42,9 +43,6 @@ interface InternalPieChartProps<T extends PieChartProps.Datum> | |
|
||
legendSegment: T | null; | ||
|
||
pinnedSegment: T | null; | ||
setPinnedSegment: React.Dispatch<React.SetStateAction<T | null>>; | ||
|
||
pieData: PieArcDatum<InternalChartDatum<T>>[]; | ||
dataSum: number; | ||
} | ||
|
@@ -76,11 +74,10 @@ export default <T extends PieChartProps.Datum>({ | |
highlightedSegment, | ||
onHighlightChange, | ||
legendSegment, | ||
pinnedSegment, | ||
setPinnedSegment, | ||
pieData, | ||
dataSum, | ||
}: InternalPieChartProps<T>) => { | ||
const [pinnedSegment, setPinnedSegment] = useState<T | null>(null); | ||
const plotRef = useRef<ChartPlotRef>(null); | ||
const containerRef = useRef<HTMLDivElement>(null); | ||
const focusedSegmentRef = useRef<SVGGElement>(null); | ||
|
@@ -158,6 +155,21 @@ export default <T extends PieChartProps.Datum>({ | |
onHighlightChange(null); | ||
}, [onHighlightChange, setTooltipOpen]); | ||
|
||
const checkMouseLeave = (event: React.MouseEvent) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deduplicated |
||
if (pinnedSegment !== null) { | ||
return; | ||
} | ||
|
||
if ( | ||
nodeContains(popoverRef.current, event.relatedTarget) || | ||
nodeContains(focusedSegmentRef.current, event.relatedTarget) | ||
) { | ||
return; | ||
} | ||
|
||
clearHighlightedSegment(); | ||
}; | ||
|
||
useEffect(() => { | ||
const onKeyDown = (event: KeyboardEvent) => { | ||
if (event.key === 'Escape') { | ||
|
@@ -194,16 +206,7 @@ export default <T extends PieChartProps.Datum>({ | |
}, | ||
[pinnedSegment, highlightSegment] | ||
); | ||
const onMouseOut = useCallback( | ||
(event: React.MouseEvent<SVGElement, MouseEvent>) => { | ||
if (pinnedSegment !== null || popoverRef.current?.contains(event.relatedTarget as Node)) { | ||
return; | ||
} | ||
|
||
clearHighlightedSegment(); | ||
}, | ||
[pinnedSegment, clearHighlightedSegment] | ||
); | ||
const onKeyDown = useCallback( | ||
(event: React.KeyboardEvent) => { | ||
const keyCode = event.keyCode; | ||
|
@@ -283,13 +286,6 @@ export default <T extends PieChartProps.Datum>({ | |
} | ||
}; | ||
|
||
const onPopoverLeave = (event: React.MouseEvent) => { | ||
if (pinnedSegment !== null || focusedSegmentRef.current!.contains(event.relatedTarget as Node)) { | ||
return; | ||
} | ||
clearHighlightedSegment(); | ||
}; | ||
|
||
return ( | ||
<div | ||
className={clsx(styles['chart-container'], fitHeight && styles['chart-container--fit-height'])} | ||
|
@@ -319,7 +315,7 @@ export default <T extends PieChartProps.Datum>({ | |
onFocus={onFocus} | ||
onBlur={onBlur} | ||
onKeyDown={onKeyDown} | ||
onMouseOut={onMouseOut} | ||
onMouseOut={checkMouseLeave} | ||
> | ||
<Segments | ||
pieData={pieData} | ||
|
@@ -331,7 +327,6 @@ export default <T extends PieChartProps.Datum>({ | |
segmentAriaRoleDescription={i18nStrings?.segmentAriaRoleDescription} | ||
onMouseDown={onMouseDown} | ||
onMouseOver={onMouseOver} | ||
onMouseOut={onMouseOut} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is redundant
The inner one can be removed |
||
/> | ||
{hasLabels && ( | ||
<Labels | ||
|
@@ -385,7 +380,7 @@ export default <T extends PieChartProps.Datum>({ | |
onDismiss={onPopoverDismiss} | ||
container={plotRef.current?.svg || null} | ||
size={detailPopoverSize} | ||
onMouseLeave={onPopoverLeave} | ||
onMouseLeave={checkMouseLeave} | ||
> | ||
{tooltipContent} | ||
{detailPopoverFooterContent && <InternalBox margin={{ top: 's' }}>{detailPopoverFooterContent}</InternalBox>} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
styles.dismissible
does not exist and this rendersclassName="undefined"