diff --git a/e2e/cypress/common/exampleAppDevTest.ts b/e2e/cypress/common/exampleAppDevTest.ts index 9841552ca9..0a32d40da4 100644 --- a/e2e/cypress/common/exampleAppDevTest.ts +++ b/e2e/cypress/common/exampleAppDevTest.ts @@ -19,7 +19,7 @@ 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'); @@ -27,7 +27,7 @@ export const exampleAppDevTest = (url: string, options?: Options) => 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'); diff --git a/e2e/cypress/common/nextInternalCommon.ts b/e2e/cypress/common/nextInternalCommon.ts index 93cdcc8383..55153cb9f2 100644 --- a/e2e/cypress/common/nextInternalCommon.ts +++ b/e2e/cypress/common/nextInternalCommon.ts @@ -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); diff --git a/e2e/cypress/e2e/react/dev.cy.ts b/e2e/cypress/e2e/react/dev.cy.ts index fca2d2fe2e..4eb64e625a 100644 --- a/e2e/cypress/e2e/react/dev.cy.ts +++ b/e2e/cypress/e2e/react/dev.cy.ts @@ -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'); }); }); diff --git a/packages/web/src/observers/general/ElementHighlighter.ts b/packages/web/src/observers/general/ElementHighlighter.ts index 1a90589cb9..569c0e2af4 100644 --- a/packages/web/src/observers/general/ElementHighlighter.ts +++ b/packages/web/src/observers/general/ElementHighlighter.ts @@ -2,7 +2,6 @@ import { TOLGEE_HIGHLIGHTER_CLASS } from '../../constants'; import { ElementMeta, TolgeeElement } from '../../types'; const HIGHLIGHTER_BASE_STYLE: Partial = { - pointerEvents: 'none', position: 'fixed', boxSizing: 'content-box', zIndex: String(Number.MAX_SAFE_INTEGER), diff --git a/packages/web/src/observers/general/MouseEventHandler.ts b/packages/web/src/observers/general/MouseEventHandler.ts index 1f51febc6b..48379ca53a 100644 --- a/packages/web/src/observers/general/MouseEventHandler.ts +++ b/packages/web/src/observers/general/MouseEventHandler.ts @@ -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(); + const keysDown = new Set(); 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; @@ -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); } @@ -81,7 +93,18 @@ 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(); @@ -89,27 +112,17 @@ export function MouseEventHandler({ } 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(); } @@ -120,43 +133,44 @@ 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( + 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) { @@ -164,17 +178,26 @@ export function MouseEventHandler({ } 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; }