diff --git a/docs/.vuepress/components/Paginated.vue b/docs/.vuepress/components/Paginated.vue
new file mode 100644
index 0000000..65fc0b8
--- /dev/null
+++ b/docs/.vuepress/components/Paginated.vue
@@ -0,0 +1,49 @@
+
+ search = query" filterable="false">
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 2ee84ca..da73379 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -119,6 +119,7 @@ module.exports = {
children: [
['guide/validation', 'Validation'],
['guide/selectable', 'Limiting Selections'],
+ ['guide/pagination', 'Pagination'],
['guide/vuex', 'Vuex'],
['guide/ajax', 'AJAX'],
['guide/loops', 'Using in Loops'],
diff --git a/docs/api/slots.md b/docs/api/slots.md
index 68e74a2..453a97f 100644
--- a/docs/api/slots.md
+++ b/docs/api/slots.md
@@ -3,6 +3,39 @@ Vue Select leverages scoped slots to allow for total customization of the presen
Slots can be used to change the look and feel of the UI, or to simply swap out text.
:::
+## Wrapper
+
+### `header`
+
+Displayed at the top of the component, above `.vs__dropdown-toggle`.
+
+- `search {string}` - the current search query
+- `loading {boolean}` - is the component loading
+- `searching {boolean}` - is the component searching
+- `filteredOptions {array}` - options filtered by the search text
+- `deselect {function}` - function to deselect an option
+
+```html
+
+```
+
+### `footer`
+
+Displayed at the bottom of the component, below `.vs__dropdown-toggle`.
+
+When implementing this slot, you'll likely need to use `appendToBody` to position the dropdown.
+Otherwise content in this slot will affect it's positioning.
+
+- `search {string}` - the current search query
+- `loading {boolean}` - is the component loading
+- `searching {boolean}` - is the component searching
+- `filteredOptions {array}` - options filtered by the search text
+- `deselect {function}` - function to deselect an option
+
+```html
+
+```
+
## Selected Option(s)
### `selected-option`
@@ -91,8 +124,38 @@ attributes : {
## Dropdown
+### `list-header`
+
+Displayed as the first item in the dropdown. No content by default. Parent element is the `
`,
+so this slot should contain a root `- `.
+
+- `search {string}` - the current search query
+- `loading {boolean}` - is the component loading
+- `searching {boolean}` - is the component searching
+- `filteredOptions {array}` - options filtered by the search text
+
+```html
+
+```
+
+### `list-footer`
+
+Displayed as the last item in the dropdown. No content by default. Parent element is the `
`,
+so this slot should contain a root `- `.
+
+- `search {string}` - the current search query
+- `loading {boolean}` - is the component loading
+- `searching {boolean}` - is the component searching
+- `filteredOptions {array}` - options filtered by the search text
+
+```html
+
+```
+
### `option`
+The current option within the dropdown, contained within `
- `.
+
- `option {Object}` - The currently iterated option from `filteredOptions`
```html
diff --git a/docs/guide/pagination.md b/docs/guide/pagination.md
new file mode 100644
index 0000000..15042dd
--- /dev/null
+++ b/docs/guide/pagination.md
@@ -0,0 +1,18 @@
+::: tip
+Pagination is supported using slots available with Vue Select 3.8 and above.
+:::
+
+Pagination can be a super helpful tool when working with large sets of data. If you have 1,000
+options, the component is going to render 1,000 DOM nodes. That's a lot of nodes to insert/remove,
+and chances are your user is only interested in a few of them anyways.
+
+To implement pagination with Vue Select, you can take advantage of the `list-footer` slot. It
+appears below all other options in the drop down list.
+
+To make pagination work properly with filtering, you'll have to handle it yourself in the parent.
+You can use the `filterable` boolean to turn off Vue Select's filtering, and then hook into the
+`search` event to use the current search query in the parent component.
+
+
+
+<<< @/.vuepress/components/Paginated.vue
diff --git a/src/components/Select.vue b/src/components/Select.vue
index 3527b77..cdf2509 100644
--- a/src/components/Select.vue
+++ b/src/components/Select.vue
@@ -4,6 +4,7 @@
@@ -1001,6 +1004,12 @@
* @returns {Object}
*/
scope () {
+ const listSlot = {
+ search: this.search,
+ loading: this.loading,
+ searching: this.searching,
+ filteredOptions: this.filteredOptions
+ };
return {
search: {
attributes: {
@@ -1032,6 +1041,7 @@
},
noOptions: {
search: this.search,
+ loading: this.loading,
searching: this.searching,
},
openIndicator: {
@@ -1041,6 +1051,10 @@
'class': 'vs__open-indicator',
},
},
+ listHeader: listSlot,
+ listFooter: listSlot,
+ header: { ...listSlot, deselect: this.deselect },
+ footer: { ...listSlot, deselect: this.deselect }
};
},
diff --git a/tests/unit/Slots.spec.js b/tests/unit/Slots.spec.js
index 4e886d5..955829b 100644
--- a/tests/unit/Slots.spec.js
+++ b/tests/unit/Slots.spec.js
@@ -68,8 +68,55 @@ describe('Scoped Slots', () => {
await Select.vm.$nextTick();
expect(noOptions).toHaveBeenCalledWith({
+ loading: false,
search: 'something not there',
searching: true,
})
});
+
+ test('header slot props', async () => {
+ const header = jest.fn();
+ const Select = mountDefault({}, {
+ scopedSlots: {header: header},
+ });
+ await Select.vm.$nextTick();
+ expect(Object.keys(header.mock.calls[0][0])).toEqual([
+ 'search', 'loading', 'searching', 'filteredOptions', 'deselect',
+ ]);
+ });
+
+ test('footer slot props', async () => {
+ const footer = jest.fn();
+ const Select = mountDefault({}, {
+ scopedSlots: {footer: footer},
+ });
+ await Select.vm.$nextTick();
+ expect(Object.keys(footer.mock.calls[0][0])).toEqual([
+ 'search', 'loading', 'searching', 'filteredOptions', 'deselect',
+ ]);
+ });
+
+ test('list-header slot props', async () => {
+ const header = jest.fn();
+ const Select = mountDefault({}, {
+ scopedSlots: {'list-header': header},
+ });
+ Select.vm.open = true;
+ await Select.vm.$nextTick();
+ expect(Object.keys(header.mock.calls[0][0])).toEqual([
+ 'search', 'loading', 'searching', 'filteredOptions',
+ ]);
+ });
+
+ test('list-footer slot props', async () => {
+ const footer = jest.fn();
+ const Select = mountDefault({}, {
+ scopedSlots: {'list-footer': footer},
+ });
+ Select.vm.open = true;
+ await Select.vm.$nextTick();
+ expect(Object.keys(footer.mock.calls[0][0])).toEqual([
+ 'search', 'loading', 'searching', 'filteredOptions',
+ ]);
+ });
});