Skip to content

Commit

Permalink
improve
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Jul 3, 2020
1 parent a1c47aa commit dcc7a54
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { createInteractionPositionTracker } from './open_context_menu';
import { fireEvent } from '@testing-library/dom';

let targetEl: Element;
const top = 100;
const left = 100;
const right = 200;
const bottom = 200;
beforeEach(() => {
targetEl = document.createElement('div');
jest.spyOn(targetEl, 'getBoundingClientRect').mockImplementation(() => ({
top,
left,
right,
bottom,
width: right - left,
height: bottom - top,
x: left,
y: top,
toJSON: () => {},
}));
document.body.append(targetEl);
});
afterEach(() => {
targetEl.remove();
});

test('should use last clicked element position if mouse position is outside target element', () => {
const { resolveLastPosition } = createInteractionPositionTracker();

fireEvent.click(targetEl, { clientX: 0, clientY: 0 });
const { x, y } = resolveLastPosition();

expect(y).toBe(bottom);
expect(x).toBe(left + (right - left) / 2);
});

test('should use mouse position if mouse inside clicked element', () => {
const { resolveLastPosition } = createInteractionPositionTracker();

const mouseX = 150;
const mouseY = 150;
fireEvent.click(targetEl, { clientX: mouseX, clientY: mouseY });

const { x, y } = resolveLastPosition();

expect(y).toBe(mouseX);
expect(x).toBe(mouseY);
});

test('should use position of previous element, if latest element is no longer in DOM', () => {
const { resolveLastPosition } = createInteractionPositionTracker();

const detachedElement = document.createElement('div');
const spy = jest.spyOn(detachedElement, 'getBoundingClientRect');

fireEvent.click(targetEl);
fireEvent.click(detachedElement);

const { x, y } = resolveLastPosition();

expect(y).toBe(bottom);
expect(x).toBe(left + (right - left) / 2);
expect(spy).not.toBeCalled();
});
64 changes: 27 additions & 37 deletions src/plugins/ui_actions/public/context_menu/open_context_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,58 +27,51 @@ let activeSession: ContextMenuSession | null = null;

const CONTAINER_ID = 'contextMenu-container';

function createInteractionPositionTracker() {
/**
* Tries to find best position for opening context menu using mousemove and click event
* Returned position is relative to document
*/
export function createInteractionPositionTracker() {
let lastMouseX = 0;
let lastMouseY = 0;
let lastClick: { el?: Element; pageX: number; pageY: number } | undefined;
const lastClicks: Array<{ el?: Element; mouseX: number; mouseY: number }> = [];
const MAX_LAST_CLICKS = 5;

document.addEventListener('click', onClick, true);
document.addEventListener('mousemove', onMouseUpdate, { passive: true });
document.addEventListener('mouseenter', onMouseUpdate, { passive: true });
function onClick(event: MouseEvent) {
lastClick = {
lastClicks.push({
el: event.target as Element,
pageX: event.pageX,
pageY: event.pageY,
};
mouseX: event.clientX,
mouseY: event.clientY,
});
if (lastClicks.length > MAX_LAST_CLICKS) {
lastClicks.shift();
}
}
function onMouseUpdate(event: MouseEvent) {
lastMouseX = event.pageY;
lastMouseY = event.pageX;
lastMouseX = event.clientX;
lastMouseY = event.clientY;
}

return {
resolveLastPosition: (): { x: number; y: number } => {
const scrollTop = document.body.scrollTop;
const scrollLeft = document.body.scrollLeft;

const lastClick = [...lastClicks]
.reverse()
.find(({ el }) => el && document.body.contains(el));
if (!lastClick) {
// fallback to last mouse position
return {
x: lastMouseX + scrollLeft,
y: lastMouseY + scrollTop,
};
}

if (!lastClick.el) {
return {
x: lastClick.pageX + scrollLeft,
y: lastClick.pageY + scrollTop,
x: lastMouseX,
y: lastMouseY,
};
}

if (!document.body.contains(lastClick.el)) {
// target element no longer in DOM
// fallback to lastMouse position
return {
x: lastMouseX + scrollLeft,
y: lastMouseY + scrollTop,
};
}
const { top, left, bottom, right } = lastClick.el!.getBoundingClientRect();

const { top, left, bottom, right } = lastClick.el.getBoundingClientRect();
const mouseX = lastClick.pageX + scrollLeft;
const mouseY = lastClick.pageY + scrollTop;
const mouseX = lastClick.mouseX;
const mouseY = lastClick.mouseY;

if (top <= mouseY && bottom >= mouseY && left <= mouseX && right >= mouseX) {
// click was inside target element
Expand All @@ -93,19 +86,16 @@ function createInteractionPositionTracker() {
y: bottom,
};
}

return {
x: window.innerWidth / 2 + scrollTop,
y: window.innerHeight / 2 + scrollLeft,
};
},
};
}

const { resolveLastPosition } = createInteractionPositionTracker();
function getOrCreateContainerElement() {
let container = document.getElementById(CONTAINER_ID);
const { x, y } = resolveLastPosition();
let { x, y } = resolveLastPosition();
y = y + window.scrollY;
x = x + window.scrollX;

if (!container) {
container = document.createElement('div');
Expand Down

0 comments on commit dcc7a54

Please sign in to comment.