mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 07:28:11 +08:00
Popovers now close when clicked out or mouse strays 100px outside bounds (#129)
This commit is contained in:
@@ -76,8 +76,11 @@ import { defineComponent } from "vue";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: {},
|
||||||
props: {
|
data() {
|
||||||
open: { type: Boolean, default: false },
|
return {
|
||||||
|
open: false,
|
||||||
|
mouseStillDown: false,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
const popoverContent = this.$refs.popoverContent as HTMLElement;
|
const popoverContent = this.$refs.popoverContent as HTMLElement;
|
||||||
@@ -91,5 +94,88 @@ export default defineComponent({
|
|||||||
if (bottomOffset < 0) popoverContent.style.top = `${bottomOffset}px`;
|
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>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="working-colors">
|
<div class="working-colors">
|
||||||
<div class="swatch-pair">
|
<div class="swatch-pair">
|
||||||
<button @click="clickSwatch(SwatchSelection.Secondary)" class="secondary swatch" style="background: white">
|
<button @click="clickSecondarySwatch" class="secondary swatch" style="background: white">
|
||||||
<PopoverMount :open="swatchOpen === SwatchSelection.Secondary">
|
<PopoverMount ref="secondarySwatchPopover">
|
||||||
<ColorPicker />
|
<ColorPicker />
|
||||||
</PopoverMount>
|
</PopoverMount>
|
||||||
</button>
|
</button>
|
||||||
<button @click="clickSwatch(SwatchSelection.Primary)" class="primary swatch" style="background: black">
|
<button @click="clickPrimarySwatch" class="primary swatch" style="background: black">
|
||||||
<PopoverMount :open="swatchOpen === SwatchSelection.Primary">
|
<PopoverMount ref="primarySwatchPopover">
|
||||||
<ColorPicker />
|
<ColorPicker />
|
||||||
</PopoverMount>
|
</PopoverMount>
|
||||||
</button>
|
</button>
|
||||||
@@ -66,12 +66,6 @@ import IconButton from "./IconButton.vue";
|
|||||||
import SwapButton from "../../../assets/svg/16x16-bounds-12x12-icon/swap.svg";
|
import SwapButton from "../../../assets/svg/16x16-bounds-12x12-icon/swap.svg";
|
||||||
import ResetColorsButton from "../../../assets/svg/16x16-bounds-12x12-icon/reset-colors.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({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
PopoverMount,
|
PopoverMount,
|
||||||
@@ -80,16 +74,14 @@ export default defineComponent({
|
|||||||
SwapButton,
|
SwapButton,
|
||||||
ResetColorsButton,
|
ResetColorsButton,
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
swatchOpen: SwatchSelection.None,
|
|
||||||
SwatchSelection,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
clickSwatch(selection: SwatchSelection) {
|
clickPrimarySwatch() {
|
||||||
if (this.swatchOpen !== selection) this.swatchOpen = selection;
|
(this.$refs.primarySwatchPopover as typeof PopoverMount).setOpen();
|
||||||
else this.swatchOpen = SwatchSelection.None;
|
(this.$refs.secondarySwatchPopover as typeof PopoverMount).setClosed();
|
||||||
|
},
|
||||||
|
clickSecondarySwatch() {
|
||||||
|
(this.$refs.secondarySwatchPopover as typeof PopoverMount).setOpen();
|
||||||
|
(this.$refs.primarySwatchPopover as typeof PopoverMount).setClosed();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user