UNPKG

uikit

Version:

UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.

481 lines (395 loc) • 14.5 kB
import { $, $$, Transition, addClass, append, attr, fragment, getIndex, html, isTag, matches, on, parent, pick, pointerDown, pointerMove, remove, removeClass, toggleClass, trigger, wrapAll, } from 'uikit-util'; import { wrapInPicture } from '../core/img'; import Modal from '../mixin/modal'; import Slideshow from '../mixin/slideshow'; import { keyMap } from '../util/keys'; import Animations from './internal/lightbox-animations'; export default { i18n: { counter: '%s / %s', }, mixins: [Modal, Slideshow], functional: true, props: { counter: Boolean, preload: Number, nav: Boolean, slidenav: Boolean, delayControls: Number, videoAutoplay: Boolean, template: String, }, data: () => ({ counter: false, preload: 1, nav: false, slidenav: true, delayControls: 3000, videoAutoplay: false, items: [], cls: 'uk-open', clsPage: 'uk-lightbox-page', clsFit: 'uk-lightbox-items-fit', clsZoom: 'uk-lightbox-zoom', attrItem: 'uk-lightbox-item', selList: '.uk-lightbox-items', selClose: '.uk-close-large', selNav: '.uk-lightbox-thumbnav, .uk-lightbox-dotnav', selCaption: '.uk-lightbox-caption', selCounter: '.uk-lightbox-counter', pauseOnHover: false, velocity: 2, Animations, template: `<div class="uk-lightbox uk-overflow-hidden"> <div class="uk-lightbox-items"></div> <div class="uk-position-top-right uk-position-small uk-transition-fade" uk-inverse> <button class="uk-lightbox-close uk-close-large" type="button" uk-close></button> </div> <div class="uk-lightbox-slidenav uk-position-center-left uk-position-medium uk-transition-fade" uk-inverse> <a href uk-slidenav-previous uk-lightbox-item="previous"></a> </div> <div class="uk-lightbox-slidenav uk-position-center-right uk-position-medium uk-transition-fade" uk-inverse> <a href uk-slidenav-next uk-lightbox-item="next"></a> </div> <div class="uk-position-center-right uk-position-medium uk-transition-fade" uk-inverse style="max-height: 90vh; overflow: auto;"> <ul class="uk-lightbox-thumbnav uk-lightbox-thumbnav-vertical uk-thumbnav uk-thumbnav-vertical"></ul> <ul class="uk-lightbox-dotnav uk-dotnav uk-dotnav-vertical"></ul> </div> <div class="uk-lightbox-counter uk-text-large uk-position-top-left uk-position-small uk-transition-fade" uk-inverse></div> <div class="uk-lightbox-caption uk-position-bottom uk-text-center uk-transition-slide-bottom uk-transition-opaque"></div> </div>`, }), created() { let $el = $(this.template); if (isTag($el, 'template')) { $el = fragment(html($el)); } const list = $(this.selList, $el); const navType = this.$props.nav; remove($$(this.selNav, $el).filter((el) => !matches(el, `.uk-${navType}`))); for (const [i, item] of this.items.entries()) { append(list, '<div>'); if (navType === 'thumbnav') { wrapAll( toThumbnavItem(item, this.videoAutoplay), append($(this.selNav, $el), `<li uk-lightbox-item="${i}"><a href></a></li>`), ); } } if (!this.slidenav) { remove($$('.uk-lightbox-slidenav', $el)); } if (!this.counter) { remove($(this.selCounter, $el)); } addClass(list, this.clsFit); const close = $('[uk-close]', $el); const closeLabel = this.t('close'); if (close && closeLabel) { close.dataset.i18n = JSON.stringify({ label: closeLabel }); } this.$mount(append(this.container, $el)); }, events: [ { name: 'click', self: true, filter: ({ bgClose }) => bgClose, delegate: ({ selList }) => `${selList} > *`, handler(e) { if (!e.defaultPrevented) { this.hide(); } }, }, { name: 'click', self: true, delegate: ({ clsZoom }) => `.${clsZoom}`, handler(e) { if (!e.defaultPrevented) { toggleClass(this.list, this.clsFit); } }, }, { name: `${pointerMove} ${pointerDown} keydown`, filter: ({ delayControls }) => delayControls, handler() { this.showControls(); }, }, { name: 'shown', self: true, handler() { this.showControls(); }, }, { name: 'hide', self: true, handler() { this.hideControls(); removeClass(this.slides, this.clsActive); Transition.stop(this.slides); }, }, { name: 'hidden', self: true, handler() { this.$destroy(true); }, }, { name: 'keyup', el: () => document, handler({ keyCode }) { if (!this.isToggled() || !this.draggable) { return; } let i = -1; if (keyCode === keyMap.LEFT) { i = 'previous'; } else if (keyCode === keyMap.RIGHT) { i = 'next'; } else if (keyCode === keyMap.HOME) { i = 0; } else if (keyCode === keyMap.END) { i = 'last'; } if (~i) { this.show(i); } }, }, { name: 'beforeitemshow', handler(e) { html($(this.selCaption, this.$el), this.getItem().caption || ''); html( $(this.selCounter, this.$el), this.t('counter', this.index + 1, this.slides.length), ); for (let j = -this.preload; j <= this.preload; j++) { this.loadItem(this.index + j); } if (this.isToggled()) { return; } this.draggable = false; e.preventDefault(); this.toggleElement(this.$el, true, false); this.animation = Animations.scale; removeClass(e.target, this.clsActive); this.stack.splice(1, 0, this.index); }, }, { name: 'itemshown', handler() { this.draggable = this.$props.draggable; }, }, { name: 'itemload', async handler(_, item) { const { source: src, type, attrs = {} } = item; this.setItem(item, '<span uk-spinner uk-inverse></span>'); if (!src) { return; } let matches; const iframeAttrs = { allowfullscreen: '', style: 'max-width: 100%; box-sizing: border-box;', 'uk-responsive': '', 'uk-video': `${Boolean(this.videoAutoplay)}`, }; // Image if (type === 'image' || isImage(src)) { const img = createEl('img'); wrapInPicture(img, item.sources); attr(img, { src, ...pick(item, ['alt', 'srcset', 'sizes']), ...attrs, }); on(img, 'load', () => this.setItem(item, parent(img) || img)); on(img, 'error', () => this.setError(item)); // Video } else if (type === 'video' || isVideo(src)) { const inline = this.videoAutoplay === 'inline'; const video = createEl('video', { src, playsinline: '', controls: inline ? null : '', loop: inline ? '' : null, poster: this.videoAutoplay ? null : item.poster, 'uk-video': inline ? 'automute: true' : Boolean(this.videoAutoplay), ...attrs, }); on(video, 'loadedmetadata', () => this.setItem(item, video)); on(video, 'error', () => this.setError(item)); // Iframe } else if (type === 'iframe' || src.match(/\.(html|php)($|\?)/i)) { this.setItem( item, createEl('iframe', { src, allowfullscreen: '', class: 'uk-lightbox-iframe', ...attrs, }), ); // YouTube } else if ( (matches = src.match( /\/\/(?:.*?youtube(-nocookie)?\..*?(?:[?&]v=|\/shorts\/)|youtu\.be\/)([\w-]{11})[&?]?(.*)?/, )) ) { this.setItem( item, createEl('iframe', { src: `https://www.youtube${matches[1] || ''}.com/embed/${matches[2]}${ matches[3] ? `?${matches[3]}` : '' }`, width: 1920, height: 1080, ...iframeAttrs, ...attrs, }), ); // Vimeo } else if ((matches = src.match(/\/\/.*?vimeo\.[a-z]+\/(\d+)[&?]?(.*)?/))) { try { const { height, width } = await ( await fetch( `https://vimeo.com/api/oembed.json?maxwidth=1920&url=${encodeURI( src, )}`, { credentials: 'omit' }, ) ).json(); this.setItem( item, createEl('iframe', { src: `https://player.vimeo.com/video/${matches[1]}${ matches[2] ? `?${matches[2]}` : '' }`, width, height, ...iframeAttrs, ...attrs, }), ); } catch (e) { this.setError(item); } } }, }, { name: 'itemloaded', handler() { this.$emit('resize'); }, }, ], update: { read() { for (const media of $$(`${this.selList} :not([controls]):is(img,video)`, this.$el)) { toggleClass( media, this.clsZoom, (media.naturalHeight || media.videoHeight) - this.$el.offsetHeight > Math.max( 0, (media.naturalWidth || media.videoWidth) - this.$el.offsetWidth, ), ); } }, events: ['resize'], }, methods: { loadItem(index = this.index) { const item = this.getItem(index); if (!this.getSlide(item).childElementCount) { trigger(this.$el, 'itemload', [item]); } }, getItem(index = this.index) { return this.items[getIndex(index, this.slides)]; }, setItem(item, content) { trigger(this.$el, 'itemloaded', [this, html(this.getSlide(item), content)]); }, getSlide(item) { return this.slides[this.items.indexOf(item)]; }, setError(item) { this.setItem(item, '<span uk-icon="icon: bolt; ratio: 2" uk-inverse></span>'); }, showControls() { clearTimeout(this.controlsTimer); this.controlsTimer = this.delayControls && setTimeout(this.hideControls, this.delayControls); addClass(this.$el, 'uk-active', 'uk-transition-active'); }, hideControls() { removeClass(this.$el, 'uk-active', 'uk-transition-active'); }, }, }; function createEl(tag, attrs) { const el = fragment(`<${tag}>`); attr(el, attrs); return el; } function toThumbnavItem(item, videoAutoplay) { const el = item.poster || (item.thumb && (item.type === 'image' || isImage(item.thumb))) ? createEl('img', { src: item.poster || item.thumb, alt: '' }) : item.thumb && (item.type === 'video' || isVideo(item.thumb)) ? createEl('video', { src: item.thumb, loop: '', playsinline: '', 'uk-video': `autoplay: ${Boolean(videoAutoplay)}; automute: true`, }) : createEl('canvas'); if (item.thumbRatio) { el.style.aspectRatio = item.thumbRatio; } return el; } function isImage(src) { return src?.match(/\.(avif|jpe?g|jfif|a?png|gif|svg|webp)($|\?)/i); } function isVideo(src) { return src?.match(/\.(mp4|webm|ogv)($|\?)/i); }