diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 368092de45c9..3f744ef6a834 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -9,7 +9,7 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import SelectorEngine from './dom/selector-engine.js' import { - defineJQueryPlugin, getElement, isDisabled, isVisible + defineJQueryPlugin, getElement, isDisabled, isVisible, parseSelector } from './util/index.js' /** @@ -210,11 +210,13 @@ class ScrollSpy extends BaseComponent { continue } - const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element) + const withDecodeUri = decodeURI(anchor.hash) + const withEscape = parseSelector(withDecodeUri) + const observableSection = SelectorEngine.findOne(withEscape, this._element) // ensure that the observableSection exists & is visible if (isVisible(observableSection)) { - this._targetLinks.set(decodeURI(anchor.hash), anchor) + this._targetLinks.set(withDecodeUri, anchor) this._observableSections.set(anchor.hash, observableSection) } } diff --git a/js/src/util/index.js b/js/src/util/index.js index c271cc5368ca..e4276ef5e83d 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -17,7 +17,7 @@ const TRANSITION_END = 'transitionend' const parseSelector = selector => { if (selector && window.CSS && window.CSS.escape) { // document.querySelector needs escaping to handle IDs (html5+) containing for instance / - selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`) + selector = selector.replace(/#([^\s"']+)/g, (match, id) => `#${CSS.escape(id)}`) } return selector diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js index fc44471c42da..1c227fc10036 100644 --- a/js/tests/unit/scrollspy.spec.js +++ b/js/tests/unit/scrollspy.spec.js @@ -194,6 +194,34 @@ describe('ScrollSpy', () => { expect(scrollSpy._targetLinks.size).toBe(1) }) + it('should take account of escaped IDs', () => { + fixtureEl.innerHTML = [ + '', + '