···5858export function clear(container: HTMLElement): void {
5959 container.replaceChildren();
6060}
6161+6262+/**
6363+ * True when an event originated from an editable element (text input, textarea,
6464+ * or contenteditable). Used to ignore global keystrokes while the user is typing
6565+ * into a field such as the sign-in form, so they don't leak into the game board.
6666+ */
6767+export function isEditableTarget(target: EventTarget | null): boolean {
6868+ if (!(target instanceof HTMLElement)) return false;
6969+ const tag = target.tagName;
7070+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable;
7171+}
+28
tests/dom.test.ts
···11+// @vitest-environment happy-dom
22+import { describe, expect, it } from 'vitest';
33+import { isEditableTarget } from '../src/ui/dom.js';
44+55+describe('isEditableTarget', () => {
66+ it('is true for an <input> (e.g. the sign-in field)', () => {
77+ expect(isEditableTarget(document.createElement('input'))).toBe(true);
88+ });
99+1010+ it('is true for a <textarea>', () => {
1111+ expect(isEditableTarget(document.createElement('textarea'))).toBe(true);
1212+ });
1313+1414+ it('is true for a contenteditable element', () => {
1515+ const div = document.createElement('div');
1616+ div.setAttribute('contenteditable', 'true');
1717+ expect(isEditableTarget(div)).toBe(true);
1818+ });
1919+2020+ it('is false for the on-screen keyboard buttons and other elements', () => {
2121+ expect(isEditableTarget(document.createElement('button'))).toBe(false);
2222+ expect(isEditableTarget(document.createElement('div'))).toBe(false);
2323+ });
2424+2525+ it('is false for a null target', () => {
2626+ expect(isEditableTarget(null)).toBe(false);
2727+ });
2828+});