-
Notifications
You must be signed in to change notification settings - Fork 235
feat: add simpler API for controlling overlays #422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ governing permissions and limitations under the License. | |
| */ | ||
|
|
||
| import { ActiveOverlay } from './active-overlay.js'; | ||
| import { OverlayOpenDetail, OverlayCloseDetail } from './overlay.js'; | ||
| import { OverlayOpenDetail } from './overlay-types'; | ||
|
|
||
| function isLeftClick(event: MouseEvent): boolean { | ||
| return event.button === 0; | ||
|
|
@@ -26,15 +26,9 @@ export class OverlayStack { | |
|
|
||
| private preventMouseRootClose = false; | ||
| private root: HTMLElement = document.body; | ||
| private onChange: (overlays: ActiveOverlay[]) => void; | ||
| private handlingResize = false; | ||
|
|
||
| public constructor( | ||
| root: HTMLElement, | ||
| onChange: (overlays: ActiveOverlay[]) => void | ||
| ) { | ||
| this.root = root; | ||
| this.onChange = onChange; | ||
| public constructor() { | ||
| this.addEventListeners(); | ||
| } | ||
|
|
||
|
|
@@ -46,6 +40,16 @@ export class OverlayStack { | |
| return this.overlays.slice(-1)[0]; | ||
| } | ||
|
|
||
| private findOverlayForContent( | ||
| overlayContent: HTMLElement | ||
| ): ActiveOverlay | undefined { | ||
| for (const item of this.overlays) { | ||
| if (overlayContent.isSameNode(item.overlayContent as HTMLElement)) { | ||
| return item; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private addEventListeners(): void { | ||
| this.document.addEventListener('click', this.handleMouseCapture, true); | ||
| this.document.addEventListener('click', this.handleMouse); | ||
|
|
@@ -67,34 +71,30 @@ export class OverlayStack { | |
| ); | ||
| } | ||
|
|
||
| public openOverlay(event: CustomEvent<OverlayOpenDetail>): void { | ||
| if (this.isOverlayActive(event.detail.content)) return; | ||
| public openOverlay(details: OverlayOpenDetail): void { | ||
| /* istanbul ignore if */ | ||
| if (this.isOverlayActive(details.content)) return; | ||
|
|
||
| requestAnimationFrame(() => { | ||
| const interaction = event.detail.interaction; | ||
| if (interaction === 'click') { | ||
| if (details.interaction === 'click') { | ||
| this.closeAllHoverOverlays(); | ||
| } else if ( | ||
| interaction === 'hover' && | ||
| this.isClickOverlayActiveForTrigger(event.detail.trigger) | ||
| details.interaction === 'hover' && | ||
| this.isClickOverlayActiveForTrigger(details.trigger) | ||
| ) { | ||
| // Don't show a hover popover if the click popover is already active | ||
| return; | ||
| } | ||
|
|
||
| const activeOverlay = ActiveOverlay.create(event, this.root); | ||
| const activeOverlay = ActiveOverlay.create(details); | ||
| this.overlays.push(activeOverlay); | ||
|
|
||
| this.onChange(this.overlays); | ||
| document.body.appendChild(activeOverlay); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the structural changes herein, if I wanted to make the "overlay root" element flexible, where would you see adding that? Having this as a The issue this supports is something that is currently in Crisp where we don't actually use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I too am a little concerned about not being able to control where we reparent overlays to. The case I'm thinking of is a modal thats contained within an area of an application, in this case I want its z-index to be above the content in the containment area, but below content in other areas outside of the container. This seems natural to me to create an overlay root thats specifically for use by this modal, with the main document.body root being used for tooltips etc. Is this a case this change supports? I think we may have even lost this capability before this change?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a feature taken away when removing @benjamind could you expand on:
Is this a case of a UI with multiple modals open at the same time, or something else? Would be a good use case to have clearly defined in the docs as a "possible" or "not possible" depending on how this comes together.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The case I was thinking of was something like this: Where we have an area where we want a contained modal, this has a mask area behind the modal and a dialog with tooltips etc inside. This is one stack of overlays. The application however supports dropdowns and tooltips that can potentially overlap this stack, but do so from outside of the contained area. I've not delved too deeply into how our overlay stack works at this time, so I'm not sure how we support this today, especially with regards to resize of the masked area etc.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, our overlays stack globally in the order in which they are opened. I could see the dialog being an overlay itself. If you opened an overlay in the dialog (tooltip on hover, click popover, etc) it would open at a higher level than the dialog. If you opened a dropdown from the header, while the dialog was open (ignoring that it is supposed to be modal), the dropdown would appear over the dialog as you would expect. A problem with explicitly restricting the overlay area to a subset of the view (e.g. the mask) is that we may restrict the positioning of something like a dropdown more than we need to. I don't know if that is something that we want? So, I guess I am saying that I think we would get reasonable results in the scenario you have outlined without multiple levels of overlay root. I am not yet convinced that the added complexity is warranted. But, I could be convinced 😁
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I think I agree that our current code covers the case for the z-stacking issue. The main concern I have is I think about responding to resize. Since the modal mask and the dialog would be centered within their container, but they are re-parented to |
||
| }); | ||
| } | ||
|
|
||
| public closeOverlay(event: CustomEvent<OverlayCloseDetail>): void { | ||
| public closeOverlay(content: HTMLElement): void { | ||
| requestAnimationFrame(() => { | ||
| const overlayContent = event.detail.content; | ||
| const overlay = this.overlays.find((item) => | ||
| overlayContent.isSameNode(item.overlayContent as HTMLElement) | ||
| ); | ||
| const overlay = this.findOverlayForContent(content); | ||
| this.hideAndCloseOverlay(overlay); | ||
| }); | ||
| } | ||
|
|
@@ -133,13 +133,14 @@ export class OverlayStack { | |
| private async hideAndCloseOverlay(overlay?: ActiveOverlay): Promise<void> { | ||
| if (overlay) { | ||
| await overlay.hide(); | ||
| overlay.remove(); | ||
| overlay.dispose(); | ||
|
|
||
| const index = this.overlays.indexOf(overlay); | ||
| /* istanbul ignore else */ | ||
| if (index >= 0) { | ||
| this.overlays[index].dispose(); | ||
| this.overlays.splice(index, 1); | ||
| } | ||
| this.onChange(this.overlays); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -164,9 +165,9 @@ export class OverlayStack { | |
|
|
||
| this.handlingResize = true; | ||
| requestAnimationFrame(() => { | ||
| this.overlays.forEach((overlay) => { | ||
| for (const overlay of this.overlays) { | ||
| overlay.updateOverlayPosition(); | ||
| }); | ||
| } | ||
| this.handlingResize = false; | ||
| }); | ||
| }; | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can have this set on the class, can we remove the
?? It would also allow us to removespectrum-web-components/packages/overlay/src/active-overlay.ts
Line 249 in e3b1ff2