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(positioning): popper 2 #4129
Conversation
d547838
to
ac931ac
Compare
Codecov Report
@@ Coverage Diff @@
## bootstrap5 #4129 +/- ##
==============================================
+ Coverage 87.82% 87.97% +0.15%
==============================================
Files 113 113
Lines 3629 3551 -78
Branches 676 654 -22
==============================================
- Hits 3187 3124 -63
+ Misses 368 357 -11
+ Partials 74 70 -4
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
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.
Hey @fbasso,
here is what I've found so far:
Big issues
- Calling
positionElements
recreates popper every time is called. This is an issue for example in dropdown when we do it every time zone is stable. We should redesign the API for repositioning I guess and either create a new popper or dopopper.update()
when necessary. - I think we should change
posisitonElements
to accept popper config as an argument to avoid component-specific code inpositioning.ts
(ex. for popover offset 8px should be hardcoded) and allowing end-users to provide custom popper configs if they need (separate PR for the last one I guess). - Popper is destroyed correctly only in tooltip and popover
Minor issues
- tooltip/popover arrow is centered correctly only AFTER the next CD, but not initially (might be related to the 1st major issue)
- dropdown/datepicker start flipping randomly on CD (definitely related to the 1st major issue)
- we should not use deep imports, but import everything form
@popper/core
- dropdown → we should check that keyboard works as expected and dropup/dropdown works right when popper flips menu positioning automatically
- tests broken with latest popper
2.10
(e2e dropdown positioning)
Everything else is inline in code!
Cheers,
Max
@@ -945,57 +944,6 @@ describe('NgbInputDatepicker', () => { | |||
|
|||
describe('positionTarget', () => { | |||
|
|||
let positionElementsSpy: jasmine.Spy; |
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.
Removing this decreases coverage for positionTarget
tests
src/dropdown/dropdown.spec.ts
Outdated
// open | ||
dropdown.open(); | ||
fixture.detectChanges(); | ||
tick(25); |
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.
Seems like popper uses promises for scheduling, so 25 is not necessary, just tick()
src/dropdown/dropdown.spec.ts
Outdated
@@ -131,49 +131,52 @@ describe('ngb-dropdown', () => { | |||
expect(dropdownEl).toHaveCssClass('dropdown'); | |||
}); | |||
|
|||
it('should allow setting a custom dropdown class (container="body")', () => { | |||
const html = ` | |||
it('should allow setting a custom dropdown class (container="body")', fakeAsync(() => { |
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 I'm not mistaken this specific test actually works fine without any changes
src/dropdown/dropdown.ts
Outdated
@@ -63,6 +63,7 @@ export class NgbDropdownItem { | |||
'[class.dropdown-menu]': 'true', | |||
'[class.show]': 'dropdown.isOpen()', | |||
'[attr.x-placement]': 'placement', |
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.
should remove x-placement
I guess?
src/util/positioning.ts
Outdated
return elOffset; | ||
const popperStartPrimaryPlacement = /^left/; | ||
const popperEndPrimaryPlacement = /^right/; | ||
function convertPlacementToBsClasses(baseClass: string, placement: PopperPlacement): string { |
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.
maybe something like getBootstrapBaseClassPlacement
. I was struggling to understand what is does.
Also unit tests for these kind of utility functions would be nice...
src/util/positioning.ts
Outdated
|
||
const bsModifier: Partial<Modifier<any, any>> = { | ||
name: 'bootstrapClasses', | ||
enabled: true, |
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 think it should be enabled: !!baseClass
to avoid executing it for components without the base class
} | ||
let mainPlacement = popperPlacements.shift(); | ||
|
||
const bsModifier: Partial<Modifier<any, any>> = { |
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.
maybe bootstrapBaseClassModifier
?
src/util/positioning.ts
Outdated
enabled: true, | ||
options: { | ||
offset: () => { | ||
return baseClass === 'bs-popover' ? [0, 12] : []; |
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.
This should be passed as a partial config only from calling positionElements
from the popover...
We should not have component-specific code here. Also 8px, not 12 → https://github.com/twbs/bootstrap/blob/main/js/src/popover.js#L25
}, | ||
}, | ||
{ | ||
name: 'preventOverflow', |
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.
don't think this is necessary
@maxokorokov, I've updated the PR to fix almost all the issues you've listed. I've refactor the positioning service to provide a ngbPositioning function to manage the creation/update/destroy lifecycle, and to be able to change the popper config from outside (required for popper.js, and open future possibilities for user to interact directly with the popper options). Some issues to be investigated yet: the arrow is still not well positioned on the tooltip, deep import not completely removed (defaultModifiers, OptionsGeneric not available on @popperjs/core) and "decreases coverage" still pending. Anyway, I think there are now noticeable changes to review ! |
6c809e7
to
cc44491
Compare
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.
Hey, @fbasso!
I did another round and left some comments.
Most issues are gone for me except for tooltip/popover arrow.
I think we should not move to services and DI at the moment and leave like this.
A couple of things:
-
this._positioning.update
is called inconsistently across components (sometimes outside zone, sometimes inside, sometimes on stable, etc) - logs/comments in the code
Cheers,
Max
src/util/positioning.ts
Outdated
popperGenerator, | ||
preventOverflow | ||
} from '@popperjs/core'; | ||
import {defaultModifiers, OptionsGeneric} from '@popperjs/core/lib/popper-lite'; |
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.
We should import things only from @popperjs/core
and avoid deep imports like this.
Also as discussed previously we could use createPopperLite
instead of popperGenerator
src/util/positioning.ts
Outdated
placement: string | Placement | PlacementArray; | ||
appendToBody?: boolean; | ||
baseClass?: string; | ||
updatePopperOptions?: (options: Partial<OptionsGeneric<any>>) => Partial<OptionsGeneric<any>>; |
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.
Why are you using OptionsGeneric<any>
everywhere instead of just Options
?
let popperOptions = updatePopperOptions(getPopperOptions(positioningOption)); | ||
if (popperInstance) { | ||
popperInstance.setOptions(popperOptions); | ||
popperInstance.update(); |
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.
No need in .update()
here, if you do setOptions
. But on the other hand I'm not sure we should call setOptions
on every update... I think we should have:
update()
setOptions(options)
destroy()
as the API ideally
* Possible values are `"top"`, `"top-start"`, `"top-end"`, `"bottom"`, `"bottom-start"`, | ||
* `"bottom-end"`, `"start"`, `"start-top"`, `"start-bottom"`, `"end"`, `"end-top"`, | ||
* `"end-bottom"` | ||
* `"top"`, `"top-start"`, `"top-left"`, `"top-end"`, `"top-right"`, |
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.
You removed the beginning "Possible values are" text
768124c
to
dea093d
Compare
Thanks for all your comments, @maxokorokov ! This is a new push with a lot of (corresponding) changes. I hope I didn't forget anything 😃 Let me know what you think... |
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.
LGTM apart from the popover.ts
implementation.
update()
should be called onStable
and not only when opening the popup.
BREAKING CHANGES: ng-bootstrap now has a dependency on `@popper/core`. All positioning logic for datepicker, dropdown, popover, tooltip and typeahead is outsourced to popper.js
BREAKING CHANGES: ng-bootstrap now has a dependency on `@popper/core`. All positioning logic for datepicker, dropdown, popover, tooltip and typeahead is outsourced to popper.js
No description provided.