UNPKG

bootstrap-vue

Version:

BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-AR

177 lines (173 loc) 5.28 kB
import Vue from './vue' 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 const 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: 'BTransporterTargetSingle', props: { nodes: { // Even though we only support a single root element, // VNodes are always passed as an array type: [Array, Function] // default: undefined } }, data: vm => { return { updatedNodes: vm.nodes } }, destroyed() { removeNode(this.$el) }, render(h) { let 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 const BTransporterSingle = /*#__PURE__*/ Vue.extend({ name: 'BTransporterSingle', 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(disabled) { disabled ? this.unmountTarget() : this.$nextTick(this.mountTarget) } } }, created() { this._bv_defaultFn = null this._bv_target = null }, beforeMount() { this.mountTarget() }, 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() { this.unmountTarget() this._bv_defaultFn = null }, methods: { // Get the element which the target should be appended to getContainer() { /* istanbul ignore else */ if (isBrowser) { const container = this.container return isString(container) ? select(container) : container } else { return null } }, // Mount the target mountTarget() { if (!this._bv_target) { const container = this.getContainer() if (container) { const el = document.createElement('div') container.appendChild(el) this._bv_target = new BTransporterTargetSingle({ el, parent: this, propsData: { // Initial nodes to be rendered nodes: concat(this.normalizeSlot('default')) } }) } } }, // Update the content of the target updateTarget() { if (isBrowser && this._bv_target) { const defaultFn = this.$scopedSlots.default if (!this.disabled) { /* istanbul ignore else: only applicable in Vue 2.5.x */ if (defaultFn && this._bv_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._bv_target.updatedNodes = defaultFn } else if (!defaultFn) { // We also need to be back compatible with non-scoped default slot (i.e. 2.5.x) this._bv_target.updatedNodes = this.$slots.default } } // Update the scoped slot function cache this._bv_defaultFn = defaultFn } }, // Unmount the target unmountTarget() { if (this._bv_target) { this._bv_target.$destroy() this._bv_target = null } } }, render(h) { if (this.disabled) { const nodes = concat(this.normalizeSlot('default')).filter(identity) if (nodes.length > 0 && !nodes[0].text) { return nodes[0] } } return h() } })