|
1 | 1 | <template> |
2 | 2 | <div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open || type === 'Dialog'" ref="floatingMenu"> |
3 | | - <div class="tail" v-if="type === 'Popover'"></div> |
| 3 | + <div class="tail" v-if="type === 'Popover'" ref="tail"></div> |
4 | 4 | <div class="floating-menu-container" ref="floatingMenuContainer"> |
5 | 5 | <LayoutCol class="floating-menu-content" data-floating-menu-content :scrollableY="scrollableY" ref="floatingMenuContent" :style="floatingMenuContentStyle"> |
6 | 6 | <slot></slot> |
@@ -201,51 +201,74 @@ export default defineComponent({ |
201 | 201 | open: false, |
202 | 202 | pointerStillDown: false, |
203 | 203 | containerResizeObserver, |
| 204 | + workspaceBounds: new DOMRect(), |
| 205 | + floatingMenuBounds: new DOMRect(), |
| 206 | + floatingMenuContentBounds: new DOMRect(), |
204 | 207 | }; |
205 | 208 | }, |
| 209 | + // Gets the client bounds of the elements and apply relevant styles to them |
| 210 | + // TODO: Use the Vue :style attribute more whilst not causing recursive updates |
206 | 211 | updated() { |
| 212 | + const workspace = document.querySelector("[data-workspace]"); |
207 | 213 | const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement; |
208 | 214 | const floatingMenuContentComponent = this.$refs.floatingMenuContent as typeof LayoutCol; |
209 | 215 | const floatingMenuContent = floatingMenuContentComponent && (floatingMenuContentComponent.$el as HTMLElement); |
210 | | - const workspace = document.querySelector("[data-workspace]"); |
211 | | -
|
212 | | - if (!floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !workspace) return; |
213 | | -
|
214 | | - const workspaceBounds = workspace.getBoundingClientRect(); |
215 | | - const floatingMenuBounds = floatingMenuContent.getBoundingClientRect(); |
| 216 | + const floatingMenu = this.$refs.floatingMenu as HTMLElement; |
| 217 | +
|
| 218 | + if (!workspace || !floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !floatingMenu) return; |
| 219 | +
|
| 220 | + this.workspaceBounds = workspace.getBoundingClientRect(); |
| 221 | + this.floatingMenuBounds = floatingMenu.getBoundingClientRect(); |
| 222 | + this.floatingMenuContentBounds = floatingMenuContent.getBoundingClientRect(); |
| 223 | +
|
| 224 | + // Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping) |
| 225 | + const tailOffset = this.type === "Popover" ? 10 : 0; |
| 226 | + if (this.direction === "Bottom") floatingMenuContent.style.top = `${tailOffset + this.floatingMenuBounds.top}px`; |
| 227 | + if (this.direction === "Top") floatingMenuContent.style.bottom = `${tailOffset + this.floatingMenuBounds.bottom}px`; |
| 228 | + if (this.direction === "Right") floatingMenuContent.style.left = `${tailOffset + this.floatingMenuBounds.left}px`; |
| 229 | + if (this.direction === "Left") floatingMenuContent.style.right = `${tailOffset + this.floatingMenuBounds.right}px`; |
| 230 | +
|
| 231 | + // Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping) |
| 232 | + const tail = this.$refs.tail as HTMLElement; |
| 233 | + if (tail) { |
| 234 | + if (this.direction === "Bottom") tail.style.top = `${this.floatingMenuBounds.top}px`; |
| 235 | + if (this.direction === "Top") tail.style.bottom = `${this.floatingMenuBounds.bottom}px`; |
| 236 | + if (this.direction === "Right") tail.style.left = `${this.floatingMenuBounds.left}px`; |
| 237 | + if (this.direction === "Left") tail.style.right = `${this.floatingMenuBounds.right}px`; |
| 238 | + } |
216 | 239 |
|
217 | 240 | type Edge = "Top" | "Bottom" | "Left" | "Right"; |
218 | | - let zeroedBorderDirection1: Edge | undefined; |
219 | | - let zeroedBorderDirection2: Edge | undefined; |
| 241 | + let zeroedBorderVertical: Edge | undefined; |
| 242 | + let zeroedBorderHorizontal: Edge | undefined; |
220 | 243 |
|
221 | 244 | if (this.direction === "Top" || this.direction === "Bottom") { |
222 | | - zeroedBorderDirection1 = this.direction === "Top" ? "Bottom" : "Top"; |
| 245 | + zeroedBorderVertical = this.direction === "Top" ? "Bottom" : "Top"; |
223 | 246 |
|
224 | | - if (floatingMenuBounds.left - this.windowEdgeMargin <= workspaceBounds.left) { |
| 247 | + if (this.floatingMenuContentBounds.left - this.windowEdgeMargin <= this.workspaceBounds.left) { |
225 | 248 | floatingMenuContent.style.left = `${this.windowEdgeMargin}px`; |
226 | | - if (workspaceBounds.left + floatingMenuContainer.getBoundingClientRect().left === 12) zeroedBorderDirection2 = "Left"; |
| 249 | + if (this.workspaceBounds.left + floatingMenuContainer.getBoundingClientRect().left === 12) zeroedBorderHorizontal = "Left"; |
227 | 250 | } |
228 | | - if (floatingMenuBounds.right + this.windowEdgeMargin >= workspaceBounds.right) { |
| 251 | + if (this.floatingMenuContentBounds.right + this.windowEdgeMargin >= this.workspaceBounds.right) { |
229 | 252 | floatingMenuContent.style.right = `${this.windowEdgeMargin}px`; |
230 | | - if (workspaceBounds.right - floatingMenuContainer.getBoundingClientRect().right === 12) zeroedBorderDirection2 = "Right"; |
| 253 | + if (this.workspaceBounds.right - floatingMenuContainer.getBoundingClientRect().right === 12) zeroedBorderHorizontal = "Right"; |
231 | 254 | } |
232 | 255 | } |
233 | 256 | if (this.direction === "Left" || this.direction === "Right") { |
234 | | - zeroedBorderDirection2 = this.direction === "Left" ? "Right" : "Left"; |
| 257 | + zeroedBorderHorizontal = this.direction === "Left" ? "Right" : "Left"; |
235 | 258 |
|
236 | | - if (floatingMenuBounds.top - this.windowEdgeMargin <= workspaceBounds.top) { |
| 259 | + if (this.floatingMenuContentBounds.top - this.windowEdgeMargin <= this.workspaceBounds.top) { |
237 | 260 | floatingMenuContent.style.top = `${this.windowEdgeMargin}px`; |
238 | | - if (workspaceBounds.top + floatingMenuContainer.getBoundingClientRect().top === 12) zeroedBorderDirection1 = "Top"; |
| 261 | + if (this.workspaceBounds.top + floatingMenuContainer.getBoundingClientRect().top === 12) zeroedBorderVertical = "Top"; |
239 | 262 | } |
240 | | - if (floatingMenuBounds.bottom + this.windowEdgeMargin >= workspaceBounds.bottom) { |
| 263 | + if (this.floatingMenuContentBounds.bottom + this.windowEdgeMargin >= this.workspaceBounds.bottom) { |
241 | 264 | floatingMenuContent.style.bottom = `${this.windowEdgeMargin}px`; |
242 | | - if (workspaceBounds.bottom - floatingMenuContainer.getBoundingClientRect().bottom === 12) zeroedBorderDirection1 = "Bottom"; |
| 265 | + if (this.workspaceBounds.bottom - floatingMenuContainer.getBoundingClientRect().bottom === 12) zeroedBorderVertical = "Bottom"; |
243 | 266 | } |
244 | 267 | } |
245 | 268 |
|
246 | | - // Remove the rounded corner from where the tail perfectly meets the corner |
247 | | - if (this.type === "Popover" && this.windowEdgeMargin === 6 && zeroedBorderDirection1 && zeroedBorderDirection2) { |
248 | | - switch (`${zeroedBorderDirection1}${zeroedBorderDirection2}`) { |
| 269 | + // Remove the rounded corner from the content where the tail perfectly meets the corner |
| 270 | + if (this.type === "Popover" && this.windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) { |
| 271 | + switch (`${zeroedBorderVertical}${zeroedBorderHorizontal}`) { |
249 | 272 | case "TopLeft": |
250 | 273 | floatingMenuContent.style.borderTopLeftRadius = "0"; |
251 | 274 | break; |
@@ -375,6 +398,7 @@ export default defineComponent({ |
375 | 398 | } |
376 | 399 | }); |
377 | 400 | } |
| 401 | +
|
378 | 402 | // Switching from open to closed |
379 | 403 | if (!newState && oldState) { |
380 | 404 | window.removeEventListener("pointermove", this.pointerMoveHandler); |
|
0 commit comments