diff --git a/docs/.vuepress/components/PositionedWithPopper.vue b/docs/.vuepress/components/PositionedWithPopper.vue
new file mode 100644
index 0000000..d7580bf
--- /dev/null
+++ b/docs/.vuepress/components/PositionedWithPopper.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 2bed2c8..2ee84ca 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -129,6 +129,7 @@ module.exports = {
collapsable: false,
children: [
['guide/keydown', 'Keydown Events'],
+ ['guide/positioning', 'Dropdown Position']
],
},
{
diff --git a/docs/api/props.md b/docs/api/props.md
index e54126f..d4d200d 100644
--- a/docs/api/props.md
+++ b/docs/api/props.md
@@ -1,3 +1,18 @@
+## appendToBody
+
+Append the dropdown element to the end of the body
+and size/position it dynamically. Use it if you have
+overflow or z-index issues.
+
+See [Dropdown Position](../guide/positioning.md) for more details.
+
+```js
+appendToBody: {
+ type: Boolean,
+ default: false
+},
+```
+
## value
Contains the currently selected value. Very similar to a
@@ -109,6 +124,30 @@ transition: {
},
```
+## calculatePosition
+
+When `appendToBody` is true, this function is responsible for positioning the drop down list.
+
+See [Dropdown Position](../guide/positioning.md) for more details.
+
+```js
+calculatePosition: {
+ type: Function,
+ /**
+ * @param dropdownList {HTMLUListElement}
+ * @param component {Vue} current instance of vue select
+ * @param width {string} calculated width in pixels of the dropdown menu
+ * @param top {string} absolute position top value in pixels relative to the document
+ * @param left {string} absolute position left value in pixels relative to the document
+ */
+ default(dropdownList, component, {width, top, left}) {
+ dropdownList.style.top = top;
+ dropdownList.style.left = left;
+ dropdownList.style.width = width;
+ }
+}
+```
+
## clearSearchOnSelect
Enables/disables clearing the search text when an option is selected.
@@ -353,10 +392,10 @@ createOption: {
## resetOnOptionsChange
When false, updating the options will not reset the selected value.
-
-Since `v3.4+` the prop accepts either a `boolean` or `function` that returns a `boolean`.
-If defined as a function, it will receive the params listed below.
+Since `v3.4+` the prop accepts either a `boolean` or `function` that returns a `boolean`.
+
+If defined as a function, it will receive the params listed below.
```js
/**
@@ -412,3 +451,4 @@ selectOnTab: {
type: Boolean,
default: false
}
+```
diff --git a/docs/guide/positioning.md b/docs/guide/positioning.md
new file mode 100644
index 0000000..ad5a4b9
--- /dev/null
+++ b/docs/guide/positioning.md
@@ -0,0 +1,33 @@
+## Default
+
+With the default CSS, Vue Select uses absolute positioning to render the dropdown menu. The root
+`.v-select` container (the components `$el`) is used as the `relative` parent for the dropdown. The
+dropdown will be displayed below the `$el` regardless of the available space.
+
+This works for most cases, but you might run into issues placing into a modal or near the bottom of
+the viewport. If you need more fine grain control, you can use calculated positioning.
+
+## Calculated
+
+If you want more control over how the dropdown is rendered, or if you're running into z-index issues,
+you may use the `appendToBody` boolean prop. When enabled, Vue Select will append the dropdown to
+the document, outside of the `.v-select` container, and position it with Javscript.
+
+When `appendToBody` is true, the positioning will be handled by the `calculatePosition` prop. This
+function is responsible for setting top/left absolute positioning values for the dropdown. The
+default implementation places the dropdown in the same position that it would normally appear.
+
+## Popper.js Integration
+
+[Popper.js](https://popper.js.org/) is an awesome, 3kb utility for calculating positions of just
+about any DOM element relative to another.
+
+By using the `appendToBody` and `calculatePosition` props, we're able to integrate directly with
+popper to calculate positioning for us.
+
+
+
+Check out the [Popper Docs](https://popper.js.org/docs/v2/modifiers/) to see the full `modifiers`
+API being used below.
+
+<<< @/.vuepress/components/PositionedWithPopper.vue{25-59}
diff --git a/docs/package.json b/docs/package.json
index 1fcd52b..79ca247 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -8,6 +8,7 @@
"build:preview": "cross-env DEPLOY_PREVIEW=true vuepress build"
},
"devDependencies": {
+ "@popperjs/core": "^2.1.0",
"@vuepress/plugin-active-header-links": "^1.0.0-alpha.47",
"@vuepress/plugin-google-analytics": "^1.0.0-alpha.47",
"@vuepress/plugin-nprogress": "^1.0.0-alpha.47",
diff --git a/docs/yarn.lock b/docs/yarn.lock
index 8548f5b..ce6091c 100644
--- a/docs/yarn.lock
+++ b/docs/yarn.lock
@@ -715,6 +715,11 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@popperjs/core@^2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.1.0.tgz#09a7a352a40508156e1256efdc015593feca28e0"
+ integrity sha512-ntN5t5spqhQv28cLfmmt1dYabsudzR5A7PU15gr/gzcT/gzqAOnYFQPaLPFraDa7ZCJG2eJ1JsO7pgXbYXGIrw==
+
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
diff --git a/src/components/Select.vue b/src/components/Select.vue
index 5d58d4c..ded68c5 100644
--- a/src/components/Select.vue
+++ b/src/components/Select.vue
@@ -53,28 +53,27 @@
-
-
-
-
- {{ getOptionLabel(option) }}
-
-
-
- Sorry, no matching options.
-
-
+
+
+
+ {{ getOptionLabel(option) }}
+
+
+
+ Sorry, no matching options.
+
+
@@ -84,6 +83,7 @@
import typeAheadPointer from '../mixins/typeAheadPointer'
import ajax from '../mixins/ajax'
import childComponents from './childComponents';
+ import appendToBody from '../directives/appendToBody';
import uniqueId from '../utility/uniqueId';
/**
@@ -94,6 +94,8 @@
mixins: [pointerScroll, typeAheadPointer, ajax],
+ directives: {appendToBody},
+
props: {
/**
* Contains the currently selected value. Very similar to a
@@ -517,6 +519,40 @@
* @return {Object}
*/
default: (map, vm) => map,
+ },
+
+ /**
+ * Append the dropdown element to the end of the body
+ * and size/position it dynamically. Use it if you have
+ * overflow or z-index issues.
+ * @type {Boolean}
+ */
+ appendToBody: {
+ type: Boolean,
+ default: false
+ },
+
+ /**
+ * When `appendToBody` is true, this function
+ * is responsible for positioning the drop
+ * down list.
+ * @since v3.7.0
+ * @see http://vue-select.org/guide/positioning.html
+ */
+ calculatePosition: {
+ type: Function,
+ /**
+ * @param dropdownList {HTMLUListElement}
+ * @param component {Vue} current instance of vue select
+ * @param width {string} calculated width in pixels of the dropdown menu
+ * @param top {string} absolute position top value in pixels relative to the document
+ * @param left {string} absolute position left value in pixels relative to the document
+ */
+ default(dropdownList, component, {width, top, left}) {
+ dropdownList.style.top = top;
+ dropdownList.style.left = left;
+ dropdownList.style.width = width;
+ }
}
},
diff --git a/src/directives/appendToBody.js b/src/directives/appendToBody.js
new file mode 100644
index 0000000..ec5f6ec
--- /dev/null
+++ b/src/directives/appendToBody.js
@@ -0,0 +1,21 @@
+export default {
+ inserted (el, bindings, {context}) {
+ if (context.appendToBody) {
+ const {height, top, left} = context.$refs.toggle.getBoundingClientRect();
+
+ context.calculatePosition(el, context, {
+ width: context.$refs.toggle.clientWidth + 'px',
+ top: (window.scrollY + top + height) + 'px',
+ left: (window.scrollX + left) + 'px',
+ });
+
+ document.body.appendChild(el);
+ }
+ },
+
+ unbind (el, bindings, vnode) {
+ if (vnode.context.appendToBody && el.parentNode) {
+ el.parentNode.removeChild(el);
+ }
+ },
+}
diff --git a/tests/unit/Dropdown.spec.js b/tests/unit/Dropdown.spec.js
index 6777410..bd39649 100755
--- a/tests/unit/Dropdown.spec.js
+++ b/tests/unit/Dropdown.spec.js
@@ -137,7 +137,7 @@ describe("Toggling Dropdown", () => {
expect(Select.vm.open).toEqual(true);
await Select.vm.$nextTick();
- expect(Select.find('.vs__dropdown-menu').element.style['display']).toEqual('none');
+ expect(Select.contains('.vs__dropdown-menu')).toBeFalsy();
expect(Select.contains('.vs__dropdown-option')).toBeFalsy();
expect(Select.contains('.vs__no-options')).toBeFalsy();
expect(Select.vm.stateClasses['vs--open']).toBeFalsy();