Skip to content

Commit a37c143

Browse files
committed
feat: scope the no-options slot
Resolves #1071, Resolves #1081
1 parent a905f42 commit a37c143

File tree

5 files changed

+125
-32
lines changed

5 files changed

+125
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<v-select>
3+
<template v-slot:no-options="{ search, searching }">
4+
<template v-if="searching">
5+
No results found for <em>{{ search }}</em>.
6+
</template>
7+
<em style="opacity: 0.5;" v-else>Start typing to search for a country.</em>
8+
</template>
9+
</v-select>
10+
</template>
11+
12+
<script>
13+
export default {
14+
name: 'BetterNoOptions',
15+
};
16+
</script>

docs/api/slots.md

+69-22
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
77

88
### `selected-option`
99

10-
#### Scope:
10+
#### Scope:
1111

1212
- `option {Object}` - A selected option
1313

1414
```html
15-
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
16-
{{ getOptionLabel(option) }}
15+
<slot
16+
name="selected-option"
17+
v-bind="(typeof option === 'object')?option:{[label]: option}"
18+
>
19+
{{ getOptionLabel(option) }}
1720
</slot>
1821
```
1922

@@ -27,45 +30,89 @@ Slots can be used to change the look and feel of the UI, or to simply swap out t
2730
- `multiple {Boolean}` - If the component supports the selection of multiple values
2831

2932
```html
30-
<slot v-for="option in valueAsArray" name="selected-option-container"
31-
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
32-
<span class="selected-tag" v-bind:key="option.index">
33-
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
34-
{{ getOptionLabel(option) }}
35-
</slot>
36-
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
37-
<span aria-hidden="true">&times;</span>
38-
</button>
39-
</span>
33+
<slot
34+
v-for="option in valueAsArray"
35+
name="selected-option-container"
36+
:option="(typeof option === 'object')?option:{[label]: option}"
37+
:deselect="deselect"
38+
:multiple="multiple"
39+
:disabled="disabled"
40+
>
41+
<span class="selected-tag" v-bind:key="option.index">
42+
<slot
43+
name="selected-option"
44+
v-bind="(typeof option === 'object')?option:{[label]: option}"
45+
>
46+
{{ getOptionLabel(option) }}
47+
</slot>
48+
<button
49+
v-if="multiple"
50+
:disabled="disabled"
51+
@click="deselect(option)"
52+
type="button"
53+
class="close"
54+
aria-label="Remove option"
55+
>
56+
<span aria-hidden="true">&times;</span>
57+
</button>
58+
</span>
4059
</slot>
4160
```
4261

4362
## Component Actions
4463

4564
### `spinner`
4665

66+
#### Scope:
67+
68+
- `loading {Boolean}` - if the component is in a loading state
69+
4770
```html
48-
<slot name="spinner">
49-
<div class="spinner" v-show="mutableLoading">Loading...</div>
71+
<slot name="spinner" v-bind="scope.spinner">
72+
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
5073
</slot>
5174
```
5275

53-
### `no-options`
76+
### `open-indicator`
5477

55-
```html
56-
<slot name="no-options">Sorry, no matching options.</slot>
78+
```js
79+
attributes : {
80+
'ref': 'openIndicator',
81+
'role': 'presentation',
82+
'class': 'vs__open-indicator',
83+
}
84+
```
85+
86+
```vue
87+
<slot name="open-indicator" v-bind="scope.openIndicator">
88+
<component :is="childComponents.OpenIndicator" v-if="!noDrop" v-bind="scope.openIndicator.attributes"/>
89+
</slot>
5790
```
5891

5992
## Dropdown
6093

6194
### `option`
6295

63-
#### Scope:
64-
6596
- `option {Object}` - The currently iterated option from `filteredOptions`
6697

6798
```html
68-
<slot name="option" v-bind="(typeof option === 'object')?option:{[label]: option}">
69-
{{ getOptionLabel(option) }}
99+
<slot
100+
name="option"
101+
v-bind="(typeof option === 'object')?option:{[label]: option}"
102+
>
103+
{{ getOptionLabel(option) }}
104+
</slot>
105+
```
106+
107+
### `no-options`
108+
109+
The no options slot is displayed in the dropdown when `filteredOptions.length === 0`.
110+
111+
- `search {String}` - the current search text
112+
- `searching {Boolean}` - if the component has search text
113+
114+
```vue
115+
<slot name="no-options" v-bind="scope.noOptions">
116+
Sorry, no matching options.
70117
</slot>
71118
```

docs/guide/slots.md

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
::: tip 🚧
22
This section of the guide is a work in progress! Check back soon for an update.
3-
Vue Select currently offers quite a few scoped slots, and you can check out the
3+
Vue Select currently offers quite a few scoped slots, and you can check out the
44
[API Docs for Slots](../api/slots.md) in the meantime while a good guide is put together.
55
:::
66

7-
#### Scoped Slot `option`
7+
### Scoped Slot `option`
88

99
vue-select provides the scoped `option` slot in order to create custom dropdown templates.
1010

1111
```html
1212
<v-select :options="options" label="title">
13-
<template v-slot:option="option">
14-
<span :class="option.icon"></span>
15-
{{ option.title }}
16-
</template>
17-
</v-select>
18-
```
13+
<template v-slot:option="option">
14+
<span :class="option.icon"></span>
15+
{{ option.title }}
16+
</template>
17+
</v-select>
18+
```
1919

2020
Using the `option` slot with props `"option"` provides the current option variable to the template.
2121

2222
<CodePen url="NXBwYG" height="500"/>
23+
24+
### Improving the default `no-options` text
25+
26+
The `no-options` slot is displayed in the dropdown when `filteredOptions === 0`. By default, it
27+
displays _Sorry, no matching options_. You can add more contextual information by using the slot
28+
in your own apps.
29+
30+
<BetterNoOptions />
31+
32+
<<< @/.vuepress/components/BetterNoOptions.vue

src/components/Select.vue

+6-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@
7070
{{ getOptionLabel(option) }}
7171
</slot>
7272
</li>
73-
<li v-if="!filteredOptions.length" class="vs__no-options" @mousedown.stop="">
74-
<slot name="no-options">Sorry, no matching options.</slot>
73+
<li v-if="filteredOptions.length === 0" class="vs__no-options" @mousedown.stop="">
74+
<slot name="no-options" v-bind="scope.noOptions">Sorry, no matching options.</slot>
7575
</li>
7676
</template>
7777
</ul>
@@ -1013,6 +1013,10 @@
10131013
spinner: {
10141014
loading: this.mutableLoading
10151015
},
1016+
noOptions: {
1017+
search: this.search,
1018+
searching: this.searching,
1019+
},
10161020
openIndicator: {
10171021
attributes: {
10181022
'ref': 'openIndicator',

tests/unit/Slots.spec.js

+16
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,20 @@ describe('Scoped Slots', () => {
5656

5757
expect(Select.find({ref: 'dropdownMenu'}).text()).toEqual('onetwothree');
5858
});
59+
60+
it('noOptions slot receives the current search text', async () => {
61+
const noOptions = jest.fn();
62+
const Select = mountDefault({}, {
63+
scopedSlots: {'no-options': noOptions},
64+
});
65+
66+
Select.vm.search = 'something not there';
67+
Select.vm.open = true;
68+
await Select.vm.$nextTick();
69+
70+
expect(noOptions).toHaveBeenCalledWith({
71+
search: 'something not there',
72+
searching: true,
73+
})
74+
});
5975
});

0 commit comments

Comments
 (0)