Skip to content

Commit f118779

Browse files
feat(wizard-select): add nullabl mwc-select web-component (#250)
1 parent 3801349 commit f118779

File tree

2 files changed

+190
-0
lines changed

2 files changed

+190
-0
lines changed

src/wizard-select.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {
2+
customElement,
3+
html,
4+
internalProperty,
5+
property,
6+
query,
7+
TemplateResult,
8+
} from 'lit-element';
9+
import { get } from 'lit-translate';
10+
11+
import { Switch } from '@material/mwc-switch';
12+
import { Select } from '@material/mwc-select';
13+
14+
/** A potentially `nullable` `Select`.
15+
*
16+
* NB: Use `maybeValue: string | null` instead of `value` if `nullable`!*/
17+
@customElement('wizard-select')
18+
export class WizardSelect extends Select {
19+
/** Whether [[`maybeValue`]] may be `null` */
20+
@property({ type: Boolean })
21+
nullable = false;
22+
private isNull = false;
23+
@internalProperty()
24+
private get null(): boolean {
25+
return this.nullable && this.isNull;
26+
}
27+
private set null(value: boolean) {
28+
if (!this.nullable || value === this.isNull) return;
29+
this.isNull = value;
30+
if (this.null) this.disable();
31+
else this.enable();
32+
}
33+
/** Replacement for `value`, can only be `null` if [[`nullable`]]. */
34+
@property({ type: String })
35+
get maybeValue(): string | null {
36+
return this.null ? null : this.value;
37+
}
38+
set maybeValue(value: string | null) {
39+
if (value === null) this.null = true;
40+
else {
41+
this.null = false;
42+
this.value = value;
43+
}
44+
}
45+
/** The default `value` displayed if [[`maybeValue`]] is `null`. */
46+
@property({ type: String })
47+
defaultValue = '';
48+
/** Additional values that cause validation to fail. */
49+
@property({ type: Array })
50+
reservedValues: string[] = [];
51+
52+
@query('mwc-switch') nullSwitch?: Switch;
53+
54+
private nulled: string | null = null;
55+
56+
private enable(): void {
57+
if (this.nulled === null) return;
58+
this.value = this.nulled;
59+
this.nulled = null;
60+
this.disabled = false;
61+
}
62+
63+
private disable(): void {
64+
if (this.nulled !== null) return;
65+
this.nulled = this.value;
66+
this.value = this.defaultValue;
67+
this.disabled = true;
68+
}
69+
70+
async firstUpdated(): Promise<void> {
71+
await super.firstUpdated();
72+
}
73+
74+
checkValidity(): boolean {
75+
if (
76+
this.reservedValues &&
77+
this.reservedValues.some(array => array === this.value)
78+
) {
79+
this.setCustomValidity(get('textfield.unique'));
80+
return false;
81+
}
82+
this.setCustomValidity(''); //Reset. Otherwise super.checkValidity always falseM
83+
return super.checkValidity();
84+
}
85+
86+
renderSwitch(): TemplateResult {
87+
if (this.nullable) {
88+
return html`<mwc-switch
89+
style="margin-left: 12px;"
90+
?checked=${!this.null}
91+
@change=${() => {
92+
this.null = !this.nullSwitch!.checked;
93+
}}
94+
></mwc-switch>`;
95+
}
96+
return html``;
97+
}
98+
99+
render(): TemplateResult {
100+
return html`
101+
<div style="display: flex; flex-direction: row;">
102+
<div style="flex: auto;">${super.render()}</div>
103+
<div style="display: flex; align-items: center; height: 56px;">
104+
${this.renderSwitch()}
105+
</div>
106+
</div>
107+
`;
108+
}
109+
}

test/unit/wizard-select.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { html, fixture, expect } from '@open-wc/testing';
2+
3+
import '../../src/wizard-select.js';
4+
import { WizardSelect } from '../../src/wizard-select.js';
5+
6+
describe('wizard-select', () => {
7+
let element: WizardSelect;
8+
const items = ['one', 'two', 'three'];
9+
beforeEach(async () => {
10+
element = await fixture(
11+
html`<wizard-select
12+
>${items.map(
13+
item => html`<mwc-list-item value="${item}">${item}</mwc-list-item>`
14+
)}</wizard-select
15+
>`
16+
);
17+
});
18+
19+
it('does not render a null value switch', () =>
20+
expect(element.nullSwitch).to.not.exist);
21+
22+
it('is enabled', () => expect(element).to.have.property('disabled', false));
23+
24+
it('returns the select value as its maybeValue', () => {
25+
element.value = 'two';
26+
expect(element.maybeValue).to.equal(element.value);
27+
});
28+
29+
describe('nullable', () => {
30+
beforeEach(async () => {
31+
element.nullable = true;
32+
element.value = 'one';
33+
await element.updateComplete;
34+
});
35+
36+
it('renders a null value switch', async () =>
37+
expect(element.nullSwitch).to.exist);
38+
39+
it('disables itself on switch toggle', async () => {
40+
expect(element).to.have.property('maybeValue', 'one');
41+
expect(element).to.have.property('disabled', false);
42+
element.nullSwitch!.click();
43+
await element.updateComplete;
44+
expect(element).to.have.property('maybeValue', null);
45+
expect(element).to.have.property('disabled', true);
46+
});
47+
48+
it('remebers its previous value on switch toggle', async () => {
49+
element.maybeValue = 'three';
50+
await element.updateComplete;
51+
element.nullSwitch!.click();
52+
await element.updateComplete;
53+
element.nullSwitch!.click();
54+
await element.updateComplete;
55+
expect(element).to.have.property('disabled', false);
56+
expect(element).to.have.property('maybeValue', 'three');
57+
});
58+
59+
describe('with a null value', () => {
60+
beforeEach(async () => {
61+
element.maybeValue = null;
62+
await element.updateComplete;
63+
});
64+
65+
it('enables itself on switch toggle', async () => {
66+
element.nullSwitch?.click();
67+
await element.updateComplete;
68+
expect(element).to.have.property('disabled', false);
69+
});
70+
71+
it('has a disabled textfield', () =>
72+
expect(element).to.have.property('disabled', true));
73+
74+
it('does not show anything in the textfield', () =>
75+
expect(element).to.have.property('value', ''));
76+
77+
it('returns null', () =>
78+
expect(element).to.have.property('maybeValue', null));
79+
});
80+
});
81+
});

0 commit comments

Comments
 (0)