Popovers now close when clicked out or mouse strays 100px outside bounds (#129)

This commit is contained in:
Keavon Chambers 2021-05-19 01:02:58 -07:00 committed by GitHub
parent b23fa6d84f
commit 4ed093bb4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 21 deletions

View file

@ -76,8 +76,11 @@ import { defineComponent } from "vue";
export default defineComponent({
components: {},
props: {
open: { type: Boolean, default: false },
data() {
return {
open: false,
mouseStillDown: false,
};
},
updated() {
const popoverContent = this.$refs.popoverContent as HTMLElement;
@ -91,5 +94,88 @@ export default defineComponent({
if (bottomOffset < 0) popoverContent.style.top = `${bottomOffset}px`;
}
},
methods: {
setOpen() {
this.open = true;
},
setClosed() {
this.open = false;
},
mouseMoveHandler(e: MouseEvent) {
const MOUSE_STRAY_DISTANCE = 100;
// Close the popover if the mouse has strayed far enough from its bounds
if (this.isMouseEventOutsidePopover(e, MOUSE_STRAY_DISTANCE)) {
this.setClosed();
}
// eslint-disable-next-line no-bitwise
const eventIncludesLmb = Boolean(e.buttons & 1);
// Clean up any messes from lost mouseup events
if (!this.open && !eventIncludesLmb) {
this.mouseStillDown = false;
window.removeEventListener("mouseup", this.mouseUpHandler);
}
},
mouseDownHandler(e: MouseEvent) {
// Close the popover if the mouse clicked outside the popover (but within stray distance)
if (this.isMouseEventOutsidePopover(e)) {
this.setClosed();
// Track if the left mouse button is now down so its later click event can be canceled
const eventIsForLmb = e.button === 0;
if (eventIsForLmb) this.mouseStillDown = true;
}
},
mouseUpHandler(e: MouseEvent) {
const eventIsForLmb = e.button === 0;
if (this.mouseStillDown && eventIsForLmb) {
// Clean up self
this.mouseStillDown = false;
window.removeEventListener("mouseup", this.mouseUpHandler);
// Prevent the click event from firing, which would normally occur right after this mouseup event
window.addEventListener("click", this.clickHandlerCapture, true);
}
},
clickHandlerCapture(e: MouseEvent) {
// Stop the click event from reopening this popover if the click event targets the popover's button
e.stopPropagation();
// Clean up self
window.removeEventListener("click", this.clickHandlerCapture, true);
},
isMouseEventOutsidePopover(e: MouseEvent, extraDistanceAllowed = 0): boolean {
const popoverContent = this.$refs.popoverContent as HTMLElement;
const popoverBounds = popoverContent.getBoundingClientRect();
if (popoverBounds.left - e.clientX >= extraDistanceAllowed) return true;
if (e.clientX - popoverBounds.right >= extraDistanceAllowed) return true;
if (popoverBounds.top - e.clientY >= extraDistanceAllowed) return true;
if (e.clientY - popoverBounds.bottom >= extraDistanceAllowed) return true;
return false;
},
},
watch: {
open(newState: boolean, oldState: boolean) {
if (newState && !oldState) {
// Close popover if mouse strays far enough away
window.addEventListener("mousemove", this.mouseMoveHandler);
// Close popover if mouse is outside (but within stray distance)
window.addEventListener("mousedown", this.mouseDownHandler);
// Cancel the subsequent click event to prevent the popover from reopening if the popover's button is the click event target
window.addEventListener("mouseup", this.mouseUpHandler);
}
if (!newState && oldState) {
window.removeEventListener("mousemove", this.mouseMoveHandler);
window.removeEventListener("mousedown", this.mouseDownHandler);
}
},
},
});
</script>

View file

@ -1,13 +1,13 @@
<template>
<div class="working-colors">
<div class="swatch-pair">
<button @click="clickSwatch(SwatchSelection.Secondary)" class="secondary swatch" style="background: white">
<PopoverMount :open="swatchOpen === SwatchSelection.Secondary">
<button @click="clickSecondarySwatch" class="secondary swatch" style="background: white">
<PopoverMount ref="secondarySwatchPopover">
<ColorPicker />
</PopoverMount>
</button>
<button @click="clickSwatch(SwatchSelection.Primary)" class="primary swatch" style="background: black">
<PopoverMount :open="swatchOpen === SwatchSelection.Primary">
<button @click="clickPrimarySwatch" class="primary swatch" style="background: black">
<PopoverMount ref="primarySwatchPopover">
<ColorPicker />
</PopoverMount>
</button>
@ -66,12 +66,6 @@ import IconButton from "./IconButton.vue";
import SwapButton from "../../../assets/svg/16x16-bounds-12x12-icon/swap.svg";
import ResetColorsButton from "../../../assets/svg/16x16-bounds-12x12-icon/reset-colors.svg";
export enum SwatchSelection {
"None" = "None",
"Primary" = "Primary",
"Secondary" = "Secondary",
}
export default defineComponent({
components: {
PopoverMount,
@ -80,16 +74,14 @@ export default defineComponent({
SwapButton,
ResetColorsButton,
},
data() {
return {
swatchOpen: SwatchSelection.None,
SwatchSelection,
};
},
methods: {
clickSwatch(selection: SwatchSelection) {
if (this.swatchOpen !== selection) this.swatchOpen = selection;
else this.swatchOpen = SwatchSelection.None;
clickPrimarySwatch() {
(this.$refs.primarySwatchPopover as typeof PopoverMount).setOpen();
(this.$refs.secondarySwatchPopover as typeof PopoverMount).setClosed();
},
clickSecondarySwatch() {
(this.$refs.secondarySwatchPopover as typeof PopoverMount).setOpen();
(this.$refs.primarySwatchPopover as typeof PopoverMount).setClosed();
},
},
});