angular / components

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

Feature request - expansion panel option to only expand/collapse on ▽ or △ #8190

Open 1619digital opened 7 years ago

1619digital commented 7 years ago

Bug, feature request, or proposal:

Feature request

What is the expected behavior?

Ability to optionally only open / close expansion panel when up/down arrow is selected rather than the entire panel title

What is the current behavior?

The entire title triggers the behaviour

What is the use-case or motivation for changing an existing behavior?

e.g. Outlook.com user interface - selecting a 'folder' is an action (e.g. edit the folder) - toggling its slider-like display requires action on the up/down button. Seems to make sense from a user interaction point of view. This can't be reproduced with the material expansion panel. It's useful when selecting the title implies 'more information' on this panel.

Is there anything else we should know?

Workaround (untested) - add click handler to mat-panel-title and do not propagate the event

julianobrasil commented 7 years ago

Maybe the cdk could have some flexibility to allow the end developer do this. The expansion panel follows the specs and should stay like that. An improvement in cdk docs, like a guide would be welcome, IMO.

julianobrasil commented 7 years ago

I think #5250 is more specific about the Guide.

leocaseiro commented 7 years ago

I've achieved that closing the expansion on click for elements that are not .mat-expansion-indicator:

<mat-expansion-panel #matExpansionPanel>
  <mat-expansion-panel-header (click)="expandPanel(matExpansionPanel, $event)">
    <mat-panel-title>title</mat-panel-title>
  </mat-expansion-panel-header>
...
</mat-expansion-panel>
expandPanel(matExpansionPanel: MatExpansionPanel, event: Event): void {
    event.stopPropagation(); // Preventing event bubbling

    if (!this._isExpansionIndicator(event.target)) {
      matExpansionPanel.close(); // Here's the magic
    }
  }

  private _isExpansionIndicator(target: EventTarget): boolean {
    const expansionIndicatorClass = 'mat-expansion-indicator';
    return (target.classList && target.classList.contains(expansionIndicatorClass) );
  }

See Plunker http://plnkr.co/edit/Q54a57?p=preview

PS: You can add some CSS to change the cursor:default/pointer

BoJIbFbI4 commented 6 years ago

@leocaseiro, thnx but this method have a bug. Yes, you can open panel on button only, but close on over panel . how can i to programme close only on button too?

tosehee75 commented 6 years ago

Bump.

Noticed the same issue.

EduMelo commented 6 years ago

@BoJIbFbI4 This can be fixed using matExpansionPanel.toggle(); istead of matExpansionPanel.close();

darkmavid commented 6 years ago

@leocaseiro

This helps, but i find when the mat expansion header is focused, space and enter trigger the collapse, and i tried binding the key events to the same method but am unable to get the toggle event to stop.

thre3eye commented 6 years ago

Just a quick note/workaround if you only care about not triggering the panel for extra buttons in the header: Just use $event.stopPropagation(); on the button, i.e.

` <button mat-icon-button (click)="doMyButtonThing();$event.stopPropagation();">

refresh
        </button>

`

I'd still prefer a setting on the control as per this issue. Thanks.

AbdelhakD commented 6 years ago

Thanks for the workarround

AbdelhakD commented 6 years ago

I occured this issue 👍 ERROR in src/app/layout/components/factor-table/factor-table.component.ts(11,34): error TS2304: Cannot find name 'MatExpansionPanel'. src/app/layout/components/factor-table/factor-table.component.ts(21,20): error TS2339: Property 'classList' does not exist on type 'EventTarget'. src/app/layout/components/factor-table/factor-table.component.ts(21,40): error TS2339: Property 'classList' does not exist on type 'EventTarget'.

any ideau

ahmet-basaran commented 6 years ago

I occured this issue 👍 ERROR in src/app/layout/components/factor-table/factor-table.component.ts(11,34): error TS2304: Cannot find name 'MatExpansionPanel'. src/app/layout/components/factor-table/factor-table.component.ts(21,20): error TS2339: Property 'classList' does not exist on type 'EventTarget'. src/app/layout/components/factor-table/factor-table.component.ts(21,40): error TS2339: Property 'classList' does not exist on type 'EventTarget'.

any ideau

Running into the same issue.

lukasvencalek commented 6 years ago

The EventTarget is HTMLElement so cast it as HTMLElement

if (!this._isExpansionIndicator(event.target as HTMLElement)) { ... private _isExpansionIndicator(target: HTMLElement): boolean {

SubhasisDebsharma commented 5 years ago

With CSS we can solve this problem. If we put a transparent mask on the header leaving some space for the button on right side, it will work. Here is the solution example:

HTML

<mat-accordion>
  <div class="wrapper">
    <div class="mask">

    </div>
    <mat-expansion-panel>
      <mat-expansion-panel-header [expandedHeight]="'48px'">
        <mat-panel-title>
          Title
        </mat-panel-title>
      </mat-expansion-panel-header>
      <p>Hello</p>
    </mat-expansion-panel>
  </div>
</mat-accordion>

CSS

.wrapper{
  position: relative;
}

.mask{
  position: absolute;
  background: transparent;
  width: calc(100% - 50px);
  z-index: 1;
  height: 48px;    
}
rafayabedi commented 5 years ago

I got a lot of expansion panels in a single page, so I can't grab each panel using Element Ref. Moreover, each panel has select or text box in header which makes me unable to put CSS layer over header. I too want similar experience of toggling expansion panel upon clicking arrow only. Is there help for me?

SubhasisDebsharma commented 5 years ago

@rafayabedi If you put the Select or Input element inside the 'div .mask' and with css if you place the elements properly will it solve your problem? Like this...

<mat-accordion>
  <div class="wrapper">

    <div class="mask">
          <input placeholder="Name">
    </div>

    <mat-expansion-panel>
      <mat-expansion-panel-header [expandedHeight]="'48px'">
        <mat-panel-title>
          Title
        </mat-panel-title>
      </mat-expansion-panel-header>
      <p>Hello</p>
    </mat-expansion-panel>
  </div>
</mat-accordion>
.wrapper{
  position: relative;
}

.mask{
  position: absolute;
  background: transparent;
  width: calc(100% - 50px);
  z-index: 1;
  height: 48px;    
}

.mask input{
  float: right;
  margin: 15px
}
sanyashvets commented 4 years ago

Issues with solutions above: @leocaseiro - event expandedChange fires 2 times @SubhasisDebsharma - because of position: absolute my layout completely crashed. Don't want to fix it

My solution easy-peasy solution:

mat-expansion-panel-header {
  pointer-events: none; // ignore all mouse events on parent
}

.mat-expansion-indicator {
  pointer-events: all; // but allow all mouse events on child indicator icon
}

Maybe it could fit someone's case the only issue - content of mat-expansion-panel-header becomes untouchable also, you may want to subscribe on<mat-expansion-panel /> click()event

faisal5170 commented 4 years ago

if you wanna only expand do like this

<mat-expansion-panel *ngFor="let account of accounts" (opened)="expandPanel(account)">

juhishw commented 4 years ago

I've achieved that closing the expansion on click for elements that are not .mat-expansion-indicator:

<mat-expansion-panel #matExpansionPanel>
  <mat-expansion-panel-header (click)="expandPanel(matExpansionPanel, $event)">
    <mat-panel-title>title</mat-panel-title>
  </mat-expansion-panel-header>
...
</mat-expansion-panel>
expandPanel(matExpansionPanel: MatExpansionPanel, event: Event): void {
    event.stopPropagation(); // Preventing event bubbling

    if (!this._isExpansionIndicator(event.target)) {
      matExpansionPanel.close(); // Here's the magic
    }
  }

  private _isExpansionIndicator(target: EventTarget): boolean {
    const expansionIndicatorClass = 'mat-expansion-indicator';
    return (target.classList && target.classList.contains(expansionIndicatorClass) );
  }

See Plunker http://plnkr.co/edit/Q54a57?p=preview

PS: You can add some CSS to change the cursor:default/pointer

I have used this approach which stops the toggle on header click but i have controller in the header only which has an expand icon on clicking on which i want this to toggle how to do that?

shweta21798 commented 4 years ago

i am using a mat-slide-toggle on mat expansion which resulted in opening of mat expansion even if i toggle the mat-slide. I used @

Just a quick note/workaround if you only care about not triggering the panel for extra buttons in the header: Just use $event.stopPropagation(); on the button, i.e.

<mat-panel-description> <button mat-icon-button (click)="doMyButtonThing();$event.stopPropagation();"> <mat-icon>refresh</mat-icon> </button> </mat-panel-description>

I'd still prefer a setting on the control as per this issue. Thanks.

Thanks alot for this, i was using mat-slide toggle on mat expansion which was causing the problem. using stopPropgation on mat-slide toggle works. thanks

1990prabhjit commented 3 years ago

I works for me by replacing matExpansionPanel.close(); with matExpansionPanel.toggle(); `<mat-expansion-panel #matExpansionPanel> <mat-expansion-panel-header (click)="expandPanel(matExpansionPanel, $event)">

title

... `

`expandPanel(matExpansionPanel: MatExpansionPanel, event: Event): void { event.stopPropagation(); if (!this._isExpansionIndicator(event.target)) { matExpansionPanel.toggle(); // just use toggle() instead of close() } }

private _isExpansionIndicator(target: EventTarget): boolean { const expansionIndicatorClass = 'mat-expansion-indicator'; return (target['classList'] && target['classList'].contains(expansionIndicatorClass) ); }`

afouadm commented 3 years ago

I know this thread is very old but it is still open so i guess others could benefit from this reply, this worked for me by simply doing this

HTML <mat-panel-title (click)="onPanelTitleClick($event)"></mat-panel-title>

TS

onPanelTitleClick(event: Event){
    event.stopPropagation();
}