Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/includes/vscode-select/vscode-select-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ export class VscodeSelectBase extends VscElement {
@property({reflect: true})
label = '';

/**
* Aria label for the button that opens the dropdown in combobox mode.
* Override this to localise the button for screen readers.
*/
@property({attribute: 'open-button-aria-label'})
openButtonAriaLabel = 'Open the list of options';

/**
* Text shown in the dropdown when no options match the current filter.
*/
@property({attribute: 'no-options-text'})
noOptionsText = 'No options';

/**
* Prefix text for the "create new option" entry shown in combobox+creatable
* mode. The rendered text will be: `${createOptionPrefix} "${filterPattern}"`.
*/
@property({attribute: 'create-option-prefix'})
createOptionPrefix = 'Add';

/**
* The element cannot be used and is not focusable.
*/
Expand Down Expand Up @@ -712,12 +732,12 @@ export class VscodeSelectBase extends VscElement {
})}
@mouseout=${this._onPlaceholderOptionMouseOut}
>
Add "${this._opts.filterPattern}"
${this.createOptionPrefix} "${this._opts.filterPattern}"
</li>`;
} else {
return isListEmpty
? html`<li class="no-options" @click=${this._onNoOptionsClick}>
No options
${this.noOptionsText}
</li>`
: nothing;
}
Expand Down
99 changes: 99 additions & 0 deletions src/vscode-multi-select/vscode-multi-select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,105 @@ describe('vscode-multi-select', () => {
expect(el.value).to.eql(['asdf']);
});

describe('i18n override props', () => {
it('selected-text overrides the badge label', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select selected-text="Ausgewählt">
<vscode-option selected>One</vscode-option>
<vscode-option>Two</vscode-option>
</vscode-multi-select>
`);
expect(el.shadowRoot?.querySelector('.select-face-badge')).lightDom.to.eq(
'1 Ausgewählt'
);
});

it('selected-text overrides the badge label when nothing is selected', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select selected-text="Ausgewählt">
<vscode-option>One</vscode-option>
<vscode-option>Two</vscode-option>
</vscode-multi-select>
`);
expect(el.shadowRoot?.querySelector('.select-face-badge')).lightDom.to.eq(
'0 Ausgewählt'
);
});

it('open-button-aria-label overrides the combobox button aria-label', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select combobox open-button-aria-label="Öffnen">
<vscode-option>One</vscode-option>
</vscode-multi-select>
`);
const btn =
el.shadowRoot?.querySelector<HTMLButtonElement>('.combobox-button');
expect(btn?.getAttribute('aria-label')).to.eq('Öffnen');
});

it('no-options-text is shown when the filter matches nothing', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select combobox no-options-text="Keine Optionen">
<vscode-option>Lorem</vscode-option>
</vscode-multi-select>
`);
el.focus();
await el.updateComplete;
await sendKeys({type: 'zzz'});
await el.updateComplete;
const noOpts = el.shadowRoot?.querySelector('.no-options');
expect(noOpts?.textContent?.trim()).to.eq('Keine Optionen');
});

it('create-option-prefix overrides the creatable suggestion text', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select
combobox
creatable
create-option-prefix="Hinzufügen"
>
<vscode-option>Lorem</vscode-option>
</vscode-multi-select>
`);
el.focus();
await el.updateComplete;
await sendKeys({type: 'Neu'});
await el.updateComplete;
const placeholder = el.shadowRoot?.querySelector('.option.placeholder');
expect(placeholder?.textContent?.trim()).to.eq('Hinzufügen "Neu"');
});

it('select-all-title overrides the select-all button tooltip', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select select-all-title="Alle auswählen">
<vscode-option>One</vscode-option>
<vscode-option>Two</vscode-option>
</vscode-multi-select>
`);
expect(el.selectAllTitle).to.eq('Alle auswählen');
});

it('deselect-all-title overrides the deselect-all button tooltip', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select deselect-all-title="Alle abwählen">
<vscode-option>One</vscode-option>
<vscode-option>Two</vscode-option>
</vscode-multi-select>
`);
expect(el.deselectAllTitle).to.eq('Alle abwählen');
});

it('accept-button-text overrides the OK button label', async () => {
const el = await fixture<VscodeMultiSelect>(html`
<vscode-multi-select accept-button-text="Bestätigen">
<vscode-option>One</vscode-option>
<vscode-option>Two</vscode-option>
</vscode-multi-select>
`);
expect(el.acceptButtonText).to.eq('Bestätigen');
});
});

it('selects multiple options with keyboard');
it('selectedIndexes sync with values');
it(
Expand Down
37 changes: 31 additions & 6 deletions src/vscode-multi-select/vscode-multi-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ export class VscodeMultiSelect
@property({reflect: true})
name: string | undefined = undefined;

/**
* Label suffix used in the selected-count badge (e.g. "3 Selected").
* Override to localise the badge text.
*/
@property({attribute: 'selected-text'})
selectedText = 'Selected';

/**
* Tooltip for the "select all" button in the dropdown controls.
*/
@property({attribute: 'select-all-title'})
selectAllTitle = 'Select all';

/**
* Tooltip for the "deselect all" button in the dropdown controls.
*/
@property({attribute: 'deselect-all-title'})
deselectAllTitle = 'Deselect all';

/**
* Label for the accept/OK button in the dropdown controls.
*/
@property({attribute: 'accept-button-text'})
acceptButtonText = 'OK';

@property({type: Array, attribute: false})
set selectedIndexes(val: number[]) {
this._opts.selectedIndexes = val;
Expand Down Expand Up @@ -332,10 +357,10 @@ export class VscodeMultiSelect
private _renderLabel() {
switch (this._opts.selectedIndexes.length) {
case 0:
return html`<span class="select-face-badge no-item">0 Selected</span>`;
return html`<span class="select-face-badge no-item">0 ${this.selectedText}</span>`;
default:
return html`<span class="select-face-badge"
>${this._opts.selectedIndexes.length} Selected</span
>${this._opts.selectedIndexes.length} ${this.selectedText}</span
>`;
}
}
Expand Down Expand Up @@ -368,7 +393,7 @@ export class VscodeMultiSelect
@keydown=${this._onComboboxInputSpaceKeyDown}
/>
<button
aria-label="Open the list of options"
aria-label=${this.openButtonAriaLabel}
class="combobox-button"
type="button"
@click=${this._onComboboxButtonClick}
Expand Down Expand Up @@ -413,7 +438,7 @@ export class VscodeMultiSelect
<button
type="button"
@click=${this._onMultiSelectAllClick}
title="Select all"
title=${this.selectAllTitle}
class="action-icon"
id="select-all"
>
Expand All @@ -422,7 +447,7 @@ export class VscodeMultiSelect
<button
type="button"
@click=${this._onMultiDeselectAllClick}
title="Deselect all"
title=${this.deselectAllTitle}
class="action-icon"
id="select-none"
>
Expand All @@ -431,7 +456,7 @@ export class VscodeMultiSelect
<vscode-button
class="button-accept"
@click=${this._onMultiAcceptClick}
>OK</vscode-button
>${this.acceptButtonText}</vscode-button
>
</div>
`
Expand Down
43 changes: 43 additions & 0 deletions src/vscode-single-select/vscode-single-select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,49 @@ describe('vscode-single-select', () => {
expect(el.value).to.eql('asdf');
});

describe('i18n override props', () => {
it('no-options-text is shown when the filter matches nothing', async () => {
const el = await fixture<VscodeSingleSelect>(html`
<vscode-single-select combobox no-options-text="Keine Optionen">
<vscode-option>Lorem</vscode-option>
<vscode-option>Ipsum</vscode-option>
</vscode-single-select>
`);
await clickOnElement(el);
await sendKeys({type: 'zzz'});
await el.updateComplete;
const noOpts = el.shadowRoot?.querySelector('.no-options');
expect(noOpts?.textContent?.trim()).to.eq('Keine Optionen');
});

it('create-option-prefix overrides the creatable suggestion text', async () => {
const el = await fixture<VscodeSingleSelect>(html`
<vscode-single-select combobox creatable create-option-prefix="Hinzufügen">
<vscode-option>Lorem</vscode-option>
<vscode-option>Ipsum</vscode-option>
<vscode-option>Dolor</vscode-option>
</vscode-single-select>
`);
el.focus();
await el.updateComplete;
await sendKeys({type: 'Neu'});
await el.updateComplete;
const placeholder = el.shadowRoot?.querySelector('.option.placeholder');
expect(placeholder?.textContent?.trim()).to.eq('Hinzufügen "Neu"');
});

it('open-button-aria-label overrides the combobox button aria-label', async () => {
const el = await fixture<VscodeSingleSelect>(html`
<vscode-single-select combobox open-button-aria-label="Öffnen">
<vscode-option>Lorem</vscode-option>
</vscode-single-select>
`);
const btn =
el.shadowRoot?.querySelector<HTMLButtonElement>('.combobox-button');
expect(btn?.getAttribute('aria-label')).to.eq('Öffnen');
});
});

//keyboard navigation
it('selects previous option with keyboard');
it('selects next option with keyboard');
Expand Down
2 changes: 1 addition & 1 deletion src/vscode-single-select/vscode-single-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export class VscodeSingleSelect
@keydown=${this._onComboboxInputSpaceKeyDown}
/>
<button
aria-label="Open the list of options"
aria-label=${this.openButtonAriaLabel}
class="combobox-button"
type="button"
@click=${this._onComboboxButtonClick}
Expand Down
Loading