From 4eb6c7be92cd89a163c05d73c71515609071a6bb Mon Sep 17 00:00:00 2001 From: Tamas Kovacs Date: Thu, 3 Apr 2025 11:24:20 +0200 Subject: [PATCH] fix(ui-a11y-utils): prevent clicking on a Tooltip from closing the parent dialog INSTUI-4475 --- cypress/component/Modal.cy.tsx | 190 +++++++++++++++++++++- packages/ui-a11y-utils/src/FocusRegion.ts | 11 +- 2 files changed, 198 insertions(+), 3 deletions(-) diff --git a/cypress/component/Modal.cy.tsx b/cypress/component/Modal.cy.tsx index 360b735a9e..1a88e7088c 100644 --- a/cypress/component/Modal.cy.tsx +++ b/cypress/component/Modal.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import React, { useState } from 'react' -import { Modal, View, Button } from '@instructure/ui' +import { Tooltip, Modal, Button, CloseButton, View } from '@instructure/ui' import 'cypress-real-events' import '../support/component' @@ -171,4 +171,192 @@ describe('', () => { cy.get('[data-testid="modal-content"]').should('not.exist') }) + + it('should not close with shouldCloseOnDocumentClick when Tooltip inside is clicked on', async () => { + const TestModal = () => { + const [open, setOpen] = useState(false) + + return ( +
+ + { + setOpen(false) + }} + shouldCloseOnDocumentClick + > + { + setOpen(false) + }} + /> + + + + +
+ ) + } + cy.mount() + + cy.contains('Open the Modal').click() + + cy.get('[data-testid="trigger"]').then(($trigger) => { + const tooltipId = $trigger.attr('data-position-target') + const tooltip = `span[data-position-content="${tooltipId}"]` + + cy.get(tooltip).should('not.be.visible') + + cy.get('[data-testid="trigger"]') + .realHover() + .then(() => { + cy.get(tooltip).should('be.visible') + }) + + cy.get(tooltip) + .realClick() + .wait(500) + .then(() => { + cy.get(tooltip).should('be.visible') + cy.get('[role="dialog"]').should('be.visible') + }) + }) + }) + + it('should not close with shouldCloseOnDocumentClick when inside Tooltip has renderTip with HTML content', async () => { + const TestModal = () => { + const [open, setOpen] = useState(false) + + return ( +
+ + { + setOpen(false) + }} + shouldCloseOnDocumentClick + > + { + setOpen(false) + }} + /> + +
HTML content
+
+ } + > + + +
+ + ) + } + cy.mount() + + cy.contains('Open the Modal').click() + + cy.get('[data-testid="trigger"]').then(($trigger) => { + const tooltipId = $trigger.attr('data-position-target') + const tooltip = `span[data-position-content="${tooltipId}"]` + + cy.get(tooltip).should('not.be.visible') + + cy.get('[data-testid="trigger"]') + .realHover() + .then(() => { + cy.get(tooltip).should('be.visible') + }) + + cy.get(tooltip) + .realClick() + .wait(500) + .then(() => { + cy.get(tooltip).should('be.visible') + cy.get('[role="dialog"]').should('be.visible') + }) + }) + }) + + it('should not close with shouldCloseOnDocumentClick when ToolTip button is focused and Tooltip is clicked', async () => { + const TestModal = () => { + const [open, setOpen] = useState(false) + + return ( +
+ + { + setOpen(false) + }} + shouldCloseOnDocumentClick + > + { + setOpen(false) + }} + /> + HTML content
}> + + + + + ) + } + cy.mount() + + cy.contains('Open the Modal').click() + + cy.get('[data-testid="trigger"]').then(($trigger) => { + const tooltipId = $trigger.attr('data-position-target') + const tooltip = `span[data-position-content="${tooltipId}"]` + + cy.get(tooltip).should('not.be.visible') + + cy.get('[data-testid="trigger"]') + .realClick() + .then(() => { + cy.get(tooltip).should('be.visible') + cy.get('[data-testid="trigger"]') + .should('have.focus') + .then(() => { + cy.get(tooltip) + .realClick() + .wait(500) + .then(() => { + cy.get('[role="dialog"]').should('be.visible') + }) + }) + }) + }) + }) }) diff --git a/packages/ui-a11y-utils/src/FocusRegion.ts b/packages/ui-a11y-utils/src/FocusRegion.ts index ab12e7a6f8..3309a1ab04 100644 --- a/packages/ui-a11y-utils/src/FocusRegion.ts +++ b/packages/ui-a11y-utils/src/FocusRegion.ts @@ -28,7 +28,8 @@ import { contains, addEventListener, ownerDocument, - findTabbable + findTabbable, + canUseDOM } from '@instructure/ui-dom-utils' import { uid } from '@instructure/uid' import { logError as error } from '@instructure/console' @@ -96,7 +97,13 @@ class FocusRegion { this._options.shouldCloseOnDocumentClick && event.button === 0 && event.detail > 0 && // if event.detail is 0 then this is a keyboard and not a mouse press - !this._contextContainsTarget + !this._contextContainsTarget && + //this prevents clicking on Tooltip from closing the parent dialog + !( + canUseDOM && + this._documentClickTarget instanceof HTMLElement && + this._documentClickTarget.closest('[role="tooltip"]') + ) ) { this.handleDismiss(event, true) }