154 lines
5.8 KiB
JavaScript
154 lines
5.8 KiB
JavaScript
import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue";
|
|
// Styles
|
|
import "./VAppBar.css";
|
|
|
|
// Components
|
|
import { makeVToolbarProps, VToolbar } from "../VToolbar/VToolbar.js"; // Composables
|
|
import { makeLayoutItemProps, useLayoutItem } from "../../composables/layout.js";
|
|
import { useProxiedModel } from "../../composables/proxiedModel.js";
|
|
import { makeScrollProps, useScroll } from "../../composables/scroll.js";
|
|
import { useSsrBoot } from "../../composables/ssrBoot.js";
|
|
import { useToggleScope } from "../../composables/toggleScope.js"; // Utilities
|
|
import { computed, ref, shallowRef, toRef, watchEffect } from 'vue';
|
|
import { genericComponent, omit, propsFactory, useRender } from "../../util/index.js"; // Types
|
|
export const makeVAppBarProps = propsFactory({
|
|
scrollBehavior: String,
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
location: {
|
|
type: String,
|
|
default: 'top',
|
|
validator: value => ['top', 'bottom'].includes(value)
|
|
},
|
|
...omit(makeVToolbarProps(), ['location']),
|
|
...makeLayoutItemProps(),
|
|
...makeScrollProps(),
|
|
height: {
|
|
type: [Number, String],
|
|
default: 64
|
|
}
|
|
}, 'VAppBar');
|
|
export const VAppBar = genericComponent()({
|
|
name: 'VAppBar',
|
|
props: makeVAppBarProps(),
|
|
emits: {
|
|
'update:modelValue': value => true
|
|
},
|
|
setup(props, {
|
|
slots
|
|
}) {
|
|
const vToolbarRef = ref();
|
|
const isActive = useProxiedModel(props, 'modelValue');
|
|
const scrollBehavior = computed(() => {
|
|
const behavior = new Set(props.scrollBehavior?.split(' ') ?? []);
|
|
return {
|
|
hide: behavior.has('hide'),
|
|
fullyHide: behavior.has('fully-hide'),
|
|
inverted: behavior.has('inverted'),
|
|
collapse: behavior.has('collapse'),
|
|
elevate: behavior.has('elevate'),
|
|
fadeImage: behavior.has('fade-image')
|
|
// shrink: behavior.has('shrink'),
|
|
};
|
|
});
|
|
const canScroll = computed(() => {
|
|
const behavior = scrollBehavior.value;
|
|
return behavior.hide || behavior.fullyHide || behavior.inverted || behavior.collapse || behavior.elevate || behavior.fadeImage ||
|
|
// behavior.shrink ||
|
|
!isActive.value;
|
|
});
|
|
const appBarHeight = computed(() => {
|
|
const height = vToolbarRef.value?.contentHeight ?? 0;
|
|
const extensionHeight = vToolbarRef.value?.extensionHeight ?? 0;
|
|
return height + extensionHeight;
|
|
});
|
|
const {
|
|
currentScroll,
|
|
scrollThreshold,
|
|
isScrollingUp,
|
|
scrollRatio,
|
|
isAtBottom,
|
|
reachedBottomWhileScrollingDown,
|
|
hasEnoughScrollableSpace
|
|
} = useScroll(props, {
|
|
canScroll,
|
|
layoutSize: appBarHeight
|
|
});
|
|
const canHide = toRef(() => scrollBehavior.value.hide || scrollBehavior.value.fullyHide);
|
|
const isCollapsed = computed(() => props.collapse || scrollBehavior.value.collapse && (scrollBehavior.value.inverted ? scrollRatio.value > 0 : scrollRatio.value === 0));
|
|
const isFlat = computed(() => props.flat || scrollBehavior.value.fullyHide && !isActive.value || scrollBehavior.value.elevate && (scrollBehavior.value.inverted ? currentScroll.value > 0 : currentScroll.value === 0));
|
|
const opacity = computed(() => scrollBehavior.value.fadeImage ? scrollBehavior.value.inverted ? 1 - scrollRatio.value : scrollRatio.value : undefined);
|
|
const height = computed(() => {
|
|
if (scrollBehavior.value.hide && scrollBehavior.value.inverted) return 0;
|
|
const height = vToolbarRef.value?.contentHeight ?? 0;
|
|
const extensionHeight = vToolbarRef.value?.extensionHeight ?? 0;
|
|
if (!canHide.value) return height + extensionHeight;
|
|
return currentScroll.value < scrollThreshold.value || scrollBehavior.value.fullyHide ? height + extensionHeight : height;
|
|
});
|
|
useToggleScope(() => !!props.scrollBehavior, () => {
|
|
watchEffect(() => {
|
|
if (!canHide.value) {
|
|
isActive.value = true;
|
|
return;
|
|
}
|
|
if (scrollBehavior.value.inverted) {
|
|
isActive.value = currentScroll.value > scrollThreshold.value;
|
|
return;
|
|
}
|
|
|
|
// If there's not enough scrollable space, don't apply scroll-hide behavior at all
|
|
// This prevents flickering/bouncing animations on short pages
|
|
if (!hasEnoughScrollableSpace.value) {
|
|
isActive.value = true;
|
|
return;
|
|
}
|
|
|
|
// Prevent navbar from showing when we reached bottom while scrolling down
|
|
// This handles the case where scroll momentum causes to hit bottom during hide transition
|
|
if (reachedBottomWhileScrollingDown.value) {
|
|
isActive.value = false;
|
|
return;
|
|
}
|
|
|
|
// Normal behavior: show when scrolling up (and not at bottom) or above threshold
|
|
isActive.value = isScrollingUp.value && !isAtBottom.value || currentScroll.value < scrollThreshold.value;
|
|
});
|
|
});
|
|
const {
|
|
ssrBootStyles
|
|
} = useSsrBoot();
|
|
const {
|
|
layoutItemStyles
|
|
} = useLayoutItem({
|
|
id: props.name,
|
|
order: computed(() => parseInt(props.order, 10)),
|
|
position: toRef(() => props.location),
|
|
layoutSize: height,
|
|
elementSize: shallowRef(undefined),
|
|
active: isActive,
|
|
absolute: toRef(() => props.absolute)
|
|
});
|
|
useRender(() => {
|
|
const toolbarProps = omit(VToolbar.filterProps(props), ['location']);
|
|
return _createVNode(VToolbar, _mergeProps({
|
|
"ref": vToolbarRef,
|
|
"class": ['v-app-bar', {
|
|
'v-app-bar--bottom': props.location === 'bottom'
|
|
}, props.class],
|
|
"style": [{
|
|
...layoutItemStyles.value,
|
|
'--v-toolbar-image-opacity': opacity.value,
|
|
height: undefined,
|
|
...ssrBootStyles.value
|
|
}, props.style]
|
|
}, toolbarProps, {
|
|
"collapse": isCollapsed.value,
|
|
"flat": isFlat.value
|
|
}), slots);
|
|
});
|
|
return {};
|
|
}
|
|
});
|
|
//# sourceMappingURL=VAppBar.js.map
|