chore(packages): move the packages to root

This commit is contained in:
Brandy Carney
2018-03-12 16:02:25 -04:00
parent 097f1a2cd3
commit d37623a2ca
1255 changed files with 38 additions and 38 deletions

19
angular/README.md Normal file
View File

@@ -0,0 +1,19 @@
# @ionic/angular
Ionic Angular specific building blocks on top of [@ionic/core](https://www.npmjs.com/package/@ionic/core) components.
## Related
* [Ionic Core Components](https://www.npmjs.com/package/@ionic/core)
* [Ionic Documentation](https://ionicframework.com/docs/)
* [Ionic Worldwide Slack](http://ionicworldwide.herokuapp.com/)
* [Ionic Forum](https://forum.ionicframework.com/)
* [Ionicons](http://ionicons.com/)
* [Stencil](https://stenciljs.com/)
* [Stencil Worldwide Slack](https://stencil-worldwide.slack.com)
## License
* [MIT](https://raw.githubusercontent.com/ionic-team/ionic/master/LICENSE)

4013
angular/package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

64
angular/package.json Normal file
View File

@@ -0,0 +1,64 @@
{
"name": "@ionic/angular",
"version": "0.0.2-29",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
"framework",
"angular",
"mobile",
"app",
"webapp",
"capacitor",
"cordova",
"progressive web app",
"pwa"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ionic-team/ionic.git"
},
"scripts": {
"build": "npm run clean && npm run compile && npm run clean-generated",
"build.link": "npm run build && node scripts/link-copy.js",
"clean": "node scripts/clean.js",
"clean-generated": "node ./scripts/clean-generated.js",
"compile": "./node_modules/.bin/ngc",
"deploy": "node scripts/deploy.js",
"lint": "tslint --project .",
"prepare.deploy": "node scripts/deploy.js --prepare",
"set.version": "node scripts/set-version.js",
"tsc": "tsc -p ."
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist/"
],
"devDependencies": {
"@angular/common": "latest",
"@angular/compiler": "latest",
"@angular/compiler-cli": "latest",
"@angular/core": "latest",
"@angular/forms": "latest",
"@angular/http": "latest",
"@angular/platform-browser": "latest",
"@angular/platform-browser-dynamic": "latest",
"@angular/router": "latest",
"@ionic/core": "^0.1.3",
"chalk": "^2.3.2",
"execa": "^0.9.0",
"fs-extra": "^5.0.0",
"glob": "7.1.2",
"inquirer": "^5.1.0",
"listr": "^0.13.0",
"rimraf": "^2.6.2",
"rxjs": "5.5.2",
"semver": "^5.5.0",
"tslint": "^5.8.0",
"tslint-ionic-rules": "0.0.13",
"typescript": "^2.6.2",
"zone.js": "0.8.18"
}
}

16
angular/scripts/README.md Normal file
View File

@@ -0,0 +1,16 @@
# npm link local development
`npm link` doesn't work as expected due to the `devDependency` on `@angular/core`. This is the work around...
npm run build.link ../ionic-conference-app
When the command above is ran from the `angular` directory, it will build `@ionic/angular` and copy the `dist` directory to the correct location of another local project. In the example above, the end result is that it copies the `dist` directory to `../ionic-conference-app/node_modules/@ionic/angular/dist`. The path given should be relative to the root of this mono repo.
# Deploy
1. `npm run prepare.deploy`
2. Review/update changelog
3. Commit updates using the package name and version number as the commit message.
4. `npm run deploy`
5. :tada:

54
angular/scripts/clean-generated.js vendored Normal file
View File

@@ -0,0 +1,54 @@
const path = require('path');
const cwd = process.cwd();
const glob = require('glob');
const rimRaf = require('rimraf');
const distDir = path.join(cwd, 'dist');
const distGeneratedNodeModules = path.join(distDir, 'node_modules');
function rimRafAsync(dir) {
return new Promise((resolve, reject) => {
rimRaf(dir, {}, err => {
if (err) {
return reject(err);
}
resolve();
})
});
}
function doGlob(globString) {
return new Promise((resolve, reject) => {
glob(globString, (err, matches) => {
if (err) {
return reject(err);
}
resolve(matches);
})
});
}
function getCodegenedFilesToDelete() {
const ngFactoryGlob = path.join(distDir, '**', '*ngfactory*');
const ngSummaryGlob = path.join(distDir, '**', '*ngsummary*');
const promises = [];
promises.push(doGlob(ngFactoryGlob));
promises.push(doGlob(ngSummaryGlob));
return Promise.all(promises).then(listOfGlobResults => {
const deleteFilePromises = [];
listOfGlobResults.forEach(fileMatches => {
fileMatches.forEach(filePath => {
deleteFilePromises.push(rimRafAsync(filePath));
})
})
return Promise.all(deleteFilePromises);
});
}
const taskPromises = [];
taskPromises.push(getCodegenedFilesToDelete());
taskPromises.push(rimRafAsync(distGeneratedNodeModules));
return Promise.all(taskPromises);

12
angular/scripts/clean.js vendored Normal file
View File

@@ -0,0 +1,12 @@
const fs = require('fs-extra');
const path = require('path');
const cleanDirs = [
'dist'
];
cleanDirs.forEach(dir => {
const cleanDir = path.join(__dirname, '../', dir);
fs.removeSync(cleanDir);
});

396
angular/scripts/deploy.js vendored Normal file
View File

@@ -0,0 +1,396 @@
/**
* Deploy script adopted from https://github.com/sindresorhus/np
* MIT License (c) Sindre Sorhus (sindresorhus.com)
*/
const chalk = require('chalk');
const execa = require('execa');
const inquirer = require('inquirer');
const Listr = require('listr');
const fs = require('fs-extra');
const path = require('path');
const semver = require('semver');
const rootDir = path.join(__dirname, '../');
const packageJsonPath = path.join(rootDir, 'package.json');
function runTasks(opts) {
const pkg = readPkg();
const tasks = [];
let newVersion = getNewVersion(pkg.version, opts.version);
if (opts.prepare) {
tasks.push(
{
title: 'Validate version',
task: () => {
if (!isValidVersionInput(opts.version)) {
throw new Error(`Version should be either ${SEMVER_INCREMENTS.join(', ')}, or a valid semver version.`);
}
if (!isVersionGreater(pkg.version, newVersion)) {
throw new Error(`New version \`${newVersion}\` should be higher than current version \`${pkg.version}\``);
}
}
}
)
}
if (opts.publish) {
tasks.push(
{
title: 'Check for pre-release version',
task: () => {
if (!pkg.private && isPrereleaseVersion(newVersion) && !opts.tag) {
throw new Error('You must specify a dist-tag using --tag when publishing a pre-release version. This prevents accidentally tagging unstable versions as "latest". https://docs.npmjs.com/cli/dist-tag');
}
}
}
)
}
tasks.push(
{
title: 'Check npm version',
skip: () => isVersionLower('6.0.0', process.version),
task: () => execa.stdout('npm', ['version', '--json']).then(json => {
const versions = JSON.parse(json);
if (!satisfies(versions.npm, '>=2.15.8 <3.0.0 || >=3.10.1')) {
throw new Error(`npm@${versions.npm} has known issues publishing when running Node.js 6. Please upgrade npm or downgrade Node and publish again. https://github.com/npm/npm/issues/5082`);
}
})
},
{
title: 'Check git tag existence',
task: () => execa('git', ['fetch'])
.then(() => {
return execa.stdout('npm', ['config', 'get', 'tag-version-prefix']);
})
.then(
output => {
tagPrefix = output;
},
() => {}
)
.then(() => execa.stdout('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagPrefix}${newVersion}`]))
.then(
output => {
if (output) {
throw new Error(`Git tag \`${tagPrefix}${newVersion}\` already exists.`);
}
},
err => {
// Command fails with code 1 and no output if the tag does not exist, even though `--quiet` is provided
// https://github.com/sindresorhus/np/pull/73#discussion_r72385685
if (err.stdout !== '' || err.stderr !== '') {
throw err;
}
}
)
},
{
title: 'Check current branch',
task: () => execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']).then(branch => {
if (branch !== 'master' && branch !== 'core') {
throw new Error('Not on `master` or `core` branch');
}
})
},
{
title: 'Check local working tree',
task: () => execa.stdout('git', ['status', '--porcelain']).then(status => {
if (status !== '') {
throw new Error('Unclean working tree. Commit or stash changes first.');
}
})
},
{
title: 'Check remote history',
task: () => execa.stdout('git', ['rev-list', '--count', '--left-only', '@{u}...HEAD']).then(result => {
if (result !== '0') {
throw new Error('Remote history differs. Please pull changes.');
}
})
}
);
if (opts.prepare) {
tasks.push(
{
title: 'Cleanup',
task: () => fs.remove('node_modules')
},
{
title: 'Install npm dependencies',
task: () => execa('npm', ['install'], { cwd: rootDir }),
}
);
}
tasks.push(
{
title: 'Run lint',
task: () => execa('npm', ['run', 'lint'], { cwd: rootDir })
},
{
title: 'Build ' + pkg.name,
task: () => execa('npm', ['run', 'build'], { cwd: rootDir })
},
{
title: 'Run tests',
task: () => execa('npm', ['test'], { cwd: rootDir })
}
);
if (opts.prepare){
tasks.push(
{
title: 'Set package.json version',
task: () => execa('npm', ['run', 'set.version', opts.version], { cwd: rootDir }),
}
);
}
if (opts.publish) {
tasks.push(
{
title: 'Publish ' + pkg.name,
task: () => execa('npm', ['publish'].concat(opts.tag ? ['--tag', opts.tag] : []), { cwd: rootDir })
},
{
title: 'Tagging the latest commit',
task: () => execa('git', ['tag', `${pkg.name}-v${opts.version}`], { cwd: rootDir })
},
{
title: 'Pushing to Github',
task: () => execa('git', ['push', '--tags'], { cwd: rootDir })
}
);
}
const listr = new Listr(tasks, { showSubtasks: false });
return listr.run()
.then(() => {
if (opts.prepare) {
console.log(`\n ${pkg.name} ${newVersion} prepared, check the diffs and commit 🕵️\n`);
} else if (opts.publish) {
console.log(`\n ${pkg.name} ${newVersion} published!! 🎉\n`);
}
})
.catch(err => {
console.log('\n', chalk.red(err), '\n');
process.exit(0);
});
}
function prepareUI() {
const pkg = readPkg();
const oldVersion = pkg.version;
console.log(`\nPrepare to publish a new version of ${chalk.bold.magenta(pkg.name)} ${chalk.dim(`(${oldVersion})`)}\n`);
const prompts = [
{
type: 'list',
name: 'version',
message: 'Select semver increment or specify new version',
pageSize: SEMVER_INCREMENTS.length + 2,
choices: SEMVER_INCREMENTS
.map(inc => ({
name: `${inc} ${prettyVersionDiff(oldVersion, inc)}`,
value: inc
}))
.concat([
new inquirer.Separator(),
{
name: 'Other (specify)',
value: null
}
]),
filter: input => isValidVersionInput(input) ? getNewVersion(oldVersion, input) : input
},
{
type: 'input',
name: 'version',
message: 'Version',
when: answers => !answers.version,
filter: input => isValidVersionInput(input) ? getNewVersion(pkg.version, input) : input,
validate: input => {
if (!isValidVersionInput(input)) {
return 'Please specify a valid semver, for example, `1.2.3`. See http://semver.org';
} else if (!isVersionGreater(oldVersion, input)) {
return `Version must be greater than ${oldVersion}`;
}
return true;
}
},
{
type: 'confirm',
name: 'confirm',
message: answers => {
return `Will bump from ${chalk.cyan(oldVersion)} to ${chalk.cyan(answers.version)}. Continue?`;
}
}
];
return inquirer
.prompt(prompts)
.then(answers => {
if (answers.confirm){
answers.prepare = true;
answers.publish = false;
runTasks(answers);
}
})
.catch(err => {
console.log('\n', chalk.red(err), '\n');
process.exit(0);
});
}
function publishUI() {
const pkg = readPkg();
const version = pkg.version;
console.log(`\nPublish a new version of ${chalk.bold.magenta(pkg.name)} ${chalk.dim(`(${version})`)}\n`);
const prompts = [
{
type: 'list',
name: 'tag',
message: 'How should this pre-release version be tagged in npm?',
when: answers => isPrereleaseVersion(version),
choices: () => execa.stdout('npm', ['view', '--json', pkg.name, 'dist-tags'])
.then(stdout => {
const existingPrereleaseTags = Object.keys(JSON.parse(stdout))
.filter(tag => tag !== 'latest');
if (existingPrereleaseTags.length === 0) {
existingPrereleaseTags.push('next');
}
return existingPrereleaseTags
.concat([
new inquirer.Separator(),
{
name: 'Other (specify)',
value: null
}
]);
})
},
{
type: 'input',
name: 'tag',
message: 'Tag',
when: answers => !pkg.private && isPrereleaseVersion(version) && !answers.tag,
validate: input => {
if (input.length === 0) {
return 'Please specify a tag, for example, `next`.';
} else if (input.toLowerCase() === 'latest') {
return 'It\'s not possible to publish pre-releases under the `latest` tag. Please specify something else, for example, `next`.';
}
return true;
}
},
{
type: 'confirm',
name: 'confirm',
message: answers => {
const tag = answers.tag;
const tagPart = tag ? ` and tag this release in npm as ${tag}` : '';
return `Will publish ${chalk.cyan(version + tagPart)}. Continue?`;
}
}
];
return inquirer
.prompt(prompts)
.then(answers => {
if (answers.confirm){
answers.version = version;
answers.prepare = false;
answers.publish = true;
runTasks(answers);
}
})
.catch(err => {
console.log('\n', chalk.red(err), '\n');
process.exit(0);
});
}
const SEMVER_INCREMENTS = ['patch', 'minor', 'major', 'prepatch', 'preminor', 'premajor', 'prerelease'];
const PRERELEASE_VERSIONS = ['prepatch', 'preminor', 'premajor', 'prerelease'];
const isValidVersion = input => Boolean(semver.valid(input));
const isValidVersionInput = input => SEMVER_INCREMENTS.indexOf(input) !== -1 || isValidVersion(input);
const isPrereleaseVersion = version => PRERELEASE_VERSIONS.indexOf(version) !== -1 || Boolean(semver.prerelease(version));
function getNewVersion(oldVersion, input) {
if (!isValidVersionInput(input)) {
throw new Error(`Version should be either ${SEMVER_INCREMENTS.join(', ')} or a valid semver version.`);
}
return SEMVER_INCREMENTS.indexOf(input) === -1 ? input : semver.inc(oldVersion, input);
};
const isVersionGreater = (oldVersion, newVersion) => {
if (!isValidVersion(newVersion)) {
throw new Error('Version should be a valid semver version.');
}
return semver.gt(newVersion, oldVersion);
};
const isVersionLower = (oldVersion, newVersion) => {
if (!isValidVersion(newVersion)) {
throw new Error('Version should be a valid semver version.');
}
return semver.lt(newVersion, oldVersion);
};
const satisfies = (version, range) => semver.satisfies(version, range);
const readPkg = () => {
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
};
function prettyVersionDiff(oldVersion, inc) {
const newVersion = getNewVersion(oldVersion, inc).split('.');
oldVersion = oldVersion.split('.');
let firstVersionChange = false;
const output = [];
for (let i = 0; i < newVersion.length; i++) {
if ((newVersion[i] !== oldVersion[i] && !firstVersionChange)) {
output.push(`${chalk.dim.cyan(newVersion[i])}`);
firstVersionChange = true;
} else if (newVersion[i].indexOf('-') >= 1) {
let preVersion = [];
preVersion = newVersion[i].split('-');
output.push(`${chalk.dim.cyan(`${preVersion[0]}-${preVersion[1]}`)}`);
} else {
output.push(chalk.reset.dim(newVersion[i]));
}
}
return output.join(chalk.reset.dim('.'));
}
const prepare = process.argv.slice(2).indexOf('--prepare') > -1;
if (prepare) {
prepareUI();
} else {
publishUI();
}

31
angular/scripts/link-copy.js vendored Normal file
View File

@@ -0,0 +1,31 @@
const fs = require('fs-extra');
const path = require('path');
let prjDir = process.argv[2];
if (!prjDir) {
throw new Error('local path required as last argument to "npm run build.link" command');
}
prjDir = path.join(__dirname, '../../../', prjDir);
const prjIonicAngular = path.join(prjDir, 'node_modules/@ionic/angular');
const ionicAngularDir = path.join(__dirname, '..');
const ionicAngularDist = path.join(ionicAngularDir, 'dist');
const ionicAngularPkgJsonPath = path.join(ionicAngularDir, 'package.json');
const ionicAngularPkgJson = require(ionicAngularPkgJsonPath);
// make sure this local project exists
fs.emptyDirSync(prjIonicAngular);
ionicAngularPkgJson.files.push('package.json');
ionicAngularPkgJson.files.forEach(f => {
const src = path.join(ionicAngularDir, f);
const dest = path.join(prjIonicAngular, f);
console.log('copying:', src, 'to', dest);
fs.copySync(src, dest);
});
const prjReadme = path.join(prjIonicAngular, 'README.md');
fs.writeFileSync(prjReadme, '@ionic/angular copied from ' + ionicAngularDir);

19
angular/scripts/set-version.js vendored Normal file
View File

@@ -0,0 +1,19 @@
const fs = require('fs');
const path = require('path');
const packageJsonPath = path.join(__dirname, '../package.json');
const packageLockPath = path.join(__dirname, '../package-lock.json');
function getVersion() {
return process.argv[process.argv.length - 1];
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
packageJson.version = getVersion();
const packageLock = JSON.parse(fs.readFileSync(packageLockPath, 'utf-8'));
packageLock.version = packageJson.version;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
fs.writeFileSync(packageLockPath, JSON.stringify(packageLock, null, 2) + '\n');

View File

@@ -0,0 +1,62 @@
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { setIonicClasses } from './util/set-ionic-classes';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-checkbox,ion-toggle',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: BooleanValueAccessor,
multi: true
}
]
})
export class BooleanValueAccessor implements ControlValueAccessor {
constructor(private element: ElementRef, private renderer: Renderer2) {
this.onChange = () => {/**/};
this.onTouched = () => {/**/};
}
onChange: (value: any) => void;
onTouched: () => void;
writeValue(value: any) {
this.renderer.setProperty(this.element.nativeElement, 'checked', value);
setIonicClasses(this.element);
}
@HostListener('ionChange', ['$event.target.checked'])
_handleIonChange(value: any) {
this.onChange(value);
setTimeout(() => {
setIonicClasses(this.element);
});
}
@HostListener('ionBlur')
_handleBlurEvent() {
this.onTouched();
setTimeout(() => {
setIonicClasses(this.element);
});
}
registerOnChange(fn: (value: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.renderer.setProperty(
this.element.nativeElement,
'disabled',
isDisabled
);
}
}

View File

@@ -0,0 +1,71 @@
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { setIonicClasses } from './util/set-ionic-classes';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-input[type=number]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: NumericValueAccessor,
multi: true
}
]
})
export class NumericValueAccessor implements ControlValueAccessor {
constructor(private element: ElementRef, private renderer: Renderer2) {
this.onChange = () => {/**/};
this.onTouched = () => {/**/};
}
onChange: (value: any) => void;
onTouched: () => void;
writeValue(value: any): void {
// The value needs to be normalized for IE9, otherwise it is set to 'null' when null
// Probably not an issue for us, but it doesn't really cost anything either
const normalizedValue = value == null ? '' : value;
this.renderer.setProperty(
this.element.nativeElement,
'value',
normalizedValue
);
setIonicClasses(this.element);
}
@HostListener('input', ['$event.target.value'])
_handleInputEvent(value: any): void {
this.onChange(value);
setTimeout(() => {
setIonicClasses(this.element);
});
}
@HostListener('ionBlur')
_handleBlurEvent(): void {
this.onTouched();
setTimeout(() => {
setIonicClasses(this.element);
});
}
registerOnChange(fn: (_: number | null) => void): void {
this.onChange = value => {
fn(value === '' ? null : parseFloat(value));
};
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.renderer.setProperty(
this.element.nativeElement,
'disabled',
isDisabled
);
}
}

View File

@@ -0,0 +1,70 @@
import { Directive, ElementRef, HostListener, Input, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { setIonicClasses } from './util/set-ionic-classes';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-radio',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: RadioValueAccessor,
multi: true
}
]
})
export class RadioValueAccessor implements ControlValueAccessor {
@Input() value: any;
onChange: (value: any) => void;
onTouched: () => void;
constructor(private element: ElementRef, private renderer: Renderer2) {
this.onChange = () => {/**/};
this.onTouched = () => {/**/};
}
writeValue(value: any) {
this.renderer.setProperty(
this.element.nativeElement,
'checked',
value === this.value
);
setIonicClasses(this.element);
}
@HostListener('ionSelect', ['$event.target.checked'])
_handleIonSelect(value: any) {
this.onChange(value);
setTimeout(() => {
setIonicClasses(this.element);
});
}
@HostListener('ionBlur')
_handleBlurEvent() {
this.onTouched();
setTimeout(() => {
setIonicClasses(this.element);
});
}
registerOnChange(fn: (value: any) => void): void {
this.onChange = () => {
fn(this.value);
};
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.renderer.setProperty(
this.element.nativeElement,
'disabled',
isDisabled
);
}
}

View File

@@ -0,0 +1,64 @@
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { setIonicClasses } from './util/set-ionic-classes';
// NOTE: May need to look at this to see if we need anything else:
// https://github.com/angular/angular/blob/5.0.2/packages/forms/src/directives/select_control_value_accessor.ts#L28-L158
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-range, ion-select, ion-radio-group, ion-segment, ion-datetime',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectValueAccessor,
multi: true
}
]
})
export class SelectValueAccessor implements ControlValueAccessor {
constructor(private element: ElementRef, private renderer: Renderer2) {
this.onChange = () => {/**/};
this.onTouched = () => {/**/};
}
onChange: (value: any) => void;
onTouched: () => void;
writeValue(value: any) {
this.renderer.setProperty(this.element.nativeElement, 'value', value);
setIonicClasses(this.element);
}
@HostListener('ionChange', ['$event.target.value'])
_handleChangeEvent(value: any) {
this.onChange(value);
setTimeout(() => {
setIonicClasses(this.element);
});
}
@HostListener('ionBlur')
_handleBlurEvent() {
this.onTouched();
setTimeout(() => {
setIonicClasses(this.element);
});
}
registerOnChange(fn: (value: any) => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.renderer.setProperty(
this.element.nativeElement,
'disabled',
isDisabled
);
}
}

View File

@@ -0,0 +1,62 @@
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { setIonicClasses } from './util/set-ionic-classes';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-input:not([type=number]),ion-textarea,ion-searchbar',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: TextValueAccessor,
multi: true
}
]
})
export class TextValueAccessor implements ControlValueAccessor {
constructor(private element: ElementRef, private renderer: Renderer2) {
this.onChange = () => {/**/};
this.onTouched = () => {/**/};
}
onChange: (value: any) => void;
onTouched: () => void;
writeValue(value: any) {
this.renderer.setProperty(this.element.nativeElement, 'value', value);
setIonicClasses(this.element);
}
@HostListener('input', ['$event.target.value'])
_handleInputEvent(value: any) {
this.onChange(value);
setTimeout(() => {
setIonicClasses(this.element);
});
}
@HostListener('ionBlur')
_handleBlurEvent() {
this.onTouched();
setTimeout(() => {
setIonicClasses(this.element);
});
}
registerOnChange(fn: (value: any) => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.renderer.setProperty(
this.element.nativeElement,
'disabled',
isDisabled
);
}
}

View File

@@ -0,0 +1,20 @@
import { ElementRef } from '@angular/core';
export function setIonicClasses(element: ElementRef) {
const classList = element.nativeElement.classList;
classList.remove('ion-invalid');
classList.remove('ion-valid');
classList.remove('ion-touched');
classList.remove('ion-untouched');
classList.remove('ion-dirty');
classList.remove('ion-pristine');
classList.forEach((cls: string) => {
if (cls === 'ng-invalid') { classList.add('ion-invalid'); }
if (cls === 'ng-valid') { classList.add('ion-valid'); }
if (cls === 'ng-touched') { classList.add('ion-touched'); }
if (cls === 'ng-untouched') { classList.add('ion-untouched'); }
if (cls === 'ng-dirty') { classList.add('ion-dirty'); }
if (cls === 'ng-pristine') { classList.add('ion-pristine'); }
});
}

View File

@@ -0,0 +1,10 @@
import { Directive, TemplateRef } from '@angular/core';
import { VirtualContext } from './virtual-utils';
/**
* @hidden
*/
@Directive({selector: '[virtualFooter]'})
export class VirtualFooter {
constructor(public templateRef: TemplateRef<VirtualContext>) {}
}

View File

@@ -0,0 +1,10 @@
import { Directive, TemplateRef } from '@angular/core';
import { VirtualContext } from './virtual-utils';
/**
* @hidden
*/
@Directive({selector: '[virtualHeader]'})
export class VirtualHeader {
constructor(public templateRef: TemplateRef<VirtualContext>) {}
}

View File

@@ -0,0 +1,10 @@
import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
import { VirtualContext } from './virtual-utils';
/**
* @hidden
*/
@Directive({selector: '[virtualItem]'})
export class VirtualItem {
constructor(public templateRef: TemplateRef<VirtualContext>, public viewContainer: ViewContainerRef) {}
}

View File

@@ -0,0 +1,59 @@
import { ChangeDetectorRef, ContentChild, Directive, ElementRef, EmbeddedViewRef } from '@angular/core';
import { VirtualItem } from './virtual-item';
import { VirtualHeader } from './virtual-header';
import { VirtualFooter } from './virtual-footer';
import { VirtualContext } from './virtual-utils';
@Directive({
selector: 'ion-virtual-scroll'
})
export class VirtualScroll {
@ContentChild(VirtualItem) itmTmp: VirtualItem;
@ContentChild(VirtualHeader) hdrTmp: VirtualHeader;
@ContentChild(VirtualFooter) ftrTmp: VirtualFooter;
constructor(
private el: ElementRef,
public cd: ChangeDetectorRef,
) {
this.el.nativeElement.itemRender = this.itemRender.bind(this);
}
private itemRender(el: HTMLElement|null, cell: any, index?: number) {
if (!el) {
const node = this.itmTmp.viewContainer.createEmbeddedView(
this.getComponent(cell.type),
new VirtualContext(null, null, null),
index
);
el = getElement(node);
(el as any)['$ionView'] = node;
}
const node = (el as any)['$ionView'];
const ctx = node.context;
ctx.$implicit = cell.value;
ctx.index = cell.index;
node.detectChanges();
return el;
}
private getComponent(type: number) {
switch (type) {
case 0: return this.itmTmp.templateRef;
case 1: return this.hdrTmp.templateRef;
case 2: return this.ftrTmp.templateRef;
}
return null;
}
}
function getElement(view: EmbeddedViewRef<VirtualContext>): HTMLElement {
const rootNodes = view.rootNodes;
for (let i = 0; i < rootNodes.length; i++) {
if (rootNodes[i].nodeType === 1) {
return rootNodes[i];
}
}
return null;
}

View File

@@ -0,0 +1,14 @@
export class VirtualContext {
constructor(public $implicit: any, public index: number, public count: number) { }
get first(): boolean { return this.index === 0; }
get last(): boolean { return this.index === this.count - 1; }
get even(): boolean { return this.index % 2 === 0; }
get odd(): boolean { return !this.even; }
}

58
angular/src/index.ts Normal file
View File

@@ -0,0 +1,58 @@
export { IonicAngularModule } from './module';
/* Navigation */
export { IonNav } from './navigation/ion-nav';
export { IonRouterOutlet } from './navigation/ion-router-outlet';
export { IonTab } from './navigation/ion-tab';
export { IonTabs } from './navigation/ion-tabs';
/* Directives */
export { VirtualScroll } from './directives/virtual-scroll';
export { VirtualItem } from './directives/virtual-item';
export { VirtualHeader } from './directives/virtual-header';
export { VirtualFooter } from './directives/virtual-footer';
/* Providers */
export { ActionSheetController, ActionSheetProxy } from './providers/action-sheet-controller';
export { AlertController, AlertProxy } from './providers/alert-controller';
export { Events } from './providers/events';
export { LoadingController, LoadingProxy } from './providers/loading-controller';
export { MenuController } from './providers/menu-controller';
export { ModalController, ModalProxy } from './providers/modal-controller';
export { Platform } from './providers/platform';
export { PopoverController, PopoverProxy } from './providers/popover-controller';
export { ToastController, ToastProxy } from './providers/toast-controller';
export * from './types/interfaces';
import { IonicWindow } from './types/interfaces';
const win = (window as IonicWindow);
const Ionic = win.Ionic;
if (Ionic) {
Ionic.ael = function ngAddEventListener(elm, eventName, cb, opts) {
if (elm.__zone_symbol__addEventListener) {
elm.__zone_symbol__addEventListener(eventName, cb, opts);
} else {
elm.addEventListener(eventName, cb, opts);
}
};
Ionic.rel = function ngRemoveEventListener(elm, eventName, cb, opts) {
if (elm.__zone_symbol__removeEventListener) {
elm.__zone_symbol__removeEventListener(eventName, cb, opts);
} else {
elm.removeEventListener(eventName, cb, opts);
}
};
Ionic.raf = function ngRequestAnimationFrame(cb: any) {
if (win.__zone_symbol__requestAnimationFrame) {
win.__zone_symbol__requestAnimationFrame(cb);
} else {
win.requestAnimationFrame(cb);
}
};
}

98
angular/src/module.ts Normal file
View File

@@ -0,0 +1,98 @@
import { CommonModule } from '@angular/common';
import {
APP_INITIALIZER,
CUSTOM_ELEMENTS_SCHEMA,
ModuleWithProviders,
NgModule
} from '@angular/core';
import { BooleanValueAccessor } from './control-value-accessors/boolean-value-accessor';
import { NumericValueAccessor } from './control-value-accessors/numeric-value-accesssor';
import { RadioValueAccessor } from './control-value-accessors/radio-value-accessor';
import { SelectValueAccessor } from './control-value-accessors/select-value-accessor';
import { TextValueAccessor } from './control-value-accessors/text-value-accessor';
/* Navigation */
import { IonNav } from './navigation/ion-nav';
import { IonRouterOutlet } from './navigation/ion-router-outlet';
import { IonTab } from './navigation/ion-tab';
import { IonTabs } from './navigation/ion-tabs';
/* Directives */
import { VirtualScroll } from './directives/virtual-scroll';
import { VirtualItem } from './directives/virtual-item';
import { VirtualHeader } from './directives/virtual-header';
import { VirtualFooter } from './directives/virtual-footer';
/* Providers */
import { ActionSheetController } from './providers/action-sheet-controller';
import { AlertController } from './providers/alert-controller';
import { Events, setupProvideEvents } from './providers/events';
import { LoadingController } from './providers/loading-controller';
import { MenuController } from './providers/menu-controller';
import { ModalController } from './providers/modal-controller';
import { Platform } from './providers/platform';
import { PopoverController } from './providers/popover-controller';
import { ToastController } from './providers/toast-controller';
@NgModule({
declarations: [
BooleanValueAccessor,
IonNav,
IonRouterOutlet,
IonTab,
IonTabs,
NumericValueAccessor,
RadioValueAccessor,
SelectValueAccessor,
TextValueAccessor,
VirtualScroll,
VirtualItem,
VirtualHeader,
VirtualFooter,
],
exports: [
BooleanValueAccessor,
IonNav,
IonRouterOutlet,
IonTab,
IonTabs,
NumericValueAccessor,
RadioValueAccessor,
SelectValueAccessor,
TextValueAccessor,
VirtualScroll,
VirtualItem,
VirtualHeader,
VirtualFooter
],
imports: [
CommonModule,
],
providers: [
ModalController,
PopoverController
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class IonicAngularModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: IonicAngularModule,
providers: [
AlertController,
ActionSheetController,
Events,
LoadingController,
MenuController,
Platform,
ToastController,
{ provide: APP_INITIALIZER, useFactory: setupProvideEvents, multi: true },
]
};
}
}

View File

@@ -0,0 +1,32 @@
import { Component, ViewEncapsulation } from '@angular/core';
import {
NavigationEnd,
NavigationStart,
Router
} from '@angular/router';
@Component({
selector: 'ion-nav',
template: '<ng-content></ng-content>',
styles: [`
ion-nav > :not(.show-page) { display: none; }
`],
encapsulation: ViewEncapsulation.None
})
export class IonNav {
constructor(router: Router) {
console.log('ion-nav');
router.events.subscribe(ev => {
if (ev instanceof NavigationStart) {
console.log('NavigationStart', ev.url);
} else if (ev instanceof NavigationEnd) {
console.log('NavigationEnd', ev.url);
}
});
}
}

View File

@@ -0,0 +1,160 @@
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef } from '@angular/core';
import { ActivatedRoute, ChildrenOutletContexts } from '@angular/router';
import * as ctrl from './router-controller';
import { runTransition } from './router-transition';
@Directive({selector: 'ion-router-outlet', exportAs: 'ionOutlet'})
export class IonRouterOutlet implements OnDestroy, OnInit {
private activated: ComponentRef<any>|null = null;
private _activatedRoute: ActivatedRoute|null = null;
private name: string;
private views: ctrl.RouteView[] = [];
@Output('activate') activateEvents = new EventEmitter<any>();
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
constructor(
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
private resolver: ComponentFactoryResolver, @Attribute('name') name: string,
private changeDetector: ChangeDetectorRef) {
this.name = name || 'primary';
parentContexts.onChildOutletCreated(this.name, this as any);
}
ngOnDestroy(): void {
ctrl.destoryViews(this.views);
this.parentContexts.onChildOutletDestroyed(this.name);
}
ngOnInit(): void {
if (!this.activated) {
// If the outlet was not instantiated at the time the route got activated we need to populate
// the outlet when it is initialized (ie inside a NgIf)
const context = this.parentContexts.getContext(this.name);
if (context && context.route) {
if (context.attachRef) {
// `attachRef` is populated when there is an existing component to mount
this.attach(context.attachRef, context.route);
} else {
// otherwise the component defined in the configuration is created
this.activateWith(context.route, context.resolver || null);
}
}
}
}
get isActivated(): boolean { return !!this.activated; }
get component(): Object {
if (!this.activated) throw new Error('Outlet is not activated');
return this.activated.instance;
}
get activatedRoute(): ActivatedRoute {
if (!this.activated) throw new Error('Outlet is not activated');
return this._activatedRoute as ActivatedRoute;
}
get activatedRouteData() {
if (this._activatedRoute) {
return this._activatedRoute.snapshot.data;
}
return {};
}
/**
* Called when the `RouteReuseStrategy` instructs to detach the subtree
*/
detach(): ComponentRef<any> {
if (!this.activated) throw new Error('Outlet is not activated');
this.location.detach();
const cmp = this.activated;
this.activated = null;
this._activatedRoute = null;
return cmp;
}
/**
* Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
*/
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
this.activated = ref;
this._activatedRoute = activatedRoute;
ctrl.attachView(this.views, this.location, ref, activatedRoute);
}
deactivate(): void {
if (this.activated) {
const c = this.component;
ctrl.deactivateView(this.views, this.activated);
this.activated = null;
this._activatedRoute = null;
this.deactivateEvents.emit(c);
}
}
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
if (this.isActivated) {
throw new Error('Cannot activate an already activated outlet');
}
this._activatedRoute = activatedRoute;
const existingView = ctrl.getExistingView(this.views, activatedRoute);
if (existingView) {
// we've already got a view hanging around
this.activated = existingView.ref;
} else {
// haven't created this view yet
const snapshot = (activatedRoute as any)._futureSnapshot;
const component = <any>snapshot.routeConfig !.component;
resolver = resolver || this.resolver;
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
this.activated = this.location.createComponent(factory, this.location.length, injector);
// keep a ref
ctrl.initRouteViewElm(this.views, this.activated, activatedRoute);
}
// Calling `markForCheck` to make sure we will run the change detection when the
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
this.changeDetector.markForCheck();
const lastDeactivatedRef = ctrl.getLastDeactivatedRef(this.views);
runTransition(this.activated, lastDeactivatedRef).then(() => {
console.log('transition end');
this.activateEvents.emit(this.activated.instance);
});
}
}
class OutletInjector implements Injector {
constructor(
private route: ActivatedRoute, private childContexts: ChildrenOutletContexts,
private parent: Injector) {}
get(token: any, notFoundValue?: any): any {
if (token === ActivatedRoute) {
return this.route;
}
if (token === ChildrenOutletContexts) {
return this.childContexts;
}
return this.parent.get(token, notFoundValue);
}
}

View File

@@ -0,0 +1,17 @@
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
@Directive({
selector: 'ion-tab'
})
export class IonTab implements OnInit {
@Input() tabLink: string;
constructor(private elementRef: ElementRef) {}
ngOnInit() {
console.log('routerLink', this.tabLink, this.elementRef.nativeElement);
}
}

View File

@@ -0,0 +1,25 @@
import { Directive, HostListener } from '@angular/core';
import { Router } from '@angular/router';
@Directive({
selector: 'ion-tabs'
})
export class IonTabs {
constructor(private router: Router) {}
@HostListener('ionTabbarClick', ['$event'])
ionTabbarClick(ev: UIEvent) {
console.log('ionTabbarClick', ev);
const tabElm: HTMLIonTabElement = ev.detail as any;
if (tabElm && tabElm.href) {
console.log('tabElm', tabElm.href);
this.router.navigateByUrl(tabElm.href);
}
}
}

View File

@@ -0,0 +1,81 @@
import { ComponentRef, ViewContainerRef } from '@angular/core';
import { ActivatedRoute, UrlSegment } from '@angular/router';
export function attachView(views: RouteView[], location: ViewContainerRef, ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
initRouteViewElm(views, ref, activatedRoute);
location.insert(ref.hostView);
}
export function initRouteViewElm(views: RouteView[], ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
views.push({
ref: ref,
urlKey: getUrlKey(activatedRoute),
deactivatedId: -1
});
(ref.location.nativeElement as HTMLElement).classList.add('ion-page');
}
export function getExistingView(views: RouteView[], activatedRoute: ActivatedRoute) {
return views.find(vw => {
return isMatchingActivatedRoute(vw.urlKey, activatedRoute);
});
}
function isMatchingActivatedRoute(existingUrlKey: string, activatedRoute: ActivatedRoute) {
const activatedUrlKey = getUrlKey(activatedRoute);
return activatedUrlKey === existingUrlKey;
}
export function getLastDeactivatedRef(views: RouteView[]) {
if (views.length < 2) {
return null;
}
return views.sort((a, b) => {
if (a.deactivatedId > b.deactivatedId) return -1;
if (a.deactivatedId < b.deactivatedId) return 1;
return 0;
})[0].ref;
}
function getUrlKey(activatedRoute: ActivatedRoute) {
const url: UrlSegment[] = (activatedRoute.url as any).value;
return url.map(u => {
return u.path + '$$' + JSON.stringify(u.parameters);
}).join('/');
}
export function deactivateView(views: RouteView[], ref: ComponentRef<any>) {
const view = views.find(vw => vw.ref === ref);
if (view) {
view.deactivatedId = deactivatedIds++;
}
}
export function destoryViews(views: RouteView[]) {
views.forEach(vw => {
vw.ref.destroy();
});
views.length = 0;
}
export interface RouteView {
urlKey: string;
ref: ComponentRef<any>;
deactivatedId: number;
}
let deactivatedIds = 0;

View File

@@ -0,0 +1,34 @@
import { ComponentRef } from '@angular/core';
export function runTransition(enteringRef: ComponentRef<any>, leavingRef: ComponentRef<any>): Promise<void> {
const enteringElm = (enteringRef && enteringRef.location && enteringRef.location.nativeElement);
const leavingElm = (leavingRef && leavingRef.location && leavingRef.location.nativeElement);
if (!enteringElm && !leavingElm) {
return Promise.resolve();
}
return transition(enteringElm, leavingElm);
}
function transition(enteringElm: HTMLElement, leavingElm: HTMLElement): Promise<void> {
console.log('transition start');
return new Promise(resolve => {
setTimeout(() => {
if (enteringElm) {
enteringElm.classList.add('show-page');
}
if (leavingElm) {
leavingElm.classList.remove('show-page');
}
resolve();
}, 750);
});
}

View File

@@ -0,0 +1,104 @@
import { Injectable } from '@angular/core';
import { ActionSheetDismissEvent, ActionSheetOptions } from '@ionic/core';
import { ensureElementInBody, hydrateElement } from '../util/util';
let actionSheetId = 0;
@Injectable()
export class ActionSheetController {
create(opts?: ActionSheetOptions): ActionSheetProxy {
return getActionSheetProxy(opts);
}
}
export function getActionSheetProxy(opts: ActionSheetOptions) {
return {
id: actionSheetId++,
state: PRESENTING,
opts: opts,
present: function() { return present(this); },
dismiss: function() { return dismiss(this); },
onDidDismiss: function(callback: (data: any, role: string) => void) {
(this as ActionSheetProxyInternal).onDidDismissHandler = callback;
},
onWillDismiss: function(callback: (data: any, role: string) => void) {
(this as ActionSheetProxyInternal).onWillDismissHandler = callback;
},
};
}
export function present(actionSheetProxy: ActionSheetProxyInternal): Promise<any> {
actionSheetProxy.state = PRESENTING;
return loadOverlay(actionSheetProxy.opts).then((actionSheetElement: HTMLIonActionSheetElement) => {
actionSheetProxy.element = actionSheetElement;
const onDidDismissHandler = (event: ActionSheetDismissEvent) => {
actionSheetElement.removeEventListener(ION_ACTION_SHEET_DID_DISMISS_EVENT, onDidDismissHandler);
if (actionSheetProxy.onDidDismissHandler) {
actionSheetProxy.onDidDismissHandler(event.detail.data, event.detail.role);
}
};
const onWillDismissHandler = (event: ActionSheetDismissEvent) => {
actionSheetElement.removeEventListener(ION_ACTION_SHEET_WILL_DISMISS_EVENT, onWillDismissHandler);
if (actionSheetProxy.onWillDismissHandler) {
actionSheetProxy.onWillDismissHandler(event.detail.data, event.detail.role);
}
};
actionSheetElement.addEventListener(ION_ACTION_SHEET_DID_DISMISS_EVENT, onDidDismissHandler);
actionSheetElement.addEventListener(ION_ACTION_SHEET_WILL_DISMISS_EVENT, onWillDismissHandler);
if (actionSheetProxy.state === PRESENTING) {
return actionSheetElement.present();
}
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
// attribute before it could async load and present itself.
// with that in mind, just return null to make the TS compiler happy
return null;
});
}
export function dismiss(actionSheetProxy: ActionSheetProxyInternal): Promise<any> {
actionSheetProxy.state = DISMISSING;
if (actionSheetProxy.element) {
if (actionSheetProxy.state === DISMISSING) {
return actionSheetProxy.element.dismiss();
}
}
// either we're not in the dismissing state
// or we're calling this before the element is created
// so just return a resolved promise
return Promise.resolve();
}
export function loadOverlay(opts: ActionSheetOptions): Promise<HTMLIonActionSheetElement> {
const element = ensureElementInBody('ion-action-sheet-controller') as HTMLIonActionSheetControllerElement;
return hydrateElement(element).then(() => {
return element.create(opts);
});
}
export interface ActionSheetProxy {
present(): Promise<void>;
dismiss(): Promise<void>;
onDidDismiss(callback: (data: any, role: string) => void): void;
onWillDismiss(callback: (data: any, role: string) => void): void;
}
export interface ActionSheetProxyInternal extends ActionSheetProxy {
id: number;
opts: ActionSheetOptions;
state: number;
element: HTMLIonActionSheetElement;
onDidDismissHandler?: (data: any, role: string) => void;
onWillDismissHandler?: (data: any, role: string) => void;
}
export const PRESENTING = 1;
export const DISMISSING = 2;
const ION_ACTION_SHEET_DID_DISMISS_EVENT = 'ionActionSheetDidDismiss';
const ION_ACTION_SHEET_WILL_DISMISS_EVENT = 'ionActionSheetWillDismiss';

View File

@@ -0,0 +1,104 @@
import { Injectable } from '@angular/core';
import { AlertDismissEvent, AlertOptions } from '@ionic/core';
import { ensureElementInBody, hydrateElement } from '../util/util';
let alertId = 0;
@Injectable()
export class AlertController {
create(opts?: AlertOptions): AlertProxy {
return getAlertProxy(opts);
}
}
export function getAlertProxy(opts: AlertOptions) {
return {
id: alertId++,
state: PRESENTING,
opts: opts,
present: function() { return present(this); },
dismiss: function() { return dismiss(this); },
onDidDismiss: function(callback: (data: any, role: string) => void) {
(this as AlertProxyInternal).onDidDismissHandler = callback;
},
onWillDismiss: function(callback: (data: any, role: string) => void) {
(this as AlertProxyInternal).onWillDismissHandler = callback;
},
};
}
export function present(alertProxy: AlertProxyInternal): Promise<any> {
alertProxy.state = PRESENTING;
return loadOverlay(alertProxy.opts).then((alertElement: HTMLIonAlertElement) => {
alertProxy.element = alertElement;
const onDidDismissHandler = (event: AlertDismissEvent) => {
alertElement.removeEventListener(ION_ALERT_DID_DISMISS_EVENT, onDidDismissHandler);
if (alertProxy.onDidDismissHandler) {
alertProxy.onDidDismissHandler(event.detail.data, event.detail.role);
}
};
const onWillDismissHandler = (event: AlertDismissEvent) => {
alertElement.removeEventListener(ION_ALERT_WILL_DISMISS_EVENT, onWillDismissHandler);
if (alertProxy.onWillDismissHandler) {
alertProxy.onWillDismissHandler(event.detail.data, event.detail.role);
}
};
alertElement.addEventListener(ION_ALERT_DID_DISMISS_EVENT, onDidDismissHandler);
alertElement.addEventListener(ION_ALERT_WILL_DISMISS_EVENT, onWillDismissHandler);
if (alertProxy.state === PRESENTING) {
return alertElement.present();
}
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
// attribute before it could async load and present itself.
// with that in mind, just return null to make the TS compiler happy
return null;
});
}
export function dismiss(alertProxy: AlertProxyInternal): Promise<any> {
alertProxy.state = DISMISSING;
if (alertProxy.element) {
if (alertProxy.state === DISMISSING) {
return alertProxy.element.dismiss();
}
}
// either we're not in the dismissing state
// or we're calling this before the element is created
// so just return a resolved promise
return Promise.resolve();
}
export function loadOverlay(opts: AlertOptions): Promise<HTMLIonAlertElement> {
const element = ensureElementInBody('ion-alert-controller') as HTMLIonAlertControllerElement;
return hydrateElement(element).then(() => {
return element.create(opts);
});
}
export interface AlertProxy {
present(): Promise<void>;
dismiss(): Promise<void>;
onDidDismiss(callback: (data: any, role: string) => void): void;
onWillDismiss(callback: (data: any, role: string) => void): void;
}
export interface AlertProxyInternal extends AlertProxy {
id: number;
opts: AlertOptions;
state: number;
element: HTMLIonAlertElement;
onDidDismissHandler?: (data: any, role: string) => void;
onWillDismissHandler?: (data: any, role: string) => void;
}
export const PRESENTING = 1;
export const DISMISSING = 2;
const ION_ALERT_DID_DISMISS_EVENT = 'ionAlertDidDismiss';
const ION_ALERT_WILL_DISMISS_EVENT = 'ionAlertWillDismiss';

View File

@@ -0,0 +1,100 @@
import { Injectable } from '@angular/core';
@Injectable()
export class Events {
private c: {[topic: string]: Function[]} = [] as any;
/**
* Subscribe to an event topic. Events that get posted to that topic will trigger the provided handler.
*
* @param {string} topic the topic to subscribe to
* @param {function} handler the event handler
*/
subscribe(topic: string, ...handlers: Function[]) {
if (!this.c[topic]) {
this.c[topic] = [];
}
handlers.forEach((handler) => {
this.c[topic].push(handler);
});
}
/**
* Unsubscribe from the given topic. Your handler will no longer receive events published to this topic.
*
* @param {string} topic the topic to unsubscribe from
* @param {function} handler the event handler
*
* @return true if a handler was removed
*/
unsubscribe(topic: string, handler: Function = null) {
const t = this.c[topic];
if (!t) {
// Wasn't found, wasn't removed
return false;
}
if (!handler) {
// Remove all handlers for this topic
delete this.c[topic];
return true;
}
// We need to find and remove a specific handler
const i = t.indexOf(handler);
if (i < 0) {
// Wasn't found, wasn't removed
return false;
}
t.splice(i, 1);
// If the channel is empty now, remove it from the channel map
if (!t.length) {
delete this.c[topic];
}
return true;
}
/**
* Publish an event to the given topic.
*
* @param {string} topic the topic to publish to
* @param {any} eventData the data to send as the event
*/
publish(topic: string, ...args: any[]) {
const t = this.c[topic];
if (!t) {
return null;
}
const responses: any[] = [];
t.forEach((handler: any) => {
responses.push(handler(...args));
});
return responses;
}
}
export function setupEvents() {
const events = new Events();
window.addEventListener('online', ev => events.publish('app:online', ev));
window.addEventListener('offline', ev => events.publish('app:offline', ev));
window.addEventListener('orientationchange', ev => events.publish('app:rotated', ev));
return events;
}
export function setupProvideEvents() {
return function() {
return setupEvents();
};
}

View File

@@ -0,0 +1,104 @@
import { Injectable } from '@angular/core';
import { LoadingDismissEvent, LoadingOptions } from '@ionic/core';
import { ensureElementInBody, hydrateElement } from '../util/util';
let loadingId = 0;
@Injectable()
export class LoadingController {
create(opts?: LoadingOptions): LoadingProxy {
return getLoadingProxy(opts);
}
}
export function getLoadingProxy(opts: LoadingOptions) {
return {
id: loadingId++,
state: PRESENTING,
opts: opts,
present: function() { return present(this); },
dismiss: function() { return dismiss(this); },
onDidDismiss: function(callback: (data: any, role: string) => void) {
(this as LoadingProxyInternal).onDidDismissHandler = callback;
},
onWillDismiss: function(callback: (data: any, role: string) => void) {
(this as LoadingProxyInternal).onWillDismissHandler = callback;
},
};
}
export function present(loadingProxy: LoadingProxyInternal): Promise<any> {
loadingProxy.state = PRESENTING;
return loadOverlay(loadingProxy.opts).then((loadingElement: HTMLIonLoadingElement) => {
loadingProxy.element = loadingElement;
const onDidDismissHandler = (event: LoadingDismissEvent) => {
loadingElement.removeEventListener(ION_LOADING_DID_DISMISS_EVENT, onDidDismissHandler);
if (loadingProxy.onDidDismissHandler) {
loadingProxy.onDidDismissHandler(event.detail.data, event.detail.role);
}
};
const onWillDismissHandler = (event: LoadingDismissEvent) => {
loadingElement.removeEventListener(ION_LOADING_WILL_DISMISS_EVENT, onWillDismissHandler);
if (loadingProxy.onWillDismissHandler) {
loadingProxy.onWillDismissHandler(event.detail.data, event.detail.role);
}
};
loadingElement.addEventListener(ION_LOADING_DID_DISMISS_EVENT, onDidDismissHandler);
loadingElement.addEventListener(ION_LOADING_WILL_DISMISS_EVENT, onWillDismissHandler);
if (loadingProxy.state === PRESENTING) {
return loadingElement.present();
}
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
// attribute before it could async load and present itself.
// with that in mind, just return null to make the TS compiler happy
return null;
});
}
export function dismiss(loadingProxy: LoadingProxyInternal): Promise<any> {
loadingProxy.state = DISMISSING;
if (loadingProxy.element) {
if (loadingProxy.state === DISMISSING) {
return loadingProxy.element.dismiss();
}
}
// either we're not in the dismissing state
// or we're calling this before the element is created
// so just return a resolved promise
return Promise.resolve();
}
export function loadOverlay(opts: LoadingOptions): Promise<HTMLIonLoadingElement> {
const element = ensureElementInBody('ion-loading-controller') as HTMLIonLoadingControllerElement;
return hydrateElement(element).then(() => {
return element.create(opts);
});
}
export interface LoadingProxy {
present(): Promise<void>;
dismiss(): Promise<void>;
onDidDismiss(callback: (data: any, role: string) => void): void;
onWillDismiss(callback: (data: any, role: string) => void): void;
}
export interface LoadingProxyInternal extends LoadingProxy {
id: number;
opts: LoadingOptions;
state: number;
element: HTMLIonLoadingElement;
onDidDismissHandler?: (data: any, role: string) => void;
onWillDismissHandler?: (data: any, role: string) => void;
}
export const PRESENTING = 1;
export const DISMISSING = 2;
const ION_LOADING_DID_DISMISS_EVENT = 'ionLoadingDidDismiss';
const ION_LOADING_WILL_DISMISS_EVENT = 'ionLoadingWillDismiss';

View File

@@ -0,0 +1,127 @@
import { ensureElementInBody } from '../util/util';
let element: HTMLIonMenuControllerElement;
export class MenuController {
constructor() {
element = ensureElementInBody('ion-menu-controller') as HTMLIonMenuControllerElement;
}
close(menuId?: string) {
return element.componentOnReady().then(() => {
return element.close(menuId);
});
}
// maintain legacy sync api
enable(enabled: boolean, menuId?: string) {
if (element && element.enable) {
return element.enable(enabled, menuId);
}
// IDK, this is not a good place to be in
return null;
}
enableAsync(menuId?: string): Promise<HTMLIonMenuElement> {
return element.componentOnReady().then(() => {
return element.enable(true, menuId);
});
}
get(menuId?: string) {
if (element && element.get) {
return element.get(menuId);
}
// IDK, this is not a good place to be in
return null;
}
getAsync(menuId?: string): Promise<HTMLIonMenuElement> {
return element.componentOnReady().then(() => {
return element.get(menuId);
});
}
getMenus() {
if (element && element.getMenus) {
return element.getMenus();
}
// IDK, this is not a good place to be in
return [];
}
getMenusAsync(): Promise<HTMLIonMenuElement[]> {
return element.componentOnReady().then(() => {
return element.getMenus();
});
}
getOpen() {
if (element && element.getOpen) {
return element.getOpen();
}
// IDK, this is not a good place to be in
return null;
}
getOpenAsync(): Promise<HTMLIonMenuElement> {
return element.componentOnReady().then(() => {
return element.getOpen();
});
}
isEnabled(menuId?: string) {
if (element && element.isEnabled) {
return element.isEnabled(menuId);
}
// IDK, this is not a good place to be in
return false;
}
isEnabledAsync(menuId?: string): Promise<boolean> {
return element.componentOnReady().then(() => {
return element.isEnabled(menuId);
});
}
isOpen(menuId?: string) {
if (element && element.isOpen) {
return element.isOpen(menuId);
}
// IDK, this is not a good place to be in
return false;
}
isOpenAsync(menuId?: string): Promise<boolean> {
return element.componentOnReady().then(() => {
return element.isOpen(menuId);
});
}
open(menuId?: string): Promise<boolean> {
return element.componentOnReady().then(() => {
return element.open(menuId);
});
}
swipeEnable(shouldEnable: boolean, menuId?: string) {
if (element && element.swipeEnable) {
return element.swipeEnable(shouldEnable, menuId);
}
// IDK, this is not a good place to be in
return null;
}
swipeEnableAsync(shouldEnable: boolean, menuId?: string): Promise<HTMLIonMenuElement> {
return element.componentOnReady().then(() => {
return element.swipeEnable(shouldEnable, menuId);
});
}
toggle(menuId?: string): Promise<boolean> {
return element.componentOnReady().then(() => {
return element.toggle(menuId);
});
}
}

View File

@@ -0,0 +1,121 @@
import {
Injectable,
} from '@angular/core';
import {
ModalDismissEvent,
ModalOptions
} from '@ionic/core';
import { ensureElementInBody, hydrateElement } from '../util/util';
let modalId = 0;
@Injectable()
export class ModalController {
create(opts?: ModalOptions): ModalProxy {
return getModalProxy(opts);
}
dismiss(data?: any, role?: string, id?: number) {
const modalController = document.querySelector('ion-modal-controller');
return modalController.componentOnReady().then(() => {
return modalController.dismiss(data, role, id);
});
}
}
export function getModalProxy(opts: ModalOptions) {
return {
id: modalId++,
state: PRESENTING,
opts: opts,
present: function() { return present(this); },
dismiss: function() { return dismiss(this); },
onDidDismiss: function(callback: (data: any, role: string) => void) {
(this as ModalProxyInternal).onDidDismissHandler = callback;
},
onWillDismiss: function(callback: (data: any, role: string) => void) {
(this as ModalProxyInternal).onWillDismissHandler = callback;
},
};
}
export function present(modalProxy: ModalProxyInternal): Promise<any> {
modalProxy.state = PRESENTING;
return loadOverlay(modalProxy.opts).then((modalElement: HTMLIonModalElement) => {
Object.assign(modalElement, modalProxy.opts);
modalProxy.element = modalElement;
const onDidDismissHandler = (event: ModalDismissEvent) => {
modalElement.removeEventListener(ION_MODAL_DID_DISMISS_EVENT, onDidDismissHandler);
if (modalProxy.onDidDismissHandler) {
modalProxy.onDidDismissHandler(event.detail.data, event.detail.role);
}
};
const onWillDismissHandler = (event: ModalDismissEvent) => {
modalElement.removeEventListener(ION_MODAL_WILL_DISMISS_EVENT, onWillDismissHandler);
if (modalProxy.onWillDismissHandler) {
modalProxy.onWillDismissHandler(event.detail.data, event.detail.role);
}
};
modalElement.addEventListener(ION_MODAL_DID_DISMISS_EVENT, onDidDismissHandler);
modalElement.addEventListener(ION_MODAL_WILL_DISMISS_EVENT, onWillDismissHandler);
if (modalProxy.state === PRESENTING) {
return modalElement.present();
}
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
// attribute before it could async load and present itself.
// with that in mind, just return null to make the TS compiler happy
return null;
});
}
export function dismiss(modalProxy: ModalProxyInternal): Promise<any> {
modalProxy.state = DISMISSING;
if (modalProxy.element) {
if (modalProxy.state === DISMISSING) {
return modalProxy.element.dismiss();
}
}
// either we're not in the dismissing state
// or we're calling this before the element is created
// so just return a resolved promise
return Promise.resolve();
}
export function loadOverlay(opts: ModalOptions): Promise<HTMLIonModalElement> {
const element = ensureElementInBody('ion-modal-controller') as HTMLIonModalControllerElement;
return hydrateElement(element).then(() => {
return element.create(opts);
});
}
export interface ModalProxy {
present(): Promise<void>;
dismiss(): Promise<void>;
onDidDismiss(callback: (data: any, role: string) => void): void;
onWillDismiss(callback: (data: any, role: string) => void): void;
}
export interface ModalProxyInternal extends ModalProxy {
id: number;
opts: ModalOptions;
state: number;
element: HTMLIonModalElement;
onDidDismissHandler?: (data: any, role: string) => void;
onWillDismissHandler?: (data: any, role: string) => void;
}
export const PRESENTING = 1;
export const DISMISSING = 2;
const ION_MODAL_DID_DISMISS_EVENT = 'ionModalDidDismiss';
const ION_MODAL_WILL_DISMISS_EVENT = 'ionModalWillDismiss';

View File

@@ -0,0 +1,228 @@
import { PlatformConfig } from '@ionic/core';
export type DocumentDirection = 'ltr' | 'rtl';
let dir: DocumentDirection = 'ltr';
let isRtl = false;
let lang = '';
export class Platform {
_element: HTMLIonPlatformElement;
constructor() {
initialize(this);
}
is(platformName: string): boolean {
return isImpl(this, platformName);
}
isAsync(platformName: string): Promise<boolean> {
return isAsyncImpl(this, platformName);
}
platforms(): string[] {
return platformsImpl(this);
}
platformsAsync(): Promise<string[]> {
return platformsAsyncImpl(this);
}
versions(): PlatformConfig[] {
return versionsImpl(this);
}
versionsAsync(): Promise<PlatformConfig[]> {
return versionsAsyncImpl(this);
}
ready(): Promise<any> {
return readyImpl(this);
}
get isRTL(): boolean {
return isRtl;
}
setDir(_dir: DocumentDirection, updateDocument: boolean) {
dir = _dir;
isRtl = dir === 'rtl';
if (updateDocument !== false) {
document.documentElement.setAttribute('dir', dir);
}
}
/**
* Returns app's language direction.
* We recommend the app's `index.html` file already has the correct `dir`
* attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
* @returns {DocumentDirection}
*/
dir(): DocumentDirection {
return dir;
}
/**
* Set the app's language and optionally the country code, which will update
* the `lang` attribute on the app's root `<html>` element.
* We recommend the app's `index.html` file already has the correct `lang`
* attribute value set, such as `<html lang="en">`. This method is useful if
* the language needs to be dynamically changed per user/session.
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
* @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
* @param {boolean} updateDocument Specifies whether the `lang` attribute of `<html>` should be updated
*/
setLang(language: string, updateDocument: boolean) {
lang = language;
if (updateDocument !== false) {
document.documentElement.setAttribute('lang', language);
}
}
/**
* Returns app's language and optional country code.
* We recommend the app's `index.html` file already has the correct `lang`
* attribute value set, such as `<html lang="en">`.
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
* @returns {string}
*/
lang(): string {
return lang;
}
/**
* Get the query string parameter
*/
getQueryParam(key: string): string {
return getQueryParamImpl(this, key);
}
/**
* Get the query string parameter
*/
getQueryParamAsync(key: string): Promise<string> {
return getQueryParamAsyncImpl(this, key);
}
height(): number {
return window.innerHeight;
}
isLandscape(): boolean {
return !this.isPortrait();
}
isPortrait(): boolean {
return window.matchMedia('(orientation: portrait)').matches;
}
testUserAgent(expression: string): boolean {
return navigator.userAgent.indexOf(expression) >= 0;
}
url() {
return window.location.href;
}
width() {
return window.innerWidth;
}
}
export function isImpl(platform: Platform, platformName: string) {
if (platform._element && platform._element.is) {
return platform._element.is(platformName);
}
return false;
}
export function isAsyncImpl(platform: Platform, platformName: string) {
return getHydratedPlatform(platform).then(() => {
return platform._element.is(platformName);
});
}
export function platformsImpl(platform: Platform): string[] {
if (platform._element && platform._element.platforms) {
return platform._element.platforms();
}
return [];
}
export function platformsAsyncImpl(platform: Platform): Promise<string[]> {
return getHydratedPlatform(platform).then(() => {
return platform._element.platforms();
});
}
export function versionsImpl(platform: Platform): PlatformConfig[] {
if (platform._element && platform._element.versions) {
return platform._element.versions();
}
return [];
}
export function versionsAsyncImpl(platform: Platform): Promise<PlatformConfig[]> {
return getHydratedPlatform(platform).then(() => {
return platform._element.versions();
});
}
export function readyImpl(platform: Platform) {
return getHydratedPlatform(platform).then(() => {
return platform._element.ready();
});
}
export function getQueryParamImpl(platform: Platform, key: string): string {
if (platform._element && platform._element.getQueryParam) {
return platform._element.getQueryParam(key);
}
return null;
}
export function getQueryParamAsyncImpl(platform: Platform, key: string) {
return getHydratedPlatform(platform).then(() => {
return platform._element.getQueryParam(key);
});
}
export function initialize(platform: Platform) {
// first see if there is an ion-app, if there is, platform will eventually show up
// if not, add platform to the document.body
const ionApp = document.querySelector('ion-app');
if (ionApp) {
return ionApp.componentOnReady(() => {
platform._element = ionApp.querySelector('ion-platform');
});
}
// okay, there isn't an ion-app, so add <ion-platform> to the document.body
let platformElement = document.querySelector('ion-platform');
if (!platformElement) {
platformElement = document.createElement('ion-platform');
document.body.appendChild(platformElement);
}
platform._element = platformElement;
}
export function getHydratedPlatform(platform: Platform): Promise<HTMLIonPlatformElement> {
if (!platform._element) {
const ionApp = document.querySelector('ion-app');
return (ionApp as any).componentOnReady(() => {
const platformEl = ionApp.querySelector('ion-platform');
return platformEl.componentOnReady().then(() => {
platform._element = platformEl;
return platformEl;
});
});
}
return platform._element.componentOnReady();
}

View File

@@ -0,0 +1,120 @@
import {
Injectable,
} from '@angular/core';
import {
PopoverDismissEvent,
PopoverOptions
} from '@ionic/core';
import { ensureElementInBody, hydrateElement } from '../util/util';
let popoverId = 0;
@Injectable()
export class PopoverController {
create(opts?: PopoverOptions): PopoverProxy {
return getPopoverProxy(opts);
}
dismiss(data?: any, role?: string, id?: number) {
const popoverController = document.querySelector('ion-popover-controller');
return popoverController.componentOnReady().then(() => {
return popoverController.dismiss(data, role, id);
});
}
}
export function getPopoverProxy(opts: PopoverOptions) {
return {
id: popoverId++,
state: PRESENTING,
opts: opts,
present: function() { return present(this); },
dismiss: function() { return dismiss(this); },
onDidDismiss: function(callback: (data: any, role: string) => void) {
(this as PopoverProxyInternal).onDidDismissHandler = callback;
},
onWillDismiss: function(callback: (data: any, role: string) => void) {
(this as PopoverProxyInternal).onWillDismissHandler = callback;
},
};
}
export function present(popoverProxy: PopoverProxyInternal): Promise<any> {
popoverProxy.state = PRESENTING;
return loadOverlay(popoverProxy.opts).then((popoverElement: HTMLIonPopoverElement) => {
Object.assign(popoverElement, popoverProxy.opts);
popoverProxy.element = popoverElement;
const onDidDismissHandler = (event: PopoverDismissEvent) => {
popoverElement.removeEventListener(ION_POPOVER_DID_DISMISS_EVENT, onDidDismissHandler);
if (popoverProxy.onDidDismissHandler) {
popoverProxy.onDidDismissHandler(event.detail.data, event.detail.role);
}
};
const onWillDismissHandler = (event: PopoverDismissEvent) => {
popoverElement.removeEventListener(ION_POPOVER_WILL_DISMISS_EVENT, onWillDismissHandler);
if (popoverProxy.onWillDismissHandler) {
popoverProxy.onWillDismissHandler(event.detail.data, event.detail.role);
}
};
popoverElement.addEventListener(ION_POPOVER_DID_DISMISS_EVENT, onDidDismissHandler);
popoverElement.addEventListener(ION_POPOVER_WILL_DISMISS_EVENT, onWillDismissHandler);
if (popoverProxy.state === PRESENTING) {
return popoverElement.present();
}
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
// attribute before it could async load and present itself.
// with that in mind, just return null to make the TS compiler happy
return null;
});
}
export function dismiss(popoverProxy: PopoverProxyInternal): Promise<any> {
popoverProxy.state = DISMISSING;
if (popoverProxy.element) {
if (popoverProxy.state === DISMISSING) {
return popoverProxy.element.dismiss();
}
}
// either we're not in the dismissing state
// or we're calling this before the element is created
// so just return a resolved promise
return Promise.resolve();
}
export function loadOverlay(opts: PopoverOptions): Promise<HTMLIonPopoverElement> {
const element = ensureElementInBody('ion-popover-controller') as HTMLIonPopoverControllerElement;
return hydrateElement(element).then(() => {
return element.create(opts);
});
}
export interface PopoverProxy {
present(): Promise<void>;
dismiss(): Promise<void>;
onDidDismiss(callback: (data: any, role: string) => void): void;
onWillDismiss(callback: (data: any, role: string) => void): void;
}
export interface PopoverProxyInternal extends PopoverProxy {
id: number;
opts: PopoverOptions;
state: number;
element: HTMLIonPopoverElement;
onDidDismissHandler?: (data: any, role: string) => void;
onWillDismissHandler?: (data: any, role: string) => void;
}
export const PRESENTING = 1;
export const DISMISSING = 2;
const ION_POPOVER_DID_DISMISS_EVENT = 'ionPopoverDidDismiss';
const ION_POPOVER_WILL_DISMISS_EVENT = 'ionPopoverWillDismiss';

View File

@@ -0,0 +1,104 @@
import { Injectable } from '@angular/core';
import { ToastDismissEvent, ToastOptions } from '@ionic/core';
import { ensureElementInBody, hydrateElement } from '../util/util';
let toastId = 0;
@Injectable()
export class ToastController {
create(opts?: ToastOptions): ToastProxy {
return getToastProxy(opts);
}
}
export function getToastProxy(opts: ToastOptions) {
return {
id: toastId++,
state: PRESENTING,
opts: opts,
present: function() { return present(this); },
dismiss: function() { return dismiss(this); },
onDidDismiss: function(callback: (data: any, role: string) => void) {
(this as ToastProxyInternal).onDidDismissHandler = callback;
},
onWillDismiss: function(callback: (data: any, role: string) => void) {
(this as ToastProxyInternal).onWillDismissHandler = callback;
},
};
}
export function present(toastProxy: ToastProxyInternal): Promise<any> {
toastProxy.state = PRESENTING;
return loadOverlay(toastProxy.opts).then((toastElement: HTMLIonToastElement) => {
toastProxy.element = toastElement;
const onDidDismissHandler = (event: ToastDismissEvent) => {
toastElement.removeEventListener(ION_TOAST_DID_DISMISS_EVENT, onDidDismissHandler);
if (toastProxy.onDidDismissHandler) {
toastProxy.onDidDismissHandler(event.detail.data, event.detail.role);
}
};
const onWillDismissHandler = (event: ToastDismissEvent) => {
toastElement.removeEventListener(ION_TOAST_WILL_DISMISS_EVENT, onWillDismissHandler);
if (toastProxy.onWillDismissHandler) {
toastProxy.onWillDismissHandler(event.detail.data, event.detail.role);
}
};
toastElement.addEventListener(ION_TOAST_DID_DISMISS_EVENT, onDidDismissHandler);
toastElement.addEventListener(ION_TOAST_WILL_DISMISS_EVENT, onWillDismissHandler);
if (toastProxy.state === PRESENTING) {
return toastElement.present();
}
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
// attribute before it could async load and present itself.
// with that in mind, just return null to make the TS compiler happy
return null;
});
}
export function dismiss(toastProxy: ToastProxyInternal): Promise<any> {
toastProxy.state = DISMISSING;
if (toastProxy.element) {
if (toastProxy.state === DISMISSING) {
return toastProxy.element.dismiss();
}
}
// either we're not in the dismissing state
// or we're calling this before the element is created
// so just return a resolved promise
return Promise.resolve();
}
export function loadOverlay(opts: ToastOptions): Promise<HTMLIonToastElement> {
const element = ensureElementInBody('ion-toast-controller') as HTMLIonToastControllerElement;
return hydrateElement(element).then(() => {
return element.create(opts);
});
}
export interface ToastProxy {
present(): Promise<void>;
dismiss(): Promise<void>;
onDidDismiss(callback: (data: any, role: string) => void): void;
onWillDismiss(callback: (data: any, role: string) => void): void;
}
export interface ToastProxyInternal extends ToastProxy {
id: number;
opts: ToastOptions;
state: number;
element: HTMLIonToastElement;
onDidDismissHandler?: (data: any, role: string) => void;
onWillDismissHandler?: (data: any, role: string) => void;
}
export const PRESENTING = 1;
export const DISMISSING = 2;
const ION_TOAST_DID_DISMISS_EVENT = 'ionToastDidDismiss';
const ION_TOAST_WILL_DISMISS_EVENT = 'ionToastWillDismiss';

View File

@@ -0,0 +1,12 @@
export interface IonicGlobal {
config: any;
ael: (elm: any, eventName: string, cb: Function, opts: any) => void;
raf: Function;
rel: (elm: any, eventName: string, cb: Function, opts: any) => void;
}
export interface IonicWindow extends Window {
Ionic: IonicGlobal;
__zone_symbol__requestAnimationFrame: Function;
}

32
angular/src/util/util.ts Normal file
View File

@@ -0,0 +1,32 @@
export function hydrateElement(element: any) {
return element.componentOnReady();
}
export function getElement(elementName: string) {
return document.querySelector(elementName);
}
export function ensureElementInBody(elementName: string) {
let element = getElement(elementName);
if (!element) {
element = document.createElement(elementName);
document.body.appendChild(element);
}
return element;
}
export function removeAllNodeChildren(element: HTMLElement) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
export function isString(something: any) {
return typeof something === 'string' ? true : false;
}
export function getIonApp(): Promise<HTMLIonAppElement> {
const element = ensureElementInBody('ion-app') as HTMLIonAppElement;
return element.componentOnReady();
}

29
angular/tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"alwaysStrict": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"dom",
"es2017"
],
"module": "es2015",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist",
"pretty": true,
"removeComments": false,
"rootDir": "src",
"sourceMap": true,
"target": "es2015"
},
"files": [
"src/index.ts"
]
}

6
angular/tslint.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": "tslint-ionic-rules",
"rules": {
"no-non-null-assertion": false
}
}