tech-systems / panes

🎉📱 Create dynamic modals, cards, panes for your applications in few steps. One instance – Thousands solutions. Any framework and free.
https://panejs.com
MIT License
681 stars 40 forks source link

[BUG] Does not work properly with Angular 11 #156

Closed isuruc20 closed 3 years ago

isuruc20 commented 3 years ago

Hi @roman-rr,

I have been using cupertino-pane for about two months with Angular 8. And it worked perfectly. Recently I've updated to Angular 11 and now it looks like the pane is not working with Angular 11.

I've using this in two different components in my app and when I navigates between those two components, sometimes it breaks up and show the drawer content right at the middle of the page/DOM.

This is the working screenshot; image

This is the broken scenario; image

In this case I cannot open the pane by clicking/tapping at the trigger element. I can access the menu items from the page but it is no longer a drawer/pane when broken. And when I refresh the page it is working randomly.

And I get these warnings in my console log after the Angular update; image

This is my latest Angular version in the project;

"@angular/core": "11.2.14"

Any help would be really appreciated. Thanks.

roman-rr commented 3 years ago

@isuruc20 Seem's that somewhere pane was destroyed. Could you please provide some code snippets or example sources to easy debug ?

isuruc20 commented 3 years ago

@roman-rr here are the code snippets that I used in the project;

HTML

<button class="selector-toggle content-selector-toggle" (click)="openDrawer()"
                    type="button">{{selectedContentItem?.displayName}}</button>
<div class="mobile-bottom-drawer">
    <div class="content">
        <ng-container *ngIf="listState==='initial'">
            <div class="header">{{selectContentMobileHeader}}</div>
            <ng-container *ngFor="let item of contentItems">
                <div class="content-item" *ngIf="!item.hide" [ngClass]="{'selected':item.selected}"
                     (click)="handleContentItemSelectionMobile(item, 'initial')">
                    <span>{{item?.displayName}}</span>
                    <fa-icon *ngIf="item.items && item.items.length > 0 && !isRTL" class="select-topic-icon"
                             [icon]="['far', 'angle-right']" [size]="'lg'"></fa-icon>
                    <fa-icon *ngIf="item.items && item.items.length > 0 && isRTL" class="select-topic-icon"
                             [icon]="['far', 'angle-left']" [size]="'lg'"></fa-icon>
                </div>
            </ng-container>
        </ng-container>

        <ng-container *ngIf="listState==='level1'">
            <div class="header">
                <div class="back-button" (click)="goLevelBack()">
                    <fa-icon *ngIf="!isRTL" class="back-icon"
                             [icon]="['far', 'angle-left']" [size]="'lg'"></fa-icon>
                    <fa-icon *ngIf="isRTL" class="back-icon"
                             [icon]="['far', 'angle-right']" [size]="'lg'"></fa-icon>
                </div>
                {{selectedMobileContentItem?.displayName}}
            </div>
            <ng-container *ngFor="let item of selectedMobileContentItem?.items">
                <div class="content-item" *ngIf="!item.hide"
                     [ngClass]="{'selected':item.selected, 'separator':item?.separator}"
                     (click)="handleContentItemSelectionMobile(item, 'level1')">
                    <span>{{item?.displayName}}</span>
                </div>
            </ng-container>
        </ng-container>
    </div>
</div>

Typescript

drawer: CupertinoPane;
listState = 'initial';

ngOnInit() {
    this.configureDrawer();
   //other code goes here
}

configureDrawer() {
    let topHeight = window.innerHeight;
    let middleHeight = 200;
    let content = <HTMLStyleElement>document.querySelector('.mobile-bottom-drawer .content');
    this.drawer = new CupertinoPane('.mobile-bottom-drawer', {
      breaks: {
        top: {enabled: true, height: topHeight, bounce: true},
        middle: {enabled: true, height: middleHeight,  bounce: true}
      },
      backdrop: true,
      initialBreak: 'middle',
      // dragBy: ['.draggable', '.mobile-bottom-drawer .header'],
      draggableOver: true,
      bottomClose: true,
      touchMoveStopPropagation: true,
      buttonDestroy: false,
      fitScreenHeight: true,
      cssClass : 'mt-drawer',
      onBackdropTap: () => this.destroyDrawer(),
      onDrag: () => {
        content.style.height = `${window.screen.height
        - this.getPaneTransformY()
        - content.offsetTop}px`;
        content.setAttribute('style', 'overflow-y: hidden !important');
        content.setAttribute('style', 'overflow-x: hidden !important');
      },
      onDidPresent: () => {
        content.setAttribute('style', 'overflow-y: auto !important');
        content.setAttribute('style', 'overflow-x: hidden !important');
        content.style.height = `${middleHeight - content.offsetTop}px`;
      },
      onTransitionEnd: () => this.setHeight()
    });
    this.drawer.hide();
  }

  openDrawer() {
    if (!this.isMobile) {
      return;
    }

    this.drawer.present({animate: true});
    this.listState = 'initial';
  }

  destroyDrawer() {
    this.drawer.destroy({animate: true});
  }

  getPaneTransformY() {
    const translateYRegex = /\.*translateY\((.*)px\)/i;
    const paneEl = <HTMLStyleElement>document.querySelector('.pane');
    return paneEl ? parseFloat(translateYRegex.exec(paneEl.style.transform)[1]) : 0;
  }

  setHeight() {
    const topHeight = window.innerHeight;
    const middleHeight = 200;
    let content = <HTMLStyleElement>document.querySelector('.mobile-bottom-drawer .content');
    content.setAttribute('style', 'overflow-y: auto !important');
    content.setAttribute('style', 'overflow-x: hidden !important');
    if (this.drawer.currentBreak() === 'top') {
      content.style.height = `${topHeight - content.offsetTop}px`;
    }
    if (this.drawer.currentBreak() === 'middle') {
      content.style.height = `${middleHeight - content.offsetTop}px`;
    }
  }

  goLevelBack() {
    this.listState = 'initial';
  }

  handleContentItemSelectionMobile(item, type) {
    if ((type === 'initial') && (item.id !== 'topic')) {
      //go to next level
      this.listState = 'level1';
      this.selectedMobileContentItem = item;
    } else if ((type === 'level1') || (item.id === 'topic')) {
      //handle select
      this.handleContentItemSelection(item);
      this.destroyDrawer();
    }
  }
roman-rr commented 3 years ago

Hey mate @isuruc20

when I navigates between those two components, sometimes it breaks up and show the drawer content right at the middle of the page/DOM

When you click on item in your list you initiate handleContentItemSelectionMobile (). I see that you use .destroy() method in this function.

Please do this steps:

  1. Try to replace this.drawer.destroy({animate: true}); with this.drawer.hide();
  2. If it's doesn't helps, please prepare for me some sources where bug will be reproduced (include the function where you change between two components) and sample data list.
roman-rr commented 3 years ago

Thank you @foxyladybug Provide a sources and i will fix it.

roman-rr commented 3 years ago

@foxyladybug

 onDidDismiss: () => {
        this.shouldShowPane = false;
        myPane.hide()

      },

You can't call .hide() if pane is dismissed. Dismissed mean - destroyed from DOM. Anyway, i don't clear understand what is your issue. Please clarify, and provide demo sources with repository, if possible.

roman-rr commented 3 years ago

I unable to fix this bug without reproduced sources. But i guess that the bug is not about angular, but about code structure. Any who's face similar issue's, i recommend todo:

  1. Do not use [hidden] directive attribute to hide pane
  2. Initiate pane class right after page loaded, this will hide pane automatically:
    ngOnInit() {
    // Initiate class here, it will hide DOM element automatically.
    this.pane = new CupertinoPane('.pane', settings);
    }