Repository: zyra/ionic2-super-tabs
Branch: master
Commit: e4882248a025
Files: 77
Total size: 133.4 KB
Directory structure:
gitextract_r57jfzom/
├── .dockerignore
├── .docs.yaml
├── .doctemplate
├── .drone.yml
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── LICENSE.md
├── README.md
├── angular/
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── angular.json
│ ├── ng-package.json
│ ├── package.json
│ ├── rollup.config.js
│ ├── rollup.config.legacy.js
│ ├── src/
│ │ ├── app-init.ts
│ │ ├── directives/
│ │ │ ├── proxies-list.txt
│ │ │ ├── proxies-utils.ts
│ │ │ └── proxies.ts
│ │ ├── public-api.ts
│ │ └── super-tabs.module.ts
│ ├── tsconfig.json
│ ├── tsconfig.legacy.json
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tslint.json
├── core/
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src/
│ │ ├── components.d.ts
│ │ ├── index.ts
│ │ ├── interface.d.ts
│ │ ├── mixins.scss
│ │ ├── super-tab/
│ │ │ ├── readme.md
│ │ │ ├── super-tab.component.scss
│ │ │ └── super-tab.component.tsx
│ │ ├── super-tab-button/
│ │ │ ├── readme.md
│ │ │ ├── super-tab-button.component.scss
│ │ │ └── super-tab-button.component.tsx
│ │ ├── super-tab-indicator/
│ │ │ ├── readme.md
│ │ │ ├── super-tab-indicator.component.scss
│ │ │ └── super-tab-indicator.component.tsx
│ │ ├── super-tabs/
│ │ │ ├── readme.md
│ │ │ ├── super-tabs.component.scss
│ │ │ └── super-tabs.component.tsx
│ │ ├── super-tabs-container/
│ │ │ ├── readme.md
│ │ │ ├── super-tabs-container.component.scss
│ │ │ └── super-tabs-container.component.tsx
│ │ ├── super-tabs-toolbar/
│ │ │ ├── readme.md
│ │ │ ├── super-tabs-toolbar.component.scss
│ │ │ └── super-tabs-toolbar.component.tsx
│ │ ├── utils.ts
│ │ └── variables.scss
│ ├── stencil.config.ts
│ └── tsconfig.json
├── doc-pages/
│ ├── configuration.md
│ ├── getting-started/
│ │ └── angular.md
│ ├── home.md
│ └── usage/
│ └── angular.md
├── lerna.json
├── package.json
├── react/
│ ├── .gitignore
│ ├── package.json
│ ├── rollup.config.js
│ ├── src/
│ │ ├── components.ts
│ │ ├── index.ts
│ │ └── react-component-lib/
│ │ ├── createComponent.tsx
│ │ ├── createControllerComponent.tsx
│ │ ├── createOverlayComponent.tsx
│ │ ├── index.ts
│ │ └── utils/
│ │ ├── attachEventProps.ts
│ │ └── index.tsx
│ └── tsconfig.json
└── super-tabs.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
.git
.idea
Dockerfile
.docs.yaml
.drone.yml
.gitignore
README.md
super-tabs.sh
docs
node_modules
core/node_modules
core/dist
core/loader
core/hydrate
core/.stencil
angular/node_modules
angular/dist
react/node_modules
react/dist
react/dist-transpiled
================================================
FILE: .docs.yaml
================================================
siteTitle: Super Tabs
description: Swipeable tabs for Ionic apps
repo: https://github.com/zyra/ionic-super-tabs
outDir: docs
baseUrl: https://zyra.github.io/ionic-super-tabs/
pagePatterns:
- name: '{{ index .PathMatches 0 1 }}'
sourceGlob: core/src/**/readme.md
pattern: '([a-z-]+)/readme.md$'
path: 'components/{{ index .PathMatches 0 1 }}'
editOnGithub: true
addToMenu: true
menuGroup: components
pages:
- name: home
title: Super Tabs - Swipeable tabs for Ionic apps
path: /
source: doc-pages/home.md
editOnGithub: true
- name: config
title: Configuration
path: configuration
source: doc-pages/configuration.md
editOnGithub: true
- name: install-ng
title: Getting started with Angular
path: getting-started/angular
source: doc-pages/getting-started/angular.md
editOnGithub: true
- name: usage-ng
title: Angular Usage Guide
path: usage/angular
source: doc-pages/usage/angular.md
editOnGithub: true
menuItems:
- title: Getting started
name: getting-started
group: true
items:
- title: Introduction
- title: Angular
link: getting-started/angular
# - title: JavaScript
# link: getting-started/javascript
- title: Configuration
link: configuration
- title: Usage guide
name: usage-guide
group: true
items:
- title: Angular
link: usage/angular
- title: Components
name: components
group: true
templates:
- name: base
source: .doctemplate
================================================
FILE: .doctemplate
================================================
{{- .Title }}
{{ if .Description }}{{ end }}
{{ if .BaseURL }}{{ end }}
{{ .Content }}
================================================
FILE: .drone.yml
================================================
---
kind: pipeline
type: docker
name: default
clone:
depth: 1
volumes:
- name: dockersock
host:
path: /var/run/docker.sock
steps:
- name: Fast build
image: docker
when:
event:
exclude:
- tag
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- docker build .
- name: Build
image: node:12
when:
event:
- tag
commands:
- npm i
- ./super-tabs.sh setup
- ./super-tabs.sh build
- name: Publish
image: node:12
when:
event:
- tag
environment:
NPM_TOKEN:
from_secret: npm_token
commands:
- echo '//registry.npmjs.org/:_authToken=$${NPM_TOKEN}' > ~/.npmrc
- ./super-tabs.sh publish
- name: Generate docs
image: harbor.zyra.ca/public/zmdocs:v1.1.0
when:
branch:
- master
commands:
- zmdocs g
- name: Publish docs
image: plugins/gh-pages
when:
event:
exclude:
- pull_request
branch:
- master
settings:
username:
from_secret: gh_user
password:
from_secret: gh_token
pages_directory: docs
#- name: Update example project
# image: plugins/downstream
# settings:
# server: https://drone.zyra.ca
# token:
# from_secret: drone_token
# repositories:
# - zyra/ionic-super-tabs-example
# fork: true
---
kind: signature
hmac: 325be6f79fe9c33c34d9376df0ffce76ae06e1e831666c434bdfa2c45837b85b
...
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .gitignore
================================================
node_modules
.idea
.DS_STORE
docs
================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.0.5](https://github.com/zyra/ionic-super-tabs/compare/v6.0.4...v6.0.5) (2019-11-13)
### Bug Fixes
* **super-tabs:** allow usage without a toolbar ([5247db7](https://github.com/zyra/ionic-super-tabs/commit/5247db7ba79c0e401e91878e78a9cc9c2763532e))
================================================
FILE: Dockerfile
================================================
FROM node:12-alpine
COPY . .
RUN npm i
RUN npx lerna bootstrap
RUN npm run build
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2019 Zyra Media Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Super Tabs
Swipeable tabs module for Ionic apps.
* [Packages](#packages)
* [Ionic 4](#ionic-4)
* [Ionic 2 / Ionic 3](#ionic-2--ionic-3)
* [Documentation](https://zyra.github.io/ionic-super-tabs)
* Getting started
* [Angular](https://zyra.github.io/ionic-super-tabs/getting-started/angular)
* JavaScript - WIP
* Usage guide
* [Angular](https://zyra.github.io/ionic-super-tabs/usage/angular)
* JavaScript - WIP
* [Configuration](https://zyra.github.io/ionic-super-tabs/configuration)
* [Example Project](https://github.com/zyra/ionic-super-tabs-example)
* [Notes](#notes)
* [License](#license)
---
## Packages
#### Ionic 4
Packages published under the `@ionic-super-tabs/` scope are compatible with `@ionic/angular@^4.0.0` _(4.0.0 and above)_.
> @ionic-super-tabs/core
>
> 
> 
> 
>
>
> @ionic-super-tabs/angular
>
> 
> 
> 
#### Ionic 2 / Ionic 3
For Ionic 2 / Ionic 3 apps, see the [v5 branch](https://github.com/zyra/ionic-super-tabs/tree/v5) for the previous version of Super Tabs.
> ionic2-super-tabs
>
> 
> 
> 
---
## Notes
This module has been tested with Angular and Stencil based apps only.
Compatibility with React and Vue has not been tested yet.
## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
================================================
FILE: angular/.gitignore
================================================
build
node_modules
dist
================================================
FILE: angular/CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.0.5](https://github.com/zyra/ionic-super-tabs/compare/v6.0.4...v6.0.5) (2019-11-13)
**Note:** Version bump only for package @ionic-super-tabs/angular
================================================
FILE: angular/angular.json
================================================
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"super-tabs": {
"projectType": "library",
"root": "",
"sourceRoot": "src",
"prefix": "super",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "tsconfig.lib.json",
"project": "ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "tsconfig.lib.prod.json"
}
}
}
}
}},
"defaultProject": "super-tabs"
}
================================================
FILE: angular/ng-package.json
================================================
{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"dest": "./dist/",
"lib": {
"entryFile": "src/public-api.ts"
},
"whitelistedNonPeerDependencies": [
"@ionic-super-tabs/core"
]
}
================================================
FILE: angular/package.json
================================================
{
"name": "@ionic-super-tabs/angular",
"version": "7.0.8",
"description": "Ionic Super Tabs bindings for Angular applications",
"scripts": {
"build": "ng build",
"prebuild": "npm run clean",
"test": "ng test",
"clean": "rm -rf dist"
},
"author": "Zyra Media Inc. ",
"license": "MIT",
"dependencies": {
"@ionic-super-tabs/core": "^7.0.8"
},
"keywords": [
"ionic",
"swipeable",
"tabs",
"module",
"component",
"angular"
],
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.8",
"@angular-devkit/build-ng-packagr": "~0.901.8",
"@angular-devkit/core": "~9.1.8",
"@angular-devkit/schematics": "~9.1.8",
"@angular/cli": "~9.1.8",
"@angular/common": "~9.1.11",
"@angular/compiler": "~9.1.11",
"@angular/compiler-cli": "~9.1.11",
"@angular/core": "~9.1.11",
"@angular/forms": "~9.1.11",
"@angular/platform-browser": "~9.1.11",
"@angular/platform-browser-dynamic": "~9.1.11",
"@angular/router": "~9.1.11",
"@types/node": "~14.0.13",
"fs-extra": "~9.0.1",
"glob": "~7.1.6",
"ng-packagr": "~9.1.5",
"rxjs": "~6.5.5",
"tsickle": "~0.38.1",
"tslint": "~5.15.0",
"typescript": "~3.8.3",
"zone.js": "~0.10.3"
}
}
================================================
FILE: angular/rollup.config.js
================================================
import resolve from 'rollup-plugin-node-resolve';
export default {
input: 'build/es2015/core.js',
output: [
{
file: 'dist/fesm2015.js',
format: 'es',
},
],
external: (id) => {
// anything else is external
// Windows: C:\xxxxxx\xxx
const colonPosition = 1;
return !(id.startsWith('.') || id.startsWith('/') || id.charAt(colonPosition) === ':');
},
plugins: [
resolve({
module: true,
}),
],
};
================================================
FILE: angular/rollup.config.legacy.js
================================================
import config from './rollup.config';
const newConfig = {
...config,
input: 'build/es5/core.js',
};
newConfig.output = [
{
file: 'dist/fesm5.js',
format: 'es'
},
{
file: 'dist/fesm5.cjs.js',
format: 'cjs'
}
];
export { newConfig as default };
================================================
FILE: angular/src/app-init.ts
================================================
import { NgZone } from '@angular/core';
import { applyPolyfills, defineCustomElements } from '@ionic-super-tabs/core/loader';
let didInitialize = false;
export function appInit(doc: Document, zone: NgZone) {
return async function () {
const win: any = doc.defaultView as any;
if (!win || didInitialize) {
return;
}
didInitialize = true;
const aelFn = '__zone_symbol__addEventListener' in (doc.body as any)
? '__zone_symbol__addEventListener'
: 'addEventListener';
await applyPolyfills();
await defineCustomElements(win, {
syncQueue: true,
raf,
jmp: (h: any) => zone.runOutsideAngular(h),
ael(elm, eventName, cb, opts) {
(elm as any)[aelFn](eventName, cb, opts);
},
rel(elm, eventName, cb, opts) {
elm.removeEventListener(eventName, cb, opts);
},
});
};
};
declare const __zone_symbol__requestAnimationFrame: any;
declare const requestAnimationFrame: any;
export const raf = (h: any) => {
if (typeof __zone_symbol__requestAnimationFrame === 'function') {
return __zone_symbol__requestAnimationFrame(h);
}
if (typeof requestAnimationFrame === 'function') {
return requestAnimationFrame(h);
}
return setTimeout(h);
};
================================================
FILE: angular/src/directives/proxies-list.txt
================================================
import * as d from './proxies';
export const DIRECTIVES = [
d.SuperTab,
d.SuperTabButton,
d.SuperTabs,
d.SuperTabsContainer,
d.SuperTabsToolbar
];
================================================
FILE: angular/src/directives/proxies-utils.ts
================================================
/* tslint:disable */
import { fromEvent } from 'rxjs';
export const proxyInputs = (Cmp: any, inputs: string[]) => {
const Prototype = Cmp.prototype;
inputs.forEach(item => {
Object.defineProperty(Prototype, item, {
get() { return this.el[item]; },
set(val: any) { this.z.runOutsideAngular(() => (this.el[item] = val)); }
});
});
};
export const proxyMethods = (Cmp: any, methods: string[]) => {
const Prototype = Cmp.prototype;
methods.forEach(methodName => {
Prototype[methodName] = function () {
const args = arguments;
return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
};
});
};
export const proxyOutputs = (instance: any, el: any, events: string[]) => {
events.forEach(eventName => instance[eventName] = fromEvent(el, eventName));
}
// tslint:disable-next-line: only-arrow-functions
export function ProxyCmp(opts: { inputs?: any; methods?: any }) {
const decorator = function(cls: any){
if (opts.inputs) {
proxyInputs(cls, opts.inputs);
}
if (opts.methods) {
proxyMethods(cls, opts.methods);
}
return cls;
};
return decorator;
}
================================================
FILE: angular/src/directives/proxies.ts
================================================
/* tslint:disable */
/* auto-generated angular directive proxies */
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
import { ProxyCmp, proxyOutputs } from './proxies-utils';
import { Components } from '@ionic-super-tabs/core'
export declare interface SuperTab extends Components.SuperTab {}
@ProxyCmp({inputs: ['loaded', 'noScroll', 'visible'], 'methods': ['getRootScrollableEl']})
@Component({ selector: 'super-tab', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['loaded', 'noScroll', 'visible'] })
export class SuperTab {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface SuperTabButton extends Components.SuperTabButton {}
@ProxyCmp({inputs: ['disabled']})
@Component({ selector: 'super-tab-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['disabled'] })
export class SuperTabButton {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface SuperTabs extends Components.SuperTabs {}
@ProxyCmp({inputs: ['activeTabIndex', 'config'], 'methods': ['setConfig', 'selectTab']})
@Component({ selector: 'super-tabs', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['activeTabIndex', 'config'] })
export class SuperTabs {
tabChange!: EventEmitter;
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['tabChange']);
}
}
export declare interface SuperTabsContainer extends Components.SuperTabsContainer {}
@ProxyCmp({inputs: ['autoScrollTop', 'swipeEnabled'], 'methods': ['scrollToTop']})
@Component({ selector: 'super-tabs-container', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['autoScrollTop', 'swipeEnabled'] })
export class SuperTabsContainer {
activeTabIndexChange!: EventEmitter;
selectedTabIndexChange!: EventEmitter;
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['activeTabIndexChange', 'selectedTabIndexChange']);
}
}
export declare interface SuperTabsToolbar extends Components.SuperTabsToolbar {}
@ProxyCmp({inputs: ['color', 'scrollable', 'scrollablePadding', 'showIndicator']})
@Component({ selector: 'super-tabs-toolbar', changeDetection: ChangeDetectionStrategy.OnPush, template: '', inputs: ['color', 'scrollable', 'scrollablePadding', 'showIndicator'] })
export class SuperTabsToolbar {
buttonClick!: EventEmitter;
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['buttonClick']);
}
}
================================================
FILE: angular/src/public-api.ts
================================================
export * from './directives/proxies';
export { SuperTabsModule } from './super-tabs.module';
================================================
FILE: angular/src/super-tabs.module.ts
================================================
import { CommonModule, DOCUMENT } from '@angular/common';
import { APP_INITIALIZER, ModuleWithProviders, NgModule, NgZone } from '@angular/core';
import { appInit } from './app-init';
import { SuperTab, SuperTabButton, SuperTabs, SuperTabsContainer, SuperTabsToolbar } from './directives/proxies';
export const DECLARATIONS = [
SuperTab,
SuperTabButton,
SuperTabs,
SuperTabsContainer,
SuperTabsToolbar,
];
@NgModule({
declarations: DECLARATIONS,
exports: DECLARATIONS,
imports: [CommonModule],
})
export class SuperTabsModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SuperTabsModule,
providers: [
{
provide: APP_INITIALIZER,
useFactory: appInit,
multi: true,
deps: [DOCUMENT, NgZone],
},
],
};
}
}
================================================
FILE: angular/tsconfig.json
================================================
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
],
"paths": {
"super-tabs": [
"dist/super-tabs"
],
"super-tabs/*": [
"dist/super-tabs/*"
]
}
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"enableIvy": false
}
}
================================================
FILE: angular/tsconfig.legacy.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"target": "es5",
"declarationDir": "build/es5",
"outDir": "build/es5"
}
}
================================================
FILE: angular/tsconfig.lib.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/lib",
"target": "es2015",
"declaration": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}
================================================
FILE: angular/tsconfig.lib.prod.json
================================================
{
"extends": "./tsconfig.lib.json",
"angularCompilerOptions": {
"enableIvy": false
}
}
================================================
FILE: angular/tslint.json
================================================
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"array-type": false,
"arrow-parens": false,
"deprecation": {
"severity": "warning"
},
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [
true,
"single"
],
"trailing-comma": false,
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
}
}
================================================
FILE: core/.gitignore
================================================
node_modules
dist
stats.json
.stencil
loader
hydrate
================================================
FILE: core/CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.0.5](https://github.com/zyra/ionic-super-tabs/compare/v6.0.4...v6.0.5) (2019-11-13)
### Bug Fixes
* **super-tabs:** allow usage without a toolbar ([5247db7](https://github.com/zyra/ionic-super-tabs/commit/5247db7ba79c0e401e91878e78a9cc9c2763532e))
================================================
FILE: core/package.json
================================================
{
"name": "@ionic-super-tabs/core",
"version": "7.0.8",
"description": "",
"main": "dist/index.js",
"module": "dist/index.mjs",
"es2015": "dist/esm/index.mjs",
"es2017": "dist/esm/index.mjs",
"jsnext:main": "dist/esm/index.mjs",
"collection:main": "dist/collection/index.js",
"collection": "dist/collection/collection-manifest.json",
"types": "dist/types/interface.d.ts",
"files": [
"dist/",
"loader/",
"hydrate/"
],
"scripts": {
"build": "stencil build"
},
"keywords": [
"ionic",
"swipeable",
"tabs",
"module",
"component"
],
"author": "Zyra Media Inc.",
"license": "MIT",
"devDependencies": {
"@ionic/core": "~5.2.1",
"@stencil/angular-output-target": "0.0.2",
"@stencil/core": "~1.14.0",
"@stencil/react-output-target": "0.0.6",
"@stencil/sass": "~1.3.1"
}
}
================================================
FILE: core/src/components.d.ts
================================================
/* eslint-disable */
/* tslint:disable */
/**
* This is an autogenerated file created by the Stencil compiler.
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { SuperTabChangeEventDetail, SuperTabsConfig } from "./interface";
export namespace Components {
interface SuperTab {
/**
* Returns the root scrollable element
*/
"getRootScrollableEl": () => Promise;
"loaded": boolean;
/**
* Set this to true to prevent vertical scrolling of this tab. Defaults to `false`. This property will automatically be set to true if there is a direct child element of `ion-content`. To override this behaviour make sure to explicitly set this property to `false`.
*/
"noScroll": boolean;
"visible": boolean;
}
interface SuperTabButton {
"active"?: boolean;
/**
* Whether the button is disabled
*/
"disabled"?: boolean;
"index"?: number;
"scrollableContainer": boolean;
}
interface SuperTabIndicator {
/**
* Toolbar position This determines the position of the indicator
*/
"toolbarPosition": 'top' | 'bottom';
}
interface SuperTabs {
/**
* Initial active tab index. Defaults to `0`.
* @type {number}
*/
"activeTabIndex": number;
/**
* Global Super Tabs configuration. This is the only place you need to configure the components. Any changes to this input will propagate to child components.
* @type {SuperTabsConfig}
*/
"config"?: SuperTabsConfig;
/**
* Set the selected tab. This will move the container and the toolbar to the selected tab.
* @param index the index of the tab you want to select
* @param animate whether you want to animate the transition
* @param emit whether you want to emit tab change event
*/
"selectTab": (index: number, animate?: boolean, emit?: boolean) => Promise;
/**
* Set/update the configuration
* @param config Configuration object
*/
"setConfig": (config: SuperTabsConfig) => Promise;
}
interface SuperTabsContainer {
/**
* Set to true to automatically scroll to the top of the tab when the button is clicked while the tab is already selected.
*/
"autoScrollTop": boolean;
"config"?: SuperTabsConfig;
/**
* @param scrollX
* @param animate
*/
"moveContainer": (scrollX: number, animate?: boolean | undefined) => Promise;
/**
* @param index Index of the tab
* @param animate Whether to animate the transition
*/
"moveContainerByIndex": (index: number, animate?: boolean | undefined) => Promise;
"reindexTabs": () => Promise;
/**
* Scroll the active tab to the top.
*/
"scrollToTop": () => Promise;
"setActiveTabIndex": (index: number, moveContainer?: boolean, animate?: boolean) => Promise;
/**
* Enable/disable swiping
*/
"swipeEnabled": boolean;
}
interface SuperTabsToolbar {
/**
* Background color. Defaults to `'primary'`
*/
"color": string | undefined;
"config"?: SuperTabsConfig;
"moveContainer": (scrollX: number, animate?: boolean | undefined) => Promise;
/**
* Whether the toolbar is scrollable. Defaults to `false`.
*/
"scrollable": boolean;
/**
* If scrollable is set to true, there will be an added padding to the left of the buttons. Setting this property to false will remove that padding. The padding is also configurable via a CSS variable.
*/
"scrollablePadding": boolean;
"setActiveTab": (index: number, align?: boolean | undefined, animate?: boolean | undefined) => Promise;
"setSelectedTab": (index: number, animate?: boolean | undefined) => Promise;
/**
* Whether to show the indicator. Defaults to `true`
*/
"showIndicator": boolean;
}
}
declare global {
interface HTMLSuperTabElement extends Components.SuperTab, HTMLStencilElement {
}
var HTMLSuperTabElement: {
prototype: HTMLSuperTabElement;
new (): HTMLSuperTabElement;
};
interface HTMLSuperTabButtonElement extends Components.SuperTabButton, HTMLStencilElement {
}
var HTMLSuperTabButtonElement: {
prototype: HTMLSuperTabButtonElement;
new (): HTMLSuperTabButtonElement;
};
interface HTMLSuperTabIndicatorElement extends Components.SuperTabIndicator, HTMLStencilElement {
}
var HTMLSuperTabIndicatorElement: {
prototype: HTMLSuperTabIndicatorElement;
new (): HTMLSuperTabIndicatorElement;
};
interface HTMLSuperTabsElement extends Components.SuperTabs, HTMLStencilElement {
}
var HTMLSuperTabsElement: {
prototype: HTMLSuperTabsElement;
new (): HTMLSuperTabsElement;
};
interface HTMLSuperTabsContainerElement extends Components.SuperTabsContainer, HTMLStencilElement {
}
var HTMLSuperTabsContainerElement: {
prototype: HTMLSuperTabsContainerElement;
new (): HTMLSuperTabsContainerElement;
};
interface HTMLSuperTabsToolbarElement extends Components.SuperTabsToolbar, HTMLStencilElement {
}
var HTMLSuperTabsToolbarElement: {
prototype: HTMLSuperTabsToolbarElement;
new (): HTMLSuperTabsToolbarElement;
};
interface HTMLElementTagNameMap {
"super-tab": HTMLSuperTabElement;
"super-tab-button": HTMLSuperTabButtonElement;
"super-tab-indicator": HTMLSuperTabIndicatorElement;
"super-tabs": HTMLSuperTabsElement;
"super-tabs-container": HTMLSuperTabsContainerElement;
"super-tabs-toolbar": HTMLSuperTabsToolbarElement;
}
}
declare namespace LocalJSX {
interface SuperTab {
"loaded"?: boolean;
/**
* Set this to true to prevent vertical scrolling of this tab. Defaults to `false`. This property will automatically be set to true if there is a direct child element of `ion-content`. To override this behaviour make sure to explicitly set this property to `false`.
*/
"noScroll": boolean;
"visible"?: boolean;
}
interface SuperTabButton {
"active"?: boolean;
/**
* Whether the button is disabled
*/
"disabled"?: boolean;
"index"?: number;
"scrollableContainer"?: boolean;
}
interface SuperTabIndicator {
/**
* Toolbar position This determines the position of the indicator
*/
"toolbarPosition"?: 'top' | 'bottom';
}
interface SuperTabs {
/**
* Initial active tab index. Defaults to `0`.
* @type {number}
*/
"activeTabIndex"?: number;
/**
* Global Super Tabs configuration. This is the only place you need to configure the components. Any changes to this input will propagate to child components.
* @type {SuperTabsConfig}
*/
"config"?: SuperTabsConfig;
/**
* Tab change event. This event fires up when a tab button is clicked, or when a user swipes between tabs. The event will fire even if the tab did not change, you can check if the tab changed by checking the `changed` property in the event detail.
*/
"onTabChange"?: (event: CustomEvent) => void;
}
interface SuperTabsContainer {
/**
* Set to true to automatically scroll to the top of the tab when the button is clicked while the tab is already selected.
*/
"autoScrollTop"?: boolean;
"config"?: SuperTabsConfig;
/**
* Emits an event when the active tab changes. An active tab is the tab that the user looking at. This event emitter will not notify you if the user has changed the current active tab. If you need that information, you should use the `tabChange` event emitted by the `super-tabs` element.
*/
"onActiveTabIndexChange"?: (event: CustomEvent) => void;
/**
* Emits events when the container moves. Selected tab index represents what the user should be seeing. If you receive a decimal as the emitted number, it means that the container is moving between tabs. This number is used for animations, and can be used for high tab customizations.
*/
"onSelectedTabIndexChange"?: (event: CustomEvent) => void;
/**
* Enable/disable swiping
*/
"swipeEnabled"?: boolean;
}
interface SuperTabsToolbar {
/**
* Background color. Defaults to `'primary'`
*/
"color"?: string | undefined;
"config"?: SuperTabsConfig;
/**
* Emits an event when a button is clicked Event data contains the clicked SuperTabButton component
*/
"onButtonClick"?: (event: CustomEvent) => void;
/**
* Whether the toolbar is scrollable. Defaults to `false`.
*/
"scrollable"?: boolean;
/**
* If scrollable is set to true, there will be an added padding to the left of the buttons. Setting this property to false will remove that padding. The padding is also configurable via a CSS variable.
*/
"scrollablePadding"?: boolean;
/**
* Whether to show the indicator. Defaults to `true`
*/
"showIndicator"?: boolean;
}
interface IntrinsicElements {
"super-tab": SuperTab;
"super-tab-button": SuperTabButton;
"super-tab-indicator": SuperTabIndicator;
"super-tabs": SuperTabs;
"super-tabs-container": SuperTabsContainer;
"super-tabs-toolbar": SuperTabsToolbar;
}
}
export { LocalJSX as JSX };
declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"super-tab": LocalJSX.SuperTab & JSXBase.HTMLAttributes;
"super-tab-button": LocalJSX.SuperTabButton & JSXBase.HTMLAttributes;
"super-tab-indicator": LocalJSX.SuperTabIndicator & JSXBase.HTMLAttributes;
"super-tabs": LocalJSX.SuperTabs & JSXBase.HTMLAttributes;
"super-tabs-container": LocalJSX.SuperTabsContainer & JSXBase.HTMLAttributes;
"super-tabs-toolbar": LocalJSX.SuperTabsToolbar & JSXBase.HTMLAttributes;
}
}
}
================================================
FILE: core/src/index.ts
================================================
export { DEFAULT_CONFIG } from './utils';
export { SuperTabsConfig } from './interface';
================================================
FILE: core/src/interface.d.ts
================================================
export * from './components';
/**
* Configuration object for the `super-tabs` component.
*/
export interface SuperTabsConfig {
/**
* Max drag angle in degrees.
*
* Defaults to `40`
*/
maxDragAngle?: number;
/**
* Drag threshold in pixels.
*
* This value defines how far the user have to swipe for the swipe to be treated as a swipe gesture.
*
* Defaults to `20`
*/
dragThreshold?: number;
/**
* Allows elements inside tabs to be dragged or scrolled through.
*
* Setting this value to `true` will allow touch events to be propagated to child components.
*
* Defaults to `false`.
*/
allowElementScroll?: boolean;
/**
* Transition duration in milliseconds.
*
* This value is used for all transitions and animations.
*
* Defaults to `150`.
*/
transitionDuration?: number;
/**
* Side menu location.
*
* If this value is set to `right` or `left`, the super tabs component will avoid listening to swipe events
* in the specified region(s) to avoid interfering with a side menu event listeners.
*
* Defaults to `undefined`.
*/
sideMenu?: 'left' | 'right' | 'both';
/**
* Side menu threshold in pixels.
*
* Defaults to `50`.
*/
sideMenuThreshold?: number;
/**
* Short swipe duration in milliseconds.
*
* Short swipe is when a user quickly swipes between tabs. If the swipe duration is less than or equal to this
* configured value, then we assume that the user wants to switch tabs.
* To disable this behaviour set this value to `0`.
*
* Defaults to `300`
*/
shortSwipeDuration?: number;
/**
* Enable debug mode.
* Defaults to `false`.
*/
debug?: boolean;
/**
* Whether the container should look and avoid elements with avoid-super-tabs attribute
*/
avoidElements?: boolean;
nativeSmoothScroll?: boolean;
lazyLoad?: boolean;
unloadWhenInvisible?: boolean;
}
/**
* Event detail emitted by the `tabChange` event from the `super-tabs` component.
*/
export interface SuperTabChangeEventDetail {
/**
* Selected tab index.
*/
index: number;
/**
* Indicates whether the tab index has changed.
*/
changed: boolean;
}
================================================
FILE: core/src/mixins.scss
================================================
@import "./variables";
@mixin st-button-variables() {
--st-base-color-active: #{$st-button-base-color-active};
--st-base-color-inactive: #{$st-button-base-color-inactive};
--st-icon-size: #{$st-button-icon-size};
--st-icon-color-inactive: #{$st-button-icon-color-inactive};
--st-icon-color-active: #{$st-button-icon-color-active};
--st-label-line-height: #{$st-button-label-line-height};
--st-label-height: #{$st-button-label-height};
--st-label-font-size: #{$st-button-label-font-size};
--st-label-text-transform: #{$st-button-label-text-transform};
--st-label-font-weight: #{$st-button-label-font-weight};
--st-label-padding-bottom: #{$st-button-label-padding-bottom};
--st-label-color-inactive: #{$st-button-label-color-inactive};
--st-label-color-active: #{$st-button-label-color-active};
}
@mixin st-tab-variables() {
--super-tab-width: #{$st-width};
--super-tab-height: #{$st-height};
}
@mixin st-indicator-variables() {
--st-indicator-height: #{$st-indicator-height};
--st-indicator-color: #{$st-indicator-color};
}
@mixin st-toolbar-variables() {
--st-scrollable-toolbar-padding-left: #{$st-scrollable-toolbar-padding-left};
--super-tabs-toolbar-background: #{$st-toolbar-background};
}
================================================
FILE: core/src/super-tab/readme.md
================================================
# super-tab
## Properties
| Property | Attribute | Description | Type | Default |
| ----------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------- |
| `loaded` | `loaded` | | `boolean` | `false` |
| `noScroll` _(required)_ | `no-scroll` | Set this to true to prevent vertical scrolling of this tab. Defaults to `false`. This property will automatically be set to true if there is a direct child element of `ion-content`. To override this behaviour make sure to explicitly set this property to `false`. | `boolean` | `undefined` |
| `visible` | `visible` | | `boolean` | `false` |
## Methods
### `getRootScrollableEl() => Promise`
Returns the root scrollable element
#### Returns
Type: `Promise`
## CSS Custom Properties
| Name | Description |
| -------------------- | -------------------------------------- |
| `--super-tab-height` | Height of the tab. Defaults to `100%`. |
| `--super-tab-width` | Width of the tab. Defaults to `100vw`. |
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
================================================
FILE: core/src/super-tab/super-tab.component.scss
================================================
@import "../variables";
:host {
/**
* @prop --super-tab-width: Width of the tab. Defaults to `100vw`.
* @prop --super-tab-height: Height of the tab. Defaults to `100%`.
*/
height: var(--super-tab-height, $st-height);
position: relative;
display: block;
overflow-x: hidden;
overflow-y: auto;
contain: size style;
&[noScroll] {
overflow-y: hidden;
}
z-index: 1;
flex-shrink: 0;
flex-grow: 0;
width: var(--super-tab-width, $st-width);
transform: translate3d(0,0,0);
box-sizing: border-box;
order: -1;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-font-smoothing: antialiased;
}
ion-nav, ion-content {
height: 100%;
max-height: 100%;
position: absolute;
> .ion-page {
position: absolute;
}
}
================================================
FILE: core/src/super-tab/super-tab.component.tsx
================================================
import { Component, ComponentInterface, Element, h, Host, Method, Prop } from '@stencil/core';
@Component({
tag: 'super-tab',
styleUrl: 'super-tab.component.scss',
shadow: true,
scoped: false,
})
export class SuperTabComponent implements ComponentInterface {
@Element() el!: HTMLSuperTabElement;
/**
* Set this to true to prevent vertical scrolling of this tab. Defaults to `false`.
*
* This property will automatically be set to true if there is
* a direct child element of `ion-content`. To override this
* behaviour make sure to explicitly set this property to `false`.
*/
@Prop({
reflect: true,
}) noScroll!: boolean;
@Prop() loaded = false;
@Prop() visible = false;
componentDidLoad() {
this.checkIonContent();
}
componentDidUpdate() {
// check for ion-content after update, in case it was dynamically loaded
this.checkIonContent();
}
/**
* Check if we have an ion-content as a child and update the `noScroll` property
* if it's not set yet.
*/
private checkIonContent() {
if (typeof this.noScroll !== 'boolean') {
const ionContentEl = this.el.querySelector('ion-content');
if (ionContentEl && ionContentEl.parentElement === this.el) {
this.noScroll = true;
}
}
}
/**
* Returns the root scrollable element
*/
@Method()
async getRootScrollableEl(): Promise {
if (!this.noScroll && this.el.scrollHeight > this.el.clientHeight) {
return this.el;
}
const ionContent: any = this.el.querySelector('ion-content');
if (ionContent) {
return ionContent.getScrollElement();
}
if (this.noScroll) {
return null;
}
return this.el;
}
render() {
return
{
this.loaded ? : null
}
;
}
}
================================================
FILE: core/src/super-tab-button/readme.md
================================================
# super-tab-button
## Properties
| Property | Attribute | Description | Type | Default |
| ---------- | ---------- | ------------------------------ | ---------------------- | ----------- |
| `disabled` | `disabled` | Whether the button is disabled | `boolean \| undefined` | `undefined` |
## CSS Custom Properties
| Name | Description |
| --------------------------- | --------------------------------------------------------------------------------------- |
| `--st-base-color-active` | base color for active buttons. Defaults to `--ion-color-contrast`. |
| `--st-base-color-inactive` | base color for inactive buttons. Defaults to `rgba(var(--ion-color-contrast-rgb), 0.7)` |
| `--st-icon-color-active` | active icon color. Defaults to `var(--st-base-color-active)` |
| `--st-icon-color-inactive` | inactive icon color. Defaults to `var(--st-base-color-inactive)` |
| `--st-icon-size` | icon size. Defaults to `24px`. |
| `--st-label-color-active` | active label color. Defaults to `var(--st-base-color-active)` |
| `--st-label-color-inactive` | inactive label color. Defaults to `var(--st-base-color-inactive)` |
| `--st-label-font-size` | Font size of the label. Defaults to `14px`. |
| `--st-label-font-weight` | Font weight of the label. Defaults to `500` |
| `--st-label-height` | label height. Defaults to `14px` |
| `--st-label-line-height` | label line height. Defaults to `14px` |
| `--st-label-padding-bottom` | label padding bottom. Defaults to `16px` |
| `--st-label-text-transform` | Text transformation to apply to the label text. Defaults to `uppercase`. |
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
================================================
FILE: core/src/super-tab-button/super-tab-button.component.scss
================================================
@import "../variables";
@mixin labelIcon {
ion-label, ion-icon, ::slotted(ion-label), ::slotted(ion-icon) {
@content
}
}
@mixin label {
ion-label, ::slotted(ion-label) {
@content
}
}
@mixin icon {
ion-icon, ::slotted(ion-icon) {
@content
}
}
:host {
/**
* @prop --st-base-color-active: base color for active buttons. Defaults to `--ion-color-contrast`.
* @prop --st-base-color-inactive: base color for inactive buttons. Defaults to `rgba(var(--ion-color-contrast-rgb), 0.7)`
* @prop --st-icon-size: icon size. Defaults to `24px`.
* @prop --st-icon-color-inactive: inactive icon color. Defaults to `var(--st-base-color-inactive)`
* @prop --st-icon-color-active: active icon color. Defaults to `var(--st-base-color-active)`
* @prop --st-label-line-height: label line height. Defaults to `14px`
* @prop --st-label-height: label height. Defaults to `14px`
* @prop --st-label-font-size: Font size of the label. Defaults to `14px`.
* @prop --st-label-text-transform: Text transformation to apply to the label text. Defaults to `uppercase`.
* @prop --st-label-font-weight: Font weight of the label. Defaults to `500`
* @prop --st-label-padding-bottom: label padding bottom. Defaults to `16px`
* @prop --st-label-color-inactive: inactive label color. Defaults to `var(--st-base-color-inactive)`
* @prop --st-label-color-active: active label color. Defaults to `var(--st-base-color-active)`
*/
flex: 1 0 0;
cursor: pointer;
position: relative;
max-width: 100%;
overflow: hidden;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
transform: translate3d(0, 0, 0);
height: 72px;
padding: 0 24px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
@include labelIcon {
transition-property: all;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-delay: 0s;
box-sizing: content-box !important;
-webkit-user-select: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
}
@include label {
color: var(--st-label-color-inactive, $st-button-label-color-inactive);
line-height: var(--st-label-line-height, $st-button-label-line-height);
height: var(--st-label-height, $st-button-label-height);
font-size: var(--st-label-font-size, $st-button-label-font-size);
text-transform: var(--st-label-text-transform, $st-button-label-text-transform);
font-weight: var(--st-label-font-weight, $st-button-label-font-weight);
padding-bottom: var(--st-label-padding-bottom, $st-button-label-padding-bottom);
}
@include icon {
color: var(--st-icon-color-inactive, $st-button-icon-color-inactive);
fill: var(--st-icon-color-inactive, $st-button-icon-color-inactive);
min-height: var(--st-icon-size, $st-button-icon-size);
min-width: var(--st-icon-size, $st-button-icon-size);
font-size: var(--st-icon-size, $st-button-icon-size);
padding-top: 12px;
}
}
@media(hover: hover) {
:host(:hover) {
background: rgba(var(--ion-color-contrast-rgb), 0.04);
}
}
:host(.active) {
@include labelIcon {
transition-delay: 75ms;
}
@include label {
color: var(--st-label-color-active, $st-button-label-color-active);
}
@include icon {
color: var(--st-icon-color-active, $st-button-label-color-active);
fill: var(--st-icon-color-active, $st-button-label-color-active);
}
}
:host(.icon-only), :host(.label-only) {
height: 48px;
justify-content: center;
}
:host(.scrollableContainer) {
flex-grow: 0;
flex-basis: auto;
min-width: 90px;
max-width: 360px;
}
::slotted {
flex-shrink: 1;
}
:host(.label-only) {
@include label {
padding-bottom: 0;
}
}
:host(.icon-only) {
@include icon {
padding-top: 0;
}
}
================================================
FILE: core/src/super-tab-button/super-tab-button.component.tsx
================================================
import { Component, ComponentInterface, Element, h, Host, Prop, State } from '@stencil/core';
const maxRetryAttempts = 1e3;
@Component({
tag: 'super-tab-button',
styleUrl: 'super-tab-button.component.scss',
shadow: true,
})
export class SuperTabButtonComponent implements ComponentInterface {
@Element() el!: HTMLSuperTabButtonElement;
/** @internal */
@Prop({ reflectToAttr: true }) active?: boolean;
/** @internal */
@Prop({ reflectToAttr: true }) index?: number;
/**
* Whether the button is disabled
*/
@Prop({ reflectToAttr: true }) disabled?: boolean;
/** @internal */
@Prop() scrollableContainer: boolean = false;
@State() label!: HTMLElement | null;
@State() icon!: HTMLElement | null;
private retryAttempts: number = 0;
componentDidLoad() {
this.indexChildren();
this.initCmp();
}
private initCmp() {
if (!this.el || !this.el.shadowRoot) {
if (++this.retryAttempts < maxRetryAttempts) {
requestAnimationFrame(() => this.initCmp());
return;
}
}
if (!this.label && !this.icon) {
this.indexChildren();
}
const slot = this.el!.shadowRoot!.querySelector('slot');
slot!.addEventListener('slotchange', () => {
this.indexChildren();
});
}
private indexChildren() {
this.label = this.el.querySelector('ion-label');
this.icon = this.el.querySelector('ion-icon');
}
render() {
return (
);
}
}
================================================
FILE: core/src/super-tab-indicator/readme.md
================================================
# super-tab-indicator
## Properties
| Property | Attribute | Description | Type | Default |
| ----------------- | ------------------ | -------------------------------------------------------------- | ------------------- | ------- |
| `toolbarPosition` | `toolbar-position` | Toolbar position This determines the position of the indicator | `"bottom" \| "top"` | `'top'` |
## Dependencies
### Used by
- [super-tabs-toolbar](../super-tabs-toolbar)
### Graph
```mermaid
graph TD;
super-tabs-toolbar --> super-tab-indicator
style super-tab-indicator fill:#f9f,stroke:#333,stroke-width:4px
```
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
================================================
FILE: core/src/super-tab-indicator/super-tab-indicator.component.scss
================================================
@import "../variables";
:host {
display: block;
height: var(--st-indicator-height, $st-indicator-height);
width: 100px;
background: var(--st-indicator-color, $st-indicator-color);
position: absolute;
pointer-events: none;
touch-action: none;
left: 0;
transform-origin: 0;
transform: translate3d(var(--st-indicator-position-x, 0), 0, 0) scale3d(var(--st-indicator-scale-x, 0), 1, 1);
transition: transform var(--st-indicator-transition-duration, 300ms) cubic-bezier(0.4, 0, 0.2, 1);
will-change: transform;
box-sizing: border-box;
order: -1;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
}
================================================
FILE: core/src/super-tab-indicator/super-tab-indicator.component.tsx
================================================
import { Component, ComponentInterface, h, Host, Prop } from '@stencil/core';
@Component({
tag: 'super-tab-indicator',
styleUrl: 'super-tab-indicator.component.scss',
shadow: true,
})
export class SuperTabIndicatorComponent implements ComponentInterface {
/**
* Toolbar position
* This determines the position of the indicator
*/
@Prop() toolbarPosition: 'top' | 'bottom' = 'top';
render() {
const style: any = {};
if (this.toolbarPosition === 'bottom') {
style.top = 0;
} else {
style.bottom = 0;
}
return (
);
}
}
================================================
FILE: core/src/super-tabs/readme.md
================================================
# super-tabs
## Properties
| Property | Attribute | Description | Type | Default |
| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | ----------- |
| `activeTabIndex` | `active-tab-index` | Initial active tab index. Defaults to `0`. | `number` | `0` |
| `config` | -- | Global Super Tabs configuration. This is the only place you need to configure the components. Any changes to this input will propagate to child components. | `SuperTabsConfig \| undefined` | `undefined` |
## Events
| Event | Description | Type |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
| `tabChange` | Tab change event. This event fires up when a tab button is clicked, or when a user swipes between tabs. The event will fire even if the tab did not change, you can check if the tab changed by checking the `changed` property in the event detail. | `CustomEvent` |
## Methods
### `selectTab(index: number, animate?: boolean, emit?: boolean) => Promise`
Set the selected tab.
This will move the container and the toolbar to the selected tab.
#### Returns
Type: `Promise`
### `setConfig(config: SuperTabsConfig) => Promise`
Set/update the configuration
#### Returns
Type: `Promise`
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
================================================
FILE: core/src/super-tabs/super-tabs.component.scss
================================================
:host {
height: 100%;
max-height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 1;
position: relative;
contain: layout size style;
order: -1;
user-select: none;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
touch-action: none;
box-sizing: border-box;
margin: 0;
padding: 0;
}
================================================
FILE: core/src/super-tabs/super-tabs.component.tsx
================================================
import {
Component,
ComponentInterface,
Element,
Event,
EventEmitter,
h,
Host,
Listen,
Method,
Prop,
Watch,
} from '@stencil/core';
import { SuperTabChangeEventDetail, SuperTabsConfig } from '../interface';
import { debugLog, DEFAULT_CONFIG } from '../utils';
const maxInitRetries: number = 1e3;
/**
* Root component that controls the other super-tab components.
*
* This component propagates configuration over to children and keeps track of the tabs state.
*/
@Component({
tag: 'super-tabs',
styleUrl: 'super-tabs.component.scss',
shadow: true,
})
export class SuperTabsComponent implements ComponentInterface {
@Element() el!: HTMLSuperTabsElement;
/**
* Tab change event.
*
* This event fires up when a tab button is clicked, or when a user swipes between tabs.
*
* The event will fire even if the tab did not change, you can check if the tab changed by checking the `changed`
* property in the event detail.
*/
@Event() tabChange!: EventEmitter;
/**
* Global Super Tabs configuration.
*
* This is the only place you need to configure the components. Any changes to this input will propagate to child
* components.
*
* @type {SuperTabsConfig}
*/
@Prop() config?: SuperTabsConfig;
/**
* Initial active tab index.
* Defaults to `0`.
*
* @type {number}
*/
@Prop({ reflectToAttr: true, mutable: true }) activeTabIndex: number = 0;
private container!: HTMLSuperTabsContainerElement;
private toolbar!: HTMLSuperTabsToolbarElement;
private _config: SuperTabsConfig = DEFAULT_CONFIG;
private initAttempts: number = 0;
private readonly initPromise: Promise;
private initPromiseResolve!: Function;
constructor() {
this.initPromise = new Promise((resolve) => {
this.initPromiseResolve = resolve;
});
}
/**
* Set/update the configuration
* @param {SuperTabsConfig} config Configuration object
*/
@Method()
async setConfig(config: SuperTabsConfig) {
this._config = { ...DEFAULT_CONFIG, ...config };
}
private propagateConfig() {
this.container && (this.container.config = this._config);
this.toolbar && (this.toolbar.config = this._config);
}
/**
* Set the selected tab.
* This will move the container and the toolbar to the selected tab.
* @param index {number} the index of the tab you want to select
* @param [animate=true] {boolean} whether you want to animate the transition
* @param [emit=true] {boolean} whether you want to emit tab change event
*/
@Method()
async selectTab(index: number, animate: boolean = true, emit: boolean = true) {
this.debug('selectTab', index, animate);
await this.initPromise;
const lastIndex = this.activeTabIndex;
if (this.container) {
await this.container.setActiveTabIndex(index, true, animate);
}
if (this.toolbar) {
await this.toolbar.setActiveTab(index, true, animate);
}
if (emit) {
this.emitTabChangeEvent(index, lastIndex);
}
this.activeTabIndex = lastIndex;
}
@Watch('config')
async onConfigChange(config: SuperTabsConfig) {
await this.setConfig(config);
}
@Listen('resize', { target: 'window', capture: false, passive: true })
onWindowResize() {
this.debug('onWindowResize');
this.toolbar && this.toolbar.setSelectedTab(this.activeTabIndex);
this.container.reindexTabs();
}
async componentWillLoad() {
if (this.config) {
await this.setConfig(this.config);
}
}
componentDidLoad() {
this.debug('componentDidLoad');
// index children
this.indexChildren();
// set the selected tab so the toolbar & container are aligned and in sync
if (this.container) {
this.container.setActiveTabIndex(this.activeTabIndex, true, false);
}
if (this.toolbar) {
this.toolbar.setActiveTab(this.activeTabIndex, true, false);
}
// listen to `slotchange` event to detect any changes in children
this.el.shadowRoot!.addEventListener('slotchange', this.onSlotchange.bind(this));
requestAnimationFrame(() => {
this.initComponent();
});
}
private initComponent() {
if (!this.container) {
if (++this.initAttempts <= maxInitRetries) {
requestAnimationFrame(() => {
this.initComponent();
});
return;
} else {
this.debug(`container still doesn't exists after ${maxInitRetries} frames`);
}
}
if (this.activeTabIndex > 0) {
if (this.container) {
this.container.moveContainerByIndex(this.activeTabIndex, false);
}
if (this.toolbar) {
this.toolbar.setActiveTab(this.activeTabIndex, true);
}
}
this.propagateConfig();
this.setupEventListeners();
this.initPromiseResolve();
}
/**
* Setup event listeners to synchronize child components
*/
private async setupEventListeners() {
if (this.container) {
await this.container.componentOnReady();
this.el.addEventListener('selectedTabIndexChange', this.onContainerSelectedTabChange.bind(this));
this.el.addEventListener('activeTabIndexChange', this.onContainerActiveTabChange.bind(this));
} else {
this.debug('setupEventListeners: container does not exist');
}
if (this.toolbar) {
await this.toolbar.componentOnReady();
this.el.addEventListener('buttonClick', this.onToolbarButtonClick.bind(this));
} else {
this.debug('setupEventListeners: toolbar does not exist');
}
}
private async onContainerSelectedTabChange(ev: any) {
this.debug('onContainerSelectedTabChange called with: ', ev);
if (this.toolbar) {
await this.toolbar.setSelectedTab(ev.detail);
}
}
private emitTabChangeEvent(newIndex: number, oldIndex?: number) {
if (typeof (newIndex as unknown) !== 'number' || newIndex < 0) {
return;
}
if (typeof oldIndex !== 'number' || oldIndex < 0) {
oldIndex = this.activeTabIndex;
}
this.tabChange.emit({
changed: newIndex !== oldIndex,
index: newIndex,
});
}
private onContainerActiveTabChange(ev: any) {
this.debug('onContainerActiveTabChange', ev);
const index: number = ev.detail;
this.emitTabChangeEvent(index);
this.activeTabIndex = index;
this.toolbar && this.toolbar.setActiveTab(index, true, true);
}
private onToolbarButtonClick(ev: any) {
this.debug('onToolbarButtonClick', ev);
const { index } = ev.detail;
this.container && this.container.setActiveTabIndex(index, true, true);
this.emitTabChangeEvent(index);
this.activeTabIndex = index;
}
private indexChildren() {
this.debug('indexChildren');
const container = this.el.querySelector('super-tabs-container');
const toolbar = this.el.querySelector('super-tabs-toolbar');
if (container && this.container !== container) {
this.container = container;
}
if (toolbar && this.toolbar !== toolbar) {
this.toolbar = toolbar;
}
this.propagateConfig();
}
private async onSlotchange() {
// re-index the child components
this.indexChildren();
// reselect the current tab to ensure that we're on the correct tab
this.selectTab(this.activeTabIndex, true, false);
}
/**
* Internal method to output values in debug mode.
*/
private debug(...vals: any[]) {
debugLog(this._config, 'tabs', vals);
}
render() {
// Render 3 slots
// Top & bottom slots allow the toolbar position to be configurable via slots.
// The nameless slot is used to hold the `super-tabs-container`.
return (
);
}
}
================================================
FILE: core/src/super-tabs-container/readme.md
================================================
# super-tabs-container
## Properties
| Property | Attribute | Description | Type | Default |
| --------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
| `autoScrollTop` | `auto-scroll-top` | Set to true to automatically scroll to the top of the tab when the button is clicked while the tab is already selected. | `boolean` | `false` |
| `swipeEnabled` | `swipe-enabled` | Enable/disable swiping | `boolean` | `true` |
## Events
| Event | Description | Type |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `activeTabIndexChange` | Emits an event when the active tab changes. An active tab is the tab that the user looking at. This event emitter will not notify you if the user has changed the current active tab. If you need that information, you should use the `tabChange` event emitted by the `super-tabs` element. | `CustomEvent` |
| `selectedTabIndexChange` | Emits events when the container moves. Selected tab index represents what the user should be seeing. If you receive a decimal as the emitted number, it means that the container is moving between tabs. This number is used for animations, and can be used for high tab customizations. | `CustomEvent` |
## Methods
### `scrollToTop() => Promise`
Scroll the active tab to the top.
#### Returns
Type: `Promise`
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
================================================
FILE: core/src/super-tabs-container/super-tabs-container.component.scss
================================================
@import "../variables";
:host {
display: flex;
flex-flow: row nowrap;
min-width: 100%;
flex: 1 1 auto;
position: relative;
box-sizing: border-box;
width: var(--super-tab-width, $st-width);
height: var(--super-tab-height, $st-height);
overflow: hidden;
transform: translate3d(0, 0, 0);
touch-action: pan-y;
user-select: none;
will-change: scroll-position;
order: -1;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
}
================================================
FILE: core/src/super-tabs-container/super-tabs-container.component.tsx
================================================
import {
Component,
ComponentInterface,
Element,
Event,
EventEmitter,
h,
Listen,
Method,
Prop,
QueueApi,
State,
} from '@stencil/core';
import { SuperTabsConfig } from '../interface';
import { checkGesture, debugLog, getTs, pointerCoord, scrollEl, STCoord } from '../utils';
@Component({
tag: 'super-tabs-container',
styleUrl: 'super-tabs-container.component.scss',
shadow: true,
})
export class SuperTabsContainerComponent implements ComponentInterface {
@Element() el!: HTMLSuperTabsContainerElement;
/** @internal */
@Prop({ mutable: true }) config?: SuperTabsConfig;
/** @internal */
@Prop({ context: 'queue' }) queue!: QueueApi;
/**
* Enable/disable swiping
*/
@Prop() swipeEnabled: boolean = true;
/**
* Set to true to automatically scroll to the top of the tab when the button is clicked while the tab is
* already selected.
*/
@Prop() autoScrollTop: boolean = false;
/**
* Emits an event when the active tab changes.
* An active tab is the tab that the user looking at.
*
* This event emitter will not notify you if the user has changed the current active tab.
* If you need that information, you should use the `tabChange` event emitted by the `super-tabs` element.
*/
@Event() activeTabIndexChange!: EventEmitter;
/**
* Emits events when the container moves.
* Selected tab index represents what the user should be seeing.
* If you receive a decimal as the emitted number, it means that the container is moving between tabs.
* This number is used for animations, and can be used for high tab customizations.
*/
@Event() selectedTabIndexChange!: EventEmitter;
@State() tabs: HTMLSuperTabElement[] = [];
private initialCoords: STCoord | undefined;
private lastPosX: number | undefined;
private isDragging: boolean = false;
private initialTimestamp?: number;
private _activeTabIndex: number | undefined;
private _selectedTabIndex?: number;
private leftThreshold: number = 0;
private rightThreshold: number = 0;
private scrollWidth: number = 0;
private slot!: HTMLSlotElement;
private ready?: boolean;
private width: number = 0;
async componentDidLoad() {
this.debug('componentDidLoad');
this.updateWidth();
await this.indexTabs();
this.slot = this.el.shadowRoot!.querySelector('slot') as HTMLSlotElement;
this.slot.addEventListener('slotchange', this.onSlotChange.bind(this));
}
private async onSlotChange() {
this.debug('onSlotChange', this.tabs.length);
this.updateWidth();
this.indexTabs();
}
async componentDidRender() {
this.updateWidth();
}
/**
* @internal
*/
@Method()
async reindexTabs() {
this.updateWidth();
await this.indexTabs();
}
/**
* @internal
*
* Moves the container to align with the specified tab index
* @param index {number} Index of the tab
* @param animate {boolean} Whether to animate the transition
*/
@Method()
moveContainerByIndex(index: number, animate?: boolean): Promise {
const scrollX = this.indexToPosition(index);
if (scrollX === 0 && index > 0) {
return Promise.resolve();
}
return this.moveContainer(scrollX, animate);
}
/**
* @internal
*
* Sets the scrollLeft property of the container
* @param scrollX {number}
* @param animate {boolean}
*/
@Method()
moveContainer(scrollX: number, animate?: boolean): Promise {
if (animate) {
scrollEl(this.el, scrollX, this.config!.nativeSmoothScroll!, this.config!.transitionDuration);
} else {
this.el.scroll(scrollX, 0);
}
return Promise.resolve();
}
/** @internal */
@Method()
async setActiveTabIndex(index: number, moveContainer: boolean = true, animate: boolean = true): Promise {
this.debug('setActiveTabIndex', index);
if (this._activeTabIndex === index) {
if (!this.autoScrollTop) {
return;
}
await this.scrollToTop();
}
if (moveContainer) {
await this.moveContainerByIndex(index, animate);
}
await this.updateActiveTabIndex(index, false);
}
/**
* Scroll the active tab to the top.
*/
@Method()
async scrollToTop() {
if (this._activeTabIndex === undefined || this.tabs === undefined) {
this.debug('activeTabIndex or tabs was undefined');
return;
}
const current = this.tabs[this._activeTabIndex];
this.queue.read(() => {
if (!current) {
this.debug('Current active tab was undefined in scrollToTop');
return;
}
// deepcode ignore PromiseNotCaughtGeneral:
current.getRootScrollableEl().then((el) => {
if (el) {
scrollEl(el, 0, this.config!.nativeSmoothScroll!, this.config!.transitionDuration);
}
});
});
}
private updateActiveTabIndex(index: number, emit: boolean = true) {
this.debug('updateActiveTabIndex', index, emit, this._activeTabIndex);
this._activeTabIndex = index;
emit && this.activeTabIndexChange.emit(this._activeTabIndex);
this.lazyLoadTabs();
}
private updateSelectedTabIndex(index: number) {
if (index === this._selectedTabIndex) {
return;
}
this._selectedTabIndex = index;
this.selectedTabIndexChange.emit(this._selectedTabIndex);
}
@Listen('touchstart')
async onTouchStart(ev: TouchEvent) {
if (!this.swipeEnabled) {
return;
}
if (this.config!.avoidElements) {
let avoid: boolean = false;
let element: any = ev.target;
if (element) {
do {
if (typeof element.getAttribute === 'function' && element.getAttribute('avoid-super-tabs')) {
return;
}
element = element.parentElement;
} while (element && !avoid);
}
}
const coords = pointerCoord(ev);
this.updateWidth();
const vw = this.width;
if (coords.x < this.leftThreshold || coords.x > vw - this.rightThreshold) {
// ignore this gesture, it started in the side menu touch zone
return;
}
if (this.config!.shortSwipeDuration! > 0) {
this.initialTimestamp = getTs();
}
this.initialCoords = coords;
this.lastPosX = coords.x;
}
@Listen('click', { passive: false, capture: true })
async onClick(ev: TouchEvent) {
if (this.isDragging) {
ev.stopImmediatePropagation();
ev.preventDefault();
}
}
@Listen('touchmove', { passive: true, capture: true })
async onTouchMove(ev: TouchEvent) {
if (!this.swipeEnabled || !this.initialCoords || typeof this.lastPosX !== 'number') {
return;
}
const coords = pointerCoord(ev);
if (!this.isDragging) {
if (!checkGesture(coords, this.initialCoords, this.config!)) {
if (Math.abs(coords.y - this.initialCoords.y) > 100) {
this.initialCoords = void 0;
this.lastPosX = void 0;
}
return;
}
this.isDragging = true;
}
// stop anything else from capturing these events, to make sure the content doesn't slide
if (!this.config!.allowElementScroll) {
ev.stopImmediatePropagation();
}
// get delta X
const deltaX: number = this.lastPosX! - coords.x;
if (deltaX === 0) {
return;
}
const scrollX = Math.max(0, Math.min(this.scrollWidth - this.width, this.el.scrollLeft + deltaX));
if (Math.floor(scrollX) === Math.floor(this.el.scrollLeft)) {
return;
}
const index = Math.round(this.positionToIndex(scrollX) * 100) / 100;
this.updateSelectedTabIndex(index);
// update last X value
this.lastPosX = coords.x;
this.el.scroll(scrollX, 0);
}
@Listen('touchend', { passive: false, capture: true })
async onTouchEnd(ev: TouchEvent) {
if (!this.swipeEnabled || !this.isDragging) {
return;
}
const coords = pointerCoord(ev);
const deltaTime: number = getTs() - this.initialTimestamp!;
const shortSwipe = this.config!.shortSwipeDuration! > 0 && deltaTime <= this.config!.shortSwipeDuration!;
const shortSwipeDelta = coords.x - this.initialCoords!.x;
let selectedTabIndex = this.calcSelectedTab();
const expectedTabIndex = Math.round(selectedTabIndex);
if (shortSwipe && expectedTabIndex === this._activeTabIndex) {
selectedTabIndex += shortSwipeDelta > 0 ? -1 : 1;
}
selectedTabIndex = this.normalizeSelectedTab(selectedTabIndex);
this.updateActiveTabIndex(selectedTabIndex);
this.moveContainerByIndex(selectedTabIndex, true);
this.isDragging = false;
this.initialCoords = void 0;
this.lastPosX = void 0;
}
private updateWidth() {
const boundingRect = this.el.getBoundingClientRect();
this.width = Math.round(boundingRect.width * 10000) / 10000;
}
private async indexTabs() {
if (this.width === 0) {
requestAnimationFrame(() => {
this.updateWidth();
this.indexTabs();
});
return;
}
const tabs = Array.from(this.el.querySelectorAll('super-tab'));
this.scrollWidth = this.width * tabs.length;
this.debug('indexTab', this.scrollWidth, this.width);
await Promise.all(tabs.map((t) => t.componentOnReady()));
this.tabs = tabs;
const activeTabIndex = this._activeTabIndex
if (this.ready && typeof activeTabIndex === 'number') {
this.moveContainerByIndex(activeTabIndex, true);
this.lazyLoadTabs();
}
if (this.config) {
switch (this.config.sideMenu) {
case 'both':
this.rightThreshold = this.leftThreshold = this.config.sideMenuThreshold || 0;
break;
case 'left':
this.leftThreshold = this.config.sideMenuThreshold || 0;
break;
case 'right':
this.rightThreshold = this.config.sideMenuThreshold || 0;
break;
}
}
if (activeTabIndex !== undefined) {
this.moveContainerByIndex(activeTabIndex, false).then(() => {
this.lazyLoadTabs();
this.ready = true;
});
}
}
private lazyLoadTabs() {
if (typeof this._activeTabIndex === 'undefined') {
this.debug('lazyLoadTabs', 'called when _activeTabIndex is undefined');
return;
}
if (!this.config) {
this.debug('lazyLoadTabs', 'called with no config available');
return;
}
const activeTab = this._activeTabIndex;
const tabs = [...this.tabs];
if (!this.config!.lazyLoad) {
for (const tab of this.tabs) {
tab.loaded = true
tab.visible = true
}
return;
}
const min = activeTab - 1;
const max = activeTab + 1;
let index = 0;
for (const tab of tabs) {
tab.visible = index >= min && index <= max;
tab.loaded = tab.visible || (this.config!.unloadWhenInvisible ? false : tab.loaded);
index++;
}
this.tabs = tabs;
}
private calcSelectedTab(): number {
const scrollX = Math.max(0, Math.min(this.scrollWidth - this.width, this.el.scrollLeft));
return this.positionToIndex(scrollX);
}
private positionToIndex(scrollX: number) {
const tabWidth = this.width;
return scrollX / tabWidth;
}
private indexToPosition(tabIndex: number) {
return Math.round(tabIndex * this.width * 10000) / 10000;
}
private normalizeSelectedTab(index: number): number {
const scrollX = Math.max(0, Math.min(this.scrollWidth - this.width, this.indexToPosition(index)));
return Math.round(scrollX / this.width);
}
/**
* Internal method to output values in debug mode.
*/
private debug(...vals: any[]) {
debugLog(this.config!, 'container', vals);
}
render() {
return ;
}
}
================================================
FILE: core/src/super-tabs-toolbar/readme.md
================================================
# super-tabs-toolbar
## Properties
| Property | Attribute | Description | Type | Default |
| ------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------- |
| `color` | `color` | Background color. Defaults to `'primary'` | `string \| undefined` | `'primary'` |
| `scrollable` | `scrollable` | Whether the toolbar is scrollable. Defaults to `false`. | `boolean` | `false` |
| `scrollablePadding` | `scrollable-padding` | If scrollable is set to true, there will be an added padding to the left of the buttons. Setting this property to false will remove that padding. The padding is also configurable via a CSS variable. | `boolean` | `true` |
| `showIndicator` | `show-indicator` | Whether to show the indicator. Defaults to `true` | `boolean` | `true` |
## Events
| Event | Description | Type |
| ------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------- |
| `buttonClick` | Emits an event when a button is clicked Event data contains the clicked SuperTabButton component | `CustomEvent` |
## CSS Custom Properties
| Name | Description |
| -------------------------------------- | ------------------------------------------------------------------ |
| `--st-indicator-color` | Indicator color. Defaults to `--ion-color-contrast`. |
| `--st-indicator-height` | Indicator height. Defaults to `2px`. |
| `--st-scrollable-toolbar-padding-left` | Left padding when `scrollable` is set to true. Defaults to `52px`. |
| `--super-tabs-toolbar-background` | Toolbar background color. Defaults to `--ion-color-base`. |
## Dependencies
### Depends on
- [super-tab-indicator](../super-tab-indicator)
### Graph
```mermaid
graph TD;
super-tabs-toolbar --> super-tab-indicator
style super-tabs-toolbar fill:#f9f,stroke:#333,stroke-width:4px
```
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*
================================================
FILE: core/src/super-tabs-toolbar/super-tabs-toolbar.component.scss
================================================
@import "../variables";
:host {
/**
* @prop --super-tabs-toolbar-background: Toolbar background color. Defaults to `--ion-color-base`.
* @prop --st-scrollable-toolbar-padding-left: Left padding when `scrollable` is set to true. Defaults to `52px`.
* @prop --st-indicator-height: Indicator height. Defaults to `2px`.
* @prop --st-indicator-color: Indicator color. Defaults to `--ion-color-contrast`.
*/
flex: 0 1 auto;
display: block;
width: 100%;
transform: translate3d(0, 0, 0);
position: relative;
background: var(--super-tabs-toolbar-background, $st-toolbar-background);
overflow: visible;
box-sizing: border-box;
pointer-events: auto;
touch-action: pan-x;
z-index: 2;
order: -1;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-font-smoothing: antialiased;
.buttons-container {
display: flex;
flex-flow: row nowrap;
width: 100%;
transform: translate3d(0, 0, 0);
overflow: hidden;
}
}
:host(:not([no-border])):after {
left: 0;
bottom: -8px;
background-position: left 0 top 0;
position: absolute;
width: 100%;
height: 8px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAIBAMAAAACWGKkAAAAFVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAASAQCkAAAAB3RSTlMFTEIzJBcOYhQUIwAAAB9JREFUCNdjEIQCBiUoYDCGAgYXKGAIhQKGNChgwAAAorMLKSCkL40AAAAASUVORK5CYII=);
background-repeat: repeat-x;
content: '';
z-index: 3;
transform: translate3d(0,0,0);
}
:host([scrollable]) .buttons-container {
will-change: scroll-position;
}
:host([scrollable][scrollable-padding]) .buttons-container {
padding-left: var(--st-scrollable-toolbar-padding-left, $st-scrollable-toolbar-padding-left);
width: calc(100% - var(--st-scrollable-toolbar-padding-left, $st-scrollable-toolbar-padding-left));
}
================================================
FILE: core/src/super-tabs-toolbar/super-tabs-toolbar.component.tsx
================================================
import {
Component,
ComponentInterface,
Element,
Event,
EventEmitter,
h,
Host,
Listen,
Method,
Prop,
State,
Watch,
} from '@stencil/core';
import { SuperTabsConfig } from '../interface';
import { checkGesture, debugLog, getNormalizedScrollX, pointerCoord, scrollEl, STCoord } from '../utils';
@Component({
tag: 'super-tabs-toolbar',
styleUrl: 'super-tabs-toolbar.component.scss',
shadow: true,
})
export class SuperTabsToolbarComponent implements ComponentInterface {
@Element() el!: HTMLSuperTabsToolbarElement;
/** @internal */
@Prop({ mutable: true }) config?: SuperTabsConfig;
/**
* Whether to show the indicator. Defaults to `true`
*/
@Prop() showIndicator: boolean = true;
/**
* Background color. Defaults to `'primary'`
*/
@Prop() color: string | undefined = 'primary';
/**
* Whether the toolbar is scrollable. Defaults to `false`.
*/
@Prop({ reflectToAttr: true }) scrollable: boolean = false;
/**
* If scrollable is set to true, there will be an added padding
* to the left of the buttons.
*
* Setting this property to false will remove that padding.
*
* The padding is also configurable via a CSS variable.
*/
@Prop({ reflectToAttr: true }) scrollablePadding: boolean = true;
/**
* Emits an event when a button is clicked
* Event data contains the clicked SuperTabButton component
*/
@Event() buttonClick!: EventEmitter;
@State() buttons: HTMLSuperTabButtonElement[] = [];
width!: number;
offsetLeft!: number;
/**
* Current indicator position.
* This value is undefined until the component is fully initialized.
* @private
*/
private indicatorPosition: number | undefined;
/**
* Current indicator width.
* This value is undefined until the component is fully initialized.
* @private
*/
private indicatorWidth: number | undefined;
/**
* Reference to the current active button
* @private
*/
private activeButton?: HTMLSuperTabButtonElement;
private activeTabIndex: number = 0;
private indicatorEl: HTMLSuperTabIndicatorElement | undefined;
private buttonsContainerEl: HTMLDivElement | undefined;
private initialCoords?: STCoord;
private lastPosX: number | undefined;
private touchStartTs: number = 0;
private lastClickTs: number = 0;
private isDragging: boolean | undefined;
private leftThreshold: number = 0;
private rightThreshold: number = 0;
private slot!: HTMLSlotElement;
private hostCls: any = {};
async componentDidLoad() {
this.setHostCls();
await this.queryButtons();
this.slot = this.el.shadowRoot!.querySelector('slot') as HTMLSlotElement;
this.slot.addEventListener('slotchange', this.onSlotChange.bind(this));
this.updateWidth();
requestAnimationFrame(() => {
this.setActiveTab(this.activeTabIndex, true, false);
});
}
componentWillUpdate() {
this.debug('componentWillUpdate');
this.updateThresholds();
}
componentDidRender() {
this.updateWidth();
}
private updateWidth() {
const cr = this.el.getBoundingClientRect();
this.width = Math.round(cr.width * 100) / 100;
this.offsetLeft = cr.left;
}
/** @internal */
@Method()
setActiveTab(index: number, align?: boolean, animate?: boolean): Promise {
index = Math.max(0, Math.min(Math.round(index), this.buttons.length - 1));
this.debug('setActiveTab', index, align, animate);
this.activeTabIndex = index;
this.markButtonActive(this.buttons[index]);
if (align) {
this.alignIndicator(index, animate);
}
return Promise.resolve();
}
/** @internal */
@Method()
setSelectedTab(index: number, animate?: boolean): Promise {
this.alignIndicator(index, animate);
return Promise.resolve();
}
/** @internal */
@Method()
moveContainer(scrollX: number, animate?: boolean): Promise {
if (!this.buttonsContainerEl) {
this.debug('moveContainer called before this.buttonsContainerEl was defined');
return Promise.resolve();
}
scrollEl(this.buttonsContainerEl, scrollX, this.config!.nativeSmoothScroll!, animate ? this.config!.transitionDuration : 0);
return Promise.resolve();
}
private getButtonFromEv(ev: any): HTMLSuperTabButtonElement | undefined {
let button: HTMLSuperTabButtonElement = ev.target;
const tag = button.tagName.toLowerCase();
if (tag !== 'super-tab-button') {
if (tag === 'super-tabs-toolbar') {
return;
}
button = button.closest('super-tab-button') as HTMLSuperTabButtonElement;
}
return button;
}
@Listen('click')
onClick(ev: any) {
if (!ev || !ev.target) {
this.debug('Got a click event with no target!', ev);
return;
}
if (Date.now() - this.touchStartTs <= 150) {
return;
}
const button = this.getButtonFromEv(ev);
if (!button) {
return;
}
this.onButtonClick(button);
}
private onButtonClick(button: HTMLSuperTabButtonElement) {
this.lastClickTs = Date.now();
this.setActiveTab(button.index as number, true, true);
this.buttonClick.emit(button);
}
@Listen('touchstart')
async onTouchStart(ev: TouchEvent) {
if (!this.scrollable) {
return;
}
this.debug('onTouchStart', ev);
const coords = pointerCoord(ev);
const vw = this.width;
if (coords.x < this.leftThreshold || coords.x > vw - this.rightThreshold) {
// ignore this gesture, it started in the side menu touch zone
return;
}
this.touchStartTs = Date.now();
this.initialCoords = coords;
this.lastPosX = coords.x;
}
@Listen('touchmove', { passive: true, capture: true })
async onTouchMove(ev: TouchEvent) {
if (!this.buttonsContainerEl || !this.scrollable || !this.initialCoords || typeof this.lastPosX !== 'number') {
return;
}
const coords = pointerCoord(ev);
if (!this.isDragging) {
const shouldCapture = checkGesture(coords, this.initialCoords!, this.config!);
if (!shouldCapture) {
if (Math.abs(coords.y - this.initialCoords.y) > 100) {
this.initialCoords = void 0;
this.lastPosX = void 0;
}
return;
}
// gesture is good, let's capture all next onTouchMove events
this.isDragging = true;
}
if (!this.isDragging) {
return;
}
ev.stopImmediatePropagation();
// get delta X
const deltaX: number = this.lastPosX - coords.x;
if (deltaX === 0) {
return;
}
// update last X value
this.lastPosX = coords.x;
requestAnimationFrame(() => {
if (!this.isDragging) {
// when swiping fast; this might run after we're already done scrolling
// which leads to "choppy" animations since this instantly scrolls to location
return;
}
// scroll container
const scrollX = getNormalizedScrollX(this.buttonsContainerEl!, this.buttonsContainerEl!.clientWidth, deltaX);
if (scrollX === this.buttonsContainerEl!.scrollLeft) {
return;
}
this.buttonsContainerEl!.scroll(scrollX, 0);
});
}
@Listen('touchend', { passive: false, capture: true })
async onTouchEnd(ev: TouchEvent) {
if (this.lastClickTs < this.touchStartTs && Date.now() - this.touchStartTs <= 150) {
const coords = pointerCoord(ev);
if (Math.abs(coords.x - this.initialCoords?.x!) < this.config?.dragThreshold!) {
const button = this.getButtonFromEv(ev);
if (!button) {
return;
}
this.onButtonClick(button);
}
}
this.isDragging = false;
this.initialCoords = void 0;
this.lastPosX = void 0;
}
@Watch('color')
async onColorUpdate() {
this.setHostCls();
}
private setHostCls() {
const cls: any = {};
if (typeof this.color === 'string' && this.color.trim().length > 0) {
cls['ion-color-' + this.color.trim()] = true;
}
this.hostCls = cls;
}
private async onSlotChange() {
this.debug('onSlotChange');
this.updateWidth();
await this.queryButtons();
await this.setActiveTab(this.activeTabIndex, true);
}
private async queryButtons() {
this.debug('Querying buttons');
const buttons = Array.from(this.el.querySelectorAll('super-tab-button'));
await Promise.all(buttons.map(b => b.componentOnReady()));
if (buttons) {
for (let i = 0; i < buttons.length; i++) {
const button = buttons[i];
button.index = i;
button.scrollableContainer = this.scrollable;
button.active = this.activeTabIndex === i;
if (button.active) {
this.activeButton = button;
}
}
}
this.buttons = buttons;
}
private updateThresholds() {
if (!this.config) {
return;
}
if (this.config!.sideMenu === 'both' || this.config!.sideMenu === 'left') {
this.leftThreshold = this.config!.sideMenuThreshold!;
}
if (this.config!.sideMenu === 'both' || this.config!.sideMenu === 'right') {
this.rightThreshold = this.config!.sideMenuThreshold!;
}
}
private markButtonActive(button: HTMLSuperTabButtonElement) {
if (!button) {
this.debug('markButtonActive', 'button was undefined!');
return;
}
if (this.activeButton) {
this.activeButton.active = false;
}
button.active = true;
this.activeButton = button;
}
private setButtonsContainerEl(el: HTMLDivElement) {
if (el) {
this.buttonsContainerEl = el;
}
}
private adjustContainerScroll(animate: boolean) {
if (!this.buttonsContainerEl) {
this.debug('adjustContainerScroll called before this.buttonsContainerEl was defined');
return;
}
let pos: number;
const ip = this.indicatorPosition!;
const iw = this.indicatorWidth!;
const mw = this.buttonsContainerEl.clientWidth;
const sp = this.buttonsContainerEl.scrollLeft;
const centerDelta = ((mw / 2 - iw / 2));
const a = Math.floor((ip + iw + centerDelta));
const b = Math.floor((ip - centerDelta));
const c = Math.floor((mw + sp));
if (a > c) {
// we need to move the segment container to the left
pos = ip + iw + centerDelta - mw;
} else if (b < sp) {
// we need to move the segment container to the right
pos = Math.max(ip - centerDelta, 0);
pos = pos > ip ? ip - mw + iw : pos;
} else {
return;
}
if (!animate) {
scrollEl(this.buttonsContainerEl, pos!, false, 50);
} else {
this.moveContainer(pos!, animate);
}
}
/**
* Align the indicator with the selected button.
* This will adjust the width and the position of the indicator element.
* @param index {number} the active tab index
* @param [animate] {boolean=false} whether to animate the transition
*/
private async alignIndicator(index: number, animate: boolean = false) {
if (!this.showIndicator || !this.indicatorEl) {
return;
}
this.debug('Aligning indicator', index);
const remainder = index % 1;
const isDragging = this.isDragging = remainder > 0;
const floor = Math.floor(index), ceil = Math.ceil(index);
const button = this.buttons[floor];
if (!button) {
return;
}
let position = button.offsetLeft;
let width = button.clientWidth;
if (isDragging && floor !== ceil) {
const buttonB = this.buttons[ceil];
if (!buttonB) {
// the scroll position we received is higher than the max possible position
// this could happen due to bad CSS (by developer or this module)
// or bad scrolling logic?
return;
}
const buttonBPosition = buttonB.offsetLeft;
const buttonBWidth = buttonB.clientWidth;
position += remainder * (buttonBPosition - position);
width += remainder * (buttonBWidth - width);
}
requestAnimationFrame(() => {
this.indicatorPosition = position;
this.indicatorWidth = width;
if (this.scrollable) {
this.adjustContainerScroll(animate || !isDragging);
}
this.indicatorEl!.style.setProperty('--st-indicator-position-x', this.indicatorPosition + 'px');
this.indicatorEl!.style.setProperty('--st-indicator-scale-x', String(this.indicatorWidth! / 100));
this.indicatorEl!.style.setProperty('--st-indicator-transition-duration', this.isDragging ? '0' : `${this.config!.transitionDuration}ms`);
});
}
/**
* Internal method to output values in debug mode.
*/
private debug(...vals: any[]) {
debugLog(this.config!, 'toolbar', vals);
}
render() {
return (
this.setButtonsContainerEl(ref)}>
{this.showIndicator &&
this.indicatorEl = ref}
toolbarPosition={this.el!.assignedSlot!.name as any}/>}
);
}
}
================================================
FILE: core/src/utils.ts
================================================
import { SuperTabsConfig } from './interface';
export const DEFAULT_CONFIG: SuperTabsConfig = {
dragThreshold: 20,
allowElementScroll: false,
maxDragAngle: 40,
sideMenuThreshold: 50,
transitionDuration: 150,
shortSwipeDuration: 300,
debug: false,
avoidElements: false,
lazyLoad: false,
unloadWhenInvisible: false,
};
export type STCoord = {
x: number;
y: number;
}
export function pointerCoord(ev: any): STCoord {
// get X coordinates for either a mouse click
// or a touch depending on the given event
if (ev) {
const changedTouches = ev.changedTouches;
if (changedTouches && changedTouches.length > 0) {
const touch = changedTouches[0];
return { x: touch.clientX, y: touch.clientY };
}
if (ev.pageX !== undefined) {
return { x: ev.pageX, y: ev.pageY };
}
}
return { x: 0, y: 0 };
}
const nativeScrollAvailable: boolean = 'scrollBehavior' in document.documentElement.style;
let _getTs: () => number;
if (window.performance && window.performance.now) {
_getTs = window.performance.now.bind(window.performance);
} else {
_getTs = Date.now.bind(Date);
}
export const getTs = _getTs;
export const easeInOutCubic = (t: number) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
function getScrollCoord(start: number, dest: number, startTime: number, currentTime: number, duration: number) {
const time = Math.min(1, (currentTime - startTime) / duration);
const timeFn = easeInOutCubic(time);
return Math.ceil((timeFn * (dest - start)) + start);
}
function scroll(el: Element, startX: number, x: number, startTime: number, duration: number) {
const currentTime = getTs();
const scrollX = startX === x ? x : getScrollCoord(startX, x, startTime, currentTime, duration);
el.scrollTo(scrollX, 0);
if (currentTime - startTime >= duration) {
return;
}
requestAnimationFrame(() => {
scroll(el, startX, x, startTime, duration);
});
}
export const scrollEl = (el: Element, x: number, native: boolean, duration: number = 300) => {
if (duration <= 0) {
requestAnimationFrame(() => {
el.scrollTo(x, 0);
});
return;
}
if (native && nativeScrollAvailable) {
el.scrollTo({
left: x,
behavior: 'smooth',
});
return;
}
requestAnimationFrame(() => {
scroll(el, el.scrollLeft, x, getTs(), duration);
});
};
export function checkGesture(newCoords: STCoord, initialCoords: STCoord, config: SuperTabsConfig): boolean {
if (!initialCoords) {
return false;
}
const radians = config.maxDragAngle! * (Math.PI / 180);
const maxCosine = Math.cos(radians);
const deltaX = newCoords.x - initialCoords.x;
const deltaY = newCoords.y - initialCoords.y;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance >= config.dragThreshold!) {
// swipe is long enough
// lets check the angle
const angle = Math.atan2(deltaY, deltaX);
const cosine = Math.cos(angle);
return Math.abs(cosine) > maxCosine;
}
return false;
}
export function getNormalizedScrollX(el: HTMLElement, width: number, delta: number = 0): number {
return Math.max(0, Math.min(el.scrollWidth - width, el.scrollLeft + delta))
}
const debugStyle1 = 'background: linear-gradient(135deg,#4150b2,#f71947); border: 1px solid #9a9a9a; color: #ffffff; border-bottom-left-radius: 2px; border-top-left-radius: 2px; padding: 2px 0 2px 4px;';
const debugStyle2 = 'background: #252b3e; border: 1px solid #9a9a9a; border-top-right-radius: 2px; border-bottom-right-radius: 2px; margin-left: -2px; padding: 2px 4px; color: white;';
export function debugLog(config: SuperTabsConfig, tag: string, vals: any[]) {
if (!config || !config.debug) {
return;
}
// Some gorgeous logging, because apparently I have lots of free time to style console logs and write this comment
console.log(`%csuper-tabs %c%s`, debugStyle1, debugStyle2, ' '.repeat(10 - tag.length) + tag, ...vals);
}
================================================
FILE: core/src/variables.scss
================================================
/**
* super-tab-button variables
*/
/// @prop - Active color of a toolbar button
$st-button-base-color-active: #{var(--ion-color-contrast)} !default;
/// @prop - Inactive color of a toolbar button
$st-button-base-color-inactive: #{rgba(var(--ion-color-contrast-rgb), 0.7)} !default;
/// @prop - Toolbar button icon size
$st-button-icon-size: 24px !default;
/// @prop - Toolbar button inactive color
$st-button-icon-color-inactive: var(--st-base-color-inactive) !default;
/// @prop - Toolbar button active color
$st-button-icon-color-active: var(--st-base-color-active) !default;
$st-button-label-line-height: 14px !default;
$st-button-label-height: 14px !default;
$st-button-label-font-size: 14px !default;
$st-button-label-text-transform: uppercase !default;
$st-button-label-font-weight: 500 !default;
$st-button-label-padding-bottom: 16px !default;
$st-button-label-color-inactive: var(--st-base-color-inactive) !default;
$st-button-label-color-active: var(--st-base-color-active) !default;
/**
* super-tab variables
*/
$st-width: 100vw !default;
$st-height: 100% !default;
/**
* super-tab-indicator variables
*/
$st-indicator-height: 2px !default;
$st-indicator-color: var(--ion-color-contrast, white) !default;
/**
* super-tab-toolbar variables
*/
$st-scrollable-toolbar-padding-left: 52px !default;
$st-toolbar-background: var(--ion-color-base) !default;
================================================
FILE: core/stencil.config.ts
================================================
import { angularOutputTarget } from '@stencil/angular-output-target';
import { Config } from '@stencil/core';
import { reactOutputTarget } from '@stencil/react-output-target';
import { sass } from '@stencil/sass';
export const config: Config = {
namespace: 'SuperTabs',
bundles: [
{ components: ['super-tabs', 'super-tabs-container', 'super-tab'] },
{ components: ['super-tabs-toolbar', 'super-tab-button'] },
{ components: ['super-tab-indicator'] },
],
plugins: [
sass(),
],
outputTargets: [
{
type: 'dist',
esmLoaderPath: '../loader',
copy: [{ src: '**/*.scss' }],
},
{
type: 'docs-readme',
strict: true,
},
{
type: 'dist-hydrate-script',
},
angularOutputTarget({
componentCorePackage: '@ionic-super-tabs/core',
directivesProxyFile: '../angular/src/directives/proxies.ts',
directivesUtilsFile: '../angular/src/directives/proxies-utils.ts',
directivesArrayFile: '../angular/src/directives/proxies-list.txt',
excludeComponents: [
'super-tab-indicator',
],
}),
reactOutputTarget({
componentCorePackage: '@ionic-super-tabs/core',
proxiesFile: '../react/src/components.ts',
excludeComponents: [
'super-tab-indicator',
],
}),
],
validateTypes: true,
};
================================================
FILE: core/tsconfig.json
================================================
{
"compilerOptions": {
"strict": true,
"alwaysStrict": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"declaration": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"jsxFactory": "h",
"lib": [
"dom",
"es2017"
],
"module": "esnext",
"moduleResolution": "node",
"noImplicitAny": false,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": ".tmp",
"pretty": true,
"removeComments": false,
"target": "es2017"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"stencil.config.ts"
],
"exclude": [
"node_modules"
]
}
================================================
FILE: doc-pages/configuration.md
================================================
# Configuration
Configuring ***Super Tabs*** can be done by setting the `config` property on a `` element.
Example:
```html
```
```ts
...
config: SuperTabsConfig = {
sideMenu: 'left',
shortSwipeDuration: 180,
};
```
## Defaults
```ts
export const DEFAULT_CONFIG: SuperTabsConfig = {
dragThreshold: 10,
allowElementScroll: false,
maxDragAngle: 40,
sideMenuThreshold: 50,
transitionDuration: 300,
shortSwipeDuration: 300,
debug: false,
avoidElements: false,
};
```
## Reference
```ts
/**
* Max drag angle in degrees.
*
* Defaults to `40`
*/
maxDragAngle?: number;
/**
* Drag threshold in pixels.
*
* This value defines how far the user have to swipe for the swipe to be treated as a swipe gesture.
*
* Defaults to `20`
*/
dragThreshold?: number;
/**
* Allows elements inside tabs to be dragged or scrolled through.
*
* Setting this value to `true` will allow touch events to be propagated to child components.
*
* Defaults to `false`.
*/
allowElementScroll?: boolean;
/**
* Transition duration in milliseconds.
*
* This value is used for all transitions and animations.
*
* Defaults to `150`.
*/
transitionDuration?: number;
/**
* Side menu location.
*
* If this value is set to `right` or `left`, the super tabs component will avoid listening to swipe events
* in the specified region(s) to avoid interfering with a side menu event listeners.
*
* Defaults to `undefined`.
*/
sideMenu?: 'left' | 'right' | 'both';
/**
* Side menu threshold in pixels.
*
* Defaults to `50`.
*/
sideMenuThreshold?: number;
/**
* Short swipe duration in milliseconds.
*
* Short swipe is when a user quickly swipes between tabs. If the swipe duration is less than or equal to this
* configured value, then we assume that the user wants to switch tabs.
* To disable this behaviour set this value to `0`.
*
* Defaults to `300`
*/
shortSwipeDuration?: number;
/**
* Enable debug mode.
* Defaults to `false`.
*/
debug?: boolean;
/**
* Whether the container should look and avoid elements with avoid-super-tabs attribute
*/
avoidElements?: boolean;
```
================================================
FILE: doc-pages/getting-started/angular.md
================================================
# Getting Started with Angular
## Installation
#### 1. Install the module
```shell
npm i --save @ionic-super-tabs/angular
```
#### 2. Import the module
You must import `SuperTabsModule.forRoot()` once only. This is usually imported in your app's root module (e.g `AppModule`),
but it can be imported in other modules depending on your app structure.
For lazy loaded app you must also import `SuperTabsModule` to have access to the provided components.
```typescript
import { SuperTabsModule } from '@ionic-super-tabs/angular';
// Root module import example
@NgModule({
...
imports: [
...
SuperTabsModule.forRoot(),
],
...
})
export class AppModule {}
// Child module import example
@NgModule({
...
imports: [
...
SuperTabsModule,
],
...
})
export class HomePageModule {}
```
See the [Angular Usage Guide](usage/angular) for examples on how to setup ***Super Tabs***.
================================================
FILE: doc-pages/home.md
================================================
# Super Tabs
Swipeable tabs for Ionic apps
## Introduction
***Super Tabs*** is a set of web components to build swipe-able views in Ionic apps.
The module was designed based on Material Design Guidelines
to provide a simple and familiar user experience.
## Support
#### Community support
Having some trouble? search existing [issues](https://github.com/zyra/ionic-super-tabs/issues) on Github, or [submit a bug report](https://github.com/zyra/ionic-super-tabs/issues/new?assignees=&labels=&template=bug_report.md&title=).
## Contributing
To request a feature create a new issue. Pull requests are welcome.
Features with minimal impact on performance or complexity have a higher probability of being implemented/merged.
## License
This project is licensed under the [MIT License](https://github.com/zyra/ionic-super-tabs/blob/master/LICENSE.md).
================================================
FILE: doc-pages/usage/angular.md
================================================
# Angular Usage Guide
* [Basic usage](usage/angular/#basic-usage)
* [Scrollable toolbar](usage/angular/#scrollable-toolbar)
* [Toolbar positioning](usage/angular/#toolbar-positioning)
* [Routing](usage/angular/#routing)
* [Theming](usage/angular/#theming)
## Basic usage
```html
Super Tabs
Page #1
Page #2
Page #3
Page #1
Page #2
Page #3
```
## Scrollable toolbar
Enabling toolbar scrolling is recommended if the toolbar gets crowded with many tabs.
To enable this feature, set the `scrollable` attribute on ``.
Example:
```html
```
## Toolbar positioning
To position the toolbar below the tabs, set the `slot` attribute to `bottom`.
Example:
```html
```
## Routing
To use external pages inside a `` you can use `` *(instead of `` in basic usage example)*.
Note that lazy loading is not supported yet, so you must pass the target page as a variable.
Example:
```html
```
```ts
import { MyPage } from '../path/to/my.page';
@Component({
...
})
export class HomePage {
myPage = MyPage;
}
```
## Theming
Styling ***Super Tabs*** can be done by setting the available CSS variables. To see the available variables check the
documentation for the specific element.
You can set variables in your page:
```scss
// home.page.scss
:host {
--super-tabs-toolbar-background: black;
--st-indicator-color: yellow;
}
```
or in your root variables:
```scss
// variables.scss
:root {
--st-base-color-inactive: rgba(255, 255, 255, 0.6);
--st-base-color-active: rgba(255, 255, 255, 0.99);
}
```
================================================
FILE: lerna.json
================================================
{
"lerna": "2.11.0",
"packages": [
"angular",
"core",
"react"
],
"version": "7.0.8"
}
================================================
FILE: package.json
================================================
{
"name": "@ionic-super-tabs/source",
"version": "0.0.0",
"description": "",
"scripts": {
"bootstrap": "lerna bootstrap",
"build": "lerna run build --concurrency 1",
"test": "lerna run test"
},
"author": "Zyra Media Inc. ",
"license": "MIT",
"devDependencies": {
"lerna": "latest",
"tslint": "latest"
}
}
================================================
FILE: react/.gitignore
================================================
dist
dist-transpiled
node_modules
================================================
FILE: react/package.json
================================================
{
"name": "@ionic-super-tabs/react",
"version": "7.0.8",
"description": "Ionic Super Tabs bindings for React applications",
"author": "Zyra Media Inc. ",
"license": "MIT",
"scripts": {
"build": "npm run clean && npm run compile",
"clean": "rm -rf dist && rm -rf dist-transpiled",
"compile": "npm run tsc && rollup -c",
"tsc": "tsc -p ."
},
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/types/index.d.ts",
"files": [
"dist/"
],
"keywords": [
"ionic",
"swipeable",
"tabs",
"module",
"component",
"react"
],
"dependencies": {
"@ionic-super-tabs/core": "^7.0.8",
"tslib": "*"
},
"devDependencies": {
"@types/node": "^12.12.14",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"fs-extra": "^8.1.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-testing-library": "^7.0.0",
"rollup": "^1.18.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.4.2",
"rollup-plugin-virtual": "^1.0.1",
"typescript": "^3.7.2"
}
}
================================================
FILE: react/rollup.config.js
================================================
// borrowed from https://github.com/ionic-team/ionic/blob/930b271a4abf680b08e6755644cece4b95053f15/packages/react/rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import sourcemaps from 'rollup-plugin-sourcemaps';
export default {
input: 'dist-transpiled/index.js',
output: [
{
file: 'dist/index.esm.js',
format: 'es',
sourcemap: true
},
{
file: 'dist/index.js',
format: 'commonjs',
preferConst: true,
sourcemap: true
}
],
external: (id) => !/^(\.|\/)/.test(id),
plugins: [
resolve(),
sourcemaps()
]
};
================================================
FILE: react/src/components.ts
================================================
/* eslint-disable */
/* tslint:disable */
/* auto-generated react proxies */
import { createReactComponent } from './react-component-lib';
import { JSX } from '@ionic-super-tabs/core';
import { defineCustomElements, applyPolyfills } from '@ionic-super-tabs/core/loader';
applyPolyfills().then(() => defineCustomElements());
export const SuperTab = /*@__PURE__*/createReactComponent('super-tab');
export const SuperTabButton = /*@__PURE__*/createReactComponent('super-tab-button');
export const SuperTabs = /*@__PURE__*/createReactComponent('super-tabs');
export const SuperTabsContainer = /*@__PURE__*/createReactComponent('super-tabs-container');
export const SuperTabsToolbar = /*@__PURE__*/createReactComponent('super-tabs-toolbar');
================================================
FILE: react/src/index.ts
================================================
export * from './components';
================================================
FILE: react/src/react-component-lib/createComponent.tsx
================================================
import React from 'react';
import {
attachEventProps,
createForwardRef,
dashToPascalCase,
isCoveredByReact,
} from './utils/index';
interface IonicReactInternalProps extends React.HTMLAttributes {
forwardedRef?: React.Ref;
ref?: React.Ref;
}
export const createReactComponent = (tagName: string) => {
const displayName = dashToPascalCase(tagName);
const ReactComponent = class extends React.Component> {
private ref: React.RefObject;
constructor(props: IonicReactInternalProps) {
super(props);
this.ref = React.createRef();
}
componentDidMount() {
this.componentDidUpdate(this.props);
}
componentDidUpdate(prevProps: IonicReactInternalProps) {
const node = this.ref.current;
attachEventProps(node, this.props, prevProps);
}
render() {
const { children, forwardedRef, style, className, ref, ...cProps } = this.props;
const propsToPass = Object.keys(cProps).reduce((acc, name) => {
const isEventProp = name.indexOf('on') === 0 && name[2] === name[2].toUpperCase();
const isDataProp = name.indexOf('data-') === 0;
const isAriaProp = name.indexOf('aria-') === 0;
if (isEventProp) {
const eventName = name.substring(2).toLowerCase();
if (typeof document !== "undefined" && isCoveredByReact(eventName)) {
(acc as any)[name] = (cProps as any)[name];
}
} else if (isDataProp || isAriaProp) {
(acc as any)[name] = (cProps as any)[name];
}
return acc;
}, {});
const newProps: any = {
...propsToPass,
ref: this.ref,
style,
className,
};
return React.createElement(tagName, newProps, children);
}
static get displayName() {
return displayName;
}
};
return createForwardRef(ReactComponent, displayName);
};
================================================
FILE: react/src/react-component-lib/createControllerComponent.tsx
================================================
import React from 'react';
import { attachEventProps } from './utils/attachEventProps';
interface LoadingElement {
present: () => any;
dismiss: () => any;
}
interface ReactControllerProps {
isOpen: boolean;
onDidDismiss: (event: CustomEvent) => void;
}
export function createControllerComponent<
OptionsType extends object,
LoadingElementType extends LoadingElement,
OverlayEventDetail
>(displayName: string, controller: { create: (options: any) => Promise }) {
const dismissEventName = `on${displayName}DidDismiss`;
type Props = OptionsType & ReactControllerProps;
return class ReactControllerComponent extends React.Component {
controller?: LoadingElementType;
constructor(props: Props) {
super(props);
}
static get displayName() {
return displayName;
}
async componentDidMount() {
const { isOpen } = this.props;
if (isOpen) {
this.present();
}
}
async componentDidUpdate(prevProps: Props) {
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps);
}
if (
this.controller &&
prevProps.isOpen !== this.props.isOpen &&
this.props.isOpen === false
) {
await this.controller.dismiss();
}
}
async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props;
const elementProps = {
...cProps,
[dismissEventName]: onDidDismiss,
};
this.controller = await controller.create({
...elementProps,
});
attachEventProps(this.controller as any, elementProps, prevProps);
this.controller.present();
}
render(): null {
return null;
}
};
}
================================================
FILE: react/src/react-component-lib/createOverlayComponent.tsx
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import { attachEventProps } from './utils/attachEventProps';
interface LoadingElement {
present: () => any;
dismiss: () => any;
}
interface ReactOverlayProps {
children?: React.ReactNode;
isOpen: boolean;
onDidDismiss?: (event: CustomEvent) => void;
}
export function createOverlayComponent<
T extends object,
LoadingElementType extends LoadingElement,
OverlayEventDetail
>(displayName: string, controller: { create: (options: any) => Promise }) {
const dismissEventName = `on${displayName}DidDismiss`;
type Props = T & ReactOverlayProps;
return class ReactOverlayComponent extends React.Component {
controller?: LoadingElementType;
el: HTMLDivElement;
constructor(props: Props) {
super(props);
this.el = document.createElement('div');
}
static get displayName() {
return displayName;
}
componentDidMount() {
if (this.props.isOpen) {
this.present();
}
}
async componentDidUpdate(prevProps: Props) {
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps);
}
if (
this.controller &&
prevProps.isOpen !== this.props.isOpen &&
this.props.isOpen === false
) {
await this.controller.dismiss();
}
}
async present(prevProps?: Props) {
// tslint:disable-next-line:no-empty
const { children, isOpen, onDidDismiss = () => {}, ...cProps } = this.props;
const elementProps = {
...cProps,
[dismissEventName]: onDidDismiss,
};
this.controller = await controller.create({
...elementProps,
component: this.el,
componentProps: {},
});
attachEventProps(this.controller as any, elementProps, prevProps);
this.controller.present();
}
render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
};
}
================================================
FILE: react/src/react-component-lib/index.ts
================================================
export { createReactComponent } from './createComponent';
export { createControllerComponent } from './createControllerComponent';
export { createOverlayComponent } from './createOverlayComponent';
================================================
FILE: react/src/react-component-lib/utils/attachEventProps.ts
================================================
export function attachEventProps(node: HTMLElement, newProps: any, oldProps: any = {}) {
const className = getClassName(node.classList, newProps, oldProps);
if (className) {
node.className = className;
}
Object.keys(newProps).forEach(name => {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'className') {
return;
}
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
}
} else {
(node as any)[name] = newProps[name];
}
});
}
export function getClassName(classList: DOMTokenList, newProps: any, oldProps: any) {
// map the classes to Maps for performance
const currentClasses = arrayToMap(classList);
const incomingPropClasses = arrayToMap(newProps.className ? newProps.className.split(' ') : []);
const oldPropClasses = arrayToMap(oldProps.className ? oldProps.className.split(' ') : []);
const finalClassNames: string[] = [];
// loop through each of the current classes on the component
// to see if it should be a part of the classNames added
currentClasses.forEach(currentClass => {
if (incomingPropClasses.has(currentClass)) {
// add it as its already included in classnames coming in from newProps
finalClassNames.push(currentClass);
incomingPropClasses.delete(currentClass);
} else if (!oldPropClasses.has(currentClass)) {
// add it as it has NOT been removed by user
finalClassNames.push(currentClass);
}
});
incomingPropClasses.forEach(s => finalClassNames.push(s));
return finalClassNames.join(' ');
}
/**
* Checks if an event is supported in the current execution environment.
* @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
export function isCoveredByReact(eventNameSuffix: string, doc: Document = document) {
const eventName = 'on' + eventNameSuffix;
let isSupported = eventName in doc;
if (!isSupported) {
const element = doc.createElement('div');
element.setAttribute(eventName, 'return;');
isSupported = typeof (element as any)[eventName] === 'function';
}
return isSupported;
}
export function syncEvent(node: Element, eventName: string, newEventHandler: (e: Event) => any) {
const eventStore = (node as any).__events || ((node as any).__events = {});
const oldEventHandler = eventStore[eventName];
// Remove old listener so they don't double up.
if (oldEventHandler) {
node.removeEventListener(eventName, oldEventHandler);
}
if (newEventHandler != null) {
// Bind new listener.
node.addEventListener(
eventName,
(eventStore[eventName] = function handler(e: Event) {
newEventHandler.call(this, e);
}),
);
}
}
function arrayToMap(arr: string[] | DOMTokenList) {
const map = new Map();
(arr as string[]).forEach((s: string) => map.set(s, s));
return map;
}
================================================
FILE: react/src/react-component-lib/utils/index.tsx
================================================
import React from 'react';
export const dashToPascalCase = (str: string) =>
str
.toLowerCase()
.split('-')
.map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
.join('');
export interface ReactProps {
class?: string;
}
export type IonicReactExternalProps = PropType & React.HTMLAttributes & ReactProps;
export const createForwardRef = (
ReactComponent: any,
displayName: string,
) => {
const forwardRef = (
props: IonicReactExternalProps,
ref: React.Ref,
) => {
return ;
};
forwardRef.displayName = displayName;
return React.forwardRef(forwardRef);
};
export * from './attachEventProps';
================================================
FILE: react/tsconfig.json
================================================
{
"compilerOptions": {
"strict": false,
"allowUnreachableCode": false,
"allowSyntheticDefaultImports": true,
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"lib": ["dom", "es2015"],
"importHelpers": true,
"module": "es2015",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist-transpiled",
"declarationDir": "dist/types",
"removeComments": false,
"inlineSources": true,
"sourceMap": true,
"jsx": "react",
"target": "es2017"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"compileOnSave": false,
"buildOnSave": false
}
================================================
FILE: super-tabs.sh
================================================
#!/bin/bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
WORK_DIR=${TMP:-$DIR}
WS_DIR="${WORK_DIR}/.super-tabs-tmp"
EXAMPLE_PROJECT_DIR_DEFAULT="$(cd "${DIR}/../" >/dev/null 2>&1 && pwd)/ionic-super-tabs-example"
EXAMPLE_PROJECT_DIR=${EXAMPLE_PROJECT_DIR:-${EXAMPLE_PROJECT_DIR_DEFAULT}}
trap '_cleanWorkspace' EXIT
trap 'exit' SIGINT SIGTERM
declare -a _cmd_setup=("setup" "Installs node modules for all packages")
declare -a _cmd_build=("build" "Builds core and angular projects" '--only ')
declare -a _cmd_copy=("copy" "Copies built packages as NPM modules to \`${EXAMPLE_PROJECT_DIR}\`")
declare -a _cmd_dev=("dev" 'Runs build and then copy')
declare -a _cmd_publish=("publish" 'Runs build and publishes package to npm' '--npm-args ')
_formatArgs() {
_args=$(printf "%s" "$2")
for arg in "${@:3}"; do
_args=$(printf "%s\n\t\t\e[37m%s\e[0m" "${_args}" "${arg}")
done
printf "%s" "${_args}"
}
_printUsage() {
declare -a args=(
"$(basename "${0}")"
"${_cmd_setup[0]}" "$(_formatArgs "${_cmd_setup[@]}")"
"${_cmd_build[0]}" "$(_formatArgs "${_cmd_build[@]}")"
"${_cmd_copy[0]}" "$(_formatArgs "${_cmd_copy[@]}")"
"${_cmd_dev[0]}" "$(_formatArgs "${_cmd_dev[@]}")"
"${_cmd_publish[0]}" "$(_formatArgs "${_cmd_publish[@]}")"
)
printf "\e[1m\e[94m................................................................
.............. ...................
.............. Ionic Super Tabs Helper ...................
.............. ...................
................................................................
\e[0m
\e[1m\e[36mUsage\e[0m: %s [options]
\e[1m\e[36mCommands:\e[0m
* \e[1m%10s:\e[0m %s
* \e[1m%10s:\e[0m %s
* \e[1m%10s:\e[0m %s
* \e[1m%10s:\e[0m %s
* \e[1m%10s:\e[0m %s
" "${args[@]}"
}
errExit() {
echo -e "\e[1m\e[91m${*}\e[0m"
exit 1
}
_exec() {
# s=$(date +%s%N | cut -b1-13)
"$@" || errExit "Failed to execute command"
# e=$(date +%s%N | cut -b1-13)
# d=$((e - s))
# _logv "executed ${*} in ${d}ms"
}
_execSilent() {
"$@" &>/dev/null
}
_setupWorkspace() {
_execSilent cd "${DIR}"
_cleanWorkspace
mkdir -p "${WS_DIR}" >/dev/null || errExit 'unable to setup workspace'
}
_cleanWorkspace() {
if [ -d "${WS_DIR}" ]; then
rm -rf "${WS_DIR}" >/dev/null || errExit 'unable to clean workspace'
fi
}
_logv() {
echo -e "\e[33m${*}\e[0m"
}
_log() {
echo -e "\e[36m${*}\e[0m"
}
_build() {
local _only
for ((i = 1; i <= $#; i++)); do
case "${!i}" in
"--only")
local _next="$((i + 1))"
_only="${!_next}"
;;
esac
done
case "${_only}" in
"angular")
_log "Building angular module"
_exec npx lerna run build --scope=@ionic-super-tabs/angular --stream --no-progress --loglevel=silent
;;
"core")
_log "Building core module"
_exec npx lerna run build --scope=@ionic-super-tabs/core --stream --no-progress --loglevel=silent
;;
"react")
_log "Building react module"
_exec npx lerna run build --scope=@ionic-super-tabs/react --stream --no-progress --loglevel=silent
;;
*)
_log "Building all modules"
_exec npx lerna run build --concurrency=1 --stream --no-progress --loglevel=silent
;;
esac
}
_copy() {
if [ ! -d "${EXAMPLE_PROJECT_DIR}" ]; then
errExit "Example project does not exists in ${EXAMPLE_PROJECT_DIR}"
fi
_log "Copying built modules to example app"
cd core || exit 1
_logv "Packing core..."
core_tgz=$(npm pack 2>&1 | tail -1)
mv "${core_tgz}" "${WS_DIR}"
cd ../angular/dist || exit 1
_logv "Packing angular..."
ng_tgz=$(npm pack 2>&1 | tail -1)
mv "${ng_tgz}" "${WS_DIR}"
cd ../../
local extractDest="${EXAMPLE_PROJECT_DIR}/node_modules/@ionic-super-tabs/"
_logv "Extracting packages to ${extractDest}"
tar xf "${WS_DIR}/${core_tgz}" -C "${extractDest}"
cp -a "${extractDest}package/." "${extractDest}/core/"
rm -rf "${extractDest}package"
tar xf "${WS_DIR}/${ng_tgz}" -C "${extractDest}"
cp -a "${extractDest}package/." "${extractDest}/angular/"
rm -rf "${extractDest}package"
_log "Good luck!"
}
_publish() {
_log "Publishing all packages to npm"
cd "${DIR}/core" || exit 1
npm publish --access public || exit 1
cd "${DIR}/angular/dist" || exit 1
npm publish --access public || exit 1
cd "${DIR}/react" || exit 1
npm publish --access public || exit 1
}
_setupWorkspace
case $1 in
"setup")
_exec npx lerna bootstrap
;;
"build")
_build "${@:2}"
;;
"copy")
_copy "${@:2}"
;;
"dev")
_build --only core
_build --only angular
_copy
;;
"publish")
_publish "${@:2}"
;;
"docs")
find "${DIR}/core/src" -type f -name '*.md' | awk 'match($0, /([a-z-]+)\/readme.md/,m) { print m[1] }' | xargs -I@ cp "${DIR}/core/src/@/readme.md" "${DIR}/docs/@.md"
;;
"exec")
cd "${DIR}" && _exec "${@:2}"
;;
*)
_printUsage
;;
esac