feat(select-rich): add has no default selection feature

This commit is contained in:
Joren Broekema 2020-03-26 10:57:40 +01:00
parent 29e1252560
commit 975a01aca9
4 changed files with 127 additions and 3 deletions

View file

@ -68,7 +68,15 @@ export class LionSelectInvoker extends LionButton {
}
return this.selectedElement.textContent;
}
return ``;
return this._noSelectionTemplate();
}
/**
* To be overriden for a placeholder, used when `hasNoDefaultSelected` is true on the select rich
*/
// eslint-disable-next-line class-methods-use-this
_noSelectionTemplate() {
return html``;
}
_beforeTemplate() {

View file

@ -79,6 +79,17 @@ export class LionSelectRich extends ScopedElementsMixin(
type: String,
attribute: 'interaction-mode',
},
/**
* When setting this to true, on initial render, no option will be selected.
* It it advisable to override `_noSelectionTemplate` method in the select-invoker
* to render some kind of placeholder initially
*/
hasNoDefaultSelected: {
type: Boolean,
reflect: true,
attribute: 'has-no-default-selected',
},
};
}
@ -184,6 +195,7 @@ export class LionSelectRich extends ScopedElementsMixin(
// for interaction states
this._listboxActiveDescendant = null;
this.__hasInitialSelectedFormElement = false;
this.hasNoDefaultSelected = false;
this._repropagationRole = 'choice-group'; // configures FormControlMixin
this.__setupEventListeners();
this.__initInteractionStates();
@ -335,7 +347,11 @@ export class LionSelectRich extends ScopedElementsMixin(
}
// the first elements checked by default
if (!this.__hasInitialSelectedFormElement && (!child.disabled || this.disabled)) {
if (
!this.hasNoDefaultSelected &&
!this.__hasInitialSelectedFormElement &&
(!child.disabled || this.disabled)
) {
child.active = true;
child.checked = true;
this.__hasInitialSelectedFormElement = true;

View file

@ -462,6 +462,51 @@ console.log(`checkedIndex: ${selectEl.checkedIndex}`); // 0
console.log(`checkedValue: ${selectEl.checkedValue}`); // 'red'
```
### No default selection
If you want to set a placeholder option with something like 'Please select', you can of course do this, the same way you would do it in a native select.
Simply put an option with a modelValue that is `null`.
```html
<lion-option .choiceValue=${null}>select a color</lion-option>
```
However, this allows the user to explicitly select this option.
Often, you may want a placeholder that appears initially, but cannot be selected explicitly by the user.
For this you can use `has-no-default-selected` attribute.
Both methods work with the `Required` validator.
<Story name="No default selected">
{html`
<lion-select-rich name="favoriteColor" label="Favorite color" has-no-default-selected>
<lion-options slot="input">
<lion-option .choiceValue=${'red'}>Red</lion-option>
<lion-option .choiceValue=${'hotpink'}>Hotpink</lion-option>
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
</lion-options>
</lion-select-rich>
`}
</Story>
```html
<lion-select-rich name="favoriteColor" label="Favorite color" has-no-default-selected>
<lion-options slot="input">
<lion-option .choiceValue=${'red'}>Red</lion-option>
<lion-option .choiceValue=${'hotpink'}>Hotpink</lion-option>
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
</lion-options>
</lion-select-rich>
```
> By default, the placeholder is completely empty in the `LionSelectInvoker`,
> but subclassers can easily override this in their extension, by the overriding `_noSelectionTemplate()` method.
### Custom Invoker
You can provide a custom invoker using the invoker slot.

View file

@ -3,7 +3,7 @@ import { formFixture as fixture } from '@lion/field/test-helpers.js';
import { OverlayController } from '@lion/overlays';
import { Required } from '@lion/validate';
import { aTimeout, defineCE, expect, html, nextFrame, unsafeStatic } from '@open-wc/testing';
import { LionSelectRich } from '../index.js';
import { LionSelectInvoker, LionSelectRich } from '../index.js';
import '../lion-option.js';
import '../lion-options.js';
import '../lion-select-rich.js';
@ -204,6 +204,21 @@ describe('lion-select-rich', () => {
expect(el.showsFeedbackFor.includes('error')).to.be.true;
});
it('supports having no default selection initially', async () => {
const el = await fixture(html`
<lion-select-rich id="color" name="color" label="Favorite color" has-no-default-selected>
<lion-options slot="input">
<lion-option .choiceValue=${'red'}>Red</lion-option>
<lion-option .choiceValue=${'hotpink'} disabled>Hotpink</lion-option>
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
</lion-options>
</lion-select-rich>
`);
expect(el.selectedElement).to.be.undefined;
expect(el.modelValue).to.equal('');
});
describe('Invoker', () => {
it('generates an lion-select-invoker if no invoker is provided', async () => {
const el = await fixture(html`
@ -711,5 +726,45 @@ describe('lion-select-rich', () => {
el.dispatchEvent(new Event('switch'));
expect(el._overlayCtrl.placementMode).to.equal('local');
});
it('supports putting a placeholder template when there is no default selection initially', async () => {
const invokerTagName = defineCE(
class extends LionSelectInvoker {
_noSelectionTemplate() {
return html`
Please select an option..
`;
}
},
);
const invokerTag = unsafeStatic(invokerTagName);
const selectTagName = defineCE(
class extends LionSelectRich {
get slots() {
return {
...super.slots,
invoker: () => document.createElement(invokerTag.d),
};
}
},
);
const selectTag = unsafeStatic(selectTagName);
const el = await fixture(html`
<${selectTag} id="color" name="color" label="Favorite color" has-no-default-selected>
<lion-options slot="input">
<lion-option .choiceValue=${'red'}>Red</lion-option>
<lion-option .choiceValue=${'hotpink'} disabled>Hotpink</lion-option>
<lion-option .choiceValue=${'teal'}>Teal</lion-option>
</lion-options>
</${selectTag}>
`);
expect(el._invokerNode.shadowRoot.getElementById('content-wrapper')).dom.to.equal(
`<div id="content-wrapper">Please select an option..</div>`,
);
expect(el.modelValue).to.equal('');
});
});
});