From 61d9a985532c0a9a62ff3f65e306dc1f8ff9ca58 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 14 Nov 2026 07:28:16 +0000 Subject: [PATCH] fix(forms): use keydown for button active state The active state listened for the deprecated keypress event. This listens for keydown instead. In Chromium 156, preventDefault on either event cancels space scrolling, and the active state and keyup click stay the same. Co-authored-by: crylan Signed-off-by: Cursor Agent --- .../state-active.controller.test.ts | 28 +++++++++++++++---- .../controllers/state-active.controller.ts | 4 +-- projects/forms/src/mixins/button.test.ts | 12 ++++---- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/projects/forms/src/internal/controllers/state-active.controller.test.ts b/projects/forms/src/internal/controllers/state-active.controller.test.ts index be441da6f6..7d3dd92bdf 100644 --- a/projects/forms/src/internal/controllers/state-active.controller.test.ts +++ b/projects/forms/src/internal/controllers/state-active.controller.test.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { userEvent } from 'vitest/browser'; import { html } from 'lit'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createFixture, removeFixture } from '@internals/testing'; @@ -57,13 +58,13 @@ describe('StateActiveController', () => { element.dispatchEvent(new MouseEvent('mouseup')); expect(element.matches(':state(active)')).toBe(false); - element.dispatchEvent(new KeyboardEvent('keypress', { code: 'Space' })); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); expect(element.matches(':state(active)')).toBe(true); element.dispatchEvent(new KeyboardEvent('keyup')); expect(element.matches(':state(active)')).toBe(false); - element.dispatchEvent(new KeyboardEvent('keypress', { code: 'Enter' })); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); expect(element.matches(':state(active)')).toBe(true); element.dispatchEvent(new FocusEvent('blur')); @@ -76,13 +77,13 @@ describe('StateActiveController', () => { expect(element.matches(':state(active)')).toBe(false); element.disabled = false; - element.dispatchEvent(new KeyboardEvent('keypress', { code: 'KeyA' })); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyA' })); expect(element.matches(':state(active)')).toBe(false); }); - it('should prevent default space keypress scrolling on the host', () => { - const space = new KeyboardEvent('keypress', { code: 'Space', cancelable: true }); - const enter = new KeyboardEvent('keypress', { code: 'Enter', cancelable: true }); + it('should prevent default on space keydown activation', () => { + const space = new KeyboardEvent('keydown', { code: 'Space', cancelable: true }); + const enter = new KeyboardEvent('keydown', { code: 'Enter', cancelable: true }); element.dispatchEvent(space); element.dispatchEvent(enter); @@ -91,6 +92,21 @@ describe('StateActiveController', () => { expect(enter.defaultPrevented).toBe(false); }); + it('should hold the active state for a real space or enter key', async () => { + element.tabIndex = 0; + element.focus(); + + await userEvent.keyboard('{Space>}'); + expect(element.matches(':state(active)')).toBe(true); + await userEvent.keyboard('{/Space}'); + expect(element.matches(':state(active)')).toBe(false); + + await userEvent.keyboard('{Enter>}'); + expect(element.matches(':state(active)')).toBe(true); + await userEvent.keyboard('{/Enter}'); + expect(element.matches(':state(active)')).toBe(false); + }); + it('should remove active listeners on disconnect', () => { element.remove(); element.dispatchEvent(new MouseEvent('mousedown')); diff --git a/projects/forms/src/internal/controllers/state-active.controller.ts b/projects/forms/src/internal/controllers/state-active.controller.ts index d14c890031..c7c4289946 100644 --- a/projects/forms/src/internal/controllers/state-active.controller.ts +++ b/projects/forms/src/internal/controllers/state-active.controller.ts @@ -13,7 +13,7 @@ export class StateActiveController implements ReactiveControll hostConnected() { attachInternals(this.host); - this.host.addEventListener('keypress', this.#onActive as EventListener); + this.host.addEventListener('keydown', this.#onActive as EventListener); this.host.addEventListener('mousedown', this.#onActive as EventListener); this.host.addEventListener('keyup', this.#onInactive); this.host.addEventListener('blur', this.#onInactive); @@ -21,7 +21,7 @@ export class StateActiveController implements ReactiveControll } hostDisconnected() { - this.host.removeEventListener('keypress', this.#onActive as EventListener); + this.host.removeEventListener('keydown', this.#onActive as EventListener); this.host.removeEventListener('mousedown', this.#onActive as EventListener); this.host.removeEventListener('keyup', this.#onInactive); this.host.removeEventListener('blur', this.#onInactive); diff --git a/projects/forms/src/mixins/button.test.ts b/projects/forms/src/mixins/button.test.ts index 4b603def3b..55fba41a63 100644 --- a/projects/forms/src/mixins/button.test.ts +++ b/projects/forms/src/mixins/button.test.ts @@ -91,24 +91,24 @@ describe('ButtonFormControlMixin', () => { button.dispatchEvent(new MouseEvent('mouseup')); expect(button.matches(':state(active)')).toBe(false); - button.dispatchEvent(new KeyboardEvent('keypress', { code: 'Space' })); + button.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); expect(button.matches(':state(active)')).toBe(true); }); it('should clear active state on blur and ignore non-activation keys', () => { - button.dispatchEvent(new KeyboardEvent('keypress', { code: 'KeyA' })); + button.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyA' })); expect(button.matches(':state(active)')).toBe(false); - button.dispatchEvent(new KeyboardEvent('keypress', { code: 'Enter' })); + button.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); expect(button.matches(':state(active)')).toBe(true); button.dispatchEvent(new FocusEvent('blur')); expect(button.matches(':state(active)')).toBe(false); }); - it('should prevent default on space keypress activation', () => { - const space = new KeyboardEvent('keypress', { code: 'Space', cancelable: true }); - const enter = new KeyboardEvent('keypress', { code: 'Enter', cancelable: true }); + it('should prevent default on space keydown activation', () => { + const space = new KeyboardEvent('keydown', { code: 'Space', cancelable: true }); + const enter = new KeyboardEvent('keydown', { code: 'Enter', cancelable: true }); button.dispatchEvent(space); button.dispatchEvent(enter);