feat(select-rich): add has no default selection feature
This commit is contained in:
parent
29e1252560
commit
975a01aca9
4 changed files with 127 additions and 3 deletions
|
|
@ -68,7 +68,15 @@ export class LionSelectInvoker extends LionButton {
|
||||||
}
|
}
|
||||||
return this.selectedElement.textContent;
|
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() {
|
_beforeTemplate() {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,17 @@ export class LionSelectRich extends ScopedElementsMixin(
|
||||||
type: String,
|
type: String,
|
||||||
attribute: 'interaction-mode',
|
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
|
// for interaction states
|
||||||
this._listboxActiveDescendant = null;
|
this._listboxActiveDescendant = null;
|
||||||
this.__hasInitialSelectedFormElement = false;
|
this.__hasInitialSelectedFormElement = false;
|
||||||
|
this.hasNoDefaultSelected = false;
|
||||||
this._repropagationRole = 'choice-group'; // configures FormControlMixin
|
this._repropagationRole = 'choice-group'; // configures FormControlMixin
|
||||||
this.__setupEventListeners();
|
this.__setupEventListeners();
|
||||||
this.__initInteractionStates();
|
this.__initInteractionStates();
|
||||||
|
|
@ -335,7 +347,11 @@ export class LionSelectRich extends ScopedElementsMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
// the first elements checked by default
|
// 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.active = true;
|
||||||
child.checked = true;
|
child.checked = true;
|
||||||
this.__hasInitialSelectedFormElement = true;
|
this.__hasInitialSelectedFormElement = true;
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,51 @@ console.log(`checkedIndex: ${selectEl.checkedIndex}`); // 0
|
||||||
console.log(`checkedValue: ${selectEl.checkedValue}`); // 'red'
|
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
|
### Custom Invoker
|
||||||
|
|
||||||
You can provide a custom invoker using the invoker slot.
|
You can provide a custom invoker using the invoker slot.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { formFixture as fixture } from '@lion/field/test-helpers.js';
|
||||||
import { OverlayController } from '@lion/overlays';
|
import { OverlayController } from '@lion/overlays';
|
||||||
import { Required } from '@lion/validate';
|
import { Required } from '@lion/validate';
|
||||||
import { aTimeout, defineCE, expect, html, nextFrame, unsafeStatic } from '@open-wc/testing';
|
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-option.js';
|
||||||
import '../lion-options.js';
|
import '../lion-options.js';
|
||||||
import '../lion-select-rich.js';
|
import '../lion-select-rich.js';
|
||||||
|
|
@ -204,6 +204,21 @@ describe('lion-select-rich', () => {
|
||||||
expect(el.showsFeedbackFor.includes('error')).to.be.true;
|
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', () => {
|
describe('Invoker', () => {
|
||||||
it('generates an lion-select-invoker if no invoker is provided', async () => {
|
it('generates an lion-select-invoker if no invoker is provided', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
|
|
@ -711,5 +726,45 @@ describe('lion-select-rich', () => {
|
||||||
el.dispatchEvent(new Event('switch'));
|
el.dispatchEvent(new Event('switch'));
|
||||||
expect(el._overlayCtrl.placementMode).to.equal('local');
|
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('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue