Skip to content

Commit a5fa500

Browse files
Tim020claude
andauthored
Fix v-b-tooltip DOM node leak in grid components (#1093) (#1099)
* Revert "Fix v-b-tooltip DOM node leak in grid/list components (#1093)" This reverts commit 6bf308c. * Fix v-b-tooltip DOM node leak in grid/list components (#1093) BVN's v-b-tooltip directive teleports a persistent DOM node to <body> for every bound element, causing quadratic growth in ResourceAvailability (scenes × mics) and linear growth in SceneDensityHeatmap and MicAllocations. Replace per-cell v-b-tooltip with a single shared BTooltip component per parent driven by @mouseenter/@Mouseleave — one DOM node regardless of row count, identical appearance and positioning via BVN's own floating-ui logic. A useHoverTooltip composable manages the shared target/text/visible state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix missing useHoverTooltip imports in mics components The composable was called in script setup but never imported, causing ReferenceError at runtime. Added explicit import to all three components. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Replace v-b-tooltip with mouseenter/mouseleave in all three mics components The merge conflict resolution had kept the old v-b-tooltip directive in the templates while only adding the useHoverTooltip script bindings. BVN's v-b-tooltip renders an inline node per bound element which, inside a CSS grid, occupies a grid cell and produces the staggered empty-column layout. Replaced all three remaining v-b-tooltip usages with the @mouseenter/@Mouseleave pattern that drives the shared BTooltip instance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a1bf340 commit a5fa500

4 files changed

Lines changed: 39 additions & 5 deletions

File tree

client-v3/src/components/show/config/mics/MicAllocations.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,10 @@
128128
<template v-else>
129129
<div
130130
v-if="allocationByCharacter[data.item.Character]?.[scene.id] != null"
131-
v-b-tooltip.hover.top="getTooltipText(data.item.Character, scene.id)"
132131
class="allocation-cell"
133132
:class="getConflictClassForCell(data.item.Character, scene.id)"
133+
@mouseenter="showTooltip(getTooltipText(data.item.Character, scene.id), $event)"
134+
@mouseleave="hideTooltip"
134135
>
135136
{{ allocationByCharacter[data.item.Character]?.[scene.id] }}
136137
<IMdiAlert
@@ -147,6 +148,7 @@
147148
</BCol>
148149
</BRow>
149150
<MicAutoPopulateModal ref="autoPopulateModalRef" @auto-populate-result="onAutoPopulateResult" />
151+
<BTooltip v-model="tooltipVisible" :target="tooltipTarget" :title="tooltipText" manual />
150152
</BContainer>
151153
</template>
152154

@@ -156,11 +158,13 @@ import { diff } from 'deep-object-diff';
156158
import { useShowStore } from '@/stores/show';
157159
import { useSystemStore } from '@/stores/system';
158160
import { useStatsTable } from '@/composables/useStatsTable';
161+
import { useHoverTooltip } from '@/composables/useHoverTooltip';
159162
import MicAutoPopulateModal from './MicAutoPopulateModal.vue';
160163
import type { MicConflict } from '@/js/micConflictUtils';
161164
162165
const showStore = useShowStore();
163166
const systemStore = useSystemStore();
167+
const { tooltipTarget, tooltipText, tooltipVisible, showTooltip, hideTooltip } = useHoverTooltip();
164168
const { sortedActs, sortedScenes, numScenesPerAct, getHeaderName, getCellName } = useStatsTable();
165169
166170
const selectedMic = ref<number | null>(null);

client-v3/src/components/show/config/mics/ResourceAvailability.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@
6767
<div
6868
v-for="micStatus in sceneData.micStatuses"
6969
:key="`mic-${micStatus.mic.id}`"
70-
v-b-tooltip.hover.top="getMicTooltip(micStatus)"
7170
class="mic-status-item"
7271
:class="micStatus.statusClass"
72+
@mouseenter="showTooltip(getMicTooltip(micStatus), $event)"
73+
@mouseleave="hideTooltip"
7374
>
7475
<div class="mic-name">{{ micStatus.mic.name ?? `Mic ${micStatus.mic.id}` }}</div>
7576
<div v-if="micStatus.character" class="mic-character">
@@ -81,11 +82,13 @@
8182
</div>
8283
</div>
8384
</div>
85+
<BTooltip v-model="tooltipVisible" :target="tooltipTarget" :title="tooltipText" manual />
8486
</template>
8587

8688
<script setup lang="ts">
8789
import { computed } from 'vue';
8890
import { useShowStore } from '@/stores/show';
91+
import { useHoverTooltip } from '@/composables/useHoverTooltip';
8992
import type { Scene, Character } from '@/types/api/show';
9093
import type { Microphone } from '@/types/api/microphones';
9194
@@ -107,6 +110,7 @@ interface SceneAvailability {
107110
withDefaults(defineProps<{ loading?: boolean }>(), { loading: false });
108111
109112
const showStore = useShowStore();
113+
const { tooltipTarget, tooltipText, tooltipVisible, showTooltip, hideTooltip } = useHoverTooltip();
110114
111115
const scenes = computed(() => showStore.micTimelineData.scenes);
112116
const microphones = computed(() => showStore.micTimelineData.microphones);

client-v3/src/components/show/config/mics/SceneDensityHeatmap.vue

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,18 @@
5757
class="scene-bar-wrapper"
5858
>
5959
<div
60-
v-b-tooltip.hover.top="
61-
`${sceneData.scene.name}: ${sceneData.micCount} microphone${sceneData.micCount !== 1 ? 's' : ''}`
62-
"
6360
class="scene-bar"
6461
:style="{
6562
backgroundColor: getDensityColor(sceneData.micCount),
6663
height: getBarHeight(sceneData.micCount) + 'px',
6764
}"
65+
@mouseenter="
66+
showTooltip(
67+
`${sceneData.scene.name}: ${sceneData.micCount} microphone${sceneData.micCount !== 1 ? 's' : ''}`,
68+
$event
69+
)
70+
"
71+
@mouseleave="hideTooltip"
6872
>
6973
<span class="mic-count">{{ sceneData.micCount }}</span>
7074
</div>
@@ -86,11 +90,13 @@
8690
</div>
8791
</div>
8892
</div>
93+
<BTooltip v-model="tooltipVisible" :target="tooltipTarget" :title="tooltipText" manual />
8994
</template>
9095

9196
<script setup lang="ts">
9297
import { computed } from 'vue';
9398
import { useShowStore } from '@/stores/show';
99+
import { useHoverTooltip } from '@/composables/useHoverTooltip';
94100
import type { Scene } from '@/types/api/show';
95101
96102
interface SceneDensityEntry {
@@ -107,6 +113,7 @@ interface ActDensityGroup {
107113
withDefaults(defineProps<{ loading?: boolean }>(), { loading: false });
108114
109115
const showStore = useShowStore();
116+
const { tooltipTarget, tooltipText, tooltipVisible, showTooltip, hideTooltip } = useHoverTooltip();
110117
111118
const maxBarHeight = 200;
112119
const minBarHeight = 20;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ref } from 'vue';
2+
3+
export function useHoverTooltip() {
4+
const tooltipTarget = ref<HTMLElement | null>(null);
5+
const tooltipText = ref('');
6+
const tooltipVisible = ref(false);
7+
8+
function showTooltip(text: string, event: MouseEvent): void {
9+
tooltipTarget.value = event.currentTarget as HTMLElement;
10+
tooltipText.value = text;
11+
tooltipVisible.value = true;
12+
}
13+
14+
function hideTooltip(): void {
15+
tooltipVisible.value = false;
16+
}
17+
18+
return { tooltipTarget, tooltipText, tooltipVisible, showTooltip, hideTooltip };
19+
}

0 commit comments

Comments
 (0)