diff options
Diffstat (limited to 'src/components/popover')
| -rw-r--r-- | src/components/popover/popover.js | 49 | ||||
| -rw-r--r-- | src/components/popover/popover.vue | 50 |
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); } |
