123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- import { withDirectives as _withDirectives, mergeProps as _mergeProps, resolveDirective as _resolveDirective, Fragment as _Fragment, createVNode as _createVNode } from "vue";
- // Styles
- import "./VImg.css";
- // Components
- import { makeVResponsiveProps, VResponsive } from "../VResponsive/VResponsive.mjs"; // Composables
- import { useBackgroundColor } from "../../composables/color.mjs";
- import { makeComponentProps } from "../../composables/component.mjs";
- import { makeRoundedProps, useRounded } from "../../composables/rounded.mjs";
- import { makeTransitionProps, MaybeTransition } from "../../composables/transition.mjs"; // Directives
- import intersect from "../../directives/intersect/index.mjs"; // Utilities
- import { computed, nextTick, onBeforeMount, onBeforeUnmount, ref, shallowRef, toRef, vShow, watch, withDirectives } from 'vue';
- import { convertToUnit, genericComponent, getCurrentInstance, propsFactory, SUPPORTS_INTERSECTION, useRender } from "../../util/index.mjs"; // Types
- // not intended for public use, this is passed in by vuetify-loader
- export const makeVImgProps = propsFactory({
- absolute: Boolean,
- alt: String,
- cover: Boolean,
- color: String,
- draggable: {
- type: [Boolean, String],
- default: undefined
- },
- eager: Boolean,
- gradient: String,
- lazySrc: String,
- options: {
- type: Object,
- // For more information on types, navigate to:
- // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
- default: () => ({
- root: undefined,
- rootMargin: undefined,
- threshold: undefined
- })
- },
- sizes: String,
- src: {
- type: [String, Object],
- default: ''
- },
- crossorigin: String,
- referrerpolicy: String,
- srcset: String,
- position: String,
- ...makeVResponsiveProps(),
- ...makeComponentProps(),
- ...makeRoundedProps(),
- ...makeTransitionProps()
- }, 'VImg');
- export const VImg = genericComponent()({
- name: 'VImg',
- directives: {
- intersect
- },
- props: makeVImgProps(),
- emits: {
- loadstart: value => true,
- load: value => true,
- error: value => true
- },
- setup(props, _ref) {
- let {
- emit,
- slots
- } = _ref;
- const {
- backgroundColorClasses,
- backgroundColorStyles
- } = useBackgroundColor(toRef(props, 'color'));
- const {
- roundedClasses
- } = useRounded(props);
- const vm = getCurrentInstance('VImg');
- const currentSrc = shallowRef(''); // Set from srcset
- const image = ref();
- const state = shallowRef(props.eager ? 'loading' : 'idle');
- const naturalWidth = shallowRef();
- const naturalHeight = shallowRef();
- const normalisedSrc = computed(() => {
- return props.src && typeof props.src === 'object' ? {
- src: props.src.src,
- srcset: props.srcset || props.src.srcset,
- lazySrc: props.lazySrc || props.src.lazySrc,
- aspect: Number(props.aspectRatio || props.src.aspect || 0)
- } : {
- src: props.src,
- srcset: props.srcset,
- lazySrc: props.lazySrc,
- aspect: Number(props.aspectRatio || 0)
- };
- });
- const aspectRatio = computed(() => {
- return normalisedSrc.value.aspect || naturalWidth.value / naturalHeight.value || 0;
- });
- watch(() => props.src, () => {
- init(state.value !== 'idle');
- });
- watch(aspectRatio, (val, oldVal) => {
- if (!val && oldVal && image.value) {
- pollForSize(image.value);
- }
- });
- // TODO: getSrc when window width changes
- onBeforeMount(() => init());
- function init(isIntersecting) {
- if (props.eager && isIntersecting) return;
- if (SUPPORTS_INTERSECTION && !isIntersecting && !props.eager) return;
- state.value = 'loading';
- if (normalisedSrc.value.lazySrc) {
- const lazyImg = new Image();
- lazyImg.src = normalisedSrc.value.lazySrc;
- pollForSize(lazyImg, null);
- }
- if (!normalisedSrc.value.src) return;
- nextTick(() => {
- emit('loadstart', image.value?.currentSrc || normalisedSrc.value.src);
- setTimeout(() => {
- if (vm.isUnmounted) return;
- if (image.value?.complete) {
- if (!image.value.naturalWidth) {
- onError();
- }
- if (state.value === 'error') return;
- if (!aspectRatio.value) pollForSize(image.value, null);
- if (state.value === 'loading') onLoad();
- } else {
- if (!aspectRatio.value) pollForSize(image.value);
- getSrc();
- }
- });
- });
- }
- function onLoad() {
- if (vm.isUnmounted) return;
- getSrc();
- pollForSize(image.value);
- state.value = 'loaded';
- emit('load', image.value?.currentSrc || normalisedSrc.value.src);
- }
- function onError() {
- if (vm.isUnmounted) return;
- state.value = 'error';
- emit('error', image.value?.currentSrc || normalisedSrc.value.src);
- }
- function getSrc() {
- const img = image.value;
- if (img) currentSrc.value = img.currentSrc || img.src;
- }
- let timer = -1;
- onBeforeUnmount(() => {
- clearTimeout(timer);
- });
- function pollForSize(img) {
- let timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
- const poll = () => {
- clearTimeout(timer);
- if (vm.isUnmounted) return;
- const {
- naturalHeight: imgHeight,
- naturalWidth: imgWidth
- } = img;
- if (imgHeight || imgWidth) {
- naturalWidth.value = imgWidth;
- naturalHeight.value = imgHeight;
- } else if (!img.complete && state.value === 'loading' && timeout != null) {
- timer = window.setTimeout(poll, timeout);
- } else if (img.currentSrc.endsWith('.svg') || img.currentSrc.startsWith('data:image/svg+xml')) {
- naturalWidth.value = 1;
- naturalHeight.value = 1;
- }
- };
- poll();
- }
- const containClasses = computed(() => ({
- 'v-img__img--cover': props.cover,
- 'v-img__img--contain': !props.cover
- }));
- const __image = () => {
- if (!normalisedSrc.value.src || state.value === 'idle') return null;
- const img = _createVNode("img", {
- "class": ['v-img__img', containClasses.value],
- "style": {
- objectPosition: props.position
- },
- "src": normalisedSrc.value.src,
- "srcset": normalisedSrc.value.srcset,
- "alt": props.alt,
- "crossorigin": props.crossorigin,
- "referrerpolicy": props.referrerpolicy,
- "draggable": props.draggable,
- "sizes": props.sizes,
- "ref": image,
- "onLoad": onLoad,
- "onError": onError
- }, null);
- const sources = slots.sources?.();
- return _createVNode(MaybeTransition, {
- "transition": props.transition,
- "appear": true
- }, {
- default: () => [withDirectives(sources ? _createVNode("picture", {
- "class": "v-img__picture"
- }, [sources, img]) : img, [[vShow, state.value === 'loaded']])]
- });
- };
- const __preloadImage = () => _createVNode(MaybeTransition, {
- "transition": props.transition
- }, {
- default: () => [normalisedSrc.value.lazySrc && state.value !== 'loaded' && _createVNode("img", {
- "class": ['v-img__img', 'v-img__img--preload', containClasses.value],
- "style": {
- objectPosition: props.position
- },
- "src": normalisedSrc.value.lazySrc,
- "alt": props.alt,
- "crossorigin": props.crossorigin,
- "referrerpolicy": props.referrerpolicy,
- "draggable": props.draggable
- }, null)]
- });
- const __placeholder = () => {
- if (!slots.placeholder) return null;
- return _createVNode(MaybeTransition, {
- "transition": props.transition,
- "appear": true
- }, {
- default: () => [(state.value === 'loading' || state.value === 'error' && !slots.error) && _createVNode("div", {
- "class": "v-img__placeholder"
- }, [slots.placeholder()])]
- });
- };
- const __error = () => {
- if (!slots.error) return null;
- return _createVNode(MaybeTransition, {
- "transition": props.transition,
- "appear": true
- }, {
- default: () => [state.value === 'error' && _createVNode("div", {
- "class": "v-img__error"
- }, [slots.error()])]
- });
- };
- const __gradient = () => {
- if (!props.gradient) return null;
- return _createVNode("div", {
- "class": "v-img__gradient",
- "style": {
- backgroundImage: `linear-gradient(${props.gradient})`
- }
- }, null);
- };
- const isBooted = shallowRef(false);
- {
- const stop = watch(aspectRatio, val => {
- if (val) {
- // Doesn't work with nextTick, idk why
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- isBooted.value = true;
- });
- });
- stop();
- }
- });
- }
- useRender(() => {
- const responsiveProps = VResponsive.filterProps(props);
- return _withDirectives(_createVNode(VResponsive, _mergeProps({
- "class": ['v-img', {
- 'v-img--absolute': props.absolute,
- 'v-img--booting': !isBooted.value
- }, backgroundColorClasses.value, roundedClasses.value, props.class],
- "style": [{
- width: convertToUnit(props.width === 'auto' ? naturalWidth.value : props.width)
- }, backgroundColorStyles.value, props.style]
- }, responsiveProps, {
- "aspectRatio": aspectRatio.value,
- "aria-label": props.alt,
- "role": props.alt ? 'img' : undefined
- }), {
- additional: () => _createVNode(_Fragment, null, [_createVNode(__image, null, null), _createVNode(__preloadImage, null, null), _createVNode(__gradient, null, null), _createVNode(__placeholder, null, null), _createVNode(__error, null, null)]),
- default: slots.default
- }), [[_resolveDirective("intersect"), {
- handler: init,
- options: props.options
- }, null, {
- once: true
- }]]);
- });
- return {
- currentSrc,
- image,
- state,
- naturalWidth,
- naturalHeight
- };
- }
- });
- //# sourceMappingURL=VImg.mjs.map
|