aboutsummaryrefslogtreecommitdiff
path: root/src/components/popover
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/popover')
-rw-r--r--src/components/popover/popover.js49
-rw-r--r--src/components/popover/popover.vue50
2 files changed, 77 insertions, 22 deletions
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index d2af59fe..d44b266b 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -4,7 +4,7 @@ const Popover = {
// Action to trigger popover: either 'hover' or 'click'
trigger: String,
- // Either 'top' or 'bottom'
+ // 'top', 'bottom', 'left', 'right'
placement: String,
// Takes object with properties 'x' and 'y', values of these can be
@@ -43,7 +43,12 @@ const Popover = {
overlayCentersSelector: String,
// Lets hover popover stay when clicking inside of it
- stayOnClick: Boolean
+ stayOnClick: Boolean,
+
+ triggerAttrs: {
+ type: Object,
+ default: {}
+ }
},
inject: ['popoversZLayer'], // override popover z layer
data () {
@@ -51,6 +56,10 @@ const Popover = {
// lockReEntry is a flag that is set when mouse cursor is leaving the popover's content
// so that if mouse goes back into popover it won't be re-shown again to prevent annoyance
// with popovers refusing to be hidden when user wants to interact with something in below popover
+ anchorEl: null,
+ // There's an issue where having teleport enabled by default causes things just...
+ // not render at all, i.e. main post status form and its emoji inputs
+ teleport: false,
lockReEntry: false,
hidden: true,
styles: {},
@@ -59,10 +68,15 @@ const Popover = {
// used to avoid blinking if hovered onto popover
graceTimeout: null,
parentPopover: null,
+ disableClickOutside: false,
childrenShown: new Set()
}
},
methods: {
+ setAnchorEl (el) {
+ this.anchorEl = el
+ this.updateStyles()
+ },
containerBoundingClientRect () {
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
return container.getBoundingClientRect()
@@ -75,7 +89,7 @@ const Popover = {
// Popover will be anchored around this element, trigger ref is the container, so
// its children are what are inside the slot. Expect only one v-slot:trigger.
- const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
+ const anchorEl = this.anchorEl || (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
// SVGs don't have offsetWidth/Height, use fallback
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
@@ -84,6 +98,8 @@ const Popover = {
const anchorStyle = getComputedStyle(anchorEl)
const topPadding = parseFloat(anchorStyle.paddingTop)
const bottomPadding = parseFloat(anchorStyle.paddingBottom)
+ const rightPadding = parseFloat(anchorStyle.paddingRight)
+ const leftPadding = parseFloat(anchorStyle.paddingLeft)
// Screen position of the origin point for popover = center of the anchor
const origin = {
@@ -170,7 +186,7 @@ const Popover = {
if (overlayCenter) {
translateX = origin.x + horizOffset
translateY = origin.y + vertOffset
- } else {
+ } else if (this.placement !== 'right' && this.placement !== 'left') {
// Default to whatever user wished with placement prop
let usingTop = this.placement !== 'bottom'
@@ -189,6 +205,25 @@ const Popover = {
const xOffset = (this.offset && this.offset.x) || 0
translateX = origin.x + horizOffset + xOffset
+ } else {
+ // Default to whatever user wished with placement prop
+ let usingRight = this.placement !== 'left'
+
+ // Handle special cases, first force to displaying on top if there's not space on bottom,
+ // regardless of what placement value was. Then check if there's not space on top, and
+ // force to bottom, again regardless of what placement value was.
+ const rightBoundary = origin.x - anchorWidth * 0.5 + (this.removePadding ? rightPadding : 0)
+ const leftBoundary = origin.x + anchorWidth * 0.5 - (this.removePadding ? leftPadding : 0)
+ if (leftBoundary + content.offsetWidth > xBounds.max) usingRight = true
+ if (rightBoundary - content.offsetWidth < xBounds.min) usingRight = false
+
+ const xOffset = (this.offset && this.offset.x) || 0
+ translateX = usingRight
+ ? rightBoundary - xOffset - content.offsetWidth
+ : leftBoundary + xOffset
+
+ const yOffset = (this.offset && this.offset.y) || 0
+ translateY = origin.y + vertOffset + yOffset
}
this.styles = {
@@ -205,6 +240,10 @@ const Popover = {
},
showPopover () {
if (this.disabled) return
+ this.disableClickOutside = true
+ setTimeout(() => {
+ this.disableClickOutside = false
+ }, 0)
const wasHidden = this.hidden
this.hidden = false
this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
@@ -265,6 +304,7 @@ const Popover = {
}
},
onClickOutside (e) {
+ if (this.disableClickOutside) return
if (this.hidden) return
if (this.$refs.content && this.$refs.content.contains(e.target)) return
if (this.$el.contains(e.target)) return
@@ -298,6 +338,7 @@ const Popover = {
}
},
mounted () {
+ this.teleport = true
let scrollable = this.$refs.trigger.closest('.column.-scrollable') ||
this.$refs.trigger.closest('.mobile-notifications')
if (!scrollable) scrollable = window
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index bd59cade..fd0fd821 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -7,11 +7,15 @@
ref="trigger"
class="button-unstyled popover-trigger-button"
type="button"
+ v-bind="triggerAttrs"
@click="onClick"
>
<slot name="trigger" />
</button>
- <teleport to="#popovers">
+ <teleport
+ :disabled="!teleport"
+ to="#popovers"
+ >
<transition name="fade">
<div
v-if="!hidden"
@@ -37,7 +41,7 @@
<script src="./popover.js" />
<style lang="scss">
-@import '../../_variables.scss';
+@import "../../variables";
.popover-trigger-button {
display: inline-block;
@@ -48,31 +52,31 @@
position: fixed;
min-width: 0;
max-width: calc(100vw - 20px);
- box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
+ box-shadow: 2px 2px 3px rgb(0 0 0 / 50%);
box-shadow: var(--popupShadow);
}
.popover-default {
- &:after {
- content: '';
+ &::after {
+ content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
- box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
+ box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
box-shadow: var(--panelShadow);
pointer-events: none;
}
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
-
background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg);
color: $fallback--text;
color: var(--popoverText, $fallback--text);
+
--faint: var(--popoverFaintText, $fallback--faint);
--faintLink: var(--popoverFaintLink, $fallback--faint);
--lightText: var(--popoverLightText, $fallback--lightText);
@@ -83,7 +87,7 @@
.dropdown-menu {
display: block;
- padding: .5rem 0;
+ padding: 0.5rem 0;
font-size: 1em;
text-align: left;
list-style: none;
@@ -93,7 +97,7 @@
.dropdown-divider {
height: 0;
- margin: .5rem 0;
+ margin: 0.5rem 0;
overflow: hidden;
border-top: 1px solid $fallback--border;
border-top: 1px solid var(--border, $fallback--border);
@@ -109,7 +113,7 @@
text-align: inherit;
white-space: nowrap;
border: none;
- border-radius: 0px;
+ border-radius: 0;
background-color: transparent;
box-shadow: none;
width: 100%;
@@ -122,21 +126,32 @@
svg {
width: 22px;
margin-right: 0.75rem;
- color: var(--menuPopoverIcon, $fallback--icon)
+ color: var(--menuPopoverIcon, $fallback--icon);
+ }
+ }
+
+ &.-has-submenu {
+ .chevron-icon {
+ margin-right: 0.25rem;
+ margin-left: 2rem;
}
}
- &:active, &:hover {
+ &:active,
+ &:hover {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
box-shadow: none;
+
--btnText: var(--selectedMenuPopoverText, $fallback--link);
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
+
svg {
color: var(--selectedMenuPopoverIcon, $fallback--icon);
+
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
}
}
@@ -150,16 +165,16 @@
max-height: 22px;
line-height: 22px;
text-align: center;
- border-radius: 0px;
+ border-radius: 0;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
- box-shadow: 0px 0px 2px black inset;
+ box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
margin-right: 0.75em;
&.menu-checkbox-checked::after {
font-size: 1.25em;
- content: '✓';
+ content: "✓";
}
&.-radio {
@@ -167,16 +182,15 @@
&.menu-checkbox-checked::after {
font-size: 2em;
- content: '•';
+ content: "•";
}
}
}
-
}
.button-default.dropdown-item {
&,
- i[class*=icon-] {
+ i[class*="icon-"] {
color: $fallback--text;
color: var(--btnText, $fallback--text);
}