mirror of
https://github.com/OpenBMB/ChatDev.git
synced 2026-05-12 19:43:36 +00:00
Fix nested UI scroll behavior
This commit is contained in:
parent
c85e1de2a7
commit
f301108263
@ -7,10 +7,11 @@ const route = useRoute()
|
||||
|
||||
// Hide the sidebar on LaunchView, BatchRunView and WorkflowWorkbench
|
||||
const showSidebar = computed(() => route.path !== '/launch' && route.path !== '/batch-run')
|
||||
const isHomeRoute = computed(() => route.path === '/')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="app-container" :class="{ 'home-route': isHomeRoute }">
|
||||
<Sidebar v-if="showSidebar" />
|
||||
<main class="main-content">
|
||||
<router-view />
|
||||
@ -25,11 +26,23 @@ const showSidebar = computed(() => route.path !== '/launch' && route.path !== '/
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.app-container.home-route {
|
||||
height: 100dvh;
|
||||
min-height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.home-route .main-content {
|
||||
overflow: hidden;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
|
||||
@ -251,7 +251,16 @@
|
||||
autocomplete="off"
|
||||
/>
|
||||
<Teleport to="body">
|
||||
<div v-if="showDropdown && !isReadOnly" class="custom-select-dropdown" :style="dropdownStyle">
|
||||
<div
|
||||
v-if="showDropdown && !isReadOnly"
|
||||
class="custom-select-dropdown"
|
||||
data-local-scroll
|
||||
:style="dropdownStyle"
|
||||
@mousedown.prevent
|
||||
@wheel="handleLocalScrollWheel"
|
||||
@scroll.stop
|
||||
@touchmove.stop
|
||||
>
|
||||
<div
|
||||
v-if="!field.required"
|
||||
class="custom-select-option"
|
||||
@ -389,7 +398,10 @@
|
||||
:id="`${modalId}-${field.name}`"
|
||||
:value="formData[field.name]"
|
||||
@input="onInput($event.target.value)"
|
||||
@wheel="handleLocalScrollWheel"
|
||||
@scroll.stop
|
||||
class="form-textarea"
|
||||
data-local-scroll
|
||||
rows="4"
|
||||
:readonly="isReadOnly"
|
||||
:class="{'input-readonly': isReadOnly}"
|
||||
@ -771,6 +783,34 @@ const onFilterInput = (event) => {
|
||||
showDropdown.value = true
|
||||
}
|
||||
|
||||
const handleLocalScrollWheel = (event) => {
|
||||
const target = event.currentTarget
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
const isDropdown = target.classList.contains('custom-select-dropdown')
|
||||
|
||||
if (target.scrollHeight <= target.clientHeight) {
|
||||
if (isDropdown) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const isScrollingUp = event.deltaY < 0
|
||||
const isScrollingDown = event.deltaY > 0
|
||||
const atTop = target.scrollTop <= 0
|
||||
const atBottom = Math.ceil(target.scrollTop + target.clientHeight) >= target.scrollHeight
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
if ((isScrollingUp && atTop) || (isScrollingDown && atBottom)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputBlur = () => {
|
||||
// Delay hiding dropdown to allow option selection
|
||||
setTimeout(() => {
|
||||
@ -897,6 +937,7 @@ const getSelectedLabel = () => {
|
||||
box-sizing: border-box;
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
overscroll-behavior: contain;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
@ -1565,6 +1606,7 @@ input:checked + .switch-slider:before {
|
||||
z-index: 10000;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
background-color: #1e1e1e;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<button class="close-button" @click="closeModal(modal.id)">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" data-local-scroll @wheel="handleLocalScrollWheel">
|
||||
<div
|
||||
v-for="field in getMainDisplayFields(modal)"
|
||||
:key="field.name + '-' + (modal.formData[field.name]?.type || '')"
|
||||
@ -128,7 +128,7 @@
|
||||
<div class="modal-header">
|
||||
<button class="close-button" @click="closeVarModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" data-local-scroll @wheel="handleLocalScrollWheel">
|
||||
<div v-if="varFormError" class="submit-error">
|
||||
{{ varFormError }}
|
||||
</div>
|
||||
@ -169,7 +169,7 @@
|
||||
<h3 class="modal-title">{{ editingListItemIndex !== null ? t('form_generator.edit_entry') : t('form_generator.add_entry') }}</h3>
|
||||
<button class="close-button" @click="closeListItemModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" data-local-scroll @wheel="handleLocalScrollWheel">
|
||||
<div v-if="listItemFormError" class="submit-error">
|
||||
{{ listItemFormError }}
|
||||
</div>
|
||||
@ -1672,6 +1672,29 @@ const submitForm = async (modalId) => {
|
||||
}
|
||||
|
||||
// Press Enter to submit topmost modal, Esc to close
|
||||
const handleLocalScrollWheel = (event) => {
|
||||
const target = event.currentTarget
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
if (target.scrollHeight <= target.clientHeight) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
const isScrollingUp = event.deltaY < 0
|
||||
const isScrollingDown = event.deltaY > 0
|
||||
const atTop = target.scrollTop <= 0
|
||||
const atBottom = Math.ceil(target.scrollTop + target.clientHeight) >= target.scrollHeight
|
||||
|
||||
if ((isScrollingUp && atTop) || (isScrollingDown && atBottom)) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeystrokes = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
// Do not intercept Enter inside textarea to allow newlines
|
||||
@ -2140,6 +2163,7 @@ defineExpose({
|
||||
padding: 15px 20px 30px 15px;
|
||||
max-height: none;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.04);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
scrollbar-width: none;
|
||||
|
||||
@ -213,7 +213,11 @@ const calculatePosition = () => {
|
||||
}
|
||||
|
||||
// Listen to scroll and zoom events to dismiss tooltip
|
||||
const handleScroll = () => {
|
||||
const handleScroll = (event) => {
|
||||
if (event.target instanceof Element && event.target.closest('[data-local-scroll]')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isVisible.value && !keyboardActive.value) {
|
||||
hideTooltip()
|
||||
}
|
||||
|
||||
@ -48,6 +48,10 @@ const isWorkflowsActive = computed(() => route.path.startsWith('/workflows'))
|
||||
|
||||
let lastScrollY = 0
|
||||
const handleScroll = (e) => {
|
||||
if (e.target instanceof Element && e.target.closest('[data-local-scroll]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentScrollY = e.target.scrollTop || window.scrollY || 0;
|
||||
// Minimize small scroll jitters
|
||||
if (Math.abs(currentScrollY - lastScrollY) < 5) return;
|
||||
|
||||
@ -68,7 +68,7 @@ const cubes = Array.from({ length: 80 }, (_, i) => ({
|
||||
<style scoped>
|
||||
.home-view {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 55px); /* Match sidebar height to avoid bottom gap */
|
||||
height: 100%;
|
||||
background-color: #1a1a1a;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user