Skip to content

Commit 0508e09

Browse files
committed
refactor(core): floating popover
1 parent 1df93a2 commit 0508e09

6 files changed

Lines changed: 86 additions & 63 deletions

File tree

packages/core/src/client/standalone/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { DocksContext } from '@vitejs/devtools-kit/client'
33
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
44
import { markRaw, ref, useTemplateRef, watch } from 'vue'
55
import DockEntriesWithCategories from '../webcomponents/components/DockEntriesWithCategories.vue'
6+
import FloatingElements from '../webcomponents/components/FloatingElements.vue'
67
import VitePlus from '../webcomponents/components/icons/VitePlus.vue'
78
import ViewBuiltinClientAuthNotice from '../webcomponents/components/ViewBuiltinClientAuthNotice.vue'
89
import ViewEntry from '../webcomponents/components/ViewEntry.vue'
@@ -53,6 +54,7 @@ function switchEntry(id: string) {
5354
</div>
5455
<div class="transition duration-200 p2">
5556
<DockEntriesWithCategories
57+
:context="context"
5658
:entries="context.docks.entries"
5759
:is-vertical="false"
5860
:selected="context.docks.selected"
@@ -75,4 +77,5 @@ function switchEntry(id: string) {
7577
/>
7678
</div>
7779
</div>
80+
<FloatingElements />
7881
</template>

packages/core/src/client/webcomponents/components/DockEmbedded.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { DocksContext } from '@vitejs/devtools-kit/client'
33
import Dock from './Dock.vue'
44
import DockPanel from './DockPanel.vue'
5-
import FloatingTooltip from './FloatingTooltip'
5+
import FloatingElements from './FloatingElements.vue'
66
77
defineProps<{
88
context: DocksContext
@@ -20,5 +20,5 @@ defineProps<{
2020
/>
2121
</template>
2222
</Dock>
23-
<FloatingTooltip />
23+
<FloatingElements />
2424
</template>

packages/core/src/client/webcomponents/components/DockEntry.vue

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,42 @@ import { useTemplateRef } from 'vue'
55
import { setFloatingTooltip } from '../state/floating-tooltip'
66
import DockIcon from './DockIcon.vue'
77
8-
const props = defineProps<{
9-
dock: DevToolsDockEntryBase
10-
isSelected?: boolean
11-
isDimmed?: boolean
12-
isVertical?: boolean
13-
badge?: string
14-
}>()
8+
const props = withDefaults(
9+
defineProps<{
10+
dock: DevToolsDockEntryBase
11+
isSelected?: boolean
12+
isDimmed?: boolean
13+
isVertical?: boolean
14+
badge?: string
15+
tooltip?: boolean
16+
}>(),
17+
{
18+
tooltip: true,
19+
},
20+
)
1521
1622
const button = useTemplateRef<HTMLButtonElement>('button')
1723
18-
function updatePos() {
19-
const rect = button.value?.getBoundingClientRect()
20-
if (rect) {
21-
setFloatingTooltip({
22-
render: props.dock.title,
23-
width: rect.width,
24-
height: rect.height,
25-
left: rect.left,
26-
top: rect.top,
27-
})
28-
}
29-
}
30-
31-
function showTitle() {
32-
updatePos()
24+
function updateTooltip() {
25+
if (!props.tooltip)
26+
return
27+
if (!button.value)
28+
return
29+
setFloatingTooltip({
30+
content: props.dock.title,
31+
el: button.value,
32+
})
3333
}
3434
3535
function clearTitle() {
36+
if (!props.tooltip)
37+
return
3638
setFloatingTooltip(null)
3739
}
3840
3941
useEventListener('pointerdown', () => {
42+
if (!props.tooltip)
43+
return
4044
setFloatingTooltip(null)
4145
})
4246
</script>
@@ -45,7 +49,7 @@ useEventListener('pointerdown', () => {
4549
<div
4650
:key="dock.id"
4751
class="relative group vite-devtools-dock-entry"
48-
@pointerenter="showTitle"
52+
@pointerenter="updateTooltip"
4953
@pointerleave="clearTitle"
5054
>
5155
<button
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup lang="ts">
2+
import { useFloatingTooltip } from '../state/floating-tooltip'
3+
import FloatingPopover from './FloatingPopover'
4+
5+
const tooltip = useFloatingTooltip()
6+
</script>
7+
8+
<template>
9+
<FloatingPopover :item="tooltip" />
10+
</template>

packages/core/src/client/webcomponents/components/FloatingTooltip.ts renamed to packages/core/src/client/webcomponents/components/FloatingPopover.ts

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { FloatingTooltip } from '../state/floating-tooltip'
2-
import { useDebounceFn } from '@vueuse/core'
3-
import { computed, defineComponent, h, ref, watch } from 'vue'
4-
import { useFloatingTooltip } from '../state/floating-tooltip'
1+
import type { PropType } from 'vue'
2+
import type { FloatingPopoverProps } from '../state/floating-tooltip'
3+
import { useDebounceFn, useElementBounding } from '@vueuse/core'
4+
import { computed, defineComponent, h, reactive, ref, watch } from 'vue'
55

66
// @unocss-include
77

@@ -10,56 +10,62 @@ const GAP = 10
1010

1111
const FloatingTooltipComponent = defineComponent({
1212
name: 'FloatingTooltip',
13-
setup() {
14-
const current = useFloatingTooltip()
15-
const box = ref<FloatingTooltip | null>(null)
13+
props: {
14+
item: {
15+
type: Object as PropType<FloatingPopoverProps | null | undefined>,
16+
required: false,
17+
},
18+
},
19+
setup(props) {
20+
const el = ref(props.item?.el)
21+
const rect = reactive(useElementBounding(el))
1622

1723
// guess alignment of the tooltip based on viewport position
1824
const align = computed<'bottom' | 'left' | 'right' | 'top'>(() => {
19-
if (!box.value)
25+
if (!props.item?.el)
2026
return 'bottom'
2127
const vw = window.innerWidth
2228
const vh = window.innerHeight
23-
if (box.value.left < DETECT_MARGIN)
29+
if (rect.left < DETECT_MARGIN)
2430
return 'right'
25-
if (box.value.left + box.value.width > vw - DETECT_MARGIN)
31+
if (rect.left + rect.width > vw - DETECT_MARGIN)
2632
return 'left'
27-
if (box.value.top < DETECT_MARGIN)
33+
if (rect.top < DETECT_MARGIN)
2834
return 'bottom'
29-
if (box.value.top + box.value.height > vh - DETECT_MARGIN)
35+
if (rect.top + rect.height > vh - DETECT_MARGIN)
3036
return 'top'
3137
return 'bottom'
3238
})
3339

3440
const style = computed(() => {
35-
if (!box.value)
41+
if (!props.item?.el)
3642
return {}
3743
switch (align.value) {
3844
case 'bottom': {
3945
return {
40-
left: `${box.value.left + box.value.width / 2}px`,
41-
top: `${box.value.top + box.value.height + GAP}px`,
46+
left: `${rect.left + rect.width / 2}px`,
47+
top: `${rect.top + rect.height + GAP}px`,
4248
transform: 'translateX(-50%)',
4349
}
4450
}
4551
case 'top': {
4652
return {
47-
left: `${box.value.left + box.value.width / 2}px`,
48-
bottom: `${window.innerHeight - box.value.top + GAP}px`,
53+
left: `${rect.left + rect.width / 2}px`,
54+
bottom: `${window.innerHeight - rect.top + GAP}px`,
4955
transform: 'translateX(-50%)',
5056
}
5157
}
5258
case 'left': {
5359
return {
54-
right: `${window.innerWidth - box.value.left + GAP}px`,
55-
top: `${box.value.top + box.value.height / 2}px`,
60+
right: `${window.innerWidth - rect.left + GAP}px`,
61+
top: `${rect.top + rect.height / 2}px`,
5662
transform: 'translateY(-50%)',
5763
}
5864
}
5965
case 'right': {
6066
return {
61-
left: `${box.value.left + box.value.width + GAP}px`,
62-
top: `${box.value.top + box.value.height / 2}px`,
67+
left: `${rect.left + rect.width + GAP}px`,
68+
top: `${rect.top + rect.height / 2}px`,
6369
transform: 'translateY(-50%)',
6470
}
6571
}
@@ -70,15 +76,18 @@ const FloatingTooltipComponent = defineComponent({
7076
})
7177

7278
const clearThrottled = useDebounceFn(() => {
73-
if (current.value == null)
74-
box.value = null
79+
if (props.item?.el == null)
80+
el.value = undefined
7581
}, 800)
7682

7783
watch(
78-
current,
84+
() => props.item,
7985
(value) => {
8086
if (value) {
81-
box.value = { ...value }
87+
if (el.value !== value.el)
88+
el.value = value.el
89+
else
90+
rect.update()
8291
}
8392
else {
8493
clearThrottled()
@@ -87,17 +96,17 @@ const FloatingTooltipComponent = defineComponent({
8796
)
8897

8998
return () => {
90-
if (!box.value?.render)
99+
if (!props.item?.content)
91100
return null
92101

93-
const content = typeof box.value.render === 'string' ? h('span', box.value.render) : box.value.render()
102+
const content = typeof props.item.content === 'string' ? h('span', props.item.content) : props.item.content()
94103

95104
return h(
96105
'div',
97106
{
98107
class: [
99108
'fixed z-floating-tooltip text-xs transition-all duration-300 w-max bg-glass border border-base rounded px2 p1',
100-
current ? 'op100' : 'op0 pointer-events-none',
109+
props.item ? 'op100' : 'op0 pointer-events-none',
101110
],
102111
style: style.value,
103112
},
Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
import type { VNode } from 'vue'
22
import { shallowRef } from 'vue'
33

4-
export interface FloatingTooltip {
5-
left: number
6-
top: number
7-
width: number
8-
height: number
9-
render: string | (() => VNode)
4+
export interface FloatingPopoverProps {
5+
el: HTMLElement
6+
content: string | (() => VNode)
107
}
118

12-
const state = shallowRef<FloatingTooltip | null>(null)
9+
const tooltip = shallowRef<FloatingPopoverProps | null>(null)
1310

14-
export function setFloatingTooltip(info: FloatingTooltip | null) {
15-
state.value = info
11+
export function setFloatingTooltip(info: FloatingPopoverProps | null) {
12+
tooltip.value = info
1613
}
1714

1815
export function useFloatingTooltip() {
19-
return state
16+
return tooltip
2017
}

0 commit comments

Comments
 (0)