Skip to content

Commit 6d20c22

Browse files
authored
Hide tooltip is popover is open on the target (#23)
* Hide tooltip is popover is open on the target * Move funciton to global scope
1 parent 88e59fb commit 6d20c22

File tree

5 files changed

+102
-39
lines changed

5 files changed

+102
-39
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store",
2424
"vue": "^3.3.4",
2525
"vue-selectable-items": "^1.0.0",
26-
"wowerlay": "1.0.0-beta.4"
26+
"wowerlay": "1.0.0-beta.10"
2727
},
2828
"files": [
2929
"README.md",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/AppSidebar.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ const moreItems = computed(() => [
5353
@click="open(REPO_LINK)"
5454
>
5555
<img
56-
title="Go to Gitification repository"
5756
draggable="false"
5857
src="/src/assets/img/icon.png"
5958
>

src/components/Popover.vue

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
2-
import { type InjectionKey, type Ref, inject, provide, ref, watch } from 'vue'
3-
import { Wowerlay, type WowerlayTransitionFn } from 'wowerlay'
2+
import { type InjectionKey, type Ref, inject, onScopeDispose, provide, ref, watch } from 'vue'
3+
import { type AlignedPlacement, type ReferenceElement, type Side, Wowerlay, type WowerlayTransitionFn } from 'wowerlay'
44
import { useEventListener } from '@vueuse/core'
55
import { useKey } from '../composables/useKey'
66
@@ -12,43 +12,37 @@ interface SlotProps {
1212
visible: boolean
1313
}
1414
15-
const popoverContextKey: InjectionKey<PopoverContext> = Symbol('PopoverContext')
15+
// We use plain string on dev mode because hot reloading brokes symbols.
16+
const popoverContextKey: InjectionKey<PopoverContext> = import.meta.env.DEV ? 'PopoverContext' as any : Symbol('PopoverContext')
1617
1718
export function usePopoverContext() {
1819
return inject(popoverContextKey)!
1920
}
20-
</script>
21-
22-
<script lang="ts" setup>
23-
const props = withDefaults(defineProps<Props>(), {
24-
wowerlayOptions: () => ({}),
25-
})
2621
27-
defineSlots<{
28-
default: (props: SlotProps) => any
29-
}>()
30-
31-
interface Props {
32-
wowerlayOptions?: Partial<Omit<InstanceType<typeof Wowerlay>['$props'], 'visible' | 'target'>>
33-
target?: HTMLElement | null
22+
const transformOriginMap: Record<AlignedPlacement | Side, string> = {
23+
'bottom-end': 'right top',
24+
'bottom-start': 'left top',
25+
'bottom': 'center top',
26+
'left-end': 'right bottom',
27+
'left-start': 'right top',
28+
'left': 'right center',
29+
'right-end': 'left bottom',
30+
'right-start': 'left top',
31+
'right': 'left center',
32+
'top-end': 'right bottom',
33+
'top-start': 'left bottom',
34+
'top': 'center bottom',
3435
}
3536
36-
const visible = ref(false)
37-
38-
provide(popoverContextKey, { visible })
39-
40-
useKey('esc', () => {
41-
visible.value = false
42-
}, { prevent: true, source: visible })
43-
4437
const handleTransition: WowerlayTransitionFn = (type, element, done) => {
45-
const placement = element.getAttribute('data-popover-placement')!.split('-')[0]
38+
const placement = element.getAttribute('data-popover-placement') as AlignedPlacement | Side
39+
const side = placement.split('-')[0] as Side
4640
47-
const vertical = placement === 'top' || placement === 'bottom'
41+
const vertical = side === 'top' || side === 'bottom'
4842
const transformFunction = vertical ? 'translateY' : 'translateX'
4943
5044
const from = {
51-
transform: `scale(0.97) ${transformFunction}(${placement === 'bottom' || placement === 'right' ? '-7px' : '7px'})`,
45+
transform: `scale(0.97) ${transformFunction}(${side === 'bottom' || side === 'right' ? '-3px' : '3px'})`,
5246
opacity: 0,
5347
}
5448
@@ -57,13 +51,75 @@ const handleTransition: WowerlayTransitionFn = (type, element, done) => {
5751
opacity: 1,
5852
}
5953
54+
const oldTransformOrigin = element.style.transformOrigin
55+
element.style.transformOrigin = transformOriginMap[placement]
56+
57+
if (type === 'leave') {
58+
const background = element.parentElement
59+
if (background) {
60+
background.style.setProperty('pointer-events', 'none')
61+
element.style.setProperty('pointer-events', 'auto')
62+
}
63+
}
64+
6065
const animation = element.animate(type === 'enter' ? [from, to] : [to, from], {
6166
duration: 200,
6267
easing: 'ease',
6368
})
6469
65-
animation.onfinish = done
70+
animation.onfinish = () => {
71+
if (type === 'enter')
72+
element.style.transformOrigin = oldTransformOrigin
73+
74+
done()
75+
}
76+
}
77+
78+
const popoverVisibleHooks = new Set<(el: ReferenceElement) => void>()
79+
80+
const runPopoverVisibleHooks = (el: ReferenceElement) => {
81+
for (const cb of popoverVisibleHooks)
82+
cb(el)
83+
}
84+
85+
export const onPopoverVisible = (cb: (el: ReferenceElement) => void) => {
86+
popoverVisibleHooks.add(cb)
87+
const cleanup = () => popoverVisibleHooks.delete(cb)
88+
onScopeDispose(cleanup)
89+
90+
return cleanup
6691
}
92+
</script>
93+
94+
<script lang="ts" setup>
95+
const props = withDefaults(defineProps<Props>(), {
96+
wowerlayOptions: () => ({}),
97+
})
98+
99+
defineSlots<{
100+
default: (props: SlotProps) => any
101+
}>()
102+
103+
interface Props {
104+
wowerlayOptions?: Partial<Omit<InstanceType<typeof Wowerlay>['$props'], 'visible' | 'target'>>
105+
target?: InstanceType<typeof Wowerlay>['$props']['target']
106+
}
107+
108+
const visible = ref(false)
109+
110+
defineExpose({
111+
show() {
112+
visible.value = true
113+
},
114+
hide() {
115+
visible.value = false
116+
},
117+
})
118+
provide(popoverContextKey, { visible })
119+
120+
useKey('esc', () => {
121+
visible.value = false
122+
}, { prevent: true, source: visible })
67123
68124
const popoverEl = ref<HTMLElement | null>(null)
69125
let lastFocusedElement: Element | null = null
@@ -74,14 +130,15 @@ watch(visible, (value) => {
74130
setTimeout(() => {
75131
popoverEl.value?.focus()
76132
})
133+
runPopoverVisibleHooks(props.target as ReferenceElement)
77134
}
78135
else {
79136
setTimeout(() => lastFocusedElement instanceof HTMLElement && lastFocusedElement.focus())
80137
}
81138
})
82139
83140
useEventListener(
84-
() => props.target,
141+
() => props.target instanceof HTMLElement ? props.target : null,
85142
'click',
86143
() => {
87144
visible.value = !visible.value
@@ -96,10 +153,12 @@ useEventListener(
96153
class="popover"
97154
tabindex="-1"
98155
:target="target"
99-
v-bind="props.wowerlayOptions"
100156
:gap="2"
101-
noBackground
157+
:backgroundAttrs="{
158+
style: { zIndex: 1500 },
159+
}"
102160
:transition="handleTransition"
161+
v-bind="props.wowerlayOptions"
103162
@update:el="(el) => popoverEl = el"
104163
>
105164
<slot
@@ -121,7 +180,6 @@ useEventListener(
121180
display: flex;
122181
flex-direction: column;
123182
padding: 4px;
124-
--wowerlay-z: 1500;
125183
126184
> * + * {
127185
margin-top: 2px;

src/components/Tooltip.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { onScopeDispose, reactive, ref } from 'vue'
33
import { Wowerlay, type WowerlayProps, type WowerlayTransitionFn } from 'wowerlay'
44
import { useEventListener } from '@vueuse/core'
55
import { useTimeoutPool } from '../composables/useTimeoutPool'
6+
import { onPopoverVisible } from './Popover.vue'
67
78
interface Props {
89
text: string
@@ -96,6 +97,11 @@ useEventListener(targetGetter, 'focus', (e) => {
9697
onScopeDispose(() => {
9798
openTooltips.delete(id)
9899
})
100+
101+
onPopoverVisible(() => {
102+
visible.value = false
103+
openTooltips.delete(id)
104+
})
99105
</script>
100106

101107
<template>

0 commit comments

Comments
 (0)