keep on keepin on

pull/1090/head
Cory LaViska 2022-12-20 13:36:53 -05:00
rodzic 46fda5f0a6
commit 1f457cdde0
5 zmienionych plików z 88 dodań i 33 usunięć

Wyświetl plik

@ -16,27 +16,29 @@ export default css`
.option {
position: relative;
display: flex;
align-items: stretch;
align-items: center;
font-family: var(--sl-font-sans);
font-size: var(--sl-font-size-medium);
font-weight: var(--sl-font-weight-normal);
line-height: var(--sl-line-height-normal);
letter-spacing: var(--sl-letter-spacing-normal);
color: var(--sl-color-neutral-700);
padding: var(--sl-spacing-2x-small) var(--sl-spacing-2x-small);
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) {
:host(:hover) .option:not(.option--current):not(.option--disabled) {
background-color: var(--sl-color-neutral-100);
color: var(--sl-color-neutral-1000);
}
.option--current {
.option--current,
.option--current.option--disabled {
background-color: var(--sl-color-primary-600);
color: var(--sl-color-neutral-0);
opacity: 1;
}
.option--disabled {
@ -48,7 +50,7 @@ export default css`
.option__label {
flex: 1 1 auto;
display: inline-block;
padding: 0 var(--sl-spacing-2x-small);
line-height: var(--sl-line-height-dense);
}
.option .option__check {
@ -56,15 +58,16 @@ export default css`
display: flex;
align-items: center;
justify-content: center;
width: 1.5em;
visibility: hidden;
padding-inline-end: var(--sl-spacing-2x-small);
}
.option--selected .option__check {
visibility: visible;
}
.option__prefix {
.option__prefix,
.option__suffix {
flex: 0 0 auto;
display: flex;
align-items: center;
@ -74,12 +77,6 @@ export default css`
margin-inline-end: var(--sl-spacing-x-small);
}
.option__suffix {
flex: 0 0 auto;
display: flex;
align-items: center;
}
.option__suffix::slotted(*) {
margin-inline-start: var(--sl-spacing-x-small);
}

Wyświetl plik

@ -1,9 +1,43 @@
import { expect, fixture, html } from '@open-wc/testing';
import { expect, fixture, html, waitUntil, aTimeout } from '@open-wc/testing';
import sinon from 'sinon';
import type SlOption from './option';
describe('<sl-option>', () => {
it('should render a component', async () => {
const el = await fixture(html` <sl-option></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-select>
`);
await expect(el).to.be.accessible();
});
expect(el).to.exist;
it('default properties', async () => {
const el = await fixture<SlOption>(html` <sl-option>Test</sl-option> `);
expect(el.value).to.equal('');
expect(el.disabled).to.be.false;
expect(el.getAttribute('aria-disabled')).to.equal('false');
});
it('changes aria attributes', async () => {
const el = await fixture<SlOption>(html` <sl-option>Test</sl-option> `);
el.disabled = true;
await aTimeout(100);
expect(el.getAttribute('aria-disabled')).to.equal('true');
});
it('emit sl-label-change when the label changes', async () => {
const el = await fixture<SlOption>(html` <sl-option>Test</sl-option> `);
const labelChangeHandler = sinon.spy();
el.textContent = 'New Text';
el.addEventListener('sl-label-change', labelChangeHandler);
await waitUntil(() => labelChangeHandler.calledOnce);
expect(labelChangeHandler).to.have.been.calledOnce;
});
});

Wyświetl plik

@ -10,28 +10,33 @@ import styles from './option.styles';
import type { CSSResultGroup } from 'lit';
/**
* @summary Short summary of the component's intended use.
* @summary Options define the selectable items within various form controls such as [select](/components/select).
*
* @since 2.0
* @status experimental
* @status stable
*
* @dependency sl-icon
*
* @event sl-label-change - Emitted when the option's label changes. For performance reasons, this event is only emitted
* when the default slot's `slotchange` event is triggered. It will not fire when the label is first set.
* when the default slot's `slotchange` event is triggered. It will not fire when the label is first set. Useful for
* parent controls that want to observe label changes without attaching an expensive mutation observer.
*
* @slot - The default slot.
* @slot example - An example slot.
* @slot - The option's label.
* @slot prefix - Used to prepend an icon or similar element to the menu item.
* @slot suffix - Used to append an icon or similar element to the menu item.
*
* @csspart checked-icon - The checked icon, an `<sl-icon>` element.
* @csspart base - The component's base wrapper.
*
* @cssproperty --example - An example CSS custom property.
* @csspart label - The option's label.
* @csspart prefix - The container that wraps the prefix.
* @csspart suffix - The container that wraps the suffix.
*/
@customElement('sl-option')
export default class SlOption extends ShoelaceElement {
static styles: CSSResultGroup = styles;
private cachedTextLabel: string;
// @ts-expect-error -- Controller is currently unused
private readonly localize = new LocalizeController(this);
@query('.option__label') defaultSlot: HTMLSlotElement;
@ -82,18 +87,18 @@ export default class SlOption extends ShoelaceElement {
render() {
return html`
<div
part="base"
class=${classMap({
option: true,
'option--current': this.current,
'option--disabled': this.disabled,
'option--selected': this.selected
})}
>
<span part="checked-icon" class="option__check">
<sl-icon name="check" library="system" aria-hidden="true"></sl-icon>
</span>
<slot name="prefix" class="option__prefix"></slot>
<slot class="option__label" @slotchange=${this.handleDefaultSlotChange}></slot>
<slot name="suffix" class="option__suffix"></slot>
<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>
<slot part="label" class="option__label" @slotchange=${this.handleDefaultSlotChange}></slot>
<slot part="suffix" name="suffix" class="option__suffix"></slot>
</div>
`;
}

Wyświetl plik

@ -29,6 +29,23 @@ describe('<sl-select>', () => {
expect(el.displayInput.disabled).to.be.true;
});
it('should not allow selection when the option is disabled', 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" disabled>Option 2</sl-option>
</sl-select>
`);
const disabledOption = el.querySelector('sl-option[disabled]')!;
await clickOnElement(el);
await waitForEvent(el, 'sl-after-show');
await clickOnElement(disabledOption);
await el.updateComplete;
expect(el.value).to.equal('option-1');
});
it('should focus the select when clicking on the label', async () => {
const el = await fixture<SlSelect>(html`
<sl-select label="Select One">

Wyświetl plik

@ -199,7 +199,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
const currentOption = this.getCurrentOption();
if (currentOption && !currentOption.disabled) {
this.setSelectedOption(currentOption);
this.displayLabel = currentOption.textContent ?? '';
this.displayLabel = (currentOption.textContent ?? '').trim();
this.value = currentOption.value;
this.valueInput.value = currentOption.value; // synchronous update for validation
this.invalid = !this.checkValidity();
@ -372,7 +372,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
if (option) {
this.setSelectedOption(option);
this.value = option.value;
this.displayLabel = option.textContent ?? '';
this.displayLabel = (option.textContent ?? '').trim();
} else {
// Clear selection
this.setSelectedOption(null);
@ -446,7 +446,9 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
if (option) {
this.value = option.value;
this.displayLabel = option.textContent ?? '';
this.valueInput.value = option.value; // synchronous update for validation
this.invalid = !this.checkValidity();
this.displayLabel = (option.textContent ?? '').trim();
} else {
// No option, reset the control
this.value = '';