mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
fix(web): close dock cards on outside click
This commit is contained in:
parent
8c492c160f
commit
cf722fafbb
2 changed files with 52 additions and 24 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
<!-- pending question/approval cards, and the composer. Only rendered inside a -->
|
<!-- pending question/approval cards, and the composer. Only rendered inside a -->
|
||||||
<!-- chat-pane group so it never leaks into files/tasks/preview/btw panes. -->
|
<!-- chat-pane group so it never leaks into files/tasks/preview/btw panes. -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { onUnmounted, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import type { ActivationBadges, ApprovalBlock, ConversationStatus, PermissionMode, QueuedPromptView, TaskItem, TodoView, UIQuestion } from '../types';
|
import type { ActivationBadges, ApprovalBlock, ConversationStatus, PermissionMode, QueuedPromptView, TaskItem, TodoView, UIQuestion } from '../types';
|
||||||
import type { AppGoal, AppModel, AppSkill, QuestionResponse, ThinkingLevel } from '../api/types';
|
import type { AppGoal, AppModel, AppSkill, QuestionResponse, ThinkingLevel } from '../api/types';
|
||||||
|
|
@ -75,11 +75,38 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const composerRef = ref<{ loadForEdit: (value: string) => void } | null>(null);
|
const composerRef = ref<{ loadForEdit: (value: string) => void } | null>(null);
|
||||||
|
const workPanelRef = ref<HTMLElement | null>(null);
|
||||||
|
const workbarRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
function loadForEdit(value: string): void {
|
function loadForEdit(value: string): void {
|
||||||
composerRef.value?.loadForEdit(value);
|
composerRef.value?.loadForEdit(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDocumentMouseDown(event: MouseEvent): void {
|
||||||
|
if (!props.dockPanel) return;
|
||||||
|
const target = event.target as Node | null;
|
||||||
|
if (!target) return;
|
||||||
|
if (workPanelRef.value?.contains(target)) return;
|
||||||
|
if (workbarRef.value?.contains(target)) return;
|
||||||
|
emit('close-dock-panel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.dockPanel,
|
||||||
|
(panel) => {
|
||||||
|
if (typeof document === 'undefined') return;
|
||||||
|
document.removeEventListener('mousedown', onDocumentMouseDown, true);
|
||||||
|
if (panel) document.addEventListener('mousedown', onDocumentMouseDown, true);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.removeEventListener('mousedown', onDocumentMouseDown, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
defineExpose({ loadForEdit });
|
defineExpose({ loadForEdit });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -87,6 +114,7 @@ defineExpose({ loadForEdit });
|
||||||
<div class="chat-dock" :class="[mobile ? 'align-mobile' : 'align-center']" @click.stop>
|
<div class="chat-dock" :class="[mobile ? 'align-mobile' : 'align-center']" @click.stop>
|
||||||
<Transition name="dock-panel">
|
<Transition name="dock-panel">
|
||||||
<div
|
<div
|
||||||
|
ref="workPanelRef"
|
||||||
v-if="dockPanel"
|
v-if="dockPanel"
|
||||||
class="dock-work-panel"
|
class="dock-work-panel"
|
||||||
@click.stop
|
@click.stop
|
||||||
|
|
@ -110,11 +138,6 @@ defineExpose({ loadForEdit });
|
||||||
>
|
>
|
||||||
{{ t('tasks.dockTodos') }} · {{ todoDoneCount }}/{{ todos?.length ?? 0 }}
|
{{ t('tasks.dockTodos') }} · {{ todoDoneCount }}/{{ todos?.length ?? 0 }}
|
||||||
</span>
|
</span>
|
||||||
<button type="button" class="dock-work-close" :aria-label="t('tasks.closePanel')" @click="emit('close-dock-panel')">
|
|
||||||
<svg viewBox="0 0 12 12" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true">
|
|
||||||
<path d="M2 2l8 8M10 2l-8 8" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dock-work-body">
|
<div class="dock-work-body">
|
||||||
<TasksPane
|
<TasksPane
|
||||||
|
|
@ -142,7 +165,7 @@ defineExpose({ loadForEdit });
|
||||||
:force-expanded="goalExpandSignal"
|
:force-expanded="goalExpandSignal"
|
||||||
@control-goal="emit('controlGoal', $event)"
|
@control-goal="emit('controlGoal', $event)"
|
||||||
/>
|
/>
|
||||||
<div v-if="hasDockWork" class="dock-workbar">
|
<div v-if="hasDockWork" ref="workbarRef" class="dock-workbar">
|
||||||
<button
|
<button
|
||||||
v-if="bashTasks.length > 0"
|
v-if="bashTasks.length > 0"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -299,23 +322,6 @@ defineExpose({ loadForEdit });
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
.dock-work-close {
|
|
||||||
margin-left: auto;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: var(--muted);
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.dock-work-close:hover {
|
|
||||||
background: var(--hover-bg);
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
.dock-work-body {
|
.dock-work-body {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,28 @@ describe('ConversationPane dock work panel', () => {
|
||||||
await chips[2]!.trigger('click');
|
await chips[2]!.trigger('click');
|
||||||
expect(wrapper.find('todo-card-stub').exists()).toBe(true);
|
expect(wrapper.find('todo-card-stub').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('closes the dock work panel when the user clicks outside it', async () => {
|
||||||
|
const tasks: TaskItem[] = [
|
||||||
|
{
|
||||||
|
id: 'task_1',
|
||||||
|
name: 'Review code',
|
||||||
|
kind: 'subagent',
|
||||||
|
state: 'run',
|
||||||
|
timing: 'Running',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const wrapper = mountPane({ tasks });
|
||||||
|
|
||||||
|
await wrapper.find('.dock-work-chip').trigger('click');
|
||||||
|
expect(wrapper.find('.dock-work-panel').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('.dock-work-close').exists()).toBe(false);
|
||||||
|
|
||||||
|
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.find('.dock-work-panel').exists()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function swarmGroup(members: { phase: SwarmGroup['members'][number]['phase']; id?: string }[]): SwarmGroup {
|
function swarmGroup(members: { phase: SwarmGroup['members'][number]['phase']; id?: string }[]): SwarmGroup {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue