angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.33k stars 6.73k forks source link

Support positioning mat-select panel under the trigger #14105

Open takamori opened 5 years ago

takamori commented 5 years ago

Bug, feature request, or proposal:

disableOptionCentering as discussed on https://material.angular.io/components/select/api does not appear to have any effect and prevents implementing the "Exposed Dropdown Menu" mentioned at https://material.io/design/components/menus.html#exposed-dropdown-menu .

What is the expected behavior?

Setting disableOptionCentering to true would change the options to appear either below or above the mat-select field.

What is the current behavior?

The options appear over the field, whether or not disableOptionCentering is used.

What are the steps to reproduce?

https://stackblitz.com/edit/angular-khatke I've tried using

#### What is the use-case or motivation for changing an existing behavior? This prevents the ability to implement the Exposed Dropdown Menu. #### Which versions of Angular, Material, OS, TypeScript, browsers are affected? Repro's at least while using Angular/Material npm 7.0.3 on Chrome 70.0.3538.77 on Mac. #### Is there anything else we should know?
crisbeto commented 5 years ago

It does work. You can see the difference in your example if you remove it, select the second option in the select and open it again.

takamori commented 5 years ago

ah.... I didn't notice (or expect) that behavior. That's not quite the same as what I was looking to do, then, since the top choice overlaps the input field, which isn't the way that Exposed Dropdown Menu is spec'd. Is there an appropriate way to implement the Exposed Dropdown Menu from the Material Design spec?

jelbourn commented 5 years ago

It looks like the "exposed dropdown" is a new addition to the spec. @crisbeto we should think about adding an API to support that. It might make sense to deprecate disableOptionCentering and instead have something like a panelPosition property

TheCom3di4n commented 5 years ago

@jelbourn I know that the functionality is a bit like a select, but wouldn't it be nicer to add it to the menu component or even ship it as a standalone component, since there are one or two differences, e.g. the style? This would also help people who read or follow the Material Design Guidelines and then look for the corresponding component in the Angular Material library.

In addition, the exposed dropdown menu also contains also things like a "user input" mode or a "inside a toolbar" mode (...I'm not even sure if you can/should use it outside of such a "toolbar"...), see the "Behaviour" section in the MDG. https://material.io/design/components/menus.html#exposed-dropdown-menu

takamori commented 5 years ago

@TheCom3di4n In my case, I was looking for the select functionality in the Material Design Guidelines, originally starting from "Text fields" and found it under menu, so I'd actually prefer that the Material Design Guideline folks consider at least providing a cross-link in the other direction.

TheCom3di4n commented 5 years ago

@takamori I fully agree with you, I also think that this is more of a select, as I already wrote above, and would feel more comfortable if they would place this component under the select in the Material Design Guidelines, since its behavior rather describes a kind of the native HTML select (...exactly one option can be selected...).

By the way, I find it really unfortunate, for reasons like these, that there is no issue tracker or public forum for the Material Design Guidelines in which you can ask questions or even start and have such discussions. But I guess that's intentional.

tahaabu commented 5 years ago

The workaround for getting the exposed dropdown might just be adding margin in the panelClass.

here's the example: https://stackblitz.com/edit/angular-khatke-8xhpbc?file=styles.css

kwong-yw commented 4 years ago

@tahaabu that visually looks correct, but once the dropdown is activated, you can't click on the menu to close the dropdown– it looks like there's a .cdk-overlay-pane masking most of the menu. Did you ever find a way to get around this?

Nikolay-Uvarov commented 4 years ago

@tahaabu We have faced with the same issue as @kwong-yw . Can't close dropdown by the arrow or by the area above and to the left in our case. CSS: margin: 32px 16px; We can't shift .cdk-overlay-pane because we have another mat-select on the same page.

mat-select-issue

Fixed(not the final version):

HTML: <mat-select #mySelect panelClass="server-group-drop-down-list" (openedChange)="openedChange($event)" TS: @ViewChild('mySelect') mySelect; ... public openedChange(opened: boolean): void { this.isOpened = opened; } @HostListener("document:click", ["$event.target"]) public onClick(targetElement) { if (this.isOpened) { const element = document.querySelector('.server-group-drop-down-list'); // your mat-select class if (!element.contains(targetElement)) { this.mySelect.close() } } }

I think you can close the dropdown on document click as well.

akmjenkins commented 4 years ago

The real way to fix this, without hacks, is to use the overlayDir property of the select and set it's positions.

https://material.angular.io/cdk/overlay/api

I do this AfterViewInit like this. There may be a better way:

export class SelectComponent implements AfterViewInit {
  @ViewChild(MatSelect) select: MatSelect;

  ngAfterViewInit() {
    this.select.overlayDir.positions = [
      {
        originX: 'center',
        originY: 'bottom',
        overlayX: 'center',
        overlayY: 'top'
      }
    ];
  }
petrskalicka commented 4 years ago

@akmjenkins Good point, but this option is already deprecated. It becomes private in version 10.

isbor commented 4 years ago

Workaround which unfortunately require some code. Also you should properly handle disabled selects.

https://stackblitz.com/edit/angular-khatke-rtiahe

jnenning commented 4 years ago

Is there any alternative for solving this problem because I have a use case where the option should stick to the bottom or the top?

eLarocque commented 4 years ago

I think that the underlying issue here is that everything happens in the overlay-pane element but it isn't accessible through the components that use it. For example, the mat-select has a panelClass Input which helps to customize the style of the panel, but in many cases it is not sufficient, especially if we need to "scope" the custom css in order to ensure it doesn't affect other components that use the cdk-overlay. What would be ideal is to also have access the cdk-overlay-pane that wraps the panel.

Maybe it makes sense to not only allow a "scope" to be added to the specific DOM panel elements of these components, but also the overlay-pane in which it resides. This is even easier to accomplish since there already exists such an Input in the overlay-directive : cdkConnectedOverlayPanelClass. If the mat-select (and possibly other such components) exposed this input through one of it's own, it would allow the customization of the overlay behaviors and styling.

I don't know if this goes against some internal architectural guidelines for components in general at Google, but if it is acceptable, maybe all components that make use of the overlay-directive could benefit from exposing the cdkConnectedOverlayPanelClass Input. In most cases the angular team would only need to support the class gets properly added to the overlay, and the responsibility of the custom behaviors and styling would fall in the hands of the developer adding the class. Might be a win-win for all.

eLarocque commented 4 years ago

Ended up using an approach similar to @isbor but with the twist of providing a custom implementation of the Overlay in the component's viewProviders. The new class extends the Overlay but adds logic to add a custom class in order to scope the overlay. The benefit to this is the classes are already set when the overlay renders, removing any kind of "temporary invalid states" that might be caused when attempting to manipulate the classes after the component renders.

See the example here: https://stackblitz.com/edit/angular-issue-14105

chandrakanthg commented 2 years ago

interesting post. Could you help me to solve my current problem with select-panel position. Issue: mat-options panel is overlapping the dropdown when the panel is not having enough space to display at the bottom of the dropdown hence I want to show it on the top of the dropdown without overlapping if we don't have enough space below.

the expected behaviour is: options cdk-overlay should display on top / below of the dropdown based on the space availability in the screen.

chandrakanthg commented 2 years ago

ngAfterViewInit() { this.select.overlayDir.positions = [ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'top' }, { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' } ]; }

this code snippet solves the above problem, we can change the positions my modifying the above option in the array.

mtschneiders commented 2 years ago

Ended up using an approach similar to @isbor but with the twist of providing a custom implementation of the Overlay in the component's viewProviders. The new class extends the Overlay but adds logic to add a custom class in order to scope the overlay. The benefit to this is the classes are already set when the overlay renders, removing any kind of "temporary invalid states" that might be caused when attempting to manipulate the classes after the component renders.

See the example here: https://stackblitz.com/edit/angular-issue-14105

On angular 11 and above, you can set the select overlay panel class via provider:

@Component({
  selector: "app-custom-select",
  templateUrl: "./custom-select.component.html",
  styleUrls: ["./custom-select.component.scss"],
  providers: [
    {
      provide: MAT_SELECT_CONFIG,
      useValue: { overlayPanelClass: 'custom-overlay-panel' },
    },
  ],
})

Example: Stackblitz