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

676 lines (572 loc) 22.7 kB
var _watch; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import { extend } from '../../vue'; import { NAME_CAROUSEL } from '../../constants/components'; import { IS_BROWSER, HAS_POINTER_EVENT_SUPPORT, HAS_TOUCH_SUPPORT } from '../../constants/env'; import { EVENT_NAME_PAUSED, EVENT_NAME_SLIDING_END, EVENT_NAME_SLIDING_START, EVENT_NAME_UNPAUSED, EVENT_OPTIONS_NO_CAPTURE } from '../../constants/events'; import { CODE_ENTER, CODE_LEFT, CODE_RIGHT, CODE_SPACE } from '../../constants/key-codes'; import { PROP_TYPE_BOOLEAN, PROP_TYPE_NUMBER, PROP_TYPE_NUMBER_STRING, PROP_TYPE_STRING } from '../../constants/props'; import { addClass, getActiveElement, reflow, removeClass, requestAF, selectAll, setAttr } from '../../utils/dom'; import { eventOn, eventOff, stopEvent } from '../../utils/events'; import { isUndefined } from '../../utils/inspect'; import { mathAbs, mathFloor, mathMax, mathMin } from '../../utils/math'; import { makeModelMixin } from '../../utils/model'; import { toInteger } from '../../utils/number'; import { noop } from '../../utils/noop'; import { sortKeys } from '../../utils/object'; import { observeDom } from '../../utils/observe-dom'; import { makeProp, makePropsConfigurable } from '../../utils/props'; import { idMixin, props as idProps } from '../../mixins/id'; import { normalizeSlotMixin } from '../../mixins/normalize-slot'; // --- Constants --- var _makeModelMixin = makeModelMixin('value', { type: PROP_TYPE_NUMBER, defaultValue: 0 }), modelMixin = _makeModelMixin.mixin, modelProps = _makeModelMixin.props, MODEL_PROP_NAME = _makeModelMixin.prop, MODEL_EVENT_NAME = _makeModelMixin.event; // Slide directional classes var DIRECTION = { next: { dirClass: 'carousel-item-left', overlayClass: 'carousel-item-next' }, prev: { dirClass: 'carousel-item-right', overlayClass: 'carousel-item-prev' } }; // Fallback Transition duration (with a little buffer) in ms var TRANS_DURATION = 600 + 50; // Time for mouse compat events to fire after touch var TOUCH_EVENT_COMPAT_WAIT = 500; // Number of pixels to consider touch move a swipe var SWIPE_THRESHOLD = 40; // PointerEvent pointer types var PointerType = { TOUCH: 'touch', PEN: 'pen' }; // Transition Event names var TransitionEndEvents = { WebkitTransition: 'webkitTransitionEnd', MozTransition: 'transitionend', OTransition: 'otransitionend oTransitionEnd', transition: 'transitionend' }; // --- Helper methods --- // Return the browser specific transitionEnd event name var getTransitionEndEvent = function getTransitionEndEvent(el) { for (var name in TransitionEndEvents) { if (!isUndefined(el.style[name])) { return TransitionEndEvents[name]; } } // Fallback /* istanbul ignore next */ return null; }; // --- Props --- export var props = makePropsConfigurable(sortKeys(_objectSpread(_objectSpread(_objectSpread({}, idProps), modelProps), {}, { background: makeProp(PROP_TYPE_STRING), controls: makeProp(PROP_TYPE_BOOLEAN, false), // Enable cross-fade animation instead of slide animation fade: makeProp(PROP_TYPE_BOOLEAN, false), // Sniffed by carousel-slide imgHeight: makeProp(PROP_TYPE_NUMBER_STRING), // Sniffed by carousel-slide imgWidth: makeProp(PROP_TYPE_NUMBER_STRING), indicators: makeProp(PROP_TYPE_BOOLEAN, false), interval: makeProp(PROP_TYPE_NUMBER, 5000), labelGotoSlide: makeProp(PROP_TYPE_STRING, 'Goto slide'), labelIndicators: makeProp(PROP_TYPE_STRING, 'Select a slide to display'), labelNext: makeProp(PROP_TYPE_STRING, 'Next slide'), labelPrev: makeProp(PROP_TYPE_STRING, 'Previous slide'), // Disable slide/fade animation noAnimation: makeProp(PROP_TYPE_BOOLEAN, false), // Disable pause on hover noHoverPause: makeProp(PROP_TYPE_BOOLEAN, false), // Sniffed by carousel-slide noTouch: makeProp(PROP_TYPE_BOOLEAN, false), // Disable wrapping/looping when start/end is reached noWrap: makeProp(PROP_TYPE_BOOLEAN, false) })), NAME_CAROUSEL); // --- Main component --- // @vue/component export var BCarousel = /*#__PURE__*/extend({ name: NAME_CAROUSEL, mixins: [idMixin, modelMixin, normalizeSlotMixin], provide: function provide() { var _this = this; return { getBvCarousel: function getBvCarousel() { return _this; } }; }, props: props, data: function data() { return { index: this[MODEL_PROP_NAME] || 0, isSliding: false, transitionEndEvent: null, slides: [], direction: null, isPaused: !(toInteger(this.interval, 0) > 0), // Touch event handling values touchStartX: 0, touchDeltaX: 0 }; }, computed: { numSlides: function numSlides() { return this.slides.length; } }, watch: (_watch = {}, _defineProperty(_watch, MODEL_PROP_NAME, function (newValue, oldValue) { if (newValue !== oldValue) { this.setSlide(toInteger(newValue, 0)); } }), _defineProperty(_watch, "interval", function interval(newValue, oldValue) { /* istanbul ignore next */ if (newValue === oldValue) { return; } if (!newValue) { // Pausing slide show this.pause(false); } else { // Restarting or Changing interval this.pause(true); this.start(false); } }), _defineProperty(_watch, "isPaused", function isPaused(newValue, oldValue) { if (newValue !== oldValue) { this.$emit(newValue ? EVENT_NAME_PAUSED : EVENT_NAME_UNPAUSED); } }), _defineProperty(_watch, "index", function index(to, from) { /* istanbul ignore next */ if (to === from || this.isSliding) { return; } this.doSlide(to, from); }), _watch), created: function created() { // Create private non-reactive props this.$_interval = null; this.$_animationTimeout = null; this.$_touchTimeout = null; this.$_observer = null; // Set initial paused state this.isPaused = !(toInteger(this.interval, 0) > 0); }, mounted: function mounted() { // Cache current browser transitionend event name this.transitionEndEvent = getTransitionEndEvent(this.$el) || null; // Get all slides this.updateSlides(); // Observe child changes so we can update slide list this.setObserver(true); }, beforeDestroy: function beforeDestroy() { this.clearInterval(); this.clearAnimationTimeout(); this.clearTouchTimeout(); this.setObserver(false); }, methods: { clearInterval: function (_clearInterval) { function clearInterval() { return _clearInterval.apply(this, arguments); } clearInterval.toString = function () { return _clearInterval.toString(); }; return clearInterval; }(function () { clearInterval(this.$_interval); this.$_interval = null; }), clearAnimationTimeout: function clearAnimationTimeout() { clearTimeout(this.$_animationTimeout); this.$_animationTimeout = null; }, clearTouchTimeout: function clearTouchTimeout() { clearTimeout(this.$_touchTimeout); this.$_touchTimeout = null; }, setObserver: function setObserver() { var on = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; this.$_observer && this.$_observer.disconnect(); this.$_observer = null; if (on) { this.$_observer = observeDom(this.$refs.inner, this.updateSlides.bind(this), { subtree: false, childList: true, attributes: true, attributeFilter: ['id'] }); } }, // Set slide setSlide: function setSlide(slide) { var _this2 = this; var direction = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; // Don't animate when page is not visible /* istanbul ignore if: difficult to test */ if (IS_BROWSER && document.visibilityState && document.hidden) { return; } var noWrap = this.noWrap; var numSlides = this.numSlides; // Make sure we have an integer (you never know!) slide = mathFloor(slide); // Don't do anything if nothing to slide to if (numSlides === 0) { return; } // Don't change slide while transitioning, wait until transition is done if (this.isSliding) { // Schedule slide after sliding complete this.$once(EVENT_NAME_SLIDING_END, function () { // Wrap in `requestAF()` to allow the slide to properly finish to avoid glitching requestAF(function () { return _this2.setSlide(slide, direction); }); }); return; } this.direction = direction; // Set new slide index // Wrap around if necessary (if no-wrap not enabled) this.index = slide >= numSlides ? noWrap ? numSlides - 1 : 0 : slide < 0 ? noWrap ? 0 : numSlides - 1 : slide; // Ensure the v-model is synched up if no-wrap is enabled // and user tried to slide pass either ends if (noWrap && this.index !== slide && this.index !== this[MODEL_PROP_NAME]) { this.$emit(MODEL_EVENT_NAME, this.index); } }, // Previous slide prev: function prev() { this.setSlide(this.index - 1, 'prev'); }, // Next slide next: function next() { this.setSlide(this.index + 1, 'next'); }, // Pause auto rotation pause: function pause(event) { if (!event) { this.isPaused = true; } this.clearInterval(); }, // Start auto rotate slides start: function start(event) { if (!event) { this.isPaused = false; } /* istanbul ignore next: most likely will never happen, but just in case */ this.clearInterval(); // Don't start if no interval, or less than 2 slides if (this.interval && this.numSlides > 1) { this.$_interval = setInterval(this.next, mathMax(1000, this.interval)); } }, // Restart auto rotate slides when focus/hover leaves the carousel /* istanbul ignore next */ restart: function restart() { if (!this.$el.contains(getActiveElement())) { this.start(); } }, doSlide: function doSlide(to, from) { var _this3 = this; var isCycling = Boolean(this.interval); // Determine sliding direction var direction = this.calcDirection(this.direction, from, to); var overlayClass = direction.overlayClass; var dirClass = direction.dirClass; // Determine current and next slides var currentSlide = this.slides[from]; var nextSlide = this.slides[to]; // Don't do anything if there aren't any slides to slide to if (!currentSlide || !nextSlide) { /* istanbul ignore next */ return; } // Start animating this.isSliding = true; if (isCycling) { this.pause(false); } this.$emit(EVENT_NAME_SLIDING_START, to); // Update v-model this.$emit(MODEL_EVENT_NAME, this.index); if (this.noAnimation) { addClass(nextSlide, 'active'); removeClass(currentSlide, 'active'); this.isSliding = false; // Notify ourselves that we're done sliding (slid) this.$nextTick(function () { return _this3.$emit(EVENT_NAME_SLIDING_END, to); }); } else { addClass(nextSlide, overlayClass); // Trigger a reflow of next slide reflow(nextSlide); addClass(currentSlide, dirClass); addClass(nextSlide, dirClass); // Transition End handler var called = false; /* istanbul ignore next: difficult to test */ var onceTransEnd = function onceTransEnd() { if (called) { return; } called = true; /* istanbul ignore if: transition events cant be tested in JSDOM */ if (_this3.transitionEndEvent) { var events = _this3.transitionEndEvent.split(/\s+/); events.forEach(function (event) { return eventOff(nextSlide, event, onceTransEnd, EVENT_OPTIONS_NO_CAPTURE); }); } _this3.clearAnimationTimeout(); removeClass(nextSlide, dirClass); removeClass(nextSlide, overlayClass); addClass(nextSlide, 'active'); removeClass(currentSlide, 'active'); removeClass(currentSlide, dirClass); removeClass(currentSlide, overlayClass); setAttr(currentSlide, 'aria-current', 'false'); setAttr(nextSlide, 'aria-current', 'true'); setAttr(currentSlide, 'aria-hidden', 'true'); setAttr(nextSlide, 'aria-hidden', 'false'); _this3.isSliding = false; _this3.direction = null; // Notify ourselves that we're done sliding (slid) _this3.$nextTick(function () { return _this3.$emit(EVENT_NAME_SLIDING_END, to); }); }; // Set up transitionend handler /* istanbul ignore if: transition events cant be tested in JSDOM */ if (this.transitionEndEvent) { var events = this.transitionEndEvent.split(/\s+/); events.forEach(function (event) { return eventOn(nextSlide, event, onceTransEnd, EVENT_OPTIONS_NO_CAPTURE); }); } // Fallback to setTimeout() this.$_animationTimeout = setTimeout(onceTransEnd, TRANS_DURATION); } if (isCycling) { this.start(false); } }, // Update slide list updateSlides: function updateSlides() { this.pause(true); // Get all slides as DOM elements this.slides = selectAll('.carousel-item', this.$refs.inner); var numSlides = this.slides.length; // Keep slide number in range var index = mathMax(0, mathMin(mathFloor(this.index), numSlides - 1)); this.slides.forEach(function (slide, idx) { var n = idx + 1; if (idx === index) { addClass(slide, 'active'); setAttr(slide, 'aria-current', 'true'); } else { removeClass(slide, 'active'); setAttr(slide, 'aria-current', 'false'); } setAttr(slide, 'aria-posinset', String(n)); setAttr(slide, 'aria-setsize', String(numSlides)); }); // Set slide as active this.setSlide(index); this.start(this.isPaused); }, calcDirection: function calcDirection() { var direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var curIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var nextIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; if (!direction) { return nextIndex > curIndex ? DIRECTION.next : DIRECTION.prev; } return DIRECTION[direction]; }, handleClick: function handleClick(event, fn) { var keyCode = event.keyCode; if (event.type === 'click' || keyCode === CODE_SPACE || keyCode === CODE_ENTER) { stopEvent(event); fn(); } }, /* istanbul ignore next: JSDOM doesn't support touch events */ handleSwipe: function handleSwipe() { var absDeltaX = mathAbs(this.touchDeltaX); if (absDeltaX <= SWIPE_THRESHOLD) { return; } var direction = absDeltaX / this.touchDeltaX; // Reset touch delta X // https://github.com/twbs/bootstrap/pull/28558 this.touchDeltaX = 0; if (direction > 0) { // Swipe left this.prev(); } else if (direction < 0) { // Swipe right this.next(); } }, /* istanbul ignore next: JSDOM doesn't support touch events */ touchStart: function touchStart(event) { if (HAS_POINTER_EVENT_SUPPORT && PointerType[event.pointerType.toUpperCase()]) { this.touchStartX = event.clientX; } else if (!HAS_POINTER_EVENT_SUPPORT) { this.touchStartX = event.touches[0].clientX; } }, /* istanbul ignore next: JSDOM doesn't support touch events */ touchMove: function touchMove(event) { // Ensure swiping with one touch and not pinching if (event.touches && event.touches.length > 1) { this.touchDeltaX = 0; } else { this.touchDeltaX = event.touches[0].clientX - this.touchStartX; } }, /* istanbul ignore next: JSDOM doesn't support touch events */ touchEnd: function touchEnd(event) { if (HAS_POINTER_EVENT_SUPPORT && PointerType[event.pointerType.toUpperCase()]) { this.touchDeltaX = event.clientX - this.touchStartX; } this.handleSwipe(); // If it's a touch-enabled device, mouseenter/leave are fired as // part of the mouse compatibility events on first tap - the carousel // would stop cycling until user tapped out of it; // here, we listen for touchend, explicitly pause the carousel // (as if it's the second time we tap on it, mouseenter compat event // is NOT fired) and after a timeout (to allow for mouse compatibility // events to fire) we explicitly restart cycling this.pause(false); this.clearTouchTimeout(); this.$_touchTimeout = setTimeout(this.start, TOUCH_EVENT_COMPAT_WAIT + mathMax(1000, this.interval)); } }, render: function render(h) { var _this4 = this; var indicators = this.indicators, background = this.background, noAnimation = this.noAnimation, noHoverPause = this.noHoverPause, noTouch = this.noTouch, index = this.index, isSliding = this.isSliding, pause = this.pause, restart = this.restart, touchStart = this.touchStart, touchEnd = this.touchEnd; var idInner = this.safeId('__BV_inner_'); // Wrapper for slides var $inner = h('div', { staticClass: 'carousel-inner', attrs: { id: idInner, role: 'list' }, ref: 'inner' }, [this.normalizeSlot()]); // Prev and next controls var $controls = h(); if (this.controls) { var makeControl = function makeControl(direction, label, handler) { var handlerWrapper = function handlerWrapper(event) { /* istanbul ignore next */ if (!isSliding) { _this4.handleClick(event, handler); } else { stopEvent(event, { propagation: false }); } }; return h('a', { staticClass: "carousel-control-".concat(direction), attrs: { href: '#', role: 'button', 'aria-controls': idInner, 'aria-disabled': isSliding ? 'true' : null }, on: { click: handlerWrapper, keydown: handlerWrapper } }, [h('span', { staticClass: "carousel-control-".concat(direction, "-icon"), attrs: { 'aria-hidden': 'true' } }), h('span', { class: 'sr-only' }, [label])]); }; $controls = [makeControl('prev', this.labelPrev, this.prev), makeControl('next', this.labelNext, this.next)]; } // Indicators var $indicators = h('ol', { staticClass: 'carousel-indicators', directives: [{ name: 'show', value: indicators }], attrs: { id: this.safeId('__BV_indicators_'), 'aria-hidden': indicators ? 'false' : 'true', 'aria-label': this.labelIndicators, 'aria-owns': idInner } }, this.slides.map(function (slide, i) { var handler = function handler(event) { _this4.handleClick(event, function () { _this4.setSlide(i); }); }; return h('li', { class: { active: i === index }, attrs: { role: 'button', id: _this4.safeId("__BV_indicator_".concat(i + 1, "_")), tabindex: indicators ? '0' : '-1', 'aria-current': i === index ? 'true' : 'false', 'aria-label': "".concat(_this4.labelGotoSlide, " ").concat(i + 1), 'aria-describedby': slide.id || null, 'aria-controls': idInner }, on: { click: handler, keydown: handler }, key: "slide_".concat(i) }); })); var on = { mouseenter: noHoverPause ? noop : pause, mouseleave: noHoverPause ? noop : restart, focusin: pause, focusout: restart, keydown: function keydown(event) { /* istanbul ignore next */ if (/input|textarea/i.test(event.target.tagName)) { return; } var keyCode = event.keyCode; if (keyCode === CODE_LEFT || keyCode === CODE_RIGHT) { stopEvent(event); _this4[keyCode === CODE_LEFT ? 'prev' : 'next'](); } } }; // Touch support event handlers for environment if (HAS_TOUCH_SUPPORT && !noTouch) { // Attach appropriate listeners (prepend event name with '&' for passive mode) /* istanbul ignore next: JSDOM doesn't support touch events */ if (HAS_POINTER_EVENT_SUPPORT) { on['&pointerdown'] = touchStart; on['&pointerup'] = touchEnd; } else { on['&touchstart'] = touchStart; on['&touchmove'] = this.touchMove; on['&touchend'] = touchEnd; } } // Return the carousel return h('div', { staticClass: 'carousel', class: { slide: !noAnimation, 'carousel-fade': !noAnimation && this.fade, 'pointer-event': HAS_TOUCH_SUPPORT && HAS_POINTER_EVENT_SUPPORT && !noTouch }, style: { background: background }, attrs: { role: 'region', id: this.safeId(), 'aria-busy': isSliding ? 'true' : 'false' }, on: on }, [$inner, $controls, $indicators]); } });