diff --git a/docs/.vuepress/components/PositionedWithPopper.vue b/docs/.vuepress/components/PositionedWithPopper.vue index d7580bf..0c69a23 100644 --- a/docs/.vuepress/components/PositionedWithPopper.vue +++ b/docs/.vuepress/components/PositionedWithPopper.vue @@ -22,7 +22,7 @@ import { createPopper } from '@popperjs/core'; export default { data: () => ({countries, placement: 'top'}), methods: { - withPopper (dropdownList, component, {width},) { + withPopper (dropdownList, component, {width}) { /** * We need to explicitly define the dropdown width since * it is usually inherited from the parent with CSS. @@ -39,7 +39,7 @@ export default { * wrapper so that we can set some styles for when the dropdown is placed * above. */ - createPopper(component.$refs.toggle, dropdownList, { + const popper = createPopper(component.$refs.toggle, dropdownList, { placement: this.placement, modifiers: [ { @@ -56,6 +56,12 @@ export default { }, }] }); + + /** + * To prevent memory leaks Popper needs to be destroyed. + * If you return function, it will be called just before dropdown is removed from DOM. + */ + return () => popper.destroy(); } } }; diff --git a/docs/api/props.md b/docs/api/props.md index d4d200d..0f157f9 100644 --- a/docs/api/props.md +++ b/docs/api/props.md @@ -128,6 +128,9 @@ transition: { When `appendToBody` is true, this function is responsible for positioning the drop down list. +If a function is returned from `calculatePosition`, it will be called when the drop down list +is removed from the DOM. This allows for any garbage collection you may need to do. + See [Dropdown Position](../guide/positioning.md) for more details. ```js @@ -139,6 +142,7 @@ calculatePosition: { * @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 + * @return {function|void} */ default(dropdownList, component, {width, top, left}) { dropdownList.style.top = top; diff --git a/src/components/Select.vue b/src/components/Select.vue index 89235cc..3527b77 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -537,9 +537,13 @@ }, /** - * When `appendToBody` is true, this function - * is responsible for positioning the drop - * down list. + * When `appendToBody` is true, this function is responsible for + * positioning the drop down list. + * + * If a function is returned from `calculatePosition`, it will + * be called when the drop down list is removed from the DOM. + * This allows for any garbage collection you may need to do. + * * @since v3.7.0 * @see http://vue-select.org/guide/positioning.html */ @@ -551,6 +555,7 @@ * @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 + * @return {function|void} */ default(dropdownList, component, {width, top, left}) { dropdownList.style.top = top; diff --git a/src/directives/appendToBody.js b/src/directives/appendToBody.js index ec5f6ec..9296c9d 100644 --- a/src/directives/appendToBody.js +++ b/src/directives/appendToBody.js @@ -3,7 +3,7 @@ export default { if (context.appendToBody) { const {height, top, left} = context.$refs.toggle.getBoundingClientRect(); - context.calculatePosition(el, context, { + el.unbindPosition = context.calculatePosition(el, context, { width: context.$refs.toggle.clientWidth + 'px', top: (window.scrollY + top + height) + 'px', left: (window.scrollX + left) + 'px', @@ -13,9 +13,14 @@ export default { } }, - unbind (el, bindings, vnode) { - if (vnode.context.appendToBody && el.parentNode) { - el.parentNode.removeChild(el); + unbind (el, bindings, {context}) { + if (context.appendToBody) { + if (el.unbindPosition && typeof el.unbindPosition === 'function') { + el.unbindPosition(); + } + if (el.parentNode) { + el.parentNode.removeChild(el); + } } }, }