kopia lustrzana https://github.com/shoelace-style/shoelace
more finishing touches + tests
rodzic
563ed81984
commit
913243f8c1
|
@ -25,11 +25,10 @@ export default css`
|
|||
color: var(--sl-color-neutral-700);
|
||||
padding: var(--sl-spacing-x-small) var(--sl-spacing-medium) var(--sl-spacing-x-small) var(--sl-spacing-x-small);
|
||||
transition: var(--sl-transition-fast) fill;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host(:hover) .option:not(.option--current):not(.option--disabled) {
|
||||
.option--hover:not(.option--current):not(.option--disabled) {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
color: var(--sl-color-neutral-1000);
|
||||
}
|
||||
|
@ -80,4 +79,11 @@ export default css`
|
|||
.option__suffix::slotted(*) {
|
||||
margin-inline-start: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
:host(:hover:not([aria-disabled='true'])) .option {
|
||||
outline: dashed 1px SelectedItem;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -6,10 +6,10 @@ describe('<sl-option>', () => {
|
|||
it('passes accessibility test', async () => {
|
||||
const el = await fixture<SlOption>(html`
|
||||
<sl-select label="Select one">
|
||||
<sl-option>Option 1</sl-option>
|
||||
<sl-option>Option 2</sl-option>
|
||||
<sl-option>Option 3</sl-option>
|
||||
<sl-option disabled>Disabled</sl-option>
|
||||
<sl-option value="1">Option 1</sl-option>
|
||||
<sl-option value="2">Option 2</sl-option>
|
||||
<sl-option value="3">Option 3</sl-option>
|
||||
<sl-option value="4" disabled>Disabled</sl-option>
|
||||
</sl-select>
|
||||
`);
|
||||
await expect(el).to.be.accessible();
|
||||
|
|
|
@ -36,6 +36,7 @@ export default class SlOption extends ShoelaceElement {
|
|||
|
||||
@state() current = false; // the user has keyed into the option, but hasn't selected it yet (shows a highlight)
|
||||
@state() selected = false; // the option is selected and has aria-selected="true"
|
||||
@state() hasHover = false; // we need this because Safari doesn't honor :hover styles while dragging
|
||||
|
||||
@query('.option__label') defaultSlot: HTMLSlotElement;
|
||||
|
||||
|
@ -94,6 +95,14 @@ export default class SlOption extends ShoelaceElement {
|
|||
}
|
||||
}
|
||||
|
||||
handleMouseEnter() {
|
||||
this.hasHover = true;
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
this.hasHover = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div
|
||||
|
@ -102,8 +111,11 @@ export default class SlOption extends ShoelaceElement {
|
|||
option: true,
|
||||
'option--current': this.current,
|
||||
'option--disabled': this.disabled,
|
||||
'option--selected': this.selected
|
||||
'option--selected': this.selected,
|
||||
'option--hover': this.hasHover
|
||||
})}
|
||||
@mouseenter=${this.handleMouseEnter}
|
||||
@mouseleave=${this.handleMouseLeave}
|
||||
>
|
||||
<sl-icon part="checked-icon" class="option__check" name="check" library="system" aria-hidden="true"></sl-icon>
|
||||
<slot part="prefix" name="prefix" class="option__prefix"></slot>
|
||||
|
|
|
@ -39,8 +39,7 @@ describe('<sl-select>', () => {
|
|||
`);
|
||||
const disabledOption = el.querySelector('sl-option[disabled]')!;
|
||||
|
||||
await clickOnElement(el);
|
||||
await waitForEvent(el, 'sl-after-show');
|
||||
await el.show();
|
||||
await clickOnElement(disabledOption);
|
||||
await el.updateComplete;
|
||||
|
||||
|
@ -81,8 +80,7 @@ describe('<sl-select>', () => {
|
|||
el.addEventListener('sl-change', changeHandler);
|
||||
el.addEventListener('sl-input', inputHandler);
|
||||
|
||||
await clickOnElement(el);
|
||||
await waitForEvent(el, 'sl-after-show');
|
||||
await el.show();
|
||||
await clickOnElement(secondOption);
|
||||
await el.updateComplete;
|
||||
|
||||
|
@ -293,9 +291,7 @@ describe('<sl-select>', () => {
|
|||
const select = form.querySelector('sl-select')!;
|
||||
const option2 = form.querySelectorAll('sl-option')![1];
|
||||
|
||||
await clickOnElement(select);
|
||||
await waitForEvent(select, 'sl-after-show');
|
||||
|
||||
await select.show();
|
||||
await clickOnElement(option2);
|
||||
await select.updateComplete;
|
||||
expect(select.value).to.equal('option-2');
|
||||
|
@ -326,4 +322,73 @@ describe('<sl-select>', () => {
|
|||
|
||||
expect(displayInput.value).to.equal('updated');
|
||||
});
|
||||
|
||||
it('should emit sl-focus and sl-blur when receiving and losing focus', async () => {
|
||||
const el = await fixture<SlSelect>(html`
|
||||
<sl-select value="option-1">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
<sl-option value="option-3">Option 3</sl-option>
|
||||
</sl-select>
|
||||
`);
|
||||
const focusHandler = sinon.spy();
|
||||
const blurHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('sl-focus', focusHandler);
|
||||
el.addEventListener('sl-blur', blurHandler);
|
||||
|
||||
el.focus();
|
||||
await el.updateComplete;
|
||||
el.blur();
|
||||
await el.updateComplete;
|
||||
|
||||
expect(focusHandler).to.have.been.calledOnce;
|
||||
expect(blurHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should emit sl-clear when the clear button is clicked', async () => {
|
||||
const el = await fixture<SlSelect>(html`
|
||||
<sl-select value="option-1" clearable>
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
<sl-option value="option-3">Option 3</sl-option>
|
||||
</sl-select>
|
||||
`);
|
||||
const clearHandler = sinon.spy();
|
||||
const clearButton = el.shadowRoot!.querySelector('[part~="clear-button"]')!;
|
||||
|
||||
el.addEventListener('sl-clear', clearHandler);
|
||||
await el.show();
|
||||
await clickOnElement(clearButton);
|
||||
await el.updateComplete;
|
||||
|
||||
expect(clearHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should emit sl-show, sl-after-show, sl-hide, and sl-after-hide events when the listbox opens and closes', async () => {
|
||||
const el = await fixture<SlSelect>(html`
|
||||
<sl-select value="option-1">
|
||||
<sl-option value="option-1">Option 1</sl-option>
|
||||
<sl-option value="option-2">Option 2</sl-option>
|
||||
<sl-option value="option-3">Option 3</sl-option>
|
||||
</sl-select>
|
||||
`);
|
||||
const showHandler = sinon.spy();
|
||||
const afterShowHandler = sinon.spy();
|
||||
const hideHandler = sinon.spy();
|
||||
const afterHideHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('sl-show', showHandler);
|
||||
el.addEventListener('sl-after-show', afterShowHandler);
|
||||
el.addEventListener('sl-hide', hideHandler);
|
||||
el.addEventListener('sl-after-hide', afterHideHandler);
|
||||
|
||||
await el.show();
|
||||
expect(showHandler).to.have.been.calledOnce;
|
||||
expect(afterShowHandler).to.have.been.calledOnce;
|
||||
|
||||
await el.hide();
|
||||
expect(hideHandler).to.have.been.calledOnce;
|
||||
expect(afterHideHandler).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -358,7 +358,6 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
this.displayInput.focus();
|
||||
}
|
||||
|
||||
// We use mousedown/mouseup instead of click to allow macOS-style menu behavior
|
||||
private handleComboboxMouseDown(event: MouseEvent) {
|
||||
const path = event.composedPath();
|
||||
const isIconButton = path.some(el => el instanceof Element && el.tagName.toLowerCase() === 'sl-icon-button');
|
||||
|
@ -396,8 +395,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
event.preventDefault();
|
||||
}
|
||||
|
||||
// We use mousedown/mouseup instead of click to allow macOS-style menu behavior
|
||||
private handleOptionMouseUp(event: MouseEvent) {
|
||||
private handleOptionClick(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
const option = target.closest('sl-option');
|
||||
const oldValue = this.value;
|
||||
|
@ -433,7 +431,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
allOptions.forEach(option => {
|
||||
if (values.includes(option.value)) {
|
||||
console.error(
|
||||
`An option with duplicate values has been found in <sl-select>. All options must be unique.`,
|
||||
`An option with duplicate values has been found in <sl-select>. All options must have unique values.`,
|
||||
option
|
||||
);
|
||||
}
|
||||
|
@ -766,7 +764,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
|
|||
part="listbox"
|
||||
class="select__listbox"
|
||||
tabindex="-1"
|
||||
@mouseup=${this.handleOptionMouseUp}
|
||||
@mouseup=${this.handleOptionClick}
|
||||
@slotchange=${this.handleDefaultSlotChange}
|
||||
></slot>
|
||||
</sl-popup>
|
||||
|
|
Ładowanie…
Reference in New Issue