Skip to content

Commit 89910fa

Browse files
ToMESSKamatyasf
authored andcommitted
fix(ui-a11y-utils): prevent clicking on a Tooltip from closing the parent dialog
INSTUI-4475
1 parent 1f6b4c8 commit 89910fa

File tree

2 files changed

+198
-3
lines changed

2 files changed

+198
-3
lines changed

cypress/component/Modal.cy.tsx

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* SOFTWARE.
2323
*/
2424
import React, { useState } from 'react'
25-
import { Modal, View, Button } from '@instructure/ui'
25+
import { Tooltip, Modal, Button, CloseButton, View } from '@instructure/ui'
2626
import 'cypress-real-events'
2727

2828
import '../support/component'
@@ -171,4 +171,192 @@ describe('<Modal/>', () => {
171171

172172
cy.get('[data-testid="modal-content"]').should('not.exist')
173173
})
174+
175+
it('should not close with shouldCloseOnDocumentClick when Tooltip inside is clicked on', async () => {
176+
const TestModal = () => {
177+
const [open, setOpen] = useState(false)
178+
179+
return (
180+
<div>
181+
<Button
182+
onClick={() => {
183+
setOpen(true)
184+
}}
185+
>
186+
Open the Modal
187+
</Button>
188+
<Modal
189+
label="modal"
190+
open={open}
191+
onDismiss={() => {
192+
setOpen(false)
193+
}}
194+
shouldCloseOnDocumentClick
195+
>
196+
<CloseButton
197+
screenReaderLabel="Close"
198+
onClick={() => {
199+
setOpen(false)
200+
}}
201+
/>
202+
<Tooltip renderTip="Tooltip!">
203+
<Button data-testid="trigger">Hello</Button>
204+
</Tooltip>
205+
</Modal>
206+
</div>
207+
)
208+
}
209+
cy.mount(<TestModal />)
210+
211+
cy.contains('Open the Modal').click()
212+
213+
cy.get('[data-testid="trigger"]').then(($trigger) => {
214+
const tooltipId = $trigger.attr('data-position-target')
215+
const tooltip = `span[data-position-content="${tooltipId}"]`
216+
217+
cy.get(tooltip).should('not.be.visible')
218+
219+
cy.get('[data-testid="trigger"]')
220+
.realHover()
221+
.then(() => {
222+
cy.get(tooltip).should('be.visible')
223+
})
224+
225+
cy.get(tooltip)
226+
.realClick()
227+
.wait(500)
228+
.then(() => {
229+
cy.get(tooltip).should('be.visible')
230+
cy.get('[role="dialog"]').should('be.visible')
231+
})
232+
})
233+
})
234+
235+
it('should not close with shouldCloseOnDocumentClick when inside Tooltip has renderTip with HTML content', async () => {
236+
const TestModal = () => {
237+
const [open, setOpen] = useState(false)
238+
239+
return (
240+
<div>
241+
<Button
242+
onClick={() => {
243+
setOpen(true)
244+
}}
245+
>
246+
Open the Modal
247+
</Button>
248+
<Modal
249+
label="modal"
250+
open={open}
251+
onDismiss={() => {
252+
setOpen(false)
253+
}}
254+
shouldCloseOnDocumentClick
255+
>
256+
<CloseButton
257+
screenReaderLabel="Close"
258+
onClick={() => {
259+
setOpen(false)
260+
}}
261+
/>
262+
<Tooltip
263+
renderTip={
264+
<div>
265+
<div>HTML content</div>
266+
</div>
267+
}
268+
>
269+
<Button data-testid="trigger">Hello</Button>
270+
</Tooltip>
271+
</Modal>
272+
</div>
273+
)
274+
}
275+
cy.mount(<TestModal />)
276+
277+
cy.contains('Open the Modal').click()
278+
279+
cy.get('[data-testid="trigger"]').then(($trigger) => {
280+
const tooltipId = $trigger.attr('data-position-target')
281+
const tooltip = `span[data-position-content="${tooltipId}"]`
282+
283+
cy.get(tooltip).should('not.be.visible')
284+
285+
cy.get('[data-testid="trigger"]')
286+
.realHover()
287+
.then(() => {
288+
cy.get(tooltip).should('be.visible')
289+
})
290+
291+
cy.get(tooltip)
292+
.realClick()
293+
.wait(500)
294+
.then(() => {
295+
cy.get(tooltip).should('be.visible')
296+
cy.get('[role="dialog"]').should('be.visible')
297+
})
298+
})
299+
})
300+
301+
it('should not close with shouldCloseOnDocumentClick when ToolTip button is focused and Tooltip is clicked', async () => {
302+
const TestModal = () => {
303+
const [open, setOpen] = useState(false)
304+
305+
return (
306+
<div>
307+
<Button
308+
onClick={() => {
309+
setOpen(true)
310+
}}
311+
>
312+
Open the Modal
313+
</Button>
314+
<Modal
315+
label="modal"
316+
open={open}
317+
onDismiss={() => {
318+
setOpen(false)
319+
}}
320+
shouldCloseOnDocumentClick
321+
>
322+
<CloseButton
323+
screenReaderLabel="Close"
324+
onClick={() => {
325+
setOpen(false)
326+
}}
327+
/>
328+
<Tooltip renderTip={<div>HTML content</div>}>
329+
<Button data-testid="trigger">Hello</Button>
330+
</Tooltip>
331+
</Modal>
332+
</div>
333+
)
334+
}
335+
cy.mount(<TestModal />)
336+
337+
cy.contains('Open the Modal').click()
338+
339+
cy.get('[data-testid="trigger"]').then(($trigger) => {
340+
const tooltipId = $trigger.attr('data-position-target')
341+
const tooltip = `span[data-position-content="${tooltipId}"]`
342+
343+
cy.get(tooltip).should('not.be.visible')
344+
345+
cy.get('[data-testid="trigger"]')
346+
.realClick()
347+
.then(() => {
348+
cy.get(tooltip).should('be.visible')
349+
cy.get('[data-testid="trigger"]')
350+
.should('have.focus')
351+
.then(() => {
352+
cy.get(tooltip)
353+
.realClick()
354+
.wait(500)
355+
.then(() => {
356+
cy.get('[role="dialog"]').should('be.visible')
357+
})
358+
})
359+
})
360+
})
361+
})
174362
})

packages/ui-a11y-utils/src/FocusRegion.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {
2828
contains,
2929
addEventListener,
3030
ownerDocument,
31-
findTabbable
31+
findTabbable,
32+
canUseDOM
3233
} from '@instructure/ui-dom-utils'
3334
import { uid } from '@instructure/uid'
3435
import { logError as error } from '@instructure/console'
@@ -96,7 +97,13 @@ class FocusRegion {
9697
this._options.shouldCloseOnDocumentClick &&
9798
event.button === 0 &&
9899
event.detail > 0 && // if event.detail is 0 then this is a keyboard and not a mouse press
99-
!this._contextContainsTarget
100+
!this._contextContainsTarget &&
101+
//this prevents clicking on Tooltip from closing the parent dialog
102+
!(
103+
canUseDOM &&
104+
this._documentClickTarget instanceof HTMLElement &&
105+
this._documentClickTarget.closest('[role="tooltip"]')
106+
)
100107
) {
101108
this.handleDismiss(event, true)
102109
}

0 commit comments

Comments
 (0)