]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Allow Tooltips/Popovers to work in shadow DOM
authorJohann-S <johann.servoire@gmail.com>
Thu, 30 Nov 2017 09:54:27 +0000 (10:54 +0100)
committerJohann-S <johann.servoire@gmail.com>
Wed, 5 Dec 2018 15:02:59 +0000 (16:02 +0100)
js/src/tooltip.js
js/src/util.js
js/tests/unit/util.js
js/tests/visual/tooltip.html
site/docs/4.1/components/popovers.md
site/docs/4.1/components/tooltips.md

index 1c40dfed341973ecb4a209135515fcd9e453aa9e..9fa53a71b44803b6ca449b8d8828381140c8d38d 100644 (file)
@@ -244,8 +244,9 @@ class Tooltip {
     if (this.isWithContent() && this._isEnabled) {
       $(this.element).trigger(showEvent)
 
+      const shadowRoot = Util.findShadowRoot(this.element)
       const isInTheDom = $.contains(
-        this.element.ownerDocument.documentElement,
+        shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement,
         this.element
       )
 
index e9665d24fdef99c917f92219a9c9067b3242b513..1d804cf9da9a6cc954ce5af4063d33bf11bbc83c 100644 (file)
@@ -142,6 +142,29 @@ const Util = {
         }
       }
     }
+  },
+
+  findShadowRoot(element) {
+    if (!document.documentElement.attachShadow) {
+      return null
+    }
+
+    // Can find the shadow root otherwise it'll return the document
+    if (typeof element.getRootNode === 'function') {
+      const root = element.getRootNode()
+      return root instanceof ShadowRoot ? root : null
+    }
+
+    if (element instanceof ShadowRoot) {
+      return element
+    }
+
+    // when we don't find a shadow root
+    if (!element.parentNode) {
+      return null
+    }
+
+    return Util.findShadowRoot(element.parentNode)
   }
 }
 
index 61727304d420405f1960694c3db8081603150e97..2fd6f6b7cb52bb670f57236f17bf85248ce687be 100644 (file)
@@ -124,4 +124,42 @@ $(function () {
     assert.expect(1)
     assert.ok(Util.supportsTransitionEnd())
   })
+
+  QUnit.test('Util.findShadowRoot should find the shadow DOM root', function (assert) {
+    // Only for newer browsers
+    if (!document.documentElement.attachShadow) {
+      assert.expect(0)
+      return
+    }
+
+    assert.expect(2)
+    var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
+    var shadowRoot = $div[0].attachShadow({
+      mode: 'open'
+    })
+    console.warn($div[0].attachShadow, shadowRoot)
+
+    assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot))
+    shadowRoot.innerHTML = '<button>Shadow Button</button>'
+    assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot.firstChild))
+  })
+
+  QUnit.test('Util.findShadowRoot should return null when attachShadow is not available', function (assert) {
+    assert.expect(1)
+
+    var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
+    if (!document.documentElement.attachShadow) {
+      assert.equal(null, Util.findShadowRoot($div[0]))
+    } else {
+      var sandbox = sinon.createSandbox()
+
+      sandbox.replace(document.documentElement, 'attachShadow', function () {
+        // to avoid empty function
+        return $div
+      })
+
+      assert.equal(null, Util.findShadowRoot($div[0]))
+      sandbox.restore()
+    }
+  })
 })
index 436f5c1cd8b0e2e6e771f2886416041613dfd16c..831f26d0a7b4f9f5b07198d0031fcaeecf62c31f 100644 (file)
           </button>
         </p>
       </div>
-      <div id="target" title="Test tooltip on transformed element"></div>
+      <div class="row">
+        <div class="col-sm-3">
+          <div id="target" title="Test tooltip on transformed element"></div>
+        </div>
+        <div id="shadow" class="pt-5"></div>
+      </div>
       <div id="customContainer"></div>
     </div>
 
     <script src="../../dist/tooltip.js"></script>
     <script>
       $(function () {
+        if (typeof document.body.attachShadow === 'function') {
+          var shadowRoot = $('#shadow')[0].attachShadow({ mode: 'open' })
+          shadowRoot.innerHTML =
+            '<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top in a shadow dom">' +
+            '  Tooltip on top in a shadow dom' +
+            '</button>' +
+            '<button id="secondTooltip" type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top in a shadow dom with container option">' +
+            '  Tooltip on top in a shadow dom' +
+            '</button>'
+
+          $(shadowRoot.firstChild).tooltip()
+          $(shadowRoot.getElementById('secondTooltip')).tooltip({
+            container: shadowRoot
+          })
+        }
         $('[data-toggle="tooltip"]').tooltip()
         $('#tooltipElement').tooltip({
           container: $('#customContainer')[0]
index f55826d7cae4392c6e54a1a855e14a2fea44fa8d..1d407a90146ef1e2558db688bee27421d9cda461 100644 (file)
@@ -20,6 +20,7 @@ Things to know when using the popover plugin:
 - Popovers for `.disabled` or `disabled` elements must be triggered on a wrapper element.
 - When triggered from anchors that wrap across multiple lines, popovers will be centered between the anchors' overall width. Use `.text-nowrap` on your `<a>`s to avoid this behavior.
 - Popovers must be hidden before their corresponding elements have been removed from the DOM.
+- Popovers can be triggered thanks to an element inside a shadow DOM.
 
 {% include callout-info-prefersreducedmotion.md %}
 
index aa3ebe077cf26606ffc523278c3887ccf6a645df..e49db563479a3d7fc96d5bf5178aa716cb8a5d97 100644 (file)
@@ -19,6 +19,7 @@ Things to know when using the tooltip plugin:
 - Tooltips for `.disabled` or `disabled` elements must be triggered on a wrapper element.
 - When triggered from hyperlinks that span multiple lines, tooltips will be centered. Use `white-space: nowrap;` on your `<a>`s to avoid this behavior.
 - Tooltips must be hidden before their corresponding elements have been removed from the DOM.
+- Tooltips can be triggered thanks to an element inside a shadow DOM.
 
 {% include callout-info-prefersreducedmotion.md %}