diff --git a/.gitignore b/.gitignore index e7dda6cb..bacbeeb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ npm-debug.log coverage/ +.idea/ dist/ diff --git a/README.md b/README.md index c6497f7e..5b24cadb 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ Or a reactive property: - `trigger` - Events triggering the tooltip separated with spaces: `'hover'`, `'click'`, `'focus'` or `'manual'` (`'manual'` can't be combined with any other event). - `show` - Boolean to manually open or hide the tooltip. - `offset` - Offset of the position (px). -- `container` - Selector: Container where the tooltip will be appended (e.g. `'body'`). Set it to `false` to append popover on target parent node. +- `container` - Selector: Container where the tooltip will be appended (e.g. `'body'`). Set it to `false` to append popover on target parent node. Provide a callback to resolve container dynamically, signature would be `callback(targetElement)`. - `boundariesElement` - DOM element for the tooltip boundaries. - `template` - HTML template of the tooltip. - `arrowSelector` - CSS selector to get the arrow element in the tooltip template. diff --git a/docs-src/PageHome.vue b/docs-src/PageHome.vue index 42216e75..93db6162 100644 --- a/docs-src/PageHome.vue +++ b/docs-src/PageHome.vue @@ -305,6 +305,10 @@ + + @@ -314,6 +318,7 @@ import screenfull from 'screenfull' import CodeSnippet from './CodeSnippet.vue' import Collapse from './Collapse.vue' import ExampleComponent from './ExampleComponent.vue' +import TooltipShadowDOMContainer from './shadow/TooltipShadowDOMContainer' const mainSnippet = ` import Vue from 'vue' @@ -612,6 +617,7 @@ export default { CodeSnippet, Collapse, ExampleComponent, + TooltipShadowDOMContainer }, data () { diff --git a/docs-src/shadow/TooltipShadowDOMContainer.vue b/docs-src/shadow/TooltipShadowDOMContainer.vue new file mode 100644 index 00000000..67543f92 --- /dev/null +++ b/docs-src/shadow/TooltipShadowDOMContainer.vue @@ -0,0 +1,38 @@ + + + diff --git a/docs-src/shadow/TooltipShadowDOMContent.vue b/docs-src/shadow/TooltipShadowDOMContent.vue new file mode 100644 index 00000000..70249620 --- /dev/null +++ b/docs-src/shadow/TooltipShadowDOMContent.vue @@ -0,0 +1,36 @@ + + + + + + + diff --git a/src/components/Popover.vue b/src/components/Popover.vue index 5b256ea5..de92e9ff 100644 --- a/src/components/Popover.vue +++ b/src/components/Popover.vue @@ -705,7 +705,7 @@ function handleGlobalClose (event, touch = false) { for (let i = 0; i < openPopovers.length; i++) { const popover = openPopovers[i] if (popover.$refs.popover) { - const contains = popover.$refs.popover.contains(event.target) + const contains = popover.$refs.popover.contains(event.composedPath()[0]) requestAnimationFrame(() => { if (event.closeAllPopover || (event.closePopover && contains) || (popover.autoHide && !contains)) { popover.$_handleGlobalClose(event, touch) diff --git a/src/lib/tooltip.js b/src/lib/tooltip.js index 1837f8ef..429e95de 100644 --- a/src/lib/tooltip.js +++ b/src/lib/tooltip.js @@ -2,7 +2,7 @@ import Popper from 'popper.js' import { getOptions, directive } from '../directives/v-tooltip' -import { addClasses, removeClasses, supportsPassive } from '../utils' +import { addClasses, isInDocument, removeClasses, supportsPassive } from '../utils' import isEqual from 'lodash/isEqual' const DEFAULT_OPTIONS = { @@ -287,8 +287,8 @@ export default class Tooltip { } _show (reference, options) { - if (options && typeof options.container === 'string') { - const container = document.querySelector(options.container) + if (options) { + const container = this._findContainer(options.container, reference) if (!container) return } @@ -490,6 +490,9 @@ export default class Tooltip { // if container is a query, get the relative element if (typeof container === 'string') { container = window.document.querySelector(container) + } else if (typeof container === 'function') { + // if container is function that returns an element, resolve it + container = container(reference) } else if (container === false) { // if container is `false`, set it to reference parent container = reference.parentNode @@ -578,9 +581,7 @@ export default class Tooltip { if (this._isOpen === false) { return } - if (!this._tooltipNode.ownerDocument.body.contains(this._tooltipNode)) { - return - } + if (!isInDocument(this._tooltipNode)) return // if we are hiding because of a mouseleave, we must check that the new // reference isn't the tooltip, because in this case we don't want to hide it diff --git a/src/utils.js b/src/utils.js index 2a1a1491..dc884b7e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -10,6 +10,25 @@ export function convertToArray (value) { return value } +/** + * Check if an element is in the DOM (Including the Shadow DOM). + * @param element + * @returns {boolean} + */ +export function isInDocument (element) { + let currentElement = element + while (currentElement && currentElement.parentNode) { + if (currentElement.parentNode === document) { + return true + } else if (currentElement.parentNode instanceof DocumentFragment) { + currentElement = currentElement.parentNode.host + } else { + currentElement = currentElement.parentNode + } + } + return false +} + /** * Add classes to an element. * This method checks to ensure that the classes don't already exist before adding them. diff --git a/types/index.d.ts b/types/index.d.ts index 7e619de0..8c373805 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -72,8 +72,7 @@ export interface GlobalVTooltipOptions { * Default container where the tooltip will be appended * @default 'body' */ - defaultContainer: string | HTMLElement | false - + defaultContainer: string | HTMLElement | false | ((targetElement: Element) => Element) defaultBoundariesElement: string | HTMLElement defaultPopperOptions: any @@ -203,4 +202,4 @@ export default vToolTip; export const VPopover: VueConstructor; export const VClosePopover: DirectiveOptions; -export const VTooltip: DirectiveOptions; \ No newline at end of file +export const VTooltip: DirectiveOptions;