Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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'));
Expand All @@ -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);
Expand All @@ -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'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ export class StateActiveController<T extends Active> 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);
this.host.addEventListener('mouseup', this.#onInactive);
}

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);
Expand Down
12 changes: 6 additions & 6 deletions projects/forms/src/mixins/button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading