Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: selectable mixin only be selective when selectabletarget is this #971

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
258b0c5
only care about the composedPath when selectableTarget is this.
nielslyngsoe Nov 26, 2024
930767e
Merge branch 'v1/contrib' into v1/bugfix/selectableMixin-only-be-sele…
nielslyngsoe Nov 26, 2024
b9609f6
correct none this select target logic
nielslyngsoe Nov 26, 2024
976b568
align table row selection
nielslyngsoe Nov 26, 2024
f905d4d
correction of selectableMixin for keyboard navigation
nielslyngsoe Nov 26, 2024
325115a
adjust table row
nielslyngsoe Nov 26, 2024
6726c19
add checkbox import
nielslyngsoe Nov 26, 2024
29a376c
Revert "align table row selection"
nielslyngsoe Nov 26, 2024
2bb4b11
optimize code
nielslyngsoe Nov 26, 2024
ef1cbae
revert to none preventing approach
nielslyngsoe Nov 26, 2024
2adab35
media card selectable solution
nielslyngsoe Nov 26, 2024
0420638
install dependency
madsrasmussen Nov 27, 2024
9f525e1
add tests for uui-color-swatch selectable
madsrasmussen Nov 27, 2024
a108472
expand stories
nielslyngsoe Nov 27, 2024
582bb93
selection mixing is selective about targets when selectable target is…
nielslyngsoe Nov 27, 2024
8adfe87
clean up
nielslyngsoe Nov 27, 2024
d61f99a
fix
nielslyngsoe Nov 27, 2024
209fbaf
Merge branch 'v1/chore/test-runner-commands' into v1/bugfix/selectabl…
madsrasmussen Nov 27, 2024
94e8210
Merge branch 'v1/bugfix/selectableMixin-only-be-selective-when-select…
madsrasmussen Nov 27, 2024
2ee8886
fix test
nielslyngsoe Nov 27, 2024
5ac142f
test keyboard interaction
madsrasmussen Nov 27, 2024
fa65695
Merge branch 'v1/bugfix/selectableMixin-only-be-selective-when-select…
madsrasmussen Nov 27, 2024
a7290ee
focus for swatch
nielslyngsoe Nov 27, 2024
8335188
menu item select only correction
nielslyngsoe Nov 27, 2024
3287111
clean up tests
nielslyngsoe Nov 27, 2024
076392e
clean up
nielslyngsoe Nov 27, 2024
6bda176
only set tabindex on this targets
nielslyngsoe Nov 27, 2024
0a9c15c
prevent click-label when select only
nielslyngsoe Nov 27, 2024
6255856
story and tests
nielslyngsoe Nov 27, 2024
41014f8
use send mouse
madsrasmussen Nov 27, 2024
605dcab
update title
madsrasmussen Nov 27, 2024
fd83e1b
wrap tests
nielslyngsoe Nov 27, 2024
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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@types/mocha": "^10.0.7",
"@web/dev-server-esbuild": "0.4.3",
"@web/test-runner": "0.18.2",
"@web/test-runner-commands": "^0.9.0",
"@web/test-runner-playwright": "0.11.0",
"autoprefixer": "10.4.17",
"babel-loader": "9.1.3",
Expand Down
79 changes: 62 additions & 17 deletions packages/uui-base/lib/mixins/SelectableMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

type Constructor<T = {}> = new (...args: any[]) => T;

export declare class SelectableMixinInterface extends LitElement {

Check warning on line 8 in packages/uui-base/lib/mixins/SelectableMixin.ts

View workflow job for this annotation

GitHub Actions / test (20)

Class declaration should be prefixed with "UUI"
/**
* Enable the ability to select this element.
* @attr
Expand Down Expand Up @@ -39,7 +39,7 @@
* @fires {UUISelectableEvent} selected - fires when the media card is selected
* @fires {UUISelectableEvent} deselected - fires when the media card is deselected
*/
class SelectableMixinClass extends superClass {

Check warning on line 42 in packages/uui-base/lib/mixins/SelectableMixin.ts

View workflow job for this annotation

GitHub Actions / test (20)

Class declaration should be prefixed with "UUI"
private _selectable = false;
/**
* Enable the ability to select this element.
Expand All @@ -52,11 +52,16 @@
}
set selectable(newVal) {
const oldVal = this._selectable;
if (oldVal === newVal) return;
this._selectable = newVal;

// Potentially problematic as a component might need focus for another feature when not selectable:
if (this.selectableTarget === this) {
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.#selectableTarget.setAttribute(
'tabindex',
`${newVal ? '0' : '-1'}`,
);
}
this.requestUpdate('selectable', oldVal);
}
Expand All @@ -71,7 +76,31 @@
@property({ type: Boolean, reflect: true })
public selected = false;

protected selectableTarget: EventTarget = this;
#selectableTarget: Element = this;
protected get selectableTarget(): EventTarget {
return this.#selectableTarget;
}
protected set selectableTarget(target: EventTarget) {
const oldTarget = this.#selectableTarget;

oldTarget.removeAttribute('tabindex');
oldTarget.removeEventListener('click', this.#onClick);
oldTarget.removeEventListener(
'keydown',
this.#onKeydown as EventListener,
);

this.#selectableTarget = target as Element;
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.#selectableTarget.setAttribute(
'tabindex',
this._selectable ? '0' : '-1',
);
}
target.addEventListener('click', this.#onClick);
target.addEventListener('keydown', this.#onKeydown as EventListener);
}

constructor(...args: any[]) {
super(...args);
Expand All @@ -80,25 +109,41 @@
}

readonly #onKeydown = (e: KeyboardEvent) => {
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();
}
if (e.code !== 'Space' && e.code !== 'Enter') return;
if (e.composedPath().indexOf(this.#selectableTarget) === 0) {
this.#onClick(e);
}
};

readonly #onClick = (e: Event) => {
const isSelectable =
this._selectable || (this.deselectable && this.selected);

if (isSelectable === false) return;

const composePath = e.composedPath();
if (
(this._selectable || (this.deselectable && this.selected)) &&
composePath.indexOf(this.selectableTarget) === 0
) {

if (this.#selectableTarget === this) {
// the selectableTarget is not specified which means we need to be selective about what we accept events from.
const isActionTag = composePath.some(el => {
const elementTagName = (el as HTMLElement).tagName;
return (
elementTagName === 'A' ||
elementTagName === 'BUTTON' ||
elementTagName === 'INPUT' ||
elementTagName === 'TEXTAREA' ||
elementTagName === 'SELECT'
);
});

// never select when clicking on a link or button
if (isActionTag) return;
}

if (composePath.indexOf(this.#selectableTarget) !== -1) {
if (e.type === 'keydown') {
e.preventDefault(); // Do not want the space key to trigger a page scroll.
}
this.#toggleSelect();
}
};
Expand Down
14 changes: 14 additions & 0 deletions packages/uui-card-media/lib/uui-card-media.story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ export const Actions: Story = {
},
};

export const Href: Story = {
args: {
'actions slot': html`<uui-button
slot="actions"
look="secondary"
label="Remove"
>Remove</uui-button
>`,
selectable: true,
href: 'https://umbraco.com',
target: '_blank',
},
};

export const Image: Story = {
args: {
slot: html`<img src="https://placedog.net/1447/?random" alt="" />`,
Expand Down
10 changes: 10 additions & 0 deletions packages/uui-color-swatch/lib/uui-color-swatch.element.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
import { property } from 'lit/decorators.js';
import { css, html, LitElement, nothing } from 'lit';
import { ref } from 'lit/directives/ref.js';
import { iconCheck } from '@umbraco-ui/uui-icon-registry-essential/lib/svgs';
import {
ActiveMixin,
Expand Down Expand Up @@ -109,10 +110,19 @@ export class UUIColorSwatchElement extends LabelMixin(
}
}

focus(options?: FocusOptions | undefined): void {
(this.selectableTarget as HTMLElement | undefined)?.focus(options);
}

#selectButtonChanged(button?: Element | undefined) {
this.selectableTarget = button || this;
}

render() {
return html`
<button
id="swatch"
${ref(this.#selectButtonChanged)}
aria-label=${this.label}
?disabled="${this.disabled}"
title="${this.label}">
Expand Down
71 changes: 70 additions & 1 deletion packages/uui-color-swatch/lib/uui-color-swatch.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { html, fixture, expect } from '@open-wc/testing';
import { html, fixture, expect, elementUpdated } from '@open-wc/testing';
import { UUIColorSwatchElement } from './uui-color-swatch.element';
import { sendMouse, sendKeys } from '@web/test-runner-commands';

describe('UUIColorSwatchElement', () => {
let element: UUIColorSwatchElement;
Expand All @@ -17,4 +18,72 @@ describe('UUIColorSwatchElement', () => {
it('passes the a11y audit', async () => {
await expect(element).shadowDom.to.be.accessible();
});

describe('selectable', () => {
beforeEach(async () => {
element.selectable = true;
});

it('can be selected when selectable', async () => {
await elementUpdated(element);
await sendMouse({
type: 'click',
position: [15, 15],
button: 'left',
});
expect(element.selected).to.be.true;
});

it('can not be selected when not selectable', async () => {
element.selectable = false;
await elementUpdated(element);
await sendMouse({
type: 'click',
position: [15, 15],
button: 'left',
});
expect(element.selected).to.be.false;
});

it('cant be selected when disabled', async () => {
element.disabled = true;
await elementUpdated(element);
await sendMouse({
type: 'click',
position: [15, 15],
button: 'left',
});
expect(element.selected).to.be.false;
});

it('can be selected with Space key', async () => {
await sendKeys({
press: 'Tab',
});
await sendKeys({
press: 'Space',
});
expect(element.selected).to.be.true;

await sendKeys({
press: 'Space',
});
expect(element.selected).to.be.false;
});

it('can be selected with Enter key', async () => {
await sendKeys({
press: 'Tab',
});
await sendKeys({
press: 'Enter',
});
expect(element.selected).to.be.true;

await sendKeys({
press: 'Enter',
});
expect(element.selected).to.be.false;
});
});
});
17 changes: 9 additions & 8 deletions packages/uui-menu-item/lib/uui-menu-item.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,11 @@ export class UUIMenuItemElement extends SelectOnlyMixin(
this.showChildren = !this.showChildren;
};

private _onLabelClicked = () => {
#onLabelClicked() {
if (this.selectOnly) return;
const event = new UUIMenuItemEvent(UUIMenuItemEvent.CLICK_LABEL);
this.dispatchEvent(event);
};
}

private _renderLabelInside() {
return html` <slot
Expand Down Expand Up @@ -195,7 +196,7 @@ export class UUIMenuItemElement extends SelectOnlyMixin(
this.target === '_blank' ? 'noopener noreferrer' : undefined,
),
)}
@click=${this._onLabelClicked}
@click=${this.#onLabelClicked}
?disabled=${this.disabled}
aria-label="${this.label}">
${this._renderLabelInside()}
Expand All @@ -206,7 +207,7 @@ export class UUIMenuItemElement extends SelectOnlyMixin(
return html` <button
id="label-button"
${ref(this._labelButtonChanged)}
@click=${this._onLabelClicked}
@click=${this.#onLabelClicked}
?disabled=${this.disabled}
aria-label="${this.label}">
${this._renderLabelInside()}
Expand All @@ -226,12 +227,12 @@ export class UUIMenuItemElement extends SelectOnlyMixin(
?open=${this.showChildren}></uui-symbol-expand>
</button>`
: ''}
${this.href ? this._renderLabelAsAnchor() : this._renderLabelAsButton()}
${this.href && this.selectOnly !== true && this.selectable !== true
? this._renderLabelAsAnchor()
: this._renderLabelAsButton()}

<div id="label-button-background"></div>
${this.selectOnly === false
? html`<slot id="actions-container" name="actions"></slot>`
: ''}
<slot id="actions-container" name="actions"></slot>
${this.loading
? html`<uui-loader-bar id="loader"></uui-loader-bar>`
: ''}
Expand Down
6 changes: 6 additions & 0 deletions packages/uui-menu-item/lib/uui-menu-item.story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ export const Selectable: Story = {
selectable: true,
},
};
export const SelectOnly: Story = {
args: {
selectable: true,
selectOnly: true,
},
};

export const Anchor: Story = {
args: {
Expand Down
Loading
Loading