Skip to content

Commit dd55c28

Browse files
committed
feat: added ability to center popup to screen
1 parent adccea6 commit dd55c28

File tree

2 files changed

+50
-15
lines changed

2 files changed

+50
-15
lines changed

src/components/LibPopup/LibPopup.stories.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,13 @@ export const Primary: Story = {
7272
<input type="checkbox" v-model="autoRotatePos">
7373
</div>
7474
<div class="test" style="display:grid;height:80vh;padding:50px;border:1px solid black;">
75+
7576
<lib-popup
7677
v-model="value"
78+
v-bind="{
79+
preferredHorizontal:args.preferredHorizontal,
80+
preferredVertical:args.preferredVertical
81+
}"
7782
>
7883
{{value}}
7984
<template #button="{extractEl}">
@@ -97,3 +102,12 @@ export const PopupTooBig = {
97102
width: "110vw",
98103
},
99104
}
105+
export const PopupCenterScreen = {
106+
...Primary,
107+
args: {
108+
...Primary,
109+
width: "500px",
110+
preferredHorizontal: ["center-screen"],
111+
preferredVertical: ["center-screen"],
112+
},
113+
}

src/components/LibPopup/LibPopup.vue

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@
1212
p-0
1313
backdrop:bg-transparent
1414
`,
15-
1615
attrs.class
1716
)"
1817
v-bind="{...attrs, class:undefined}"
1918
:is="useBackdrop ? 'dialog' : 'div'"
2019
ref="dialogEl"
21-
@mousedown="useBackdrop ? mousedown = true : undefined"
22-
@mouseup.self="useBackdrop && handleMouseup"
20+
@mousedown.self="handleMouseup"
2321
>
24-
<div v-if="useBackdrop || modelValue" class="scrollbar-hidden fixed overflow-scroll" :style="`top:${pos.y}px;left:${pos.x}px;${pos.maxWidth ? `max-width:${pos.maxWidth}px` : ''}`">
22+
<div v-if="useBackdrop || modelValue"
23+
class="scrollbar-hidden fixed overflow-scroll"
24+
:style="`
25+
top:${pos.y}px;
26+
left:${pos.x}px;
27+
${pos.maxWidth ? `max-width:${pos.maxWidth}px` : ''}
28+
${pos.maxHeight ? `max-height:${pos.maxHeight}px` : ''}
29+
`"
30+
>
2531
<slot
2632
name="popup"
2733
:position="pos"
@@ -33,7 +39,7 @@
3339
</template>
3440

3541
<script setup lang="ts">
36-
import { onMounted, type PropType, ref, watch } from "vue"
42+
import { onMounted, type PropType, ref, useAttrs, watch } from "vue"
3743
3844
import { twMerge } from "../../helpers/twMerge.js"
3945
import { linkableByIdProps } from "../shared/props.js"
@@ -42,8 +48,8 @@ import { linkableByIdProps } from "../shared/props.js"
4248
const props = defineProps({
4349
...linkableByIdProps(),
4450
useBackdrop: { type: Boolean, required: false, default: true },
45-
preferredHorizontal: { type: Array as PropType<("center" | "right" | "left" | "either")[]>, default: () => ["center", "right", "left", "either"]},
46-
preferredVertical: { type: Array as PropType<("top" | "bottom" | "either")[]>, default: () => ["top", "bottom", "either"]},
51+
preferredHorizontal: { type: Array as PropType<("center" | "right" | "left" | "either" | "center-screen")[]>, default: () => ["center", "right", "left", "either"]},
52+
preferredVertical: { type: Array as PropType<("top" | "bottom" | "either" | "center-screen")[]>, default: () => ["top", "bottom", "either"]},
4753
})
4854
const attrs = useAttrs()
4955
defineOptions({ name: "lib-popup" })
@@ -53,7 +59,7 @@ const dialogEl = ref<HTMLDialogElement | null>(null)
5359
const popupEl = ref<HTMLElement | null>(null)
5460
const buttonEl = ref<HTMLElement | null>(null)
5561
56-
const pos = ref<{ x: number, y: number, maxWidth?: number }>({} as any)
62+
const pos = ref<{ x: number, y: number, maxWidth?: number, maxHeight?: number }>({} as any)
5763
const modelValue = defineModel<boolean>({ default: false })
5864
let isOpen = false
5965
@@ -83,7 +89,7 @@ const recompute = (): void => {
8389
pos.value = {} as any
8490
return
8591
}
86-
const finalPos: { x: number, y: number, maxWidth: number } = {} as any
92+
const finalPos: { x: number, y: number, maxWidth?: number, maxHeight?: number } = {} as any
8793
8894
const el = buttonEl.value.getBoundingClientRect()
8995
const veil = getVeilBoundingRect(props.useBackdrop ? dialogEl.value : document.body)
@@ -97,10 +103,20 @@ const recompute = (): void => {
97103
const spaceBottom = (veil.y + veil.height) - (el.y + el.height)
98104
99105
const { preferredHorizontal, preferredVertical } = props
106+
let maxWidth: number
107+
let maxHeight: number
100108
/* eslint-disable no-labels */
101109
outerloop:
102110
for (const type of preferredHorizontal) {
103111
switch (type) {
112+
case "center-screen":
113+
if (popup.width < veil.width) {
114+
finalPos.x = (veil.width / 2) - (popup.width / 2)
115+
} else {
116+
finalPos.x = 0
117+
maxWidth = finalPos.x
118+
}
119+
break
104120
case "center":
105121
if (spaceLeftFromCenter >= (popup.width / 2) &&
106122
spaceRightFromCenter >= (popup.width / 2)) {
@@ -133,6 +149,14 @@ const recompute = (): void => {
133149
outerloop:
134150
for (const type of preferredVertical) {
135151
switch (type) {
152+
case "center-screen":
153+
if (popup.height < veil.height) {
154+
finalPos.y = (veil.height / 2) - (popup.height / 2)
155+
} else {
156+
finalPos.y = 0
157+
maxHeight = finalPos.y
158+
}
159+
break
136160
case "top":
137161
if (spaceTop >= popup.height) {
138162
finalPos.y = el.y - popup.height; break outerloop
@@ -150,7 +174,8 @@ const recompute = (): void => {
150174
}
151175
}
152176
}
153-
finalPos.maxWidth = veil.width - finalPos.x
177+
finalPos.maxWidth = maxWidth ?? veil.width - finalPos.x
178+
finalPos.maxHeight = maxHeight ?? veil.height - finalPos.y
154179
/* eslint-enable no-labels */
155180
pos.value = finalPos
156181
})
@@ -198,13 +223,9 @@ watch([() => modelValue.value, () => popupEl.value], () => {
198223
})
199224
200225
201-
const mousedown = ref(false)
202226
const handleMouseup = ($event: MouseEvent) => {
203227
$event.preventDefault()
204-
if (mousedown.value) {
205-
toggle()
206-
mousedown.value = false
207-
}
228+
toggle()
208229
}
209230
onMounted(() => {
210231
recompute()

0 commit comments

Comments
 (0)