diff --git a/packages/uui-base/lib/mixins/SelectableMixin.ts b/packages/uui-base/lib/mixins/SelectableMixin.ts index f29bd0225..94c05dde3 100644 --- a/packages/uui-base/lib/mixins/SelectableMixin.ts +++ b/packages/uui-base/lib/mixins/SelectableMixin.ts @@ -54,8 +54,8 @@ export const SelectableMixin = >( const oldVal = this._selectable; this._selectable = newVal; // Potentially problematic as a component might need focus for another feature when not selectable: - if (!this.selectableTarget) { - // If not selectable target, then make it self selectable. (A selectable target should be made focusable by the component itself) + if (this.selectableTarget === this) { + // If the selectable target, then make it self selectable. (A different selectable target should be made focusable by the component itself) this.setAttribute('tabindex', `${newVal ? '0' : '-1'}`); } this.requestUpdate('selectable', oldVal); @@ -80,10 +80,16 @@ export const SelectableMixin = >( } private handleSelectKeydown = (e: KeyboardEvent) => { - //if (e.composedPath().indexOf(this.selectableTarget) !== -1) { - if (this.selectableTarget === this) { - if (e.key !== ' ' && e.key !== 'Enter') return; - this._toggleSelect(); + const composePath = e.composedPath(); + if ( + (this._selectable || (this.deselectable && this.selected)) && + composePath.indexOf(this.selectableTarget) === 0 + ) { + if (this.selectableTarget === this) { + if (e.code !== 'Space' && e.code !== 'Enter') return; + this._toggleSelect(); + e.preventDefault(); + } } }; @@ -112,7 +118,7 @@ export const SelectableMixin = >( } private _toggleSelect() { - // Only allow for select-interaction if selectable is true. Deselectable is ignorered in this case, we do not want a DX where only deselection is a possibility.. + // Only allow for select-interaction if selectable is true. Deselectable is ignored in this case, we do not want a DX where only deselection is a possibility.. if (!this.selectable) return; if (this.deselectable === false) { this._select(); diff --git a/packages/uui-card-block-type/lib/uui-card-block-type.test.ts b/packages/uui-card-block-type/lib/uui-card-block-type.test.ts index a930ef19b..e58a404ee 100644 --- a/packages/uui-card-block-type/lib/uui-card-block-type.test.ts +++ b/packages/uui-card-block-type/lib/uui-card-block-type.test.ts @@ -79,7 +79,7 @@ describe('UUICardBlockTypeElement', () => { describe('events', () => { describe('open', () => { it('emits a open event when open-part is clicked', async () => { - const listener = oneEvent(element, UUICardEvent.OPEN, false); + const listener = oneEvent(element, UUICardEvent.OPEN); const infoElement: HTMLElement | null = element.shadowRoot!.querySelector('#open-part'); infoElement?.click(); @@ -93,13 +93,34 @@ describe('UUICardBlockTypeElement', () => { it('emits a selected event when selectable', async () => { element.selectable = true; await elementUpdated(element); - const listener = oneEvent(element, UUISelectableEvent.SELECTED, false); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); element.click(); const event = await listener; expect(event).to.exist; expect(event.type).to.equal(UUISelectableEvent.SELECTED); expect(element.selected).to.be.true; }); + + it('can be selected with keyboard', async () => { + element.selectable = true; + await elementUpdated(element); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUISelectableEvent.SELECTED); + expect(element.selected).to.be.true; + + const unselectedListener = oneEvent( + element, + UUISelectableEvent.DESELECTED, + ); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); + const event2 = await unselectedListener; + expect(event2).to.exist; + expect(event2.type).to.equal(UUISelectableEvent.DESELECTED); + expect(element.selected).to.be.false; + }); }); describe('deselect', () => { @@ -107,11 +128,7 @@ describe('UUICardBlockTypeElement', () => { element.selectable = true; element.selected = true; await elementUpdated(element); - const listener = oneEvent( - element, - UUISelectableEvent.DESELECTED, - false, - ); + const listener = oneEvent(element, UUISelectableEvent.DESELECTED); element.click(); const event = await listener; expect(event).to.exist; diff --git a/packages/uui-card-content-node/lib/uui-card-content-node.test.ts b/packages/uui-card-content-node/lib/uui-card-content-node.test.ts index a95aa5b4c..bf5266651 100644 --- a/packages/uui-card-content-node/lib/uui-card-content-node.test.ts +++ b/packages/uui-card-content-node/lib/uui-card-content-node.test.ts @@ -75,7 +75,7 @@ describe('UUICardContentNodeElement', () => { describe('events', () => { describe('open', () => { it('emits a open event when info is clicked', async () => { - const listener = oneEvent(element, UUICardEvent.OPEN, false); + const listener = oneEvent(element, UUICardEvent.OPEN); const infoElement: HTMLElement | null = element.shadowRoot!.querySelector('#open-part'); infoElement?.click(); @@ -85,7 +85,7 @@ describe('UUICardContentNodeElement', () => { }); it('emits a open event when icon is clicked', async () => { - const listener = oneEvent(element, UUICardEvent.OPEN, false); + const listener = oneEvent(element, UUICardEvent.OPEN); const iconElement: HTMLElement | null = element.shadowRoot!.querySelector('#icon'); iconElement?.click(); @@ -99,13 +99,34 @@ describe('UUICardContentNodeElement', () => { it('emits a selected event when selectable', async () => { element.selectable = true; await elementUpdated(element); - const listener = oneEvent(element, UUISelectableEvent.SELECTED, false); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); element.click(); const event = await listener; expect(event).to.exist; expect(event.type).to.equal(UUISelectableEvent.SELECTED); expect(element.selected).to.be.true; }); + + it('can be selected with keyboard', async () => { + element.selectable = true; + await elementUpdated(element); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUISelectableEvent.SELECTED); + expect(element.selected).to.be.true; + + const unselectedListener = oneEvent( + element, + UUISelectableEvent.DESELECTED, + ); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); + const event2 = await unselectedListener; + expect(event2).to.exist; + expect(event2.type).to.equal(UUISelectableEvent.DESELECTED); + expect(element.selected).to.be.false; + }); }); describe('deselect', () => { @@ -113,11 +134,7 @@ describe('UUICardContentNodeElement', () => { element.selectable = true; element.selected = true; await elementUpdated(element); - const listener = oneEvent( - element, - UUISelectableEvent.DESELECTED, - false, - ); + const listener = oneEvent(element, UUISelectableEvent.DESELECTED); element.click(); const event = await listener; expect(event).to.exist; diff --git a/packages/uui-card-media/lib/uui-card-media.test.ts b/packages/uui-card-media/lib/uui-card-media.test.ts index 902c483fa..08b577422 100644 --- a/packages/uui-card-media/lib/uui-card-media.test.ts +++ b/packages/uui-card-media/lib/uui-card-media.test.ts @@ -78,7 +78,7 @@ describe('UUICardMediaElement', () => { describe('events', () => { describe('open', () => { it('emits a open event when open-part is clicked', async () => { - const listener = oneEvent(element, UUICardEvent.OPEN, false); + const listener = oneEvent(element, UUICardEvent.OPEN); const infoElement: HTMLElement | null = element.shadowRoot!.querySelector('#open-part'); infoElement?.click(); @@ -92,13 +92,33 @@ describe('UUICardMediaElement', () => { it('emits a selected event when selectable', async () => { element.selectable = true; await elementUpdated(element); - const listener = oneEvent(element, UUISelectableEvent.SELECTED, false); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); element.click(); const event = await listener; expect(event).to.exist; expect(event.type).to.equal(UUISelectableEvent.SELECTED); expect(element.selected).to.be.true; }); + it('can be selected with keyboard', async () => { + element.selectable = true; + await elementUpdated(element); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUISelectableEvent.SELECTED); + expect(element.selected).to.be.true; + + const unselectedListener = oneEvent( + element, + UUISelectableEvent.DESELECTED, + ); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); + const event2 = await unselectedListener; + expect(event2).to.exist; + expect(event2.type).to.equal(UUISelectableEvent.DESELECTED); + expect(element.selected).to.be.false; + }); }); describe('deselect', () => { @@ -106,11 +126,7 @@ describe('UUICardMediaElement', () => { element.selectable = true; element.selected = true; await elementUpdated(element); - const listener = oneEvent( - element, - UUISelectableEvent.DESELECTED, - false, - ); + const listener = oneEvent(element, UUISelectableEvent.DESELECTED); element.click(); const event = await listener; expect(event).to.exist; diff --git a/packages/uui-card-user/lib/uui-card-user.test.ts b/packages/uui-card-user/lib/uui-card-user.test.ts index c58ecaa47..9dedbf443 100644 --- a/packages/uui-card-user/lib/uui-card-user.test.ts +++ b/packages/uui-card-user/lib/uui-card-user.test.ts @@ -77,7 +77,7 @@ describe('UUICardUserElement', () => { describe('events', () => { describe('open', () => { it('emits a open event when open-part is clicked', async () => { - const listener = oneEvent(element, UUICardEvent.OPEN, false); + const listener = oneEvent(element, UUICardEvent.OPEN); const infoElement: HTMLElement | null = element.shadowRoot!.querySelector('#open-part'); infoElement?.click(); @@ -91,13 +91,35 @@ describe('UUICardUserElement', () => { it('emits a selected event when selectable', async () => { element.selectable = true; await elementUpdated(element); - const listener = oneEvent(element, UUISelectableEvent.SELECTED, false); + const listener = oneEvent(element, UUISelectableEvent.SELECTED); element.click(); const event = await listener; expect(event).to.exist; expect(event.type).to.equal(UUISelectableEvent.SELECTED); expect(element.selected).to.be.true; }); + + it('can be selected with keyboard', async () => { + element.selectable = true; + await elementUpdated(element); + + const listener = oneEvent(element, UUISelectableEvent.SELECTED); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' })); + const event = await listener; + expect(event).to.exist; + expect(event.type).to.equal(UUISelectableEvent.SELECTED); + expect(element.selected).to.be.true; + + const unselectedListener = oneEvent( + element, + UUISelectableEvent.DESELECTED, + ); + element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' })); + const event2 = await unselectedListener; + expect(event2).to.exist; + expect(event2.type).to.equal(UUISelectableEvent.DESELECTED); + expect(element.selected).to.be.false; + }); }); describe('deselect', () => { @@ -105,11 +127,7 @@ describe('UUICardUserElement', () => { element.selectable = true; element.selected = true; await elementUpdated(element); - const listener = oneEvent( - element, - UUISelectableEvent.DESELECTED, - false, - ); + const listener = oneEvent(element, UUISelectableEvent.DESELECTED); element.click(); const event = await listener; expect(event).to.exist;