You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
5.5 KiB
179 lines
5.5 KiB
4 years ago
|
import Vue from '../vue';
|
||
|
import { NAME_TRANSPORTER_SINGLE, NAME_TRANSPORTER_TARGET_SINGLE } from '../constants/components';
|
||
|
import identity from './identity';
|
||
|
import { concat } from './array';
|
||
|
import { removeNode, select } from './dom';
|
||
|
import { isBrowser } from './env';
|
||
|
import { isFunction, isString } from './inspect';
|
||
|
import { HTMLElement } from './safe-types';
|
||
|
import normalizeSlotMixin from '../mixins/normalize-slot'; // BTransporterSingle/BTransporterTargetSingle:
|
||
|
//
|
||
|
// Single root node portaling of content, which retains parent/child hierarchy
|
||
|
// Unlike Portal-Vue where portaled content is no longer a descendent of its
|
||
|
// intended parent components
|
||
|
//
|
||
|
// Private components for use by Tooltips, Popovers and Modals
|
||
|
//
|
||
|
// Based on vue-simple-portal
|
||
|
// https://github.com/LinusBorg/vue-simple-portal
|
||
|
// Transporter target used by BTransporterSingle
|
||
|
// Supports only a single root element
|
||
|
// @vue/component
|
||
|
|
||
|
var BTransporterTargetSingle = /*#__PURE__*/Vue.extend({
|
||
|
// As an abstract component, it doesn't appear in the $parent chain of
|
||
|
// components, which means the next parent of any component rendered inside
|
||
|
// of this one will be the parent from which is was portal'd
|
||
|
abstract: true,
|
||
|
name: NAME_TRANSPORTER_TARGET_SINGLE,
|
||
|
props: {
|
||
|
nodes: {
|
||
|
// Even though we only support a single root element,
|
||
|
// VNodes are always passed as an array
|
||
|
type: [Array, Function] // default: undefined
|
||
|
|
||
|
}
|
||
|
},
|
||
|
data: function data(vm) {
|
||
|
return {
|
||
|
updatedNodes: vm.nodes
|
||
|
};
|
||
|
},
|
||
|
destroyed: function destroyed() {
|
||
|
removeNode(this.$el);
|
||
|
},
|
||
|
render: function render(h) {
|
||
|
var nodes = isFunction(this.updatedNodes) ? this.updatedNodes({}) : this.updatedNodes;
|
||
|
nodes = concat(nodes).filter(Boolean);
|
||
|
/* istanbul ignore else */
|
||
|
|
||
|
if (nodes && nodes.length > 0 && !nodes[0].text) {
|
||
|
return nodes[0];
|
||
|
} else {
|
||
|
/* istanbul ignore next */
|
||
|
return h();
|
||
|
}
|
||
|
}
|
||
|
}); // This component has no root element, so only a single VNode is allowed
|
||
|
// @vue/component
|
||
|
|
||
|
export var BTransporterSingle = /*#__PURE__*/Vue.extend({
|
||
|
name: NAME_TRANSPORTER_SINGLE,
|
||
|
mixins: [normalizeSlotMixin],
|
||
|
props: {
|
||
|
disabled: {
|
||
|
type: Boolean,
|
||
|
default: false
|
||
|
},
|
||
|
container: {
|
||
|
// String: CSS selector,
|
||
|
// HTMLElement: Element reference
|
||
|
// Mainly needed for tooltips/popovers inside modals
|
||
|
type: [String, HTMLElement],
|
||
|
default: 'body'
|
||
|
},
|
||
|
tag: {
|
||
|
// This should be set to match the root element type
|
||
|
type: String,
|
||
|
default: 'div'
|
||
|
}
|
||
|
},
|
||
|
watch: {
|
||
|
disabled: {
|
||
|
immediate: true,
|
||
|
handler: function handler(disabled) {
|
||
|
disabled ? this.unmountTarget() : this.$nextTick(this.mountTarget);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
created: function created() {
|
||
|
// Create private non-reactive props
|
||
|
this.$_defaultFn = null;
|
||
|
this.$_target = null;
|
||
|
},
|
||
|
beforeMount: function beforeMount() {
|
||
|
this.mountTarget();
|
||
|
},
|
||
|
updated: function updated() {
|
||
|
// We need to make sure that all children have completed updating
|
||
|
// before rendering in the target
|
||
|
// `vue-simple-portal` has the this in a `$nextTick()`,
|
||
|
// while `portal-vue` doesn't
|
||
|
// Just trying to see if the `$nextTick()` delay is required or not
|
||
|
// Since all slots in Vue 2.6.x are always functions
|
||
|
this.updateTarget();
|
||
|
},
|
||
|
beforeDestroy: function beforeDestroy() {
|
||
|
this.unmountTarget();
|
||
|
this.$_defaultFn = null;
|
||
|
},
|
||
|
methods: {
|
||
|
// Get the element which the target should be appended to
|
||
|
getContainer: function getContainer() {
|
||
|
/* istanbul ignore else */
|
||
|
if (isBrowser) {
|
||
|
var container = this.container;
|
||
|
return isString(container) ? select(container) : container;
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
},
|
||
|
// Mount the target
|
||
|
mountTarget: function mountTarget() {
|
||
|
if (!this.$_target) {
|
||
|
var container = this.getContainer();
|
||
|
|
||
|
if (container) {
|
||
|
var el = document.createElement('div');
|
||
|
container.appendChild(el);
|
||
|
this.$_target = new BTransporterTargetSingle({
|
||
|
el: el,
|
||
|
parent: this,
|
||
|
propsData: {
|
||
|
// Initial nodes to be rendered
|
||
|
nodes: concat(this.normalizeSlot())
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
// Update the content of the target
|
||
|
updateTarget: function updateTarget() {
|
||
|
if (isBrowser && this.$_target) {
|
||
|
var defaultFn = this.$scopedSlots.default;
|
||
|
|
||
|
if (!this.disabled) {
|
||
|
/* istanbul ignore else: only applicable in Vue 2.5.x */
|
||
|
if (defaultFn && this.$_defaultFn !== defaultFn) {
|
||
|
// We only update the target component if the scoped slot
|
||
|
// function is a fresh one. The new slot syntax (since Vue 2.6)
|
||
|
// can cache unchanged slot functions and we want to respect that here
|
||
|
this.$_target.updatedNodes = defaultFn;
|
||
|
} else if (!defaultFn) {
|
||
|
// We also need to be back compatible with non-scoped default slot (i.e. 2.5.x)
|
||
|
this.$_target.updatedNodes = this.$slots.default;
|
||
|
}
|
||
|
} // Update the scoped slot function cache
|
||
|
|
||
|
|
||
|
this.$_defaultFn = defaultFn;
|
||
|
}
|
||
|
},
|
||
|
// Unmount the target
|
||
|
unmountTarget: function unmountTarget() {
|
||
|
this.$_target && this.$_target.$destroy();
|
||
|
this.$_target = null;
|
||
|
}
|
||
|
},
|
||
|
render: function render(h) {
|
||
|
if (this.disabled) {
|
||
|
var nodes = concat(this.normalizeSlot()).filter(identity);
|
||
|
|
||
|
if (nodes.length > 0 && !nodes[0].text) {
|
||
|
return nodes[0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return h();
|
||
|
}
|
||
|
});
|