diff --git a/packages/ui-drilldown/src/Drilldown/__new-tests__/Drilldown.test.tsx b/packages/ui-drilldown/src/Drilldown/__new-tests__/Drilldown.test.tsx
index 8cec5e08d3..f4fc6537fb 100644
--- a/packages/ui-drilldown/src/Drilldown/__new-tests__/Drilldown.test.tsx
+++ b/packages/ui-drilldown/src/Drilldown/__new-tests__/Drilldown.test.tsx
@@ -104,6 +104,33 @@ describe('', () => {
expect(options.length).toBe(0)
expect(notAllowedChild).not.toBeInTheDocument()
})
+
+ it('should not crash for weird option ids', async () => {
+ const onSelect = vi.fn()
+ const weirdID = 'some"_weird!@#$%^&*()\\|`id'
+ render(
+
+
+ {data.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+ )
+ const option_1 = screen.getByTestId(weirdID + 'opt_1')
+ await userEvent.click(option_1)
+
+ await waitFor(() => {
+ expect(onSelect).toHaveBeenCalled()
+ })
+ })
})
describe('id prop', () => {
diff --git a/packages/ui-drilldown/src/Drilldown/index.tsx b/packages/ui-drilldown/src/Drilldown/index.tsx
index 725ea55850..e358302ed6 100644
--- a/packages/ui-drilldown/src/Drilldown/index.tsx
+++ b/packages/ui-drilldown/src/Drilldown/index.tsx
@@ -585,9 +585,8 @@ class Drilldown extends Component {
focusOption(id: string) {
const container = this._containerElement
-
const optionElement = container?.querySelector(
- `[id="${id}"]`
+ `[id="${CSS.escape(id)}"]`
) as HTMLSpanElement
optionElement?.focus()
@@ -816,7 +815,7 @@ class Drilldown extends Component {
if (event.type === 'keydown' && href) {
const optionEl = this._drilldownRef?.querySelector(
- `#${id}`
+ `#${CSS.escape(id)}`
) as HTMLLinkElement
const isLink = optionEl.tagName.toLowerCase() === 'a'
@@ -1516,7 +1515,9 @@ class Drilldown extends Component {
: this.currentPage.children[0]?.props.id
if (!targetId) return null
- return this._popover?._contentElement?.querySelector(`#${targetId}`)
+ return this._popover?._contentElement?.querySelector(
+ `#${CSS.escape(targetId)}`
+ )
}}
elementRef={(element) => {
// setting ref for "Popover" version, the popover root
diff --git a/packages/ui-select/src/Select/__new-tests__/Select.test.tsx b/packages/ui-select/src/Select/__new-tests__/Select.test.tsx
index 99bd7c69b5..d7df05ce80 100644
--- a/packages/ui-select/src/Select/__new-tests__/Select.test.tsx
+++ b/packages/ui-select/src/Select/__new-tests__/Select.test.tsx
@@ -316,6 +316,29 @@ describe('', () => {
consoleErrorMock.mockRestore()
})
+ it('should not crash for weird option ids', async () => {
+ const weirdID = 'some_`w@ei:r|!@#$%^&*(()|\\.l/d"id'
+ vi.useFakeTimers()
+ const { container } = render(
+
+ )
+ vi.advanceTimersToNextFrame()
+ vi.useRealTimers()
+ const input = container.querySelector('input')
+ expect(input).toBeInTheDocument()
+ })
+
it('should have role button in Safari without onInputChange', async () => {
const { container } = render(
diff --git a/packages/ui-select/src/Select/index.tsx b/packages/ui-select/src/Select/index.tsx
index 2c421c033c..77f6841b3a 100644
--- a/packages/ui-select/src/Select/index.tsx
+++ b/packages/ui-select/src/Select/index.tsx
@@ -291,22 +291,21 @@ class Select extends Component {
}
scrollToOption(id?: string) {
- if (this._listView) {
- const option = this._listView.querySelector(`[id="${id}"]`)
- if (!option) return
-
- const listItem = option.parentNode
- const parentTop = getBoundingClientRect(this._listView).top
- const elemTop = getBoundingClientRect(listItem).top
- const parentBottom = parentTop + this._listView.clientHeight
- const elemBottom =
- elemTop + (listItem ? (listItem as Element).clientHeight : 0)
-
- if (elemBottom > parentBottom) {
- this._listView.scrollTop += elemBottom - parentBottom
- } else if (elemTop < parentTop) {
- this._listView.scrollTop -= parentTop - elemTop
- }
+ if (!this._listView || !id) return
+ const option = this._listView.querySelector(`[id="${CSS.escape(id)}"]`)
+ if (!option) return
+
+ const listItem = option.parentNode
+ const parentTop = getBoundingClientRect(this._listView).top
+ const elemTop = getBoundingClientRect(listItem).top
+ const parentBottom = parentTop + this._listView.clientHeight
+ const elemBottom =
+ elemTop + (listItem ? (listItem as Element).clientHeight : 0)
+
+ if (elemBottom > parentBottom) {
+ this._listView.scrollTop += elemBottom - parentBottom
+ } else if (elemTop < parentTop) {
+ this._listView.scrollTop -= parentTop - elemTop
}
}
diff --git a/packages/ui-tabs/src/Tabs/index.tsx b/packages/ui-tabs/src/Tabs/index.tsx
index a0f7479794..f4537df159 100644
--- a/packages/ui-tabs/src/Tabs/index.tsx
+++ b/packages/ui-tabs/src/Tabs/index.tsx
@@ -331,10 +331,12 @@ class Tabs extends Component {
// this is needed because keypress cancels scrolling. So we have to trigger the scrolling
// one "tick" later than the keypress
setTimeout(() => {
- this.state.withTabListOverflow &&
- this.showActiveTabIfOverlayed(
- this._tabList!.querySelector(`#tab-${id}`)
- )
+ if (this.state.withTabListOverflow) {
+ const tab = id
+ ? this._tabList!.querySelector(`#tab-${CSS.escape(id)}`)
+ : null
+ this.showActiveTabIfOverlayed(tab)
+ }
}, 0)
}
diff --git a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx
index 3bc5a23081..75e61427f1 100644
--- a/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx
+++ b/packages/ui-top-nav-bar/src/TopNavBar/TopNavBarLayout/SmallViewportLayout/index.tsx
@@ -310,7 +310,7 @@ class TopNavBarSmallViewportLayout extends Component<
setTimeout(() => {
const container = document.getElementById(this._trayContainerId)
const firstOption = container?.querySelector(
- `[id="${targetId}"]`
+ `[id="${CSS.escape(targetId)}"]`
) as HTMLSpanElement
firstOption?.focus()
if (this._drilldownRef) {