-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
feat(modal): stacked modals #2640
Conversation
@@ -3,7 +3,8 @@ import {Component, Input} from '@angular/core'; | |||
@Component({ | |||
selector: 'ngb-modal-backdrop', | |||
template: '', | |||
host: {'[class]': '"modal-backdrop fade show" + (backdropClass ? " " + backdropClass : "")'} | |||
host: | |||
{'[class]': '"modal-backdrop fade show" + (backdropClass ? " " + backdropClass : "")', 'style': 'z-index: 1050'} |
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.
I'm surprised that we don't need to adjust z-index
on windows themselves...
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.
@pkozlowski-opensource it's ok to not adjust manually z-index
.
According to W3C:
Each box belongs to one stacking context. Each positioned box in a given stacking context has an integer stack level, which is its position on the z-axis relative other stack levels within the same stacking context. Boxes with greater stack levels are always formatted in front of boxes with lower stack levels. Boxes may have negative stack levels. Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order.
So it should be fine for us.
src/modal/modal-stack.ts
Outdated
activeModal.close = (result: any) => { ngbModalRef.close(result); }; | ||
activeModal.dismiss = (reason: any) => { ngbModalRef.dismiss(reason); }; | ||
|
||
this._applyWindowOptions(windowCmptRef.instance, options); | ||
renderer.addClass(this._document.body, 'modal-open'); |
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.
Could we check if there are no open modals open already and skip DOM manipulation if there is already an open modal?
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.
Done. addClass
will be called only when _modalRefs
has 1 modal.
@Maxkorz thnx for the PR - I did a first pass on it and it looks really good! Just left minor nit comment. I would like to spend more time testing it but if all goes well we will land it in 3.2.0. Thnx again! |
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.
Could you please have a look ?
|
||
open(moduleCFR: ComponentFactoryResolver, contentInjector: Injector, content: any, options): NgbModalRef { | ||
const containerEl = | ||
isDefined(options.container) ? this._document.querySelector(options.container) : this._document.body; | ||
const renderer = this._rendererFactory.createRenderer(null, null); |
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.
Is there any specific reason you are using RendererFactory2
here to create a new renderer instance instead of injecting directly private _renderer: Renderer2
in constructor ?
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.
@benouat this is a service so I don't think you can inject Renderer2
(it is only injectable in components)
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.
Oh right ! my bad. I tend to always forget modal things are occurring from services
The overall code LGTM! Nonetheless I have a few comments:
|
@benouat Thanks for the review!
|
@benouat Keyboard issue has been fixed. |
|
||
ngbWindowCmpt.onDestroy(() => { | ||
const index = this._windowCmpts.indexOf(ngbWindowCmpt); | ||
if (index > -1) { |
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.
I copied this check from _registerModalRef
. Is it really necessary to check the existence of an element before removing it from an array?
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.
With splice()
yes it is needed, because splice( negative-index-here, ...)
will remove from the end, e.g. : [1, 2 , 3].splice(-1, 1)
-> [1, 2]
As a sidenote, I think we could only fire this._activeWindowCmptHasChanged.next();
if we remove a componentRef. If nothing is removed it is not needed.
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.
As a sidenote, I think we could only fire this._activeWindowCmptHasChanged.next(); if we remove a componentRef. If nothing is removed it is not needed.
I subscribe to _activeWindowCmptHasChanged
in constructor in order to call ngbFocusTrap
on the top/activated/latest (you get the idea) WindowCmpt
. So it should fire on push
, and on splice
(to trap the focus again on the previously opened modal window)
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.
I was just talking about moving the call inside the if
:
ngbWindowCmpt.onDestroy(() => {
const index = this._windowCmpts.indexOf(ngbWindowCmpt);
if (index > -1) {
this._windowCmpts.splice(index, 1);
this._activeWindowCmptHasChanged.next();
}
// this._activeWindowCmptHasChanged.next(); move it inside the upper if
});
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.
oh, sure, I misunderstood you.
Done
}); | ||
})); | ||
|
||
it('should dismiss modals on ESC in correct order', () => { |
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.
ESC didn't work because ngbFocusTrap
was preventing focus to go out of the first modal window.
I fixed it by moving ngbFocusTrap
to the NgbModalStack
service, but I'm not sure how to test it. The test seems right, but no matter where I call ngbFocusTrap
it still passes. And I couldn't find any tests for Focus Trap
, so I assume it's not testable?
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.
Your changes look good to me. No matter where we call the ngbFocusTrap
, test does not need to be changed I think.
@Maxkorz for the backdrop I don't think we need to change anything right now. Let's keep it as it, with one new backdrop per modal. We can always refactor and change it later. |
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.
One more time a very big thank you for this contribution 👍
Let's merge it.
Before submitting a pull request, please make sure you have at least performed the following:
P.S. if you think the project is not ready to have stacked modals in the demo, at least consider merging changes related to the
modal-open
class.