Skip to content

Commit

Permalink
feat: improve mouse handler (#3274)
Browse files Browse the repository at this point in the history
  • Loading branch information
stepan662 authored Nov 2, 2023
1 parent 1d1ec51 commit 44cbf78
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 63 deletions.
4 changes: 2 additions & 2 deletions e2e/cypress/common/exampleAppDevTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ export const exampleAppDevTest = (url: string, options?: Options) =>

it('title can be translated', () => {
cy.contains('What To Pack').invoke('attr', '_tolgee').should('exist');
cy.contains('What To Pack').trigger('keydown', { key: 'Alt' }).click();
cy.contains('What To Pack').click({ altKey: true });
getDevUi()
.contains('Quick translation', { timeout: 60 * 1000 })
.should('be.visible');
});

it('placeholder can be translated', () => {
cy.contains('What To Pack').invoke('attr', '_tolgee').should('exist');
cy.get('input').trigger('keydown', { key: 'Alt' }).click();
cy.get('input').click({ altKey: true });
getDevUi()
.contains('Quick translation', { timeout: 60 * 1000 })
.should('be.visible');
Expand Down
7 changes: 1 addition & 6 deletions e2e/cypress/common/nextInternalCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { getDevUi, getDevUiRoot } from './devUiTools';
import { Scope } from './types';

export const openUI = (translation = 'What To Pack') => {
cy.contains(translation)
.should('be.visible')
.trigger('keydown', { key: 'Alt' })
.trigger('mouseover')
.click();
cy.window().trigger('keyup', { key: 'Alt' });
cy.contains(translation).should('be.visible').click({ altKey: true });
getDevUiRoot().should('exist');
getDevUi().find('textarea').contains(translation).should('be.visible');
cy.wait(300);
Expand Down
4 changes: 2 additions & 2 deletions e2e/cypress/e2e/react/dev.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ context('React app in dev mode', () => {
});

it('opens inner translation correctly', () => {
cy.gcy('translationInner').trigger('keydown', { key: 'Alt' }).click();
cy.gcy('translationInner').click({ altKey: true });
getDevUi().contains('translation_inner').should('be.visible');
});

it('opens outer translation correctly', () => {
cy.gcy('translationOuter').trigger('keydown', { key: 'Alt' }).click();
cy.gcy('translationOuter').click({ altKey: true });
getDevUi().contains('translation_outer').should('be.visible');
});
});
Expand Down
1 change: 0 additions & 1 deletion packages/web/src/observers/general/ElementHighlighter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { TOLGEE_HIGHLIGHTER_CLASS } from '../../constants';
import { ElementMeta, TolgeeElement } from '../../types';

const HIGHLIGHTER_BASE_STYLE: Partial<CSSStyleDeclaration> = {
pointerEvents: 'none',
position: 'fixed',
boxSizing: 'content-box',
zIndex: String(Number.MAX_SAFE_INTEGER),
Expand Down
127 changes: 75 additions & 52 deletions packages/web/src/observers/general/MouseEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,30 @@ type Props = {
options: ObserverOptionsInternal;
};

const MODIFIER_MAP = new Map<
ModifierKey,
'ctrlKey' | 'altKey' | 'metaKey' | 'shiftKey'
>([
['Control', 'ctrlKey'],
['Alt', 'altKey'],
['Meta', 'metaKey'],
['Shift', 'shiftKey'],
]);

export function MouseEventHandler({
highlightKeys,
elementStore,
onClick,
options,
}: Props) {
let keysDown = new Set<ModifierKey>();
const keysDown = new Set<ModifierKey>();
let highlighted: TolgeeElement | undefined;
let cursorPosition: Coordinates | undefined;
let subscribedEvents: [
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
][] = [];

const documentOrShadowRoot = (options.targetElement?.getRootNode() ||
document) as unknown as ShadowRoot;
Expand Down Expand Up @@ -65,13 +80,10 @@ export function MouseEventHandler({

let newHighlighted: TolgeeElement | undefined;
if (position && areKeysDown()) {
const element = documentOrShadowRoot.elementFromPoint(
position.x,
position.y
);
if (element) {
newHighlighted = getClosestTolgeeElement(element);
}
const elements =
documentOrShadowRoot.elementsFromPoint(position.x, position.y) || [];

newHighlighted = getClosestTolgeeElement(elements);
}
highlight(newHighlighted);
}
Expand All @@ -81,35 +93,36 @@ export function MouseEventHandler({
updateHighlight();
}

function updateModifiers(e: MouseEvent | KeyboardEvent) {
for (const [modifier, modifierProperty] of MODIFIER_MAP.entries()) {
if (keysDown.has(modifier) && !e[modifierProperty]) {
keysDown.delete(modifier);
} else if (!keysDown.has(modifier) && e[modifierProperty]) {
keysDown.add(modifier);
}
}
}

function blockEvents(e: MouseEvent) {
updateModifiers(e);
if (areKeysDown() && !isInUiDialog(e.target as Element)) {
e.stopPropagation();
e.preventDefault();
}
}

function onMouseMove(e: MouseEvent) {
updateModifiers(e);
updateCursorPosition({ x: e.clientX, y: e.clientY });
}

function onBlur() {
keysDown = new Set();
// keysChanged.emit(areKeysDown());
updateHighlight();
}

function onKeyDown(e: KeyboardEvent) {
const modifierKey = e.key as unknown as ModifierKey;
if (modifierKey !== undefined) {
keysDown.add(modifierKey);
// keysChanged.emit(areKeysDown());
}
updateModifiers(e);
updateHighlight();
}

function onKeyUp(e: KeyboardEvent) {
keysDown.delete(e.key as unknown as ModifierKey);
// keysChanged.emit(areKeysDown());
updateModifiers(e);
updateHighlight();
}

Expand All @@ -120,61 +133,71 @@ export function MouseEventHandler({

function handleClick(e: MouseEvent) {
blockEvents(e);
updateModifiers(e);
updateCursorPosition({ x: e.clientX, y: e.clientY });
if (areKeysDown() && highlighted) {
onClick(e, highlighted);
unhighlight();
}
}

function subscribe<K extends keyof DocumentEventMap>(
type: K,
listener: (ev: DocumentEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
) {
targetDocument.addEventListener(type, listener, options);
subscribedEvents.push([type, listener as any, options]);
}

function initEventListeners() {
targetDocument.addEventListener('blur', onBlur, eCapture);
targetDocument.addEventListener('keydown', onKeyDown, eCapture);
targetDocument.addEventListener('keyup', onKeyUp, eCapture);
targetDocument.addEventListener('mousemove', onMouseMove, ePassive);
targetDocument.addEventListener('scroll', onScroll, ePassive);
targetDocument.addEventListener('click', handleClick, eCapture);
subscribe('keydown', onKeyDown, eCapture);
subscribe('keyup', onKeyUp, eCapture);
subscribe('mousemove', onMouseMove, ePassive);

targetDocument.addEventListener('mouseenter', blockEvents, eCapture);
targetDocument.addEventListener('mouseover', blockEvents, eCapture);
targetDocument.addEventListener('mouseout', blockEvents, eCapture);
targetDocument.addEventListener('mouseleave', blockEvents, eCapture);
targetDocument.addEventListener('mousedown', blockEvents, eCapture);
targetDocument.addEventListener('mouseup', blockEvents, eCapture);
subscribe('scroll', onScroll, ePassive);
subscribe('click', handleClick, eCapture);

subscribe('mouseenter', blockEvents, eCapture);
subscribe('mouseover', blockEvents, eCapture);
subscribe('mouseout', blockEvents, eCapture);
subscribe('mouseleave', blockEvents, eCapture);
subscribe('mousedown', blockEvents, eCapture);
subscribe('mouseup', blockEvents, eCapture);
}

function removeEventListeners() {
targetDocument.removeEventListener('blur', onBlur, eCapture);
targetDocument.removeEventListener('keydown', onKeyDown, eCapture);
targetDocument.removeEventListener('keyup', onKeyUp, eCapture);
targetDocument.removeEventListener('mousemove', onMouseMove, ePassive);

targetDocument.removeEventListener('scroll', onScroll, ePassive);
targetDocument.removeEventListener('click', handleClick, eCapture);

targetDocument.removeEventListener('mouseenter', blockEvents, eCapture);
targetDocument.removeEventListener('mouseover', blockEvents, eCapture);
targetDocument.removeEventListener('mouseout', blockEvents, eCapture);
targetDocument.removeEventListener('mouseleave', blockEvents, eCapture);
targetDocument.removeEventListener('mousedown', blockEvents, eCapture);
targetDocument.removeEventListener('mouseup', blockEvents, eCapture);
for (const params of subscribedEvents) {
targetDocument.removeEventListener(...params);
}
subscribedEvents = [];
}

function isInUiDialog(element: Element) {
return Boolean(findAncestor(element, (el) => el.id === DEVTOOLS_ID));
}

function getClosestTolgeeElement(
element: Element
elements: Element[]
): TolgeeElement | undefined {
return findAncestor(element, (el) =>
elementStore.get(el as TolgeeElement)
) as TolgeeElement;
for (const element of elements) {
const result = findAncestor(element, (el) =>
elementStore.get(el as TolgeeElement)
) as TolgeeElement | undefined | null;

if (result !== undefined) {
return result || undefined;
}
}
}

function findAncestor(
element: Element,
func: (el: Element) => any
): Element | undefined {
): Element | undefined | null {
if (element.id === DEVTOOLS_ID) {
return null;
}
if (func(element)) {
return element;
}
Expand Down

0 comments on commit 44cbf78

Please sign in to comment.