Skip to content

Commit

Permalink
Merge pull request #1164 from HJK181/bug/gh-1161
Browse files Browse the repository at this point in the history
Consider custom element when checking if event is
  • Loading branch information
JohannesKlauss authored Nov 2, 2024
2 parents fad934e + d76df65 commit 0928710
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
17 changes: 15 additions & 2 deletions src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ export function isKeyboardEventTriggeredByInput(ev: KeyboardEvent): boolean {
}

export function isHotkeyEnabledOnTag(
{ target }: KeyboardEvent,
event: KeyboardEvent,
enabledOnTags: readonly FormTags[] | boolean = false
): boolean {
const targetTagName = target && (target as HTMLElement).tagName
const {target, composed} = event;

let targetTagName;
if (isCustomElement(target as HTMLElement) && composed) {
targetTagName = event.composedPath()[0] && (event.composedPath()[0] as HTMLElement).tagName;
} else {
targetTagName = target && (target as HTMLElement).tagName;
}
if (isReadonlyArray(enabledOnTags)) {
return Boolean(
targetTagName && enabledOnTags && enabledOnTags.some((tag) => tag.toLowerCase() === targetTagName.toLowerCase())
Expand All @@ -35,6 +41,13 @@ export function isHotkeyEnabledOnTag(
return Boolean(targetTagName && enabledOnTags && enabledOnTags === true)
}

export function isCustomElement(element: HTMLElement): boolean {
// We just do a basic check w/o any complex RegEx or validation against the list of legacy names containing a hyphen,
// as none of them is likely to be an event target, and it won't hurt anyway if we miss.
// see: https://html.spec.whatwg.org/multipage/custom-elements.html#prod-potentialcustomelementname
return !!element.tagName && !element.tagName.startsWith("-") && element.tagName.includes("-");
}

export function isScopeActive(activeScopes: string[], scopes?: Scopes): boolean {
if (activeScopes.length === 0 && scopes) {
console.warn(
Expand Down
47 changes: 46 additions & 1 deletion tests/useHotkeys.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
useCallback,
useState,
} from 'react'
import { createEvent, fireEvent, render, screen, renderHook } from '@testing-library/react'
import { createEvent, fireEvent, render, screen, renderHook, within } from '@testing-library/react'

const wrapper =
(initialScopes: string[]): JSXElementConstructor<{ children: ReactElement }> =>
Expand Down Expand Up @@ -491,6 +491,51 @@ test('should be disabled on form tags by default', async () => {
expect(getByTestId('form-tag')).toHaveValue('A')
})

test('should be disabled on form tags inside custom elements by default', async () => {
const user = userEvent.setup()
const callback = jest.fn()

customElements.define(
"custom-input",
class extends HTMLElement {
constructor() {
super();

const inputEle = document.createElement("input");
inputEle.setAttribute("type", "text");
inputEle.setAttribute("data-testid", "input");

const shadowRoot = this.attachShadow({
mode: "open"
});

shadowRoot.appendChild(inputEle);
}
},
);

const Component = ({ cb }: { cb: HotkeyCallback }) => {
useHotkeys<HTMLDivElement>('a', cb)

// @ts-ignore
return <custom-input data-testid={'form-tag'}/>
}

const { getByTestId } = render(<Component cb={callback} />)

await user.keyboard('A')

expect(callback).toHaveBeenCalledTimes(1)

// @ts-ignore
await user.click(within(getByTestId('form-tag').shadowRoot).getByTestId('input'))
await user.keyboard('A')

expect(callback).toHaveBeenCalledTimes(1)
// @ts-ignore
expect(within(getByTestId('form-tag').shadowRoot).getByTestId('input')).toHaveValue('A')
})

test('should be enabled on given form tags', async () => {
const user = userEvent.setup()
const callback = jest.fn()
Expand Down

0 comments on commit 0928710

Please sign in to comment.