diff options
author | Daniel Calviño Sánchez <danxuliu@gmail.com> | 2020-01-08 02:02:15 +0100 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2020-01-09 20:19:17 +0100 |
commit | 895f21640a2dba391a345124b0b8b20484f46499 (patch) | |
tree | c33d8109b117c8f572c96bf75fec8f9f8044af59 /src/mixins/vueAtReparenter.js | |
parent | 80ec5435de65b72c493eff21e095602cc91aead4 (diff) |
Add helper to reparent the panel of the vue-at component
The panel of the vue-at component is a child of the root element of the
component. In some cases a different parent may be needed, but the
component does not provide any way to change that, so a helper mixin
that works around that limitation was added.
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
Diffstat (limited to 'src/mixins/vueAtReparenter.js')
-rw-r--r-- | src/mixins/vueAtReparenter.js | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/src/mixins/vueAtReparenter.js b/src/mixins/vueAtReparenter.js new file mode 100644 index 000000000..3c36d0984 --- /dev/null +++ b/src/mixins/vueAtReparenter.js @@ -0,0 +1,127 @@ +/** + * + * @copyright Copyright (c) 2020, Daniel Calviño Sánchez <danxuliu@gmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import Vue from 'vue' + +/** + * Mixin to reparent the panel of the vue-at component to a specific element. + * + * By default the panel of the vue-at component is a child of the root element + * of the component. In some cases this may not be desirable (for example, if + * a parent element uses "overflow: hidden" and causes the panel to be + * partially hidden), so this mixin reparents the panel to a specific element + * when it is shown. + * + * Components using this mixin require a reference called "at" to the vue-at + * component. The desired parent element can be specified using the + * "atWhoPanelParentSelector" property. + */ +export default { + + data: function() { + return { + /** + * The selector for the HTML element to reparent the vue-at panel to. + */ + atWhoPanelParentSelector: 'body', + at: null, + atWhoPanelElement: null, + originalWrapElement: null, + } + }, + + computed: { + /** + * Returns the "atwho" property of the vue-at component. + * + * The "atwho" property is an object when the panel is open and null + * when the panel is closed. + * + * @returns {Object} the "atwho" property of the vue-at component. + */ + atwho() { + if (!this.at) { + return null + } + + return this.at.atwho + }, + }, + + watch: { + /** + * Reparents the panel of the vue-at component when shown. + * + * Besides reparenting the panel its position needs to be adjusted to + * the new parent. The panel is initially a child of the "wrap" element + * of vue-at and vue-at calculates the position of the panel based on + * that element. Fortunately the reference to that element is not used + * for anything else, so it can be modified while the panel is open to + * point to the new parent. + * + * @param {Object} atwho current value of atwho + * @param {Object} atwhoOld previous value of atwho + */ + atwho(atwho, atwhoOld) { + // Only check whether the object existed or not; its properties are + // not relevant. + if ((atwho && atwhoOld) || (!atwho && !atwhoOld)) { + return + } + + if (atwho) { + // Panel will be opened in next tick; defer moving it to the + // proper parent until that happens + Vue.nextTick(function() { + this.atWhoPanelElement = this.at.$refs.wrap.querySelector('.atwho-panel') + + this.originalWrapElement = this.at.$refs.wrap + this.at.$refs.wrap = window.document.querySelector(this.atWhoPanelParentSelector) + + const atWhoPanelParentSelector = window.document.querySelector(this.atWhoPanelParentSelector) + atWhoPanelParentSelector.appendChild(this.atWhoPanelElement) + + // The position of the panel will be automatically adjusted + // due to the reactivity, but that will happen in next tick. + // To prevent a flicker due to the change of the panel + // position the style is explicitly adjusted now. + const { top, left } = this.at._computedWatchers.style.get() + this.atWhoPanelElement.style.top = top + this.atWhoPanelElement.style.left = left + }.bind(this)) + } else { + this.at.$refs.wrap = this.originalWrapElement + this.originalWrapElement = null + + // Panel will be closed in next tick; move it back to the + // expected parent before that happens. + this.at.$refs.wrap.appendChild(this.atWhoPanelElement) + } + }, + }, + + mounted() { + // $refs is not reactive and its contents are set after the initial + // render. + this.at = this.$refs.at + }, + +} |