UNPKG

bootstrap-vue

Version:

With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens

203 lines (191 loc) 6.1 kB
import { Vue, extend, isVue3 } from '../../vue' import { NAME_TRANSPORTER, NAME_TRANSPORTER_TARGET } from '../../constants/components' import { IS_BROWSER } from '../../constants/env' import { PROP_TYPE_ARRAY_FUNCTION, PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../../constants/props' import { HTMLElement } from '../../constants/safe-types' import { concat } from '../../utils/array' import { removeNode, select } from '../../utils/dom' import { identity } from '../../utils/identity' import { isFunction, isString } from '../../utils/inspect' import { normalizeSlotMixin } from '../../mixins/normalize-slot' import { makeProp } from '../../utils/props' import { createNewChildComponent } from '../../utils/create-new-child-component' // --- Helper components --- // BVTransporter/BVTransporterTarget: // // 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 BVTransporter // Supports only a single root element // @vue/component const BVTransporterTarget = /*#__PURE__*/ 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, props: { // Even though we only support a single root element, // VNodes are always passed as an array nodes: makeProp(PROP_TYPE_ARRAY_FUNCTION) }, data: vm => { return { updatedNodes: vm.nodes } }, destroyed() { removeNode(this.$el) }, render(h) { const { updatedNodes } = this let $nodes = isFunction(updatedNodes) ? updatedNodes({}) : updatedNodes $nodes = concat($nodes).filter(identity) if ($nodes && $nodes.length > 0 && !$nodes[0].text) { return $nodes[0] } /* istanbul ignore next */ return h() } }) // --- Props --- export const props = { // String: CSS selector, // HTMLElement: Element reference // Mainly needed for tooltips/popovers inside modals container: makeProp([HTMLElement, PROP_TYPE_STRING], 'body'), disabled: makeProp(PROP_TYPE_BOOLEAN, false), // This should be set to match the root element type tag: makeProp(PROP_TYPE_STRING, 'div') } // --- Main component --- // @vue/component const BVTransporterVue2 = /*#__PURE__*/ extend({ name: NAME_TRANSPORTER, mixins: [normalizeSlotMixin], props, watch: { disabled: { immediate: true, handler(disabled) { disabled ? this.unmountTarget() : this.$nextTick(this.mountTarget) } } }, created() { // Create private non-reactive props this.$_defaultFn = null this.$_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.$_defaultFn = null }, methods: { // Get the element which the target should be appended to getContainer() { /* istanbul ignore else */ if (IS_BROWSER) { const { container } = this return isString(container) ? select(container) : container } else { return null } }, // Mount the target mountTarget() { if (!this.$_target) { const $container = this.getContainer() if ($container) { const $el = document.createElement('div') $container.appendChild($el) this.$_target = createNewChildComponent(this, BVTransporterTarget, { el: $el, propsData: { // Initial nodes to be rendered nodes: concat(this.normalizeSlot()) } }) } } }, // Update the content of the target updateTarget() { if (IS_BROWSER && this.$_target) { const 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() { this.$_target && this.$_target.$destroy() this.$_target = null } }, render(h) { // This component has no root element, so only a single VNode is allowed if (this.disabled) { const $nodes = concat(this.normalizeSlot()).filter(identity) if ($nodes.length > 0 && !$nodes[0].text) { return $nodes[0] } } return h() } }) const BVTransporterVue3 = /*#__PURE__*/ extend({ name: NAME_TRANSPORTER, mixins: [normalizeSlotMixin], props, render(h) { if (this.disabled) { const $nodes = concat(this.normalizeSlot()).filter(identity) if ($nodes.length > 0) { return $nodes[0] } } return h( Vue.Teleport, { to: this.container }, this.normalizeSlot() ) } }) export const BVTransporter = isVue3 ? BVTransporterVue3 : BVTransporterVue2