Another Swipeable Tabs Concept

Here is a quick stab at swipeable tabs, this time as an Angular attribute directive. I have not had time to continue work on this, but I thought I’d post in case anyone wanted to take it further.

Modify app.module.ts:

import { TabSwipeDirective } from '../some-directory/tabswipe.directive';
...
* Then add TabSwipeDirective to declarations[...] list of NgModule

tabswipe.directive.ts:

import { Directive, ElementRef, Input, Inject, forwardRef } from '@angular/core';

import { Gesture } from 'ionic-angular/gestures/gesture';

import { Tabs, Tab } from 'ionic-angular';

@Directive({

  selector: '[tab-swipe]'
})
export class TabSwipeDirective {

  @Input('tab-swipe') direction = 'left';

  @Input('swipe-threshold') threshold = 40;

  private _sourceElementRef: ElementRef;
  private _sourceGesture: Gesture;
  private _sourceElement: HTMLElement;
  private _sourceTab: Tab;
  private _destElement: HTMLElement;
  private _destTab: Tab;

  private _tabIndex = 0;

  constructor(el: ElementRef, @Inject(forwardRef(() => Tabs)) private _tabs:Tabs) {

    this._sourceElementRef = el;
    this._sourceElement = el.nativeElement;

    this._sourceGesture = new Gesture(this._sourceElement);
    this._sourceGesture.listen();
    this._sourceGesture.on('panstart', (ev) => this._panStart(ev));
    this._sourceGesture.on('panend', (ev) => this._panEnd(ev));
    this._sourceGesture.on('panleft panright', (ev) => this._panMove(ev));

    console.log(this._tabs);
  }

  ngAfterViewInit() {

    for (let idx = 0; idx < this._tabs._tabs.length; idx++) {

      if (this._tabs._tabs[idx]._tabId === this._sourceElementRef.nativeElement.id) {

        this._tabIndex = idx; break;
      }
    }

    this._sourceTab = this._tabs._tabs[this._tabIndex];
  }

  _panStart(ev) {

    if (this.direction === 'left') {

      this._destTab = this._tabs._tabs[this._tabIndex+1];
      this._destElement = this._destTab._elementRef.nativeElement;

    } else if (this.direction === 'right') {

      this._destTab = this._tabs._tabs[this._tabIndex-1];
      this._destElement = this._destTab._elementRef.nativeElement;
    }
  }

  _panEnd(ev) {

    if ((this.direction === 'right' && ( ev.deltaX < this.threshold || ev.deltaX < 0)) ||
        (this.direction === 'left'  && (-ev.deltaX < this.threshold || ev.deltaX > 0))) {

      this._destElement.className.replace(' show-tab','');
      this._tabs.select(this._sourceTab);

    } else {

      this._tabs.select(this._destTab);
    }

    this._sourceElement.style.transform = 'translate(0px,0px)';
    this._destElement.style.transform = 'translate(0px,0px)';
  }

  _panMove(ev) {

    let delta = ev.deltaX;

    if (ev.deltaX < 0 && this.direction === 'right') delta = 0;
    if (ev.deltaX > 0 && this.direction === 'left') delta = 0;

    let offset = 0;

    if (this.direction === 'right') {

      offset = -this._destElement.offsetWidth;

    } else if (this.direction === 'left') {

      offset = this._destElement.offsetWidth;

      if (-delta > offset) offset = -delta;
    }

    this._sourceElement.style.transform = 'translate(' + delta + 'px,0px)';
    this._destElement.style.transform = 'translate(' + (offset + delta) + 'px,0px)';
    this._destElement.className = this._destElement.className.replace(' show-tab','') + ' show-tab';
  }
}

Modify your tabs page:

<ion-tabs selectedIndex="1">
	<ion-tab [root]="tabContacts" tabIcon="contacts" tab-swipe="left"></ion-tab>
	<ion-tab [root]="tabSettings" tabIcon="settings" tab-swipe="right"></ion-tab>
</ion-tabs>

Open issues:

  • On first swipe, the other tabs are not loaded, so they are blank.
  • Doesn’t always prevent swiping the wrong direction and can get stuck on top-most page.
  • Ideally, the ion-header should not move with the page, it should transition in a platform-specific way.
  • Doesn’t currently support swiping in both directions for a tab that is in the “middle” of two other tabs.
  • The tab-swipe=“left/right” part is supposed to set which direction swipes are available for a particular page, there should also be an ‘auto’ and ‘none’ option.
  • Clicking a tab icon doesn’t slide the tabs.

It’s probably buggy in other ways too, but I think if it were completed, this would be a good bridge until this is added to the framework…