summaryrefslogtreecommitdiffstats
path: root/src/mixins/vueAtReparenter.js
diff options
context:
space:
mode:
authorDaniel Calviño Sánchez <danxuliu@gmail.com>2020-01-08 02:02:15 +0100
committerJoas Schilling <coding@schilljs.com>2020-01-09 20:19:17 +0100
commit895f21640a2dba391a345124b0b8b20484f46499 (patch)
treec33d8109b117c8f572c96bf75fec8f9f8044af59 /src/mixins/vueAtReparenter.js
parent80ec5435de65b72c493eff21e095602cc91aead4 (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.js127
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
+ },
+
+}