mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
chore(packages): move the packages to root
This commit is contained in:
19
angular/README.md
Normal file
19
angular/README.md
Normal 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
4013
angular/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
64
angular/package.json
Normal file
64
angular/package.json
Normal 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
16
angular/scripts/README.md
Normal 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
54
angular/scripts/clean-generated.js
vendored
Normal 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
12
angular/scripts/clean.js
vendored
Normal 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
396
angular/scripts/deploy.js
vendored
Normal 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
31
angular/scripts/link-copy.js
vendored
Normal 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
19
angular/scripts/set-version.js
vendored
Normal 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');
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
70
angular/src/control-value-accessors/radio-value-accessor.ts
Normal file
70
angular/src/control-value-accessors/radio-value-accessor.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
64
angular/src/control-value-accessors/select-value-accessor.ts
Normal file
64
angular/src/control-value-accessors/select-value-accessor.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
62
angular/src/control-value-accessors/text-value-accessor.ts
Normal file
62
angular/src/control-value-accessors/text-value-accessor.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'); }
|
||||
});
|
||||
}
|
||||
10
angular/src/directives/virtual-footer.ts
Normal file
10
angular/src/directives/virtual-footer.ts
Normal 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>) {}
|
||||
}
|
||||
10
angular/src/directives/virtual-header.ts
Normal file
10
angular/src/directives/virtual-header.ts
Normal 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>) {}
|
||||
}
|
||||
10
angular/src/directives/virtual-item.ts
Normal file
10
angular/src/directives/virtual-item.ts
Normal 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) {}
|
||||
}
|
||||
59
angular/src/directives/virtual-scroll.ts
Normal file
59
angular/src/directives/virtual-scroll.ts
Normal 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;
|
||||
}
|
||||
14
angular/src/directives/virtual-utils.ts
Normal file
14
angular/src/directives/virtual-utils.ts
Normal 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
58
angular/src/index.ts
Normal 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
98
angular/src/module.ts
Normal 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 },
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
32
angular/src/navigation/ion-nav.ts
Normal file
32
angular/src/navigation/ion-nav.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
160
angular/src/navigation/ion-router-outlet.ts
Normal file
160
angular/src/navigation/ion-router-outlet.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
17
angular/src/navigation/ion-tab.ts
Normal file
17
angular/src/navigation/ion-tab.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
25
angular/src/navigation/ion-tabs.ts
Normal file
25
angular/src/navigation/ion-tabs.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
81
angular/src/navigation/router-controller.ts
Normal file
81
angular/src/navigation/router-controller.ts
Normal 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;
|
||||
34
angular/src/navigation/router-transition.ts
Normal file
34
angular/src/navigation/router-transition.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
104
angular/src/providers/action-sheet-controller.ts
Normal file
104
angular/src/providers/action-sheet-controller.ts
Normal 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';
|
||||
104
angular/src/providers/alert-controller.ts
Normal file
104
angular/src/providers/alert-controller.ts
Normal 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';
|
||||
100
angular/src/providers/events.ts
Normal file
100
angular/src/providers/events.ts
Normal 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();
|
||||
};
|
||||
}
|
||||
104
angular/src/providers/loading-controller.ts
Normal file
104
angular/src/providers/loading-controller.ts
Normal 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';
|
||||
127
angular/src/providers/menu-controller.ts
Normal file
127
angular/src/providers/menu-controller.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
121
angular/src/providers/modal-controller.ts
Normal file
121
angular/src/providers/modal-controller.ts
Normal 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';
|
||||
|
||||
228
angular/src/providers/platform.ts
Normal file
228
angular/src/providers/platform.ts
Normal 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();
|
||||
}
|
||||
120
angular/src/providers/popover-controller.ts
Normal file
120
angular/src/providers/popover-controller.ts
Normal 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';
|
||||
|
||||
104
angular/src/providers/toast-controller.ts
Normal file
104
angular/src/providers/toast-controller.ts
Normal 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';
|
||||
12
angular/src/types/interfaces.ts
Normal file
12
angular/src/types/interfaces.ts
Normal 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
32
angular/src/util/util.ts
Normal 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
29
angular/tsconfig.json
Normal 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
6
angular/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "tslint-ionic-rules",
|
||||
"rules": {
|
||||
"no-non-null-assertion": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user