259 lines
8.7 KiB
JavaScript
259 lines
8.7 KiB
JavaScript
import { createVNode as _createVNode, createElementVNode as _createElementVNode, normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, withDirectives as _withDirectives } from "vue";
|
|
// Styles
|
|
import "./VWindow.css";
|
|
|
|
// Components
|
|
import { VBtn } from "../VBtn/index.js"; // Composables
|
|
import { makeComponentProps } from "../../composables/component.js";
|
|
import { useGroup } from "../../composables/group.js";
|
|
import { useLocale, useRtl } from "../../composables/locale.js";
|
|
import { makeTagProps } from "../../composables/tag.js";
|
|
import { makeThemeProps, provideTheme } from "../../composables/theme.js"; // Directives
|
|
import vTouch from "../../directives/touch/index.js"; // Utilities
|
|
import { computed, nextTick, provide, ref, shallowRef, toRef, watch } from 'vue';
|
|
import { convertToUnit, genericComponent, IN_BROWSER, PREFERS_REDUCED_MOTION, propsFactory, useRender } from "../../util/index.js";
|
|
import { getScrollParent } from "../../util/getScrollParent.js"; // Types
|
|
export const VWindowSymbol = Symbol.for('vuetify:v-window');
|
|
export const VWindowGroupSymbol = Symbol.for('vuetify:v-window-group');
|
|
export const makeVWindowProps = propsFactory({
|
|
continuous: Boolean,
|
|
nextIcon: {
|
|
type: [Boolean, String, Function, Object],
|
|
default: '$next'
|
|
},
|
|
prevIcon: {
|
|
type: [Boolean, String, Function, Object],
|
|
default: '$prev'
|
|
},
|
|
reverse: Boolean,
|
|
showArrows: {
|
|
type: [Boolean, String],
|
|
validator: v => typeof v === 'boolean' || v === 'hover'
|
|
},
|
|
verticalArrows: [Boolean, String],
|
|
touch: {
|
|
type: [Object, Boolean],
|
|
default: undefined
|
|
},
|
|
direction: {
|
|
type: String,
|
|
default: 'horizontal'
|
|
},
|
|
modelValue: null,
|
|
disabled: Boolean,
|
|
selectedClass: {
|
|
type: String,
|
|
default: 'v-window-item--active'
|
|
},
|
|
// TODO: mandatory should probably not be exposed but do this for now
|
|
mandatory: {
|
|
type: [Boolean, String],
|
|
default: 'force'
|
|
},
|
|
crossfade: Boolean,
|
|
transitionDuration: Number,
|
|
...makeComponentProps(),
|
|
...makeTagProps(),
|
|
...makeThemeProps()
|
|
}, 'VWindow');
|
|
export const VWindow = genericComponent()({
|
|
name: 'VWindow',
|
|
directives: {
|
|
vTouch
|
|
},
|
|
props: makeVWindowProps(),
|
|
emits: {
|
|
'update:modelValue': value => true
|
|
},
|
|
setup(props, {
|
|
slots
|
|
}) {
|
|
const {
|
|
themeClasses
|
|
} = provideTheme(props);
|
|
const {
|
|
isRtl
|
|
} = useRtl();
|
|
const {
|
|
t
|
|
} = useLocale();
|
|
const group = useGroup(props, VWindowGroupSymbol);
|
|
const rootRef = ref();
|
|
const isRtlReverse = computed(() => isRtl.value ? !props.reverse : props.reverse);
|
|
const isReversed = shallowRef(false);
|
|
const transition = computed(() => {
|
|
if (props.crossfade) {
|
|
return 'v-window-crossfade-transition';
|
|
}
|
|
const axis = props.direction === 'vertical' ? 'y' : 'x';
|
|
const reverse = isRtlReverse.value ? !isReversed.value : isReversed.value;
|
|
const direction = reverse ? '-reverse' : '';
|
|
return `v-window-${axis}${direction}-transition`;
|
|
});
|
|
const transitionCount = shallowRef(0);
|
|
const transitionHeight = ref(undefined);
|
|
const activeIndex = computed(() => {
|
|
return group.items.value.findIndex(item => group.selected.value.includes(item.id));
|
|
});
|
|
|
|
// Fix for https://github.com/vuetifyjs/vuetify/issues/18447
|
|
watch(activeIndex, (newVal, oldVal) => {
|
|
let scrollableParent;
|
|
const savedScrollPosition = {
|
|
left: 0,
|
|
top: 0
|
|
};
|
|
if (IN_BROWSER && oldVal >= 0) {
|
|
scrollableParent = getScrollParent(rootRef.value);
|
|
savedScrollPosition.left = scrollableParent?.scrollLeft;
|
|
savedScrollPosition.top = scrollableParent?.scrollTop;
|
|
}
|
|
const itemsLength = group.items.value.length;
|
|
const lastIndex = itemsLength - 1;
|
|
if (itemsLength <= 2) {
|
|
isReversed.value = newVal < oldVal;
|
|
} else if (newVal === lastIndex && oldVal === 0) {
|
|
isReversed.value = false;
|
|
} else if (newVal === 0 && oldVal === lastIndex) {
|
|
isReversed.value = true;
|
|
} else {
|
|
isReversed.value = newVal < oldVal;
|
|
}
|
|
nextTick(() => {
|
|
if (!IN_BROWSER || !scrollableParent) return;
|
|
const currentScrollY = scrollableParent.scrollTop;
|
|
if (currentScrollY !== savedScrollPosition.top) {
|
|
scrollableParent.scrollTo({
|
|
...savedScrollPosition,
|
|
behavior: 'instant'
|
|
});
|
|
}
|
|
requestAnimationFrame(() => {
|
|
if (!scrollableParent) return;
|
|
const rafScrollY = scrollableParent.scrollTop;
|
|
if (rafScrollY !== savedScrollPosition.top) {
|
|
scrollableParent.scrollTo({
|
|
...savedScrollPosition,
|
|
behavior: 'instant'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}, {
|
|
flush: 'sync'
|
|
}); // Run synchronously before DOM updates
|
|
|
|
provide(VWindowSymbol, {
|
|
transition,
|
|
isReversed,
|
|
transitionCount,
|
|
transitionHeight,
|
|
rootRef
|
|
});
|
|
const canMoveBack = toRef(() => props.continuous || activeIndex.value !== 0);
|
|
const canMoveForward = toRef(() => props.continuous || activeIndex.value !== group.items.value.length - 1);
|
|
function prev() {
|
|
canMoveBack.value && group.prev();
|
|
}
|
|
function next() {
|
|
canMoveForward.value && group.next();
|
|
}
|
|
const arrows = computed(() => {
|
|
const arrows = [];
|
|
const prevProps = {
|
|
icon: isRtl.value ? props.nextIcon : props.prevIcon,
|
|
class: `v-window__${isRtlReverse.value ? 'right' : 'left'}`,
|
|
onClick: group.prev,
|
|
'aria-label': t('$vuetify.carousel.prev')
|
|
};
|
|
arrows.push(canMoveBack.value ? slots.prev ? slots.prev({
|
|
props: prevProps
|
|
}) : _createVNode(VBtn, prevProps, null) : _createElementVNode("div", null, null));
|
|
const nextProps = {
|
|
icon: isRtl.value ? props.prevIcon : props.nextIcon,
|
|
class: `v-window__${isRtlReverse.value ? 'left' : 'right'}`,
|
|
onClick: group.next,
|
|
'aria-label': t('$vuetify.carousel.next')
|
|
};
|
|
arrows.push(canMoveForward.value ? slots.next ? slots.next({
|
|
props: nextProps
|
|
}) : _createVNode(VBtn, nextProps, null) : _createElementVNode("div", null, null));
|
|
return arrows;
|
|
});
|
|
const touchOptions = computed(() => {
|
|
if (props.touch === false) return props.touch;
|
|
const options = {
|
|
left: () => {
|
|
isRtlReverse.value ? prev() : next();
|
|
},
|
|
right: () => {
|
|
isRtlReverse.value ? next() : prev();
|
|
},
|
|
start: ({
|
|
originalEvent
|
|
}) => {
|
|
originalEvent.stopPropagation();
|
|
}
|
|
};
|
|
return {
|
|
...options,
|
|
...(props.touch === true ? {} : props.touch)
|
|
};
|
|
});
|
|
function onKeyDown(e) {
|
|
if (props.direction === 'horizontal' && e.key === 'ArrowLeft' || props.direction === 'vertical' && e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
prev();
|
|
nextTick(() => {
|
|
canMoveBack.value ? focusArrow(0) : focusArrow(1);
|
|
});
|
|
}
|
|
if (props.direction === 'horizontal' && e.key === 'ArrowRight' || props.direction === 'vertical' && e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
next();
|
|
nextTick(() => {
|
|
canMoveForward.value ? focusArrow(1) : focusArrow(0);
|
|
});
|
|
}
|
|
}
|
|
function focusArrow(index) {
|
|
const arrow = arrows.value[index];
|
|
if (!arrow) return;
|
|
const arrowEl = Array.isArray(arrow) ? arrow[0] : arrow;
|
|
arrowEl.el?.focus();
|
|
}
|
|
useRender(() => _withDirectives(_createVNode(props.tag, {
|
|
"ref": rootRef,
|
|
"class": _normalizeClass(['v-window', {
|
|
'v-window--show-arrows-on-hover': props.showArrows === 'hover',
|
|
'v-window--vertical-arrows': !!props.verticalArrows,
|
|
'v-window--crossfade': !!props.crossfade
|
|
}, themeClasses.value, props.class]),
|
|
"style": _normalizeStyle([props.style, {
|
|
'--v-window-transition-duration': !PREFERS_REDUCED_MOTION() ? convertToUnit(props.transitionDuration, 'ms') : null
|
|
}])
|
|
}, {
|
|
default: () => [_createElementVNode("div", {
|
|
"class": "v-window__container",
|
|
"style": {
|
|
height: transitionHeight.value
|
|
}
|
|
}, [slots.default?.({
|
|
group
|
|
}), props.showArrows !== false && _createElementVNode("div", {
|
|
"class": _normalizeClass(['v-window__controls', {
|
|
'v-window__controls--left': props.verticalArrows === 'left' || props.verticalArrows === true
|
|
}, {
|
|
'v-window__controls--right': props.verticalArrows === 'right'
|
|
}]),
|
|
"onKeydown": onKeyDown
|
|
}, [arrows.value])]), slots.additional?.({
|
|
group
|
|
})]
|
|
}), [[vTouch, touchOptions.value]]));
|
|
return {
|
|
group
|
|
};
|
|
}
|
|
});
|
|
//# sourceMappingURL=VWindow.js.map
|