Skip to content

Commit f9a493e

Browse files
committed
feat(popup): exposed function to set the background element
This is helpful in some complex cases to constrain the position to be inside some element.
1 parent 26d17b3 commit f9a493e

File tree

2 files changed

+37
-29
lines changed

2 files changed

+37
-29
lines changed

src/components/LibPopup/LibPopup.vue

+35-27
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { getFallbackId, type LinkableByIdProps,type TailwindClassProp } from "..
4646
import { twMerge } from "../../helpers/twMerge.js"
4747
import { castType } from "@alanscodelog/utils/castType.js"
4848
import { isArray } from "@alanscodelog/utils/isArray.js"
49-
import type { IPopupReference, PopupPosition, PopupPositioner, PopupPositionModifier, PopupSpaceInfo } from "../../types.js"
49+
import type { IPopupReference, PopupPosition, PopupPositioner, PopupPositionModifier, PopupSpaceInfo, SimpleDOMRect } from "../../types.js"
5050

5151
const fallbackId = getFallbackId()
5252
// eslint-disable-next-line no-use-before-define
@@ -65,19 +65,19 @@ defineOptions({ name: "lib-popup" })
6565
const dialogEl = ref<HTMLDialogElement | null>(null)
6666
const popupEl = ref<IPopupReference | null>(null)
6767
const buttonEl = ref<IPopupReference | null>(null)
68+
const backgroundEl = ref<IPopupReference | null>(null)
6869

6970
const pos = ref<PopupPosition>({} as any)
7071
const modelValue = defineModel<boolean>({ default: false })
7172
let isOpen = false
7273

7374

7475
/**
75-
* We don't have access to the dialog backdrop and without extra styling, it's of 0 width/height,
76-
* positioned in the center of the screen, with margins taking up all the space.
76+
* We don't have access to the dialog backdrop and without extra styling, it's of 0 width/height, positioned in the center of the screen, with margins taking up all the space.
7777
*
78-
* This returns a modified rect that makes more logical sense. It's also available when we aren't using the dialog element.
78+
* This returns a modified rect that makes more logical sense.
7979
*/
80-
const getVeilBoundingRect = (el: HTMLElement): Omit<DOMRect, "toJSON"> => {
80+
const getDialogBoundingRect = (el: HTMLElement): SimpleDOMRect => {
8181
const rect = el.getBoundingClientRect()
8282
return {
8383
x: 0,
@@ -90,7 +90,7 @@ const getVeilBoundingRect = (el: HTMLElement): Omit<DOMRect, "toJSON"> => {
9090
right: 0,
9191
}
9292
}
93-
let lastButtonElPos: ReturnType<IPopupReference["getBoundingClientRect"]> | undefined
93+
let lastButtonElPos: SimpleDOMRect | undefined
9494
const recompute = (force: boolean = false): void => {
9595
requestAnimationFrame(() => {
9696
const horzHasCenterScreen = isArray(props.preferredHorizontal)
@@ -107,7 +107,11 @@ const recompute = (force: boolean = false): void => {
107107
return
108108
}
109109
const el = buttonEl.value?.getBoundingClientRect()
110-
const veil = getVeilBoundingRect(props.useBackdrop ? dialogEl.value : document.body)
110+
const bg = backgroundEl.value?.getBoundingClientRect() ?? (
111+
props.useBackdrop
112+
? getDialogBoundingRect(dialogEl.value)
113+
: document.body.getBoundingClientRect()
114+
)
111115
const popup = popupEl.value.getBoundingClientRect()
112116

113117
let finalPos: { x: number, y: number, maxWidth?: number, maxHeight?: number } = {} as any
@@ -136,30 +140,30 @@ const recompute = (force: boolean = false): void => {
136140
bottom: 0,
137141
}
138142
if (el) {
139-
space.left = (el.x + el.width) - veil.x
140-
space.leftLeft = el.x - veil.x
141-
space.right = (veil.x + veil.width) - (el.x + el.width)
142-
space.rightRight = veil.x + veil.width - el.x
143-
space.leftFromCenter = (el.x + (el.width / 2)) - veil.x
144-
space.rightFromCenter = (veil.x + veil.width) - (el.x + (el.width / 2))
145-
space.topFromCenter = (el.y + (el.height / 2)) - veil.y
146-
space.bottomFromCenter = (veil.y + veil.height) - (el.y + (el.height / 2))
147-
space.top = el.y - veil.y
148-
space.bottom = (veil.y + veil.height) - (el.y + el.height)
143+
space.left = (el.x + el.width) - bg.x
144+
space.leftLeft = el.x - bg.x
145+
space.right = (bg.x + bg.width) - (el.x + el.width)
146+
space.rightRight = bg.x + bg.width - el.x
147+
space.leftFromCenter = (el.x + (el.width / 2)) - bg.x
148+
space.rightFromCenter = (bg.x + bg.width) - (el.x + (el.width / 2))
149+
space.topFromCenter = (el.y + (el.height / 2)) - bg.y
150+
space.bottomFromCenter = (bg.y + bg.height) - (el.y + (el.height / 2))
151+
space.top = el.y - bg.y
152+
space.bottom = (bg.y + bg.height) - (el.y + el.height)
149153
}
150154
const { preferredHorizontal, preferredVertical } = props
151155
let maxWidth: number | undefined
152156
let maxHeight: number | undefined
153157
if (typeof preferredHorizontal === "function") {
154-
finalPos.x = preferredHorizontal(el, popup, veil, space)
158+
finalPos.x = preferredHorizontal(el, popup, bg, space)
155159
} else {
156160
/* eslint-disable no-labels */
157161
outerloop:
158162
for (const type of preferredHorizontal) {
159163
switch (type) {
160164
case "center-screen":
161-
if (popup.width < veil.width) {
162-
finalPos.x = (veil.width / 2) - (popup.width / 2)
165+
if (popup.width < bg.width) {
166+
finalPos.x = (bg.width / 2) - (popup.width / 2)
163167
} else {
164168
finalPos.x = 0
165169
maxWidth = finalPos.x
@@ -198,7 +202,7 @@ const recompute = (force: boolean = false): void => {
198202
if (space.right >= popup.width) {
199203
finalPos.x = el.x + el.width; break outerloop
200204
} else {
201-
finalPos.x = veil.x + veil.width - popup.width; break outerloop
205+
finalPos.x = bg.x + bg.width - popup.width; break outerloop
202206
}
203207

204208
case "right":
@@ -226,14 +230,14 @@ const recompute = (force: boolean = false): void => {
226230
}
227231
}
228232
if (typeof preferredVertical === "function") {
229-
finalPos.y = preferredVertical(el, popup, veil, space)
233+
finalPos.y = preferredVertical(el, popup, bg, space)
230234
} else {
231235
outerloop:
232236
for (const type of preferredVertical) {
233237
switch (type) {
234238
case "center-screen":
235-
if (popup.height < veil.height) {
236-
finalPos.y = (veil.height / 2) - (popup.height / 2)
239+
if (popup.height < bg.height) {
240+
finalPos.y = (bg.height / 2) - (popup.height / 2)
237241
} else {
238242
finalPos.y = 0
239243
maxHeight = finalPos.y
@@ -263,7 +267,7 @@ const recompute = (force: boolean = false): void => {
263267
if (space.bottom >= popup.height) {
264268
finalPos.y = el.y + el.height; break outerloop
265269
} else {
266-
finalPos.y = veil.y + veil.height - popup.height; break outerloop
270+
finalPos.y = bg.y + bg.height - popup.height; break outerloop
267271
}
268272
case "center-most":
269273
case "center":
@@ -299,7 +303,7 @@ const recompute = (force: boolean = false): void => {
299303
finalPos.maxHeight = maxHeight ?? undefined
300304
/* eslint-enable no-labels */
301305
if (props.modifyPosition) {
302-
finalPos = props.modifyPosition(finalPos, el, popup, veil, space)
306+
finalPos = props.modifyPosition(finalPos, el, popup, bg, space)
303307
}
304308
pos.value = finalPos
305309
lastButtonElPos = el
@@ -363,7 +367,11 @@ defineExpose({
363367
recompute,
364368
setReference: (el: IPopupReference | null) => {
365369
buttonEl.value = el
366-
}
370+
},
371+
setBackground: (el: IPopupReference | null) => {
372+
backgroundEl.value = el
373+
},
374+
367375
})
368376

369377
</script>

src/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export type PopupPositioner = (
9393
/** Reference is only undefined, if you did not specify a button element or use the exposed setReference. The function is still called, because there are other ways you might want to still position the popup (e.g. center-screen or some similar variation). */
9494
reference: SimpleDOMRect | undefined,
9595
popup: SimpleDOMRect | DOMRect,
96-
veil: SimpleDOMRect | DOMRect,
96+
bg: SimpleDOMRect | DOMRect,
9797
space: PopupSpaceInfo
9898
) => number
9999

@@ -102,6 +102,6 @@ export type PopupPositionModifier = (
102102
/** This will only be called with the reference element as undefined when one of the preferred positions is center-screen or it's a function. */
103103
reference: SimpleDOMRect | undefined,
104104
popup: SimpleDOMRect | DOMRect,
105-
veil: SimpleDOMRect | DOMRect,
105+
bg: SimpleDOMRect | DOMRect,
106106
space: PopupSpaceInfo
107107
) => PopupPosition

0 commit comments

Comments
 (0)