Skip to content
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

Merged
merged 1 commit into from Oct 26, 2021
Merged

Conversation

fbasso
Copy link
Member

@fbasso fbasso commented Jul 20, 2021

No description provided.

@codecov-commenter
Copy link

codecov-commenter commented Aug 30, 2021

Codecov Report

Merging #4129 (739bf65) into bootstrap5 (9598506) will increase coverage by 0.15%.
The diff coverage is 98.00%.

Impacted file tree graph

@@              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     
Flag Coverage Δ
e2e 45.98% <94.00%> (-0.65%) ⬇️
unit 87.41% <97.87%> (+0.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/datepicker/datepicker-input.ts 95.77% <ø> (-1.41%) ⬇️
src/typeahead/typeahead.ts 96.09% <ø> (ø)
src/util/positioning.ts 95.12% <97.05%> (+10.82%) ⬆️
src/dropdown/dropdown.ts 96.17% <100.00%> (+0.04%) ⬆️
src/popover/popover.ts 95.65% <100.00%> (ø)
src/tooltip/tooltip.ts 98.27% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9598506...739bf65. Read the comment docs.

Copy link
Member

@maxokorokov maxokorokov left a 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 do popper.update() when necessary.
  • I think we should change posisitonElements to accept popper config as an argument to avoid component-specific code in positioning.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;
Copy link
Member

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

// open
dropdown.open();
fixture.detectChanges();
tick(25);
Copy link
Member

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()

@@ -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(() => {
Copy link
Member

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

@@ -63,6 +63,7 @@ export class NgbDropdownItem {
'[class.dropdown-menu]': 'true',
'[class.show]': 'dropdown.isOpen()',
'[attr.x-placement]': 'placement',
Copy link
Member

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/popover/popover.ts Outdated Show resolved Hide resolved
return elOffset;
const popperStartPrimaryPlacement = /^left/;
const popperEndPrimaryPlacement = /^right/;
function convertPlacementToBsClasses(baseClass: string, placement: PopperPlacement): string {
Copy link
Member

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...


const bsModifier: Partial<Modifier<any, any>> = {
name: 'bootstrapClasses',
enabled: true,
Copy link
Member

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>> = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe bootstrapBaseClassModifier ?

enabled: true,
options: {
offset: () => {
return baseClass === 'bs-popover' ? [0, 12] : [];
Copy link
Member

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',
Copy link
Member

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

@fbasso
Copy link
Member Author

fbasso commented Oct 14, 2021

@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 !

@fbasso fbasso force-pushed the popper2 branch 2 times, most recently from 6c809e7 to cc44491 Compare October 14, 2021 14:37
Copy link
Member

@maxokorokov maxokorokov left a 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

popperGenerator,
preventOverflow
} from '@popperjs/core';
import {defaultModifiers, OptionsGeneric} from '@popperjs/core/lib/popper-lite';
Copy link
Member

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

placement: string | Placement | PlacementArray;
appendToBody?: boolean;
baseClass?: string;
updatePopperOptions?: (options: Partial<OptionsGeneric<any>>) => Partial<OptionsGeneric<any>>;
Copy link
Member

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();
Copy link
Member

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"`,
Copy link
Member

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

@fbasso fbasso force-pushed the popper2 branch 3 times, most recently from 768124c to dea093d Compare October 22, 2021 13:19
@fbasso
Copy link
Member Author

fbasso commented Oct 22, 2021

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...

@Zwergenpunk Zwergenpunk mentioned this pull request Oct 25, 2021
49 tasks
Copy link
Member

@maxokorokov maxokorokov left a 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.

src/popover/popover.ts Outdated Show resolved Hide resolved
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
@maxokorokov maxokorokov merged commit d2ca848 into ng-bootstrap:bootstrap5 Oct 26, 2021
maxokorokov pushed a commit that referenced this pull request Oct 26, 2021
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
@fbasso fbasso deleted the popper2 branch November 8, 2021 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants