chore(): migrate vue to typescript (#15928)

This commit is contained in:
Modus Create
2018-10-24 09:22:36 -04:00
committed by Mike Hartington
parent d800c48734
commit e251ca71b4
61 changed files with 1339 additions and 7860 deletions

View File

@ -1,3 +0,0 @@
{
"presets": ["env"]
}

View File

@ -1,28 +0,0 @@
module.exports = {
env: {
browser: true,
node: true,
es6: true,
},
extends: [
'prettier',
'eslint:recommended',
'plugin:vue/recommended',
'plugin:jest/recommended',
'plugin:promise/recommended',
],
plugins: ['prettier', 'promise', 'jest'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2017,
},
rules: {
'no-console': 0,
'prettier/prettier': 'error',
'linebreak-style': ['error', 'unix'],
'promise/no-callback-in-promise': 0,
indent: ['error', 2],
semi: ['error', 'never'],
quotes: ['error', 'single'],
},
}

2
vue/.gitignore vendored
View File

@ -3,6 +3,8 @@
dist/
node_modules/
src/**/*.js
types/**/*.map
# ignore log files
*.log

View File

@ -1,6 +0,0 @@
{
"printWidth": 100,
"singleQuote": true,
"semi": false,
"trailingComma": "es5"
}

View File

@ -10,6 +10,8 @@
<script>window.disableIonicTransitions = true</script>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>

View File

@ -8,6 +8,8 @@
<meta name="msapplication-tap-highlight" content="no">
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>

View File

@ -8,6 +8,8 @@
<meta name="msapplication-tap-highlight" content="no">
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>

View File

@ -8,6 +8,8 @@
<meta name="msapplication-tap-highlight" content="no">
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>

View File

@ -8,6 +8,8 @@
<meta name="msapplication-tap-highlight" content="no">
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>

View File

@ -8,6 +8,8 @@
<meta name="msapplication-tap-highlight" content="no">
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>

82
vue/cookbook/tabs.html Normal file
View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ionic v4 + Vue.js - Ionic tabs</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vue-class-component@latest/dist/vue-class-component.js"></script>
<script src="https://unpkg.com/vue-property-decorator@latest/lib/vue-property-decorator.umd.js"></script>
<script src="https://unpkg.com/@modus/ionic-vue@latest/dist/ionic-vue.js"></script>
<script src="https://unpkg.com/@ionic/core@latest/dist/ionic.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@ionic/core@latest/css/ionic.bundle.css"/>
</head>
<body>
<ion-app>
<Home/>
</ion-app>
<script>
const Toolbar = Vue.component('Toolbar', {
name: 'Toolbar',
props: { title: String, backURL: String },
template: `<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button :default-href="backURL"/>
</ion-buttons>
<ion-title>{{ title }}</ion-title>
</ion-toolbar>
</ion-header>`
})
const Home = Vue.component('Home', {
template: `<ion-page class="ion-page">
<toolbar title="Home"/>
<ion-content class="ion-content" padding>
<ion-tabs>
<ion-tab href="schedule" label="schedule" icon="calendar" name="schedule">
<ion-vue-router/>
</ion-tab>
<ion-tab href="speakers" label="speakers" icon="contacts" name="speakers">
<ion-vue-router name="speakers"/>
</ion-tab>
</ion-tabs>
</ion-content>
</ion-page>`
})
const Page = {
template: '<ion-button @click="goBack">Go back</ion-button>',
methods: {
goBack() {
this.$router.back()
}
}
}
const Schedule = {
template: `<ion-button @click="$router.push('page')">To page</ion-button>`
}
const Speakers = {
template: '<h1>Speakers list</h1>'
}
Vue.config.ignoredElements.push(/^ion-/)
new Vue({
router: new IonicVue.IonicVueRouter({
routes: [
{ path: '/', components: {default: Schedule, speakers: Speakers} },
{ path: '/page', component: Page }
],
}),
}).$mount('ion-app')
</script>
</body>
</html>

View File

@ -1,12 +0,0 @@
module.exports = {
testURL: 'http://localhost/',
moduleFileExtensions: ['js', 'vue'],
moduleNameMapper: {
'^vue$': 'vue/dist/vue.common.js',
},
transform: {
'^.+\\.js$': '<rootDir>/node_modules/babel-jest',
'.*\\.(vue)$': '<rootDir>/node_modules/jest-vue-preprocessor',
},
"testResultsProcessor": "jest-sonar-reporter"
}

6949
vue/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
"license": "MIT",
"repository": "github:ionic-team/ionic",
"main": "dist/ionic-vue.common.js",
"typings": "types/index.d.ts",
"module": "dist/ionic-vue.esm.js",
"unpkg": "dist/ionic-vue.js",
"jsdelivr": "dist/ionic-vue.js",
@ -15,7 +16,9 @@
],
"files": [
"src/",
"dist/*.js"
"types/",
"dist/*.js",
"dist/*.ts"
],
"bugs": {
"url": "https://github.com/ionic-team/ionic/issues"
@ -25,6 +28,7 @@
"ion",
"vuejs",
"vue",
"typescript",
"router",
"routing",
"plugin",
@ -42,27 +46,21 @@
"watch": "rollup -c -w",
"build": "NODE_ENV=production rollup -c --configProd",
"clean": "node scripts/clean.js",
"lint": "eslint src/**/* test/**/*",
"lint": "tslint --project .",
"lint.fix": "tslint --project . --fix",
"test": "jest --coverage --verbose"
},
"devDependencies": {
"babel-jest": "^23.4.2",
"babel-preset-env": "^1.7.0",
"buble": "^0.19.3",
"eslint": "^5.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-jest": "^21.18.0",
"eslint-plugin-prettier": "^2.6.2",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-vue": "^4.5.0",
"fs-extra": "^7.0.0",
"jest": "^23.4.2",
"jest-sonar-reporter": "^2.0.0",
"jest-vue-preprocessor": "^1.4.0",
"rollup": "^0.62.0",
"rollup-plugin-buble": "^0.19.2",
"rollup-plugin-terser": "^1.0.1",
"rollup-plugin-vue": "^4.3.0"
"rollup-plugin-vue": "^4.3.0",
"rollup-plugin-typescript2": "^0.17.1",
"tslib": "1.9.3",
"tslint": "5.11.0",
"tslint-ionic-rules": "0.0.19",
"typescript": "3.1.1",
"vue-class-component": "^6.2.0",
"vue-property-decorator": "^7.2.0"
},
"dependencies": {
"vue": "^2.5.17",

View File

@ -1,13 +1,12 @@
import path from 'path'
import buble from 'rollup-plugin-buble'
import vue from 'rollup-plugin-vue'
import { terser } from 'rollup-plugin-terser'
import typescript from 'rollup-plugin-typescript2'
import { version as packageVersion } from './package.json'
const version = process.env.VERSION || packageVersion
const banner = `
/*!
const banner = `/*!
* @ionic/vue v${version}
* ${new Date().getFullYear()} Modus Create
* @license MIT
@ -31,28 +30,29 @@ function outputConfig(suffix, format, opts = {}) {
function baseConfig() {
return {
input: resolve('./src/index.js'),
input: resolve('./src/index.ts'),
output: [
outputConfig('', 'umd', { globals: {} }),
outputConfig('', 'umd', {
globals: {
vue: 'Vue',
'vue-class-component': 'VueClassComponent',
'vue-property-decorator': 'vue-property-decorator',
},
}),
outputConfig('.esm', 'esm'),
outputConfig('.common', 'cjs'),
],
external: [
'vue',
'vue-router',
'vue-class-component',
'vue-property-decorator',
'@ionic/core/loader',
'@ionic/core/css/ionic.bundle.css',
'@ionic/core/dist/ionic/svg',
'ionicons/dist/collection/icon/icon.css',
],
plugins: [
vue(),
buble({
transforms: {
dangerousForOf: true,
},
}),
],
plugins: [vue(), typescript({ useTsconfigDeclarationDir: true })],
}
}

View File

@ -1,15 +0,0 @@
// A proxy method that initializes the controller and calls requested method
export function proxyMethod(tag, method, ...opts) {
return initController(tag).then(ctrl => ctrl[method].apply(ctrl, opts))
}
// Initialize an Ionic controller and append it to DOM
export function initController(tag) {
let element = document.querySelector(tag)
if (element) {
return element.componentOnReady()
}
return document.body.appendChild(document.createElement(tag)).componentOnReady()
}

17
vue/src/api-utils.ts Normal file
View File

@ -0,0 +1,17 @@
import { HTMLStencilElement } from './interfaces';
// A proxy method that initializes the controller and calls requested method
export function proxyMethod(tag: string, method: string, ...opts: any[]): Promise<any> {
return initController(tag).then((ctrl: any) => ctrl[method].apply(ctrl, opts));
}
// Initialize an Ionic controller and append it to DOM
export function initController(tag: string): Promise<HTMLStencilElement> {
let element = document.querySelector(tag) as HTMLElement;
if (!element) {
element = document.body.appendChild(document.createElement(tag));
}
return (element as HTMLStencilElement).componentOnReady();
}

View File

@ -1,103 +0,0 @@
import Delegate from './framework-delegate'
import ProxyController from './proxy-controller'
import ProxyMenuController from './proxy-menu-controller'
import ProxyDelegateController from './proxy-delegate-controller'
let _Vue, _Delegate
export default class Api {
// Create or return a ActionSheetController instance
get actionSheetController() {
return getOrCreateController('ion-action-sheet-controller')
}
// Create or return an AlertController instance
get alertController() {
return getOrCreateController('ion-alert-controller')
}
// Create or return a LoadingController instance
get loadingController() {
return getOrCreateController('ion-loading-controller')
}
// Create or return a MenuController instance
get menuController() {
return getOrCreateMenuController('ion-menu-controller')
}
// Create or return a ModalController instance
get modalController() {
return getOrCreateDelegatedController('ion-modal-controller')
}
// Create or return a PopoverController instance
get popoverController() {
return getOrCreateDelegatedController('ion-popover-controller')
}
// Create or return a ToastController instance
get toastController() {
return getOrCreateController('ion-toast-controller')
}
}
// Cached controllers
Api.cache = {
'ion-action-sheet-controller': null,
'ion-alert-controller': null,
'ion-loading-controller': null,
'ion-menu-controller': null,
'ion-modal-controller': null,
'ion-popover-controller': null,
'ion-toast-controller': null,
}
Api.install = function(Vue) {
// If installed - skip
if (Api.install.installed && _Vue === Vue) {
return
}
_Vue = Vue
_Delegate = new Delegate(Vue)
Api.install.installed = true
// Ignore Ionic custom elements
Vue.config.ignoredElements.push(/^ion-/)
// Give access to the API methods
Object.defineProperty(Vue.prototype, '$ionic', {
get() {
return new Api()
},
})
}
// Get existing Base controller instance or initialize a new one
function getOrCreateController(tag) {
if (!Api.cache[tag]) {
Api.cache[tag] = new ProxyController(tag)
}
return Api.cache[tag]
}
// Get existing Menu controller instance or initialize a new one
function getOrCreateMenuController(tag) {
if (!Api.cache[tag]) {
Api.cache[tag] = new ProxyMenuController(tag)
}
return Api.cache[tag]
}
// Get existing Delegated controller instance or initialize a new one
function getOrCreateDelegatedController(tag) {
if (!Api.cache[tag]) {
Api.cache[tag] = new ProxyDelegateController(tag, _Delegate)
}
return Api.cache[tag]
}

109
vue/src/api.ts Normal file
View File

@ -0,0 +1,109 @@
import Vue, { PluginFunction } from 'vue';
import { ApiCache, FrameworkDelegate } from './interfaces';
import Delegate from './framework-delegate';
import ProxyController from './proxy-controller';
import ProxyMenuController from './proxy-menu-controller';
import ProxyDelegateController from './proxy-delegate-controller';
let _Vue: typeof Vue, _Delegate: FrameworkDelegate;
export default class Api {
static cache: ApiCache;
static installed = false;
static install: PluginFunction<never>;
// Create or return a ActionSheetController instance
get actionSheetController(): ProxyController {
return getOrCreateController('ion-action-sheet-controller');
}
// Create or return an AlertController instance
get alertController(): ProxyController {
return getOrCreateController('ion-alert-controller');
}
// Create or return a LoadingController instance
get loadingController(): ProxyController {
return getOrCreateController('ion-loading-controller');
}
// Create or return a MenuController instance
get menuController(): ProxyMenuController {
return getOrCreateMenuController('ion-menu-controller');
}
// Create or return a ModalController instance
get modalController(): ProxyDelegateController {
return getOrCreateDelegatedController('ion-modal-controller');
}
// Create or return a PopoverController instance
get popoverController(): ProxyDelegateController {
return getOrCreateDelegatedController('ion-popover-controller');
}
// Create or return a ToastController instance
get toastController(): ProxyController {
return getOrCreateController('ion-toast-controller');
}
}
// Cached controllers
Api.cache = {
'ion-action-sheet-controller': null,
'ion-alert-controller': null,
'ion-loading-controller': null,
'ion-menu-controller': null,
'ion-modal-controller': null,
'ion-popover-controller': null,
'ion-toast-controller': null,
};
Api.install = (Vue): void => {
// If installed - skip
if (Api.installed && _Vue === Vue) {
return;
}
_Vue = Vue;
_Delegate = new Delegate(Vue);
Api.installed = true;
// Ignore Ionic custom elements
Vue.config.ignoredElements.push(/^ion-/);
// Give access to the API methods
Object.defineProperty(Vue.prototype, '$ionic', {
get() {
return new Api();
},
});
};
// Get existing Base controller instance or initialize a new one
function getOrCreateController(tag: string): ProxyController {
if (!Api.cache[tag]) {
Api.cache[tag] = new ProxyController(tag);
}
return Api.cache[tag];
}
// Get existing Menu controller instance or initialize a new one
function getOrCreateMenuController(tag: string): ProxyMenuController {
if (!Api.cache[tag]) {
Api.cache[tag] = new ProxyMenuController(tag);
}
return Api.cache[tag];
}
// Get existing Delegated controller instance or initialize a new one
function getOrCreateDelegatedController(tag: string): ProxyDelegateController {
if (!Api.cache[tag]) {
Api.cache[tag] = new ProxyDelegateController(tag, _Delegate);
}
return Api.cache[tag];
}

View File

@ -4,18 +4,13 @@
</div>
</template>
<script>
import catchIonicGoBack from '../mixins/catch-ionic-go-back.js'
<script lang="ts">
import { Prop } from 'vue-property-decorator';
import Component, { mixins } from 'vue-class-component';
import CatchIonicGoBack from '../mixins/catch-ionic-go-back';
export default {
name: 'IonVueRouter',
mixins: [catchIonicGoBack],
props: {
// A name to call "named views" by
name: {
type: String,
default: 'default',
},
},
@Component
export default class IonVueRouter extends mixins(CatchIonicGoBack) {
@Prop({ default: 'default'}) name!: string;
}
</script>

View File

@ -23,123 +23,112 @@
</ion-router-outlet>
</template>
<script>
import catchIonicGoBack from '../mixins/catch-ionic-go-back.js'
<script lang="ts">
import { Prop } from 'vue-property-decorator';
import Component, { mixins } from 'vue-class-component';
import CatchIonicGoBack from '../mixins/catch-ionic-go-back';
import { IonRouterOutlet } from '../interfaces';
export default {
name: 'IonVueRouter',
mixins: [catchIonicGoBack],
props: {
// A name to call "named views" by
name: {
type: String,
default: 'default',
},
// Set CSS classes during transitions
bindCSS: {
type: Boolean,
default: false,
},
// Animate transitions or not
animated: {
type: Boolean,
default: true,
},
},
data() {
return {
// Currently visible component
leavingEl: null,
@Component
export default class IonVueRouter extends mixins(CatchIonicGoBack) {
@Prop({ default: 'default'}) name!: string;
@Prop({ default: false }) bindCSS!: boolean;
@Prop({ default: true }) animated!: boolean;
// Component to be rendered
enteringEl: null,
// Currently visible component
leavingEl: HTMLElement;
// Component to be rendered
enteringEl: HTMLElement;
// Flag to see if we're still in a transition
inTransition = false;
customTransition = false;
// Flag to see if we're still in a transition
inTransition: false,
customTransition: false,
}
},
created() {
// Cancel navigation if there's a running transition
this.$router.beforeEach((to, from, next) => {
this.$router.beforeEach((to, _from, next) => {
this.customTransition = to.meta.customTransition || false
return this.$nextTick(() => {
return next(!this.inTransition)
return next(!this.inTransition as false);
})
})
},
methods: {
transition(enteringEl, leavingEl) {
// Get the reference to the Ionic component handling the transitions
const ionRouterOutlet = this.$refs.ionRouterOutlet
}
// The Ionic framework didn't load - skip animations
if (typeof ionRouterOutlet.componentOnReady === 'undefined') {
return
}
transition(enteringEl: HTMLElement, leavingEl: HTMLElement) {
// Get the reference to the Ionic component handling the transitions
const ionRouterOutlet = this.$refs.ionRouterOutlet as IonRouterOutlet;
// Skip animations if there's no component to navigate to
// or the current and the "to-be-rendered" components are the same
if (!enteringEl || enteringEl === leavingEl) {
return
}
// The Ionic framework didn't load - skip animations
if (typeof ionRouterOutlet.componentOnReady === 'undefined') {
return;
}
// Add the proper Ionic classes, important for smooth transitions
enteringEl.classList.add('ion-page', 'ion-page-invisible')
// Skip animations if there's no component to navigate to
// or the current and the "to-be-rendered" components are the same
if (!enteringEl || enteringEl === leavingEl) {
return;
}
// Commit to the transition as soon as the Ionic Router Outlet is ready
return ionRouterOutlet.componentOnReady().then(el => {
return el.commit(enteringEl, leavingEl, {
deepWait: true,
duration: this.getDuration(),
direction: this.getDirection(),
showGoBack: this.$router.canGoBack(),
})
})
},
// Instant transition if we don't want to animate
getDuration() {
return !this.animated ? 0 : undefined
},
// Get the navigation direction from the router
getDirection() {
return this.$router.direction === 1 ? 'forward' : 'back'
},
// Set the component to be rendered before we render the new route
beforeEnter(el) {
this.enteringEl = el
},
// Remember the current component before we leave the route
beforeLeave(el) {
this.leavingEl = el
},
// Transition when we leave the route
leave(el, done) {
const promise = this.transition(this.enteringEl, el)
// Add the proper Ionic classes, important for smooth transitions
enteringEl.classList.add('ion-page', 'ion-page-invisible')
this.inTransition = true
// Commit to the transition as soon as the Ionic Router Outlet is ready
return ionRouterOutlet.componentOnReady().then(el => {
return el.commit(enteringEl, leavingEl, {
deepWait: true,
duration: this.getDuration(),
direction: this.getDirection(),
showGoBack: this.$router.canGoBack(),
});
});
}
// Skip any transition if we don't get back a Promise
if (!promise) {
this.inTransition = false
return done()
}
// Instant transition if we don't want to animate
getDuration() {
return !this.animated ? 0 : undefined;
}
// Perform navigation once the transition was finished
return promise.then(() => {
this.inTransition = false
return done(true)
})
},
// Enter the new route
enter(el, done) {
done()
},
afterEnter(/* el */) {},
enterCancelled(/* el */) {},
afterLeave(/* el */) {},
leaveCancelled(/* el */) {},
},
// Get the navigation direction from the router
getDirection() {
return this.$router.direction === 1 ? 'forward' : 'back';
}
// Set the component to be rendered before we render the new route
beforeEnter(el: HTMLElement) {
this.enteringEl = el;
}
// Remember the current component before we leave the route
beforeLeave(el: HTMLElement) {
this.leavingEl = el;
}
// Transition when we leave the route
leave(el: HTMLElement, done: (opts?: boolean) => void): void {
const promise = this.transition(this.enteringEl, el);
this.inTransition = true;
// Skip any transition if we don't get back a Promise
if (!promise) {
this.inTransition = false;
return done();
}
// Perform navigation once the transition was finished
promise.then(() => {
this.inTransition = false;
return done(true);
});
}
// Enter the new route
enter(_el: HTMLElement, done: () => void): void {
done();
}
afterEnter(/* el */) {}
enterCancelled(/* el */) {}
afterLeave(/* el */) {}
leaveCancelled(/* el */) {}
}
</script>

View File

@ -1,74 +0,0 @@
export default class Delegate {
constructor(Vue) {
this.Vue = Vue
}
// Attach the passed Vue component to DOM
attachViewToDom(parentElement, component, opts, classes) {
// Handle HTML elements
if (isElement(component)) {
// Add any classes to the element
addClasses(component, classes)
// Append the element to DOM
parentElement.appendChild(component)
return Promise.resolve(component)
}
// Get the Vue controller
return this.vueController(component).then(controller => {
const vueComponent = this.vueComponent(controller, opts)
// Add any classes to the Vue component's root element
addClasses(vueComponent.$el, classes)
// Append the Vue component to DOM
parentElement.appendChild(vueComponent.$el)
return vueComponent.$el
})
}
// Remove the earlier created Vue component from DOM
removeViewFromDom(parentElement, childElement) {
// Destroy the Vue component instance
if (childElement.__vue__) {
childElement.__vue__.$destroy()
}
return Promise.resolve()
}
// Handle creation of sync and async components
vueController(component) {
return Promise.resolve(
typeof component === 'function' && component.cid === undefined
? component().then(c => this.Vue.extend(isESModule(c) ? c.default : c))
: this.Vue.extend(component)
)
}
// Create a new instance of the Vue component
vueComponent(controller, opts) {
return new controller(opts).$mount()
}
}
// Check Symbol support
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'
// Check if object is an ES module
function isESModule(obj) {
return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
}
// Check if value is an Element
function isElement(el) {
return typeof Element !== 'undefined' && el instanceof Element
}
// Add an array of classes to an element
function addClasses(element, classes = []) {
for (const cls of classes) {
element.classList.add(cls)
}
}

View File

@ -0,0 +1,75 @@
import { VueConstructor } from 'vue';
import { EsModule, FrameworkDelegate, HTMLVueElement, WebpackFunction } from './interfaces';
export default class Delegate implements FrameworkDelegate {
constructor(public vue: VueConstructor) {}
// Attach the passed Vue component to DOM
attachViewToDom(parentElement: HTMLElement, component: HTMLElement | WebpackFunction | object | VueConstructor, opts?: object, classes?: string[]): Promise<HTMLElement> {
// Handle HTML elements
if (isElement(component)) {
// Add any classes to the element
addClasses(component as HTMLElement, classes);
// Append the element to DOM
parentElement.appendChild(component as HTMLElement);
return Promise.resolve(component as HTMLElement);
}
// Get the Vue controller
return this.vueController(component).then((controller: VueConstructor) => {
const vueComponent = this.vueComponent(controller, opts);
// Add any classes to the Vue component's root element
addClasses(vueComponent.$el, classes);
// Append the Vue component to DOM
parentElement.appendChild(vueComponent.$el);
return vueComponent.$el;
});
}
// Remove the earlier created Vue component from DOM
removeViewFromDom(_parentElement: HTMLElement, childElement: HTMLVueElement): Promise<void> {
// Destroy the Vue component instance
if (childElement.__vue__) {
childElement.__vue__.$destroy();
}
return Promise.resolve();
}
// Handle creation of sync and async components
vueController(component: WebpackFunction | object | VueConstructor): Promise<VueConstructor> {
return Promise.resolve(
typeof component === 'function' && (component as WebpackFunction).cid === undefined
? (component as WebpackFunction)().then((cmp: any) => this.vue.extend(isESModule(cmp) ? cmp.default : cmp))
: this.vue.extend(component)
);
}
// Create a new instance of the Vue component
vueComponent(controller: VueConstructor, opts?: object) {
return new controller(opts).$mount();
}
}
// Check Symbol support
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
// Check if object is an ES module
function isESModule(obj: EsModule) {
return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module');
}
// Check if value is an Element
function isElement(el: any) {
return typeof Element !== 'undefined' && el instanceof Element;
}
// Add an array of classes to an element
function addClasses(element: HTMLElement, classes: string[] = []) {
for (const cls of classes) {
element.classList.add(cls);
}
}

View File

@ -1,3 +0,0 @@
export { default as Ionic } from './ionic'
export { default as IonicAPI } from './api'
export { default as IonicVueRouter } from './router'

3
vue/src/index.ts Normal file
View File

@ -0,0 +1,3 @@
export { default as Ionic } from './ionic';
export { default as IonicAPI } from './api';
export { default as IonicVueRouter } from './router';

89
vue/src/interfaces.ts Normal file
View File

@ -0,0 +1,89 @@
import Vue from 'vue';
import IonicApi from './api';
import VueRouter from 'vue-router';
import { RouterOptions } from 'vue-router/types/router';
declare module 'vue/types/vue' {
interface Vue {
$ionic: IonicApi;
}
}
declare module 'vue-router/types/router' {
interface VueRouter {
direction: number;
directionOverride: number | null;
canGoBack(): boolean;
}
}
export interface HTMLVueElement extends HTMLElement {
__vue__: Vue;
}
export interface VueWindow extends Window {
Vue: typeof Vue;
VueRouter: typeof VueRouter;
disableIonicTransitions: boolean;
}
export interface WebpackFunction extends Function {
cid: number;
}
export interface EsModule extends Object {
__esModule?: boolean;
[Symbol.toStringTag]: string;
}
export interface HTMLStencilElement extends HTMLElement {
componentOnReady(): Promise<this>;
componentOnReady(done: (el?: this) => void): void;
forceUpdate(): void;
}
export interface FrameworkDelegate {
attachViewToDom(parentElement: HTMLElement, component: HTMLElement | WebpackFunction | object | Vue, opts?: object, classes?: string[]): Promise<HTMLElement>;
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLVueElement): Promise<void>;
}
export interface IonBackButton extends HTMLStencilElement {
defaultHref?: string;
}
export interface IonRouterOutlet extends HTMLStencilElement {
commit(enterinEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: object | undefined): Promise<boolean>;
}
export interface ApiCache {
[key: string]: any;
}
export interface RouterArgs extends RouterOptions {
direction: number;
viewCount: number;
}
export interface ProxyControllerInterface {
create(opts: object): Promise<HTMLElement>;
dismiss(): Promise<void>;
getTop(): Promise<HTMLElement>;
}
export interface ProxyDelegateOptions extends Object {
[key: string]: any;
delegate?: FrameworkDelegate;
}
export interface ProxyMenuControllerInterface {
open(menuId?: string): Promise<boolean>;
close(menuId?: string): Promise<boolean>;
toggle(menuId?: string): Promise<boolean>;
enable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
swipeEnable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
isOpen(menuId?: string): Promise<boolean>;
isEnabled(menuId?: string): Promise<boolean>;
get(menuId?: string): Promise<HTMLElement>;
getOpen(): Promise<HTMLElement>;
getMenus(): Promise<HTMLElement>;
}

View File

@ -1,10 +0,0 @@
import '@ionic/core/css/ionic.bundle.css'
import 'ionicons/dist/collection/icon/icon.css'
import '@ionic/core/dist/ionic/svg'
import { defineCustomElements } from '@ionic/core/loader'
export default {
init(opts = {}) {
defineCustomElements(window, opts)
},
}

10
vue/src/ionic.ts Normal file
View File

@ -0,0 +1,10 @@
import '@ionic/core/css/ionic.bundle.css';
import 'ionicons/dist/collection/icon/icon.css';
import '@ionic/core/dist/ionic/svg';
import { defineCustomElements } from '@ionic/core/loader';
export default {
init(): void {
defineCustomElements(window);
},
};

View File

@ -1,27 +0,0 @@
export default {
methods: {
// Catch the bubbled-up event from the Ionic's back button
catchIonicGoBack(event) {
// We only care for the event coming from Ionic's back button
const backButton = event.target && event.target.closest('ion-back-button')
if (!backButton) return
let defaultHref
// Explicitly override router direction
// This will always trigger a back transition
this.$router.directionOverride = -1
// If we can go back - do so
// otherwise if there's a default fall-back - use it
// else - skip
if (this.$router.canGoBack()) {
event.preventDefault()
this.$router.back()
} else if (undefined !== (defaultHref = backButton.defaultHref)) {
event.preventDefault()
this.$router.push(defaultHref)
}
},
},
}

View File

@ -0,0 +1,36 @@
import Vue from 'vue';
import Router from '../router';
import Component from 'vue-class-component';
import { IonBackButton } from '../interfaces';
@Component
export default class CatchIonicGoBack extends Vue {
// Catch the bubbled-up event from the Ionic's back button
catchIonicGoBack(event: Event): void {
if (!event.target) return;
// We only care for the event coming from Ionic's back button
const backButton = (event.target as HTMLElement).closest('ion-back-button') as IonBackButton;
if (!backButton) return;
const $router = this.$router as Router;
let defaultHref: string;
// Explicitly override router direction to always trigger a back transition
$router.directionOverride = -1;
// If we can go back - do so
if ($router.canGoBack()) {
event.preventDefault();
$router.back();
return;
}
// If there's a default fallback - use it
defaultHref = backButton.defaultHref as string;
if (undefined !== defaultHref) {
event.preventDefault();
$router.push(defaultHref);
}
}
}

View File

@ -1,20 +0,0 @@
import * as apiUtils from './api-utils'
// A proxy class that allows early access to controller methods
export default class ProxyController {
constructor(tag) {
this.tag = tag
}
create(opts = {}) {
return apiUtils.proxyMethod(this.tag, 'create', opts)
}
dismiss() {
return apiUtils.proxyMethod(this.tag, 'dismiss')
}
getTop() {
return apiUtils.proxyMethod(this.tag, 'getTop')
}
}

View File

@ -0,0 +1,19 @@
import * as apiUtils from './api-utils';
import { ProxyControllerInterface } from './interfaces';
// A proxy class that allows early access to controller methods
export default class ProxyController implements ProxyControllerInterface {
constructor(public tag: string) {}
create(opts: object = {}): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'create', opts);
}
dismiss(): Promise<void> {
return apiUtils.proxyMethod(this.tag, 'dismiss');
}
getTop(): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'getTop');
}
}

View File

@ -1,17 +0,0 @@
import ProxyController from './proxy-controller'
// A proxy class that allows early access to controller methods
export default class ProxyDelegateController extends ProxyController {
constructor(tag, delegate) {
super(tag)
if (!ProxyDelegateController.delegate) {
ProxyDelegateController.delegate = delegate
}
}
create(opts = {}) {
opts.delegate = ProxyDelegateController.delegate
return super.create(opts)
}
}

View File

@ -0,0 +1,20 @@
import ProxyController from './proxy-controller';
import { FrameworkDelegate, ProxyDelegateOptions } from './interfaces';
// A proxy class that allows early access to controller methods
export default class ProxyDelegateController extends ProxyController {
static delegate: FrameworkDelegate;
constructor(public tag: string, delegate: FrameworkDelegate) {
super(tag);
if (!ProxyDelegateController.delegate) {
ProxyDelegateController.delegate = delegate;
}
}
create(opts: ProxyDelegateOptions = {} as ProxyDelegateOptions) {
opts.delegate = ProxyDelegateController.delegate;
return super.create(opts);
}
}

View File

@ -1,58 +0,0 @@
import * as apiUtils from './api-utils'
// A proxy class that allows early access to controller methods
export default class ProxyMenuController {
constructor(tag) {
this.tag = tag
}
// Open a menu
open(menuId) {
return apiUtils.proxyMethod(this.tag, 'open', menuId)
}
// Close a menu
close(menuId) {
return apiUtils.proxyMethod(this.tag, 'close', menuId)
}
// Toggle a menu
toggle(menuId) {
return apiUtils.proxyMethod(this.tag, 'toggle', menuId)
}
// Enable or disable a menu
enable(shouldEnable, menuId) {
return apiUtils.proxyMethod(this.tag, 'enable', shouldEnable, menuId)
}
// Enable or disable the ability to swipe open the menu
swipeEnable(shouldEnable, menuId) {
return apiUtils.proxyMethod(this.tag, 'swipeEnable', shouldEnable, menuId)
}
// Check if specific or any menu is open
isOpen(menuId) {
return apiUtils.proxyMethod(this.tag, 'isOpen', menuId)
}
// Check is certain menu is enabled
isEnabled(menuId) {
return apiUtils.proxyMethod(this.tag, 'isEnabled', menuId)
}
// Get specific or first menu instance
get(menuId) {
return apiUtils.proxyMethod(this.tag, 'get', menuId)
}
// Get an instance of an open menu
getOpen() {
return apiUtils.proxyMethod(this.tag, 'getOpen')
}
// Get an array of all menus
getMenus() {
return apiUtils.proxyMethod(this.tag, 'getMenus')
}
}

View File

@ -0,0 +1,57 @@
import * as apiUtils from './api-utils';
import { ProxyMenuControllerInterface } from './interfaces';
// A proxy class that allows early access to controller methods
export default class ProxyMenuController implements ProxyMenuControllerInterface {
constructor(public tag: string) {}
// Open a menu
open(menuId?: string): Promise<boolean> {
return apiUtils.proxyMethod(this.tag, 'open', menuId);
}
// Close a menu
close(menuId?: string): Promise<boolean> {
return apiUtils.proxyMethod(this.tag, 'close', menuId);
}
// Toggle a menu
toggle(menuId?: string): Promise<boolean> {
return apiUtils.proxyMethod(this.tag, 'toggle', menuId);
}
// Enable or disable a menu
enable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'enable', shouldEnable, menuId);
}
// Enable or disable the ability to swipe open the menu
swipeEnable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'swipeEnable', shouldEnable, menuId);
}
// Check if specific or any menu is open
isOpen(menuId?: string): Promise<boolean> {
return apiUtils.proxyMethod(this.tag, 'isOpen', menuId);
}
// Check is certain menu is enabled
isEnabled(menuId?: string): Promise<boolean> {
return apiUtils.proxyMethod(this.tag, 'isEnabled', menuId);
}
// Get specific or first menu instance
get(menuId?: string): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'get', menuId);
}
// Get an instance of an open menu
getOpen(): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'getOpen');
}
// Get an array of all menus
getMenus(): Promise<HTMLElement> {
return apiUtils.proxyMethod(this.tag, 'getMenus');
}
}

View File

@ -1,101 +0,0 @@
import VueRouter from 'vue-router'
import IonVueRouter from './components/ion-vue-router.vue'
import IonVueRouterTransitionless from './components/ion-vue-router-transitionless.vue'
const inBrowser = typeof window !== 'undefined'
// Detect environment (browser, module, etc.)
const _VueRouter = inBrowser && window.VueRouter ? window.VueRouter : VueRouter
// Extend the official VueRouter
export default class Router extends _VueRouter {
constructor(...args) {
super(...args)
// The direction user navigates in
this.direction = args.direction || 1
// Override normal direction
this.directionOverride = null
// Number of views navigated
this.viewCount = args.viewCount || 0
// Stack of previous routes
this.prevRouteStack = []
// Extend the existing history object
this.extendHistory()
}
extendHistory() {
// Save a reference to the original method
this.history._updateRoute = this.history.updateRoute
this.history.updateRoute = nextRoute => {
// Guesstimate the direction of the next route
this.direction = this.guessDirection(nextRoute)
// Override the direction
if (this.directionOverride) {
this.direction = this.directionOverride
}
// Increment or decrement the view count
this.viewCount += this.direction
// Call the original method
this.history._updateRoute(nextRoute)
// Reset direction for overrides
this.directionOverride = null
}
}
canGoBack() {
// We can display the back button if we're not on /
// or there were more than 1 views rendered
return this.viewCount > 1 && this.currentRoute.fullPath.length > 1
}
guessDirection(nextRoute) {
if (this.prevRouteStack.length !== 0) {
const prevRoute = this.prevRouteStack[this.prevRouteStack.length - 1]
// Last route is the same as the next one - go back
// If we're going to / reset the stack otherwise pop a route
if (prevRoute.fullPath === nextRoute.fullPath) {
if (prevRoute.fullPath.length === 1) {
this.prevRouteStack = []
} else {
this.prevRouteStack.pop()
}
return -1
}
}
// Forward movement, push next route to stack
if (this.history.current.fullPath !== nextRoute.fullPath) {
this.prevRouteStack.push(this.history.current)
}
return 1
}
}
Router.install = function(Vue, { disableIonicTransitions } = {}) {
// If already installed - skip
if (Router.install.installed) {
return
}
Router.install.installed = true
// Install the official VueRouter
_VueRouter.install(Vue)
// Register the IonVueRouter component globally
// either with default Ionic transitions turned on or off
Vue.component('IonVueRouter', disableIonicTransitions ? IonVueRouterTransitionless : IonVueRouter)
}
// Auto-install when Vue is found (i.e. in browser via <script> tag)
if (inBrowser && window.Vue) {
window.Vue.use(Router, { disableIonicTransitions: window.disableIonicTransitions })
}

115
vue/src/router.ts Normal file
View File

@ -0,0 +1,115 @@
import VueRouter, { Route } from 'vue-router';
import { PluginFunction } from 'vue';
import { RouterArgs, VueWindow } from './interfaces';
import IonVueRouter from './components/ion-vue-router.vue';
import IonVueRouterTransitionless from './components/ion-vue-router-transitionless.vue';
const vueWindow = window as VueWindow;
const inBrowser: boolean = typeof window !== 'undefined';
// Detect environment (browser, module, etc.)
const _VueRouter: typeof VueRouter = inBrowser && vueWindow.VueRouter ? vueWindow.VueRouter : VueRouter;
// Extend the official VueRouter
export default class Router extends _VueRouter {
direction: number;
directionOverride: number | null;
viewCount: number;
prevRouteStack: Route[];
history: any;
static installed: boolean;
static install: PluginFunction<never>;
constructor(args: RouterArgs = {} as RouterArgs) {
super(args);
// The direction user navigates in
this.direction = args.direction || 1;
// Override normal direction
this.directionOverride = null;
// Number of views navigated
this.viewCount = args.viewCount || 0;
// Stack of previous routes
this.prevRouteStack = [];
// Extend the existing history object
this.extendHistory();
}
extendHistory(): void {
// Save a reference to the original method
this.history._updateRoute = this.history.updateRoute;
this.history.updateRoute = (nextRoute: Route) => {
// Guesstimate the direction of the next route
this.direction = this.guessDirection(nextRoute);
// Override the direction
if (this.directionOverride) {
this.direction = this.directionOverride;
}
// Increment or decrement the view count
this.viewCount += this.direction;
// Call the original method
this.history._updateRoute(nextRoute);
// Reset direction for overrides
this.directionOverride = null;
};
}
canGoBack(): boolean {
// We can display the back button if we're not on /
// or there were more than 1 views rendered
return this.viewCount > 1 && this.currentRoute.fullPath.length > 1;
}
guessDirection(nextRoute: Route): number {
if (this.prevRouteStack.length !== 0) {
const prevRoute: Route = this.prevRouteStack[this.prevRouteStack.length - 1];
// Last route is the same as the next one - go back
// If we're going to / reset the stack otherwise pop a route
if (prevRoute.fullPath === nextRoute.fullPath) {
if (prevRoute.fullPath.length === 1) {
this.prevRouteStack = [];
} else {
this.prevRouteStack.pop();
}
return -1;
}
}
// Forward movement, push next route to stack
if (this.history.current.fullPath !== nextRoute.fullPath) {
this.prevRouteStack.push(this.history.current);
}
return 1;
}
}
Router.install = (Vue, { disableIonicTransitions = false }: { disableIonicTransitions?: boolean } = {}): void => {
// If already installed - skip
if (Router.installed) {
return;
}
Router.installed = true;
// Install the official VueRouter
_VueRouter.install(Vue);
// Register the IonVueRouter component globally
// either with default Ionic transitions turned on or off
Vue.component('IonVueRouter', disableIonicTransitions ? IonVueRouterTransitionless : IonVueRouter);
};
// Auto-install when Vue is found (i.e. in browser via <script> tag)
if (inBrowser && vueWindow.Vue) {
vueWindow.Vue.use(Router, { disableIonicTransitions: vueWindow.disableIonicTransitions });
}

4
vue/src/sfc.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

View File

@ -1,184 +0,0 @@
import Vue from 'vue'
import API from '../src/api.js'
const api = new API()
beforeEach(() => {
HTMLElement.prototype.componentOnReady = function() {
const el = this
el.create = function(props = {}) {
return Object.assign(el, props)
}
el.dismiss = function() {
return el
}
el.getTop = el.dismiss
el.open = el.create
el.close = el.dismiss
el.toggle = el.dismiss
el.enable = el.dismiss
el.swipeEnable = el.dismiss
el.isOpen = el.dismiss
el.isEnabled = el.dismiss
el.get = el.dismiss
el.getOpen = el.dismiss
el.getMenus = el.dismiss
return Promise.resolve(el)
}
})
afterEach(() => {
HTMLElement.prototype.componentOnReady = undefined
})
describe('API', () => {
it('Installs correctly', () => {
Vue.use(API)
const app = new Vue()
expect(typeof app.$ionic).toBe('object')
expect(API.install(Vue)).toBeFalsy()
})
it('Creates action sheet controller', () => {
expect.assertions(3)
api.actionSheetController
.dismiss()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.actionSheetController
.getTop()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
return api.actionSheetController.create().then(c => {
return expect(c).toBeTruthy()
})
})
it('Creates alert controllers', () => {
expect.assertions(3)
// Creates initial element
api.alertController
.create({ foo: 'bar' })
.then(c => {
return expect(c.foo).toBe('bar')
})
.catch(err => err)
// Returns previous element with extra props
return api.alertController.create({ bar: 'foo' }).then(c => {
expect(c.foo).toBe('bar')
return expect(c.bar).toBe('foo')
})
})
it('Creates loading controllers', done => {
return api.loadingController.create({ bar: 'foo' }).then(c => {
expect(c.bar).toBe('foo')
return done()
})
})
it('Creates modal controllers', done => {
return api.modalController.create({ bar: 'foo' }).then(c => {
expect(c.bar).toBe('foo')
return done()
})
})
it('Creates popover controllers', done => {
return api.popoverController.create({ bar: 'foo' }).then(c => {
expect(c.bar).toBe('foo')
return done()
})
})
it('Creates toast controllers', done => {
return api.toastController.create({ bar: 'foo' }).then(c => {
expect(c.bar).toBe('foo')
return done()
})
})
it('Creates menu controllers', done => {
expect.assertions(10)
api.menuController
.close()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.toggle()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.enable()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.swipeEnable()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.isOpen()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.isEnabled()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.get()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.getOpen()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
api.menuController
.getMenus()
.then(c => {
return expect(c).toBeTruthy()
})
.catch(err => err)
return api.menuController.open({ bar: 'foo' }).then(c => {
expect(c.bar).toBe('foo')
return done()
})
})
})

View File

@ -1,73 +0,0 @@
import Vue from 'vue'
import Delegate from '../src/framework-delegate.js'
const delegate = new Delegate(Vue)
const app = document.createElement('div')
app.id = 'app'
document.body.appendChild(app)
describe('Framework delegation', () => {
it('Attaches components to DOM', () => {
expect.assertions(2)
const component = {
template: '<p>foo</p>',
}
const data = {
data() {
return { foo: 'bar' }
},
}
return delegate.attachViewToDom(app, component, data, ['foo']).then(el => {
expect(el.classList.contains('foo')).toBeTruthy()
expect(el.__vue__.foo).toBe('bar')
return
})
})
it('Attaches lazy loaded components to DOM', () => {
expect.assertions(2)
const component = function() {
return Promise.resolve({
render(h) {
return h('p')
},
})
}
const data = {
data() {
return { foo: 'bar' }
},
}
return delegate.attachViewToDom(app, component, data, ['foo']).then(el => {
expect(el.classList.contains('foo')).toBeTruthy()
expect(el.__vue__.foo).toBe('bar')
return
})
})
it('Attaches HTML elements to DOM', () => {
expect.assertions(1)
const element = document.createElement('p')
return delegate.attachViewToDom(app, element, null, ['foo']).then(el => {
return expect(el.classList.contains('foo')).toBeTruthy()
})
})
it('Removes from DOM', () => {
expect.assertions(2)
const div = document.querySelector('p')
expect(typeof div.__vue__).toBe('object')
return delegate.removeViewFromDom(app, div).then(() => {
return expect(div.__vue__).toBe(null)
})
})
})

View File

@ -1,43 +0,0 @@
import Vue from 'vue'
import Router from '../src/router.js'
import IonVueRouterTransitionless from '../src/components/ion-vue-router-transitionless.vue'
Vue.use(Router)
describe('IonVueRouter', () => {
it('Catches back button click event', () => {
const constructor = Vue.extend(IonVueRouterTransitionless)
const component = new constructor({ router: new Router({ mode: 'abstract' }) })
expect(component.catchIonicGoBack({})).toBeFalsy()
component.$router.push('/')
component.$router.push('/foo')
expect(component.$route.fullPath).toBe('/foo')
// Go back
component.catchIonicGoBack(mockBackEvent('/'))
expect(component.$route.fullPath).toBe('/')
// Should not go back
component.catchIonicGoBack(mockBackEvent())
expect(component.$route.fullPath).toBe('/')
// Go back to default route
component.catchIonicGoBack(mockBackEvent('/bar'))
expect(component.$route.fullPath).toBe('/bar')
})
})
function mockBackEvent(route) {
return {
target: {
closest() {
return {
defaultHref: route,
}
},
},
preventDefault() {},
}
}

View File

@ -1,170 +0,0 @@
import Vue from 'vue'
import Router from '../src/router.js'
import IonVueRouter from '../src/components/ion-vue-router.vue'
describe('IonVueRouter', () => {
Vue.use(Router)
Vue.config.ignoredElements.push(/^ion-/)
const app = new Vue({
components: { Toolbar: Toolbar() },
render(h) {
return h('ion-vue-router')
},
router: new Router({
mode: 'abstract',
routes: [{ path: '/', component: Home() }, { path: '/page', component: Page() }],
}),
}).$mount()
it('Renders the home route correctly', done => {
app.$router.push('/')
setTimeout(() => {
expect(app.$el.textContent.trim()).toBe('Home Go to page')
done()
}, 1)
})
it('Renders the page route correctly', done => {
app.$router.push('/page')
setTimeout(() => {
expect(app.$el.textContent.trim()).toBe('Page Go home')
done()
}, 1)
})
it('Renders back route correctly', done => {
app.$router.go(-1)
setTimeout(() => {
expect(app.$el.textContent.trim()).toBe('Home Go to page')
done()
}, 1)
})
it('Sets the default data correctly', () => {
expect(typeof IonVueRouter.data).toBe('function')
expect(IonVueRouter.data()).toMatchObject({
leavingEl: null,
enteringEl: null,
})
})
it('Sets the default props correctly', () => {
const constructor = Vue.extend(IonVueRouter)
const component = new constructor({ router: new Router() })
expect(component.bindCss).toBeFalsy()
expect(component.animated).toBeTruthy()
expect(component.name).toBe('default')
})
it('Transitions correctly', () => {
expect.assertions(3)
const constructor = Vue.extend(IonVueRouter)
const component = new constructor({ router: new Router() })
component.$refs.ionRouterOutlet = mockIonRouterOutlet()
expect(component.transition()).toBeFalsy()
component.enteringEl = document.createElement('div')
component.leave(document.createElement('h1'), res => {
expect(res).toBeTruthy()
})
return component
.transition(document.createElement('div'), document.createElement('h1'))
.then(res => {
return expect(res).toBeTruthy()
})
})
it('Gets duration correctly', () => {
const constructor = Vue.extend(IonVueRouter)
const component = new constructor({ router: new Router() })
expect(component.getDuration()).toBe(undefined)
component.animated = false
expect(component.getDuration()).toBe(0)
})
it('Gets direction correctly', () => {
const constructor = Vue.extend(IonVueRouter)
const component = new constructor({ router: new Router() })
expect(component.getDirection()).toBe('forward')
component.$router.direction = -1
expect(component.getDirection()).toBe('back')
})
it('Runs stub methods correctly', () => {
const constructor = Vue.extend(IonVueRouter)
const component = new constructor({ router: new Router() })
component.enterCancelled()
component.leaveCancelled()
})
})
function Toolbar() {
return Vue.component('Toolbar', {
name: 'Toolbar',
props: {
backURL: { type: String, default: '' },
title: { type: String, default: '' },
},
template: `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button :default-href="backURL"/>
</ion-buttons>
<ion-title>{{ title }}</ion-title>
</ion-toolbar>
</ion-header>`,
})
}
function Home() {
return Vue.component('Home', {
template: `
<ion-page class="ion-page">
<toolbar title="Home"/>
<ion-content class="ion-content" padding>
<router-link to="/page">Go to page</router-link>
</ion-content>
</ion-page>`,
})
}
function Page() {
return Vue.component('Page', {
methods: {
goHome() {
this.$router.back()
},
},
template: `
<ion-page class="ion-page">
<toolbar title="Page"/>
<ion-content class="ion-content" padding>
<ion-button @click="goHome">Go home</ion-button>
</ion-content>
</ion-page>`,
})
}
function mockIonRouterOutlet() {
return {
componentOnReady() {
return new Promise(resolve => {
return resolve({
commit() {
return true
},
})
})
},
}
}

View File

@ -1,9 +0,0 @@
import Vue from 'vue'
describe('Router node', () => {
it('Sets globals correctly', () => {
window.Vue = undefined
global.Vue = Vue
require('../src/router.js')
})
})

View File

@ -1,45 +0,0 @@
import Vue from 'vue'
import Router from '../src/router.js'
describe('Router', () => {
it('Installs correctly', () => {
Vue.use(Router)
const app = new Vue({
router: new Router(),
})
expect(typeof app.$router).toBe('object')
expect(typeof app.$options.components.IonVueRouter).toBe('function')
expect(Router.install()).toBeFalsy()
})
it('Navigates correctly', () => {
const r = new Router({ mode: 'abstract' })
r.push('/')
expect(r.viewCount).toBe(1)
expect(r.direction).toBe(1)
expect(r.canGoBack()).toBeFalsy()
r.push('/foo')
expect(r.viewCount).toBe(2)
expect(r.direction).toBe(1)
expect(r.canGoBack()).toBeTruthy()
r.push('/bar')
expect(r.viewCount).toBe(3)
expect(r.direction).toBe(1)
expect(r.canGoBack()).toBeTruthy()
r.go(-1)
expect(r.viewCount).toBe(2)
expect(r.direction).toBe(-1)
expect(r.canGoBack()).toBeTruthy()
r.go(-1)
expect(r.viewCount).toBe(1)
expect(r.direction).toBe(-1)
expect(r.canGoBack()).toBeFalsy()
})
})

37
vue/tsconfig.json Normal file
View File

@ -0,0 +1,37 @@
{
"compilerOptions": {
"alwaysStrict": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"declaration": true,
"declarationMap": true,
"declarationDir": "types",
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"jsxFactory": "h",
"lib": [
"dom",
"es2017"
],
"module": "es2015",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist",
"pretty": true,
"removeComments": false,
"rootDir": "src",
"strictPropertyInitialization": false,
"target": "es5"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

7
vue/tslint.json Normal file
View File

@ -0,0 +1,7 @@
{
"extends": "tslint-ionic-rules",
"rules": {
"no-non-null-assertion": false,
"no-import-side-effect": [true, {"ignore-module": "(svg|\\.css)$"}]
}
}

4
vue/types/api-utils.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
import { HTMLStencilElement } from './interfaces';
export declare function proxyMethod(tag: string, method: string, ...opts: any[]): Promise<any>;
export declare function initController(tag: string): Promise<HTMLStencilElement>;
//# sourceMappingURL=api-utils.d.ts.map

18
vue/types/api.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import { PluginFunction } from 'vue';
import { ApiCache } from './interfaces';
import ProxyController from './proxy-controller';
import ProxyMenuController from './proxy-menu-controller';
import ProxyDelegateController from './proxy-delegate-controller';
export default class Api {
static cache: ApiCache;
static installed: boolean;
static install: PluginFunction<never>;
readonly actionSheetController: ProxyController;
readonly alertController: ProxyController;
readonly loadingController: ProxyController;
readonly menuController: ProxyMenuController;
readonly modalController: ProxyDelegateController;
readonly popoverController: ProxyDelegateController;
readonly toastController: ProxyController;
}
//# sourceMappingURL=api.d.ts.map

View File

@ -0,0 +1,7 @@
import CatchIonicGoBack from '../mixins/catch-ionic-go-back';
declare const IonVueRouter_base: import("vue-class-component/lib/declarations").VueClass<CatchIonicGoBack>;
export default class IonVueRouter extends IonVueRouter_base {
name: string;
}
export {};
//# sourceMappingURL=ion-vue-router-transitionless.vue?rollup-plugin-vue=script.d.ts.map

View File

@ -0,0 +1,25 @@
import CatchIonicGoBack from '../mixins/catch-ionic-go-back';
declare const IonVueRouter_base: import("vue-class-component/lib/declarations").VueClass<CatchIonicGoBack>;
export default class IonVueRouter extends IonVueRouter_base {
name: string;
bindCSS: boolean;
animated: boolean;
leavingEl: HTMLElement;
enteringEl: HTMLElement;
inTransition: boolean;
customTransition: boolean;
created(): void;
transition(enteringEl: HTMLElement, leavingEl: HTMLElement): Promise<boolean> | undefined;
getDuration(): 0 | undefined;
getDirection(): "forward" | "back";
beforeEnter(el: HTMLElement): void;
beforeLeave(el: HTMLElement): void;
leave(el: HTMLElement, done: (opts?: boolean) => void): void;
enter(_el: HTMLElement, done: () => void): void;
afterEnter(): void;
enterCancelled(): void;
afterLeave(): void;
leaveCancelled(): void;
}
export {};
//# sourceMappingURL=ion-vue-router.vue?rollup-plugin-vue=script.d.ts.map

11
vue/types/framework-delegate.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { VueConstructor } from 'vue';
import { FrameworkDelegate, HTMLVueElement, WebpackFunction } from './interfaces';
export default class Delegate implements FrameworkDelegate {
vue: VueConstructor;
constructor(vue: VueConstructor);
attachViewToDom(parentElement: HTMLElement, component: HTMLElement | WebpackFunction | object | VueConstructor, opts?: object, classes?: string[]): Promise<HTMLElement>;
removeViewFromDom(_parentElement: HTMLElement, childElement: HTMLVueElement): Promise<void>;
vueController(component: WebpackFunction | object | VueConstructor): Promise<VueConstructor>;
vueComponent(controller: VueConstructor, opts?: object): import("vue/types/vue").CombinedVueInstance<import("vue/types/vue").Vue, object, object, object, Record<never, any>>;
}
//# sourceMappingURL=framework-delegate.d.ts.map

4
vue/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export { default as Ionic } from './ionic';
export { default as IonicAPI } from './api';
export { default as IonicVueRouter } from './router';
//# sourceMappingURL=index.d.ts.map

75
vue/types/interfaces.d.ts vendored Normal file
View File

@ -0,0 +1,75 @@
import Vue from 'vue';
import IonicApi from './api';
import VueRouter from 'vue-router';
import { RouterOptions } from 'vue-router/types/router';
declare module 'vue/types/vue' {
interface Vue {
$ionic: IonicApi;
}
}
declare module 'vue-router/types/router' {
interface VueRouter {
direction: number;
directionOverride: number | null;
canGoBack(): boolean;
}
}
export interface HTMLVueElement extends HTMLElement {
__vue__: Vue;
}
export interface VueWindow extends Window {
Vue: typeof Vue;
VueRouter: typeof VueRouter;
disableIonicTransitions: boolean;
}
export interface WebpackFunction extends Function {
cid: number;
}
export interface EsModule extends Object {
__esModule?: boolean;
[Symbol.toStringTag]: string;
}
export interface HTMLStencilElement extends HTMLElement {
componentOnReady(): Promise<this>;
componentOnReady(done: (el?: this) => void): void;
forceUpdate(): void;
}
export interface FrameworkDelegate {
attachViewToDom(parentElement: HTMLElement, component: HTMLElement | WebpackFunction | object | Vue, opts?: object, classes?: string[]): Promise<HTMLElement>;
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLVueElement): Promise<void>;
}
export interface IonBackButton extends HTMLStencilElement {
defaultHref?: string;
}
export interface IonRouterOutlet extends HTMLStencilElement {
commit(enterinEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: object | undefined): Promise<boolean>;
}
export interface ApiCache {
[key: string]: any;
}
export interface RouterArgs extends RouterOptions {
direction: number;
viewCount: number;
}
export interface ProxyControllerInterface {
create(opts: object): Promise<HTMLElement>;
dismiss(): Promise<void>;
getTop(): Promise<HTMLElement>;
}
export interface ProxyDelegateOptions extends Object {
[key: string]: any;
delegate?: FrameworkDelegate;
}
export interface ProxyMenuControllerInterface {
open(menuId?: string): Promise<boolean>;
close(menuId?: string): Promise<boolean>;
toggle(menuId?: string): Promise<boolean>;
enable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
swipeEnable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
isOpen(menuId?: string): Promise<boolean>;
isEnabled(menuId?: string): Promise<boolean>;
get(menuId?: string): Promise<HTMLElement>;
getOpen(): Promise<HTMLElement>;
getMenus(): Promise<HTMLElement>;
}
//# sourceMappingURL=interfaces.d.ts.map

8
vue/types/ionic.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import '@ionic/core/css/ionic.bundle.css';
import 'ionicons/dist/collection/icon/icon.css';
import '@ionic/core/dist/ionic/svg';
declare const _default: {
init(): void;
};
export default _default;
//# sourceMappingURL=ionic.d.ts.map

View File

@ -0,0 +1,5 @@
import Vue from 'vue';
export default class CatchIonicGoBack extends Vue {
catchIonicGoBack(event: Event): void;
}
//# sourceMappingURL=catch-ionic-go-back.d.ts.map

9
vue/types/proxy-controller.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { ProxyControllerInterface } from './interfaces';
export default class ProxyController implements ProxyControllerInterface {
tag: string;
constructor(tag: string);
create(opts?: object): Promise<HTMLElement>;
dismiss(): Promise<void>;
getTop(): Promise<HTMLElement>;
}
//# sourceMappingURL=proxy-controller.d.ts.map

View File

@ -0,0 +1,9 @@
import ProxyController from './proxy-controller';
import { FrameworkDelegate, ProxyDelegateOptions } from './interfaces';
export default class ProxyDelegateController extends ProxyController {
tag: string;
static delegate: FrameworkDelegate;
constructor(tag: string, delegate: FrameworkDelegate);
create(opts?: ProxyDelegateOptions): Promise<HTMLElement>;
}
//# sourceMappingURL=proxy-delegate-controller.d.ts.map

16
vue/types/proxy-menu-controller.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
import { ProxyMenuControllerInterface } from './interfaces';
export default class ProxyMenuController implements ProxyMenuControllerInterface {
tag: string;
constructor(tag: string);
open(menuId?: string): Promise<boolean>;
close(menuId?: string): Promise<boolean>;
toggle(menuId?: string): Promise<boolean>;
enable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
swipeEnable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
isOpen(menuId?: string): Promise<boolean>;
isEnabled(menuId?: string): Promise<boolean>;
get(menuId?: string): Promise<HTMLElement>;
getOpen(): Promise<HTMLElement>;
getMenus(): Promise<HTMLElement>;
}
//# sourceMappingURL=proxy-menu-controller.d.ts.map

19
vue/types/router.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
import VueRouter, { Route } from 'vue-router';
import { PluginFunction } from 'vue';
import { RouterArgs } from './interfaces';
declare const _VueRouter: typeof VueRouter;
export default class Router extends _VueRouter {
direction: number;
directionOverride: number | null;
viewCount: number;
prevRouteStack: Route[];
history: any;
static installed: boolean;
static install: PluginFunction<never>;
constructor(args?: RouterArgs);
extendHistory(): void;
canGoBack(): boolean;
guessDirection(nextRoute: Route): number;
}
export {};
//# sourceMappingURL=router.d.ts.map

75
vue/types/types/interfaces.d.ts vendored Normal file
View File

@ -0,0 +1,75 @@
import Vue from 'vue';
import IonicApi from '../api';
import VueRouter from 'vue-router';
import { RouterOptions } from 'vue-router/types/router';
declare module 'vue/types/vue' {
interface Vue {
$ionic: IonicApi;
}
}
declare module 'vue-router/types/router' {
interface VueRouter {
direction: number;
directionOverride: number | null;
canGoBack(): boolean;
}
}
export interface HTMLVueElement extends HTMLElement {
__vue__: Vue;
}
export interface VueWindow extends Window {
Vue: typeof Vue;
VueRouter: typeof VueRouter;
disableIonicTransitions: boolean;
}
export interface WebpackFunction extends Function {
cid: number;
}
export interface EsModule extends Object {
__esModule?: boolean;
[Symbol.toStringTag]: string;
}
export interface HTMLStencilElement extends HTMLElement {
componentOnReady(): Promise<this>;
componentOnReady(done: (el?: this) => void): void;
forceUpdate(): void;
}
export interface FrameworkDelegate {
attachViewToDom(parentElement: HTMLElement, component: HTMLElement | WebpackFunction | object | Vue, opts?: object, classes?: string[]): Promise<HTMLElement>;
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLVueElement): Promise<void>;
}
export interface IonBackButton extends HTMLStencilElement {
defaultHref?: string;
}
export interface IonRouterOutlet extends HTMLStencilElement {
commit(enterinEl: HTMLElement, leavingEl: HTMLElement | undefined, opts?: object | undefined): Promise<boolean>;
}
export interface ApiCache {
[key: string]: any;
}
export interface RouterArgs extends RouterOptions {
direction: number;
viewCount: number;
}
export interface ProxyControllerInterface {
create(opts: object): Promise<HTMLElement>;
dismiss(): Promise<void>;
getTop(): Promise<HTMLElement>;
}
export interface ProxyDelegateOptions extends Object {
[key: string]: any;
delegate: FrameworkDelegate;
}
export interface ProxyMenuControllerInterface {
open(menuId?: string): Promise<boolean>;
close(menuId?: string): Promise<boolean>;
toggle(menuId?: string): Promise<boolean>;
enable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
swipeEnable(shouldEnable: boolean, menuId?: string): Promise<HTMLElement>;
isOpen(menuId?: string): Promise<boolean>;
isEnabled(menuId?: string): Promise<boolean>;
get(menuId?: string): Promise<HTMLElement>;
getOpen(): Promise<HTMLElement>;
getMenus(): Promise<HTMLElement>;
}
//# sourceMappingURL=interfaces.d.ts.map