diff --git a/.erector b/.erector
new file mode 100644
index 0000000..8ec321f
--- /dev/null
+++ b/.erector
@@ -0,0 +1 @@
+[{"name":"name","answer":"ng2-fittext"},{"name":"packageName","answer":"ng2-fittext"},{"name":"readmeTitle","answer":"Ng2 Fittext"},{"name":"repoUrl","answer":"https://github.com/lokenxo/ng2-fittext"},{"name":"git","answer":""},{"name":"moduleName","answer":"Ng2FittextModule"},{"name":"version","answer":"1.0.25"}]
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 7c0acad..5b80600 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,6 @@
-# Node generated files
-node_modules
-npm-debug.log
-# OS generated files
-Thumbs.db
-.DS_Store
-# Ignored files
-*.js
-*.map
-*.d.ts
-.idea
\ No newline at end of file
+build
+coverage
+dist
+debug.log
+node_modules
+out-tsc
diff --git a/.idea/fittext2.iml b/.idea/fittext2.iml
new file mode 100644
index 0000000..24643cc
--- /dev/null
+++ b/.idea/fittext2.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..1beadde
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..e9cda36
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..99d2330
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ DEFINITION_ORDER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1510652992413
+
+
+ 1510652992413
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index 087aff4..700a198 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,7 +1,24 @@
-# Node generated files
+*.spec.ts
+*.tgz
+.erector
+.gitignore
+.npmignore
+.vscode
+build
+coverage
+debug.log
+DEVELOPMENT.md
+dist
+index.ts
+karma.conf.js
node_modules
-npm-debug.log
-# OS generated files
-Thumbs.db
-.DS_Store
-# Ignored files
\ No newline at end of file
+out-tsc
+src
+tasks
+test.ts
+tsconfig.*json
+tslint.json
+typings
+typings.json
+vendor.ts
+webpack
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
new file mode 100644
index 0000000..404554b
--- /dev/null
+++ b/DEVELOPMENT.md
@@ -0,0 +1,120 @@
+# Developing Ng2 Fittext
+
+## Tasks
+
+The following commands are available through `npm run` (or, if configured
+`ngl`):
+
+Command | Purpose
+--- | ---
+build | Runs code through build process via Angular compiler (ngc)
+g | Generate code files (see above)
+lint | Verify code matches linting rules
+start | Run Webpack's dev-server on project (can be run as `npm start`)
+[test](#unit) | Execute unit tests (can be run as `npm test `)
+tagVersion | Creates tag for new version and publishes
+
+## Adding External Scripts
+
+To add an external script, add it with a `script-loader!` prefix to the
+`scripts` array of `entry` in `webpack/webpack.dev.js` (for the dev server)
+and add it to the files array of `karma.conf.js` (for testing).
+
+An example, adding the file at `node_modules/ext-dep/dist/dep.min.js`:
+
+```javascript
+/** webpack.dev.js **/
+module.exports = {
+ // other config
+ entry: {
+ app: [ path.resolve(rootDir, 'examples', 'example.main') ],
+ scripts: [
+ // this is the external script line
+ 'script-loader!' + path.resolve(rootDir, 'node_modules/ext-dep/dep.min')
+ ],
+ vendor: [ path.resolve(rootDir, 'src', 'vendor')],
+ styles: [ path.resolve(rootDir, 'examples', 'styles.scss') ]
+ },
+ // rest of config
+};
+
+/** karma.conf.js **/
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine'],
+ files: [
+ // this is the external script line
+ 'node_modules/hammerjs/hammer.min.js',
+ { pattern: './src/test.js', watched: false }
+ ],
+ // rest of config
+ });
+};
+```
+
+## Unit Testing
+
+Unit testing is done using Karma and Webpack. The setup is all done during the `initialize` command.
+The provided testing commands will watch your files for changes.
+
+The two following command is provided by default:
+
+```shell
+npm test
+```
+
+This command calls the script at `tasks/test.js` and runs the Karma test runner to execute the tests.
+Prior to running Karma, the `test` command looks for a command line argument, if the argument is known,
+it will run the associated configuration, otherwise it will run the default configuration.
+
+#### Configurations
+
+Type | Testing TypeScript
+--- | ---
+default | Run through PhantomJS one time with no file watching
+all | Run through Chrome & PhantomJS with files being watched & tests automatically re-run
+headless| Run through PhantomJS with files being watched & tests automatically re-run
+watch | Run through Chrome with files being watched & tests automatically re-run
+
+Note that Chrome still requires a manual refresh on the Debug tab to see updated test results.
+
+## Packaging
+
+Packaging is as simple as publishing to NPM by doing
+
+```shell
+npm run tagVersion
+```
+
+To test your packages output before publishing, you can run
+
+```shell
+npm pack
+```
+
+Which will generate a compressed file containing your library as it will look when packaged up and
+published to NPM. The basic structure of a published library is:
+
+```
+|__bundles/
+ |__ng2-fittext.umd.js
+ |__ng2-fittext.umd.js.map
+ |__ng2-fittext.umd.min.js
+ |__ng2-fittext.bundle.min.js.map
+|__index.d.ts
+|__package.json
+|__README.md
+|__*.d.ts
+|__ng2-fittext.d.ts
+|__ng2-fittext.module.d.ts
+|__ng2-fittext.es5.js
+|__ng2-fittext.es5.js.map
+|__ng2-fittext.js
+|__ng2-fittext.js.map
+|__ng2-fittext.metadata.json
+```
+
+As you can see, the packaging removes any files specific to developing your
+library. It, more importantly, creates distribution files for usage with many
+different module systems.
diff --git a/examples/example.component.html b/examples/example.component.html
new file mode 100644
index 0000000..e69de29
diff --git a/examples/example.component.scss b/examples/example.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/examples/example.component.ts b/examples/example.component.ts
new file mode 100644
index 0000000..7c63445
--- /dev/null
+++ b/examples/example.component.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'example-root',
+ templateUrl: './example.component.html',
+ styleUrls: []
+})
+export class ExampleComponent { }
diff --git a/examples/example.main.ts b/examples/example.main.ts
new file mode 100644
index 0000000..3413002
--- /dev/null
+++ b/examples/example.main.ts
@@ -0,0 +1,4 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { ExampleModule } from './example.module';
+
+platformBrowserDynamic().bootstrapModule(ExampleModule);
diff --git a/examples/example.module.ts b/examples/example.module.ts
new file mode 100644
index 0000000..5e0987f
--- /dev/null
+++ b/examples/example.module.ts
@@ -0,0 +1,18 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { ExampleComponent } from './example.component';
+import { Ng2FittextModule } from '../index';
+
+@NgModule({
+ declarations: [
+ ExampleComponent
+ ],
+ imports: [
+ BrowserModule,
+ Ng2FittextModule
+ ],
+ providers: [],
+ bootstrap: [ExampleComponent]
+})
+export class ExampleModule { }
diff --git a/examples/index.html b/examples/index.html
new file mode 100644
index 0000000..7b6f6b5
--- /dev/null
+++ b/examples/index.html
@@ -0,0 +1,12 @@
+
+
+
+ Ng2FittextModule Tutorial
+
+
+
+
+
+ Loading...
+
+
\ No newline at end of file
diff --git a/examples/styles.scss b/examples/styles.scss
new file mode 100644
index 0000000..e69de29
diff --git a/index.ts b/index.ts
index 7107bfa..8420b10 100644
--- a/index.ts
+++ b/index.ts
@@ -1 +1 @@
-export {Ng2FittextModule} from './src/ng2fittext.module';
\ No newline at end of file
+export * from './src';
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..ec7d988
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,113 @@
+'use strict';
+
+const erectorUtils = require('erector-set/src/utils');
+const fs = require('fs');
+const path = require('path');
+
+module.exports = function (config) {
+ const base = {
+ basePath: '',
+ frameworks: ['jasmine'],
+ files: [
+ { pattern: './src/test.js', watched: false }
+ ],
+ mime: {
+ 'text/x-typescript': ['ts','tsx']
+ },
+ plugins: [
+ 'karma-chrome-launcher',
+ 'karma-jasmine',
+ 'karma-phantomjs-launcher',
+ 'karma-coverage-istanbul-reporter',
+ 'karma-sourcemap-loader',
+ 'karma-webpack'
+ ],
+ preprocessors: {
+ './src/test.js': ['webpack', 'sourcemap']
+ },
+ coverageIstanbulReporter: {
+ dir: './coverage',
+ fixWebpackSourcePaths: true,
+ reports: ['html', 'lcovonly']
+ },
+ reporters: ['progress', 'coverage-istanbul'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome', 'PhantomJS'],
+ singleRun: false,
+ webpackServer: { noInfo: true }
+ };
+ const fullConfig = mergeCustomConfig(base, config);
+
+ config.set(fullConfig);
+};
+
+const mergeCustomConfig = (base, karmaConfig) => {
+ const customConfigPath = path.resolve(
+ __dirname,
+ 'configs',
+ 'karma.conf.js'
+ );
+ let fullConfig = base;
+
+ if (fs.existsSync(customConfigPath)) {
+ fullConfig = mergeConfigs(base, require(customConfigPath), karmaConfig);
+ }
+
+ return fullConfig;
+};
+
+const mergeConfigs = (base, custom, karmaConfig) => {
+ let mergedConfig = base;
+
+ if (erectorUtils.checkIsType(custom, 'function')) {
+ custom = custom(karmaConfig);
+ }
+
+ if (custom) {
+ const arrays = mergeConfigArrays(base, custom);
+ const objects = mergeConfigObjects(base, custom);
+ const primitives = mergeConfigPrimitives(base, custom);
+ const customAttributes = Object.assign({}, arrays, objects, primitives);
+
+ mergedConfig = Object.assign(
+ {}, base, customAttributes
+ );
+ }
+
+ return mergedConfig;
+};
+
+const mergeConfigArrays = (base, custom) => {
+ const attributes = ['browsers', 'files', 'plugins', 'reporters'];
+ return mergeConfigAttributes(base, custom, attributes, (baseAttribute, customAttribute) =>
+ erectorUtils.mergeDeep(baseAttribute, customAttribute)
+ );
+};
+
+const mergeConfigObjects = (base, custom) => {
+ const attributes = ['preprocessors'];
+ return mergeConfigAttributes(base, custom, attributes, (baseAttribute, customAttribute) =>
+ Object.assign(customAttribute, baseAttribute)
+ );
+};
+
+const mergeConfigPrimitives = (base, custom) => {
+ const attributes = ['color', 'logLevel', 'port'];
+
+ return mergeConfigAttributes(base, custom, attributes, (baseAttribute, customAttribute) =>
+ customAttribute
+ );
+};
+
+const mergeConfigAttributes = (base, custom, attributes, callback) => {
+ return attributes.reduce((config, attribute) => {
+ if (attribute in custom) {
+ config[attribute] = callback(base[attribute], custom[attribute]);
+ }
+
+ return config;
+ }, {});
+};
diff --git a/ng2fittext.ts b/ng2fittext.ts
deleted file mode 100644
index 7107bfa..0000000
--- a/ng2fittext.ts
+++ /dev/null
@@ -1 +0,0 @@
-export {Ng2FittextModule} from './src/ng2fittext.module';
\ No newline at end of file
diff --git a/package.json b/package.json
index ef915fa..c9e6e72 100644
--- a/package.json
+++ b/package.json
@@ -1,43 +1,85 @@
{
"name": "ng2-fittext",
"version": "1.0.24",
- "description": "An Angular2 directive for autoscale the font size of an element to fit an upper level container.",
- "main": "index.js",
+ "description": "Ng2 Fittext, an Angular library",
+ "main": "./bundles/ng2-fittext.umd.js",
+ "module": "./ng2-fittext.es5.js",
+ "es2015": "./ng2-fittext.js",
+ "typings": "./ng2-fittext.d.ts",
"scripts": {
- "prepublish": "tsc",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "build": "node ./tasks/build",
+ "g": "node ./node_modules/angular-librarian",
+ "lint": "tslint ./src/**/*.ts",
+ "postbuild": "rimraf build",
+ "posttagVersion": "npm run build && npm publish dist",
+ "prebuild": "rimraf dist out-tsc",
+ "start": "webpack-dev-server --open --config ./webpack/webpack.dev.js",
+ "tagVersion": "np --no-publish",
+ "test": "node ./tasks/test"
},
- "repository": {
- "type": "git",
- "url": "git+https://github.com/lokenxo/ng2-fittext.git"
- },
- "keywords": [
- "angular2",
- "ng2",
- "fittext",
- "ng2-fittext",
- "responsivefont",
- "ng-fittext angular2",
- "angular2 fittext",
- "ng2 fittext"
- ],
- "author": "Lorenzo Iovino",
+ "keywords": [],
+ "author": "",
"license": "ISC",
- "bugs": {
- "url": "https://github.com/lokenxo/ng2-fittext/issues"
- },
- "homepage": "https://github.com/lokenxo/ng2-fittext#readme",
- "peerDependencies": {
- "@angular/core": ">=4.0.1",
- "rxjs": "^5.4.0"
+ "dependencies": {
+ "@angular/common": "^4.0.0",
+ "@angular/compiler": "^4.0.0",
+ "@angular/core": "^4.0.0",
+ "@angular/platform-browser": "^4.0.0",
+ "@angular/platform-browser-dynamic": "^4.0.0",
+ "core-js": "^2.4.1",
+ "rxjs": "^5.0.1",
+ "zone.js": "0.8.12"
},
"devDependencies": {
- "typescript": "~2.2.2"
+ "@angular/compiler-cli": "^4.0.0",
+ "@types/jasmine": "2.5.38",
+ "@types/node": "^6.0.42",
+ "angular-librarian": "1.0.0-beta.14",
+ "angular2-template-loader": "0.6.0",
+ "awesome-typescript-loader": "^3.0.0",
+ "codelyzer": "~3.0.0",
+ "css-loader": "^0.26.1",
+ "css-to-string-loader": "^0.1.3",
+ "extract-text-webpack-plugin": "^2.1.0",
+ "file-loader": "^0.8.5",
+ "fs-extra": "^2.1.2",
+ "html-webpack-plugin": "^2.19.0",
+ "istanbul-instrumenter-loader": "^1.2.0",
+ "jasmine-core": "2.5.2",
+ "jasmine-spec-reporter": "2.5.0",
+ "karma": "1.2.0",
+ "karma-chrome-launcher": "^2.0.0",
+ "karma-coverage-istanbul-reporter": "^1.3.0",
+ "karma-jasmine": "^1.0.2",
+ "karma-phantomjs-launcher": "^1.0.2",
+ "karma-sourcemap-loader": "^0.3.7",
+ "karma-webpack": "^2.0.0",
+ "node-sass": "^4.1.1",
+ "np": "^2.12.0",
+ "phantomjs-prebuilt": "^2.1.7",
+ "raw-loader": "^0.5.1",
+ "rimraf": "^2.5.3",
+ "rollup": "0.43.0",
+ "rollup-plugin-commonjs": "^8.0.2",
+ "rollup-plugin-node-resolve": "3.0.0",
+ "rollup-plugin-sourcemaps": "0.4.2",
+ "rollup-plugin-uglify": "2.0.1",
+ "sass-loader": "^4.0.1",
+ "script-loader": "^0.7.0",
+ "semver": "5.3.0",
+ "source-map-loader": "^0.1.5",
+ "style-loader": "^0.13.1",
+ "tslint": "^5.0.0",
+ "tslint-loader": "^3.0.0",
+ "typescript": "~2.2.1",
+ "typings": "^0.8.1",
+ "url-loader": "^0.5.7",
+ "webpack": "^2.2.0",
+ "webpack-dev-server": "^2.2.0",
+ "webpack-merge": "^0.14.0",
+ "webpack-node-externals": "^1.5.4"
},
- "publishConfig": {
- "registry": "https://registry.npmjs.org/"
- },
- "dependencies": {
- "@angular/core": "^4.3.4"
+ "repository": {
+ "url": "https://github.com/lokenxo/ng2-fittext.git"
}
}
diff --git a/src/directives/ng2-fittext.directive.spec.ts b/src/directives/ng2-fittext.directive.spec.ts
new file mode 100644
index 0000000..acae1d4
--- /dev/null
+++ b/src/directives/ng2-fittext.directive.spec.ts
@@ -0,0 +1,15 @@
+/* tslint:disable:no-unused-variable */
+import {
+ async,
+ TestBed
+} from '@angular/core/testing';
+
+import { Ng2FittextDirective } from './ng2-fittext.directive';
+
+describe('Ng2FittextDirective', () => {
+ it('', () => {
+ const directive = new Ng2FittextDirective();
+
+ expect(directive).toBeTruthy();
+ });
+});
diff --git a/src/directives/ng2-fittext.directive.ts b/src/directives/ng2-fittext.directive.ts
new file mode 100644
index 0000000..d0e4342
--- /dev/null
+++ b/src/directives/ng2-fittext.directive.ts
@@ -0,0 +1,134 @@
+import {Directive, ElementRef, Renderer, Input, AfterViewInit, AfterViewChecked, HostListener, OnInit, OnChanges} from '@angular/core';
+
+@Directive({
+ selector: '[fittext]'
+})
+export class Ng2FittextDirective implements AfterViewInit, OnInit, OnChanges, AfterViewChecked {
+
+ @Input('fittext') fittext: any;
+ @Input('activateOnResize') activateOnResize: boolean;
+ @Input('container') container: any;
+ @Input('activateOnInputEvents') activateOnInputEvents: boolean;
+ @Input('useMaxFontSize') useMaxFontSize: boolean;
+ @Input('minFontSize') minFontSize = 7;
+ @Input('modelToWatch') modelToWatch: any;
+ private maxFontSize = 1000;
+ private fontSize = 1000;
+ private speed = 1.05;
+ private done = false;
+
+ constructor(public el: ElementRef, public renderer: Renderer) { }
+
+ setFontSize(fontSize) {
+ if (this.isVisible() && this.done === false) {
+ if (fontSize < this.minFontSize) {
+ // force that font size will never be lower than minimal allowed font size
+ fontSize = this.minFontSize;
+ }
+
+ this.fontSize = fontSize;
+ return this.el.nativeElement.style.setProperty('font-size', (fontSize).toString() + 'px');
+ }
+ }
+
+ calculateFontSize(fontSize, speed) {
+ // TODO Do with Gauss
+ return Math.floor(fontSize / speed);
+ }
+
+ checkOverflow(parent: any, children: any) {
+ const overflowX = children.scrollWidth - parent.clientWidth;
+ const overflowY = children.clientHeight - parent.clientHeight;
+ return (overflowX > 1 || overflowY > 1);
+ }
+
+ @HostListener('window:resize', ['$event'])
+ onResize() {
+ this.done = false;
+ if (this.activateOnResize && this.fittext) {
+ if (this.activateOnInputEvents && this.fittext) {
+ this.setFontSize(this.getStartFontSizeFromHeight());
+ } else {
+ this.setFontSize(this.getStartFontSizeFromWeight());
+ }
+
+ this.ngAfterViewInit();
+ }
+ }
+
+ @HostListener('input', ['$event'])
+ onInputEvents() {
+ this.done = false;
+ if (this.activateOnInputEvents && this.fittext) {
+ this.setFontSize(this.getStartFontSizeFromHeight());
+ this.ngAfterViewInit();
+ }
+ }
+
+ ngOnInit() {
+ this.done = false;
+ if (this.useMaxFontSize) {
+ this.maxFontSize = parseInt(this.getComputetStyle().fontSize, null);
+ }
+
+ if (this.fittext) {
+ this.setFontSize(this.maxFontSize);
+ }
+
+ this.el.nativeElement.style.setProperty('will-change', 'content');
+ }
+
+ ngAfterViewInit() {
+ if (this.isVisible() && this.done === false) {
+ if (this.fittext) {
+ const overflow = this.container ? this.checkOverflow(this.container, this.el.nativeElement)
+ : this.checkOverflow(this.el.nativeElement.parentElement, this.el.nativeElement);
+ if (overflow) {
+ if (this.fontSize > this.minFontSize) {
+ // iterate only until font size is bigger than minimal value
+ this.setFontSize(this.calculateFontSize(this.fontSize, this.speed));
+ this.ngAfterViewInit();
+ }
+ } else {
+ if (this.useMaxFontSize) {
+ if (this.fontSize > this.maxFontSize) {
+ this.maxFontSize = parseInt(this.getComputetStyle().fontSize, null);
+ this.setFontSize(this.maxFontSize);
+ }
+ }
+ this.done = true;
+ }
+ }
+ }
+ }
+
+ ngOnChanges(changes: any): void {
+ if (changes.modelToWatch) {
+ // change of model to watch - call ngAfterViewInit where is implemented logic to change size
+ setTimeout(_ => this.ngAfterViewInit() );
+ }
+ }
+
+ ngAfterViewChecked() {
+ if (this.fontSize > this.minFontSize) {
+ this.setFontSize(this.getStartFontSizeFromHeight());
+ this.ngAfterViewInit();
+ }
+ }
+
+ private getComputetStyle(): CSSStyleDeclaration {
+ return window.getComputedStyle(this.container ? this.container : this.el.nativeElement.parentElement);
+ }
+
+ private getStartFontSizeFromHeight(): number {
+ return this.container ? this.container.clientHeight : this.el.nativeElement.parentElement.clientHeight;
+ }
+
+ private getStartFontSizeFromWeight(): number {
+ return this.container ? this.container.clientWidth : this.el.nativeElement.parentElement.clientWidth;
+ }
+
+ private isVisible(): boolean {
+ return this.getStartFontSizeFromHeight() > 0;
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..5fc5940
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1 @@
+export * from './ng2-fittext.module';
diff --git a/src/ng2-fittext.module.ts b/src/ng2-fittext.module.ts
new file mode 100644
index 0000000..9d3bf8d
--- /dev/null
+++ b/src/ng2-fittext.module.ts
@@ -0,0 +1,23 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { Ng2FittextDirective } from './directives/ng2-fittext.directive';
+
+@NgModule({
+ declarations: [
+ Ng2FittextDirective
+ ],
+ exports: [
+ Ng2FittextDirective
+ ],
+ imports: [
+ CommonModule
+ ]
+})
+export class Ng2FittextModule {
+ static forRoot() {
+ return {
+ ngModule: Ng2FittextModule,
+ providers: []
+ };
+ }
+}
diff --git a/src/ng2fittext.directive.spec.ts b/src/ng2fittext.directive.spec.ts
deleted file mode 100644
index 09f82f2..0000000
--- a/src/ng2fittext.directive.spec.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { FittextDirective } from './fittext.directive';
-
-describe('FittextDirective', () => {
- it('should create an instance', () => {
- const directive = new FittextDirective();
- expect(directive).toBeTruthy();
- });
-});
diff --git a/src/ng2fittext.directive.ts b/src/ng2fittext.directive.ts
deleted file mode 100644
index 1bd3092..0000000
--- a/src/ng2fittext.directive.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import {Directive, ElementRef, Renderer, Input, AfterViewInit, AfterViewChecked, HostListener, OnInit, OnChanges, SimpleChanges} from '@angular/core';
-
-@Directive({
- selector: '[fittext]'
-})
-export class Ng2FittextDirective implements AfterViewInit, OnInit, OnChanges, AfterViewChecked {
-
- @Input('fittext') fittext: any;
- @Input('activateOnResize') activateOnResize: boolean;
- @Input('container') container: any;
- @Input('activateOnInputEvents') activateOnInputEvents: boolean;
- @Input('useMaxFontSize') useMaxFontSize: boolean;
- @Input('minFontSize') minFontSize = 7;
- @Input('modelToWatch') modelToWatch: any;
- private maxFontSize: number = 1000;
- private fontSize: number = 1000;
- private speed: number = 1.05;
- private done: boolean = false;
-
- constructor(public el: ElementRef, public renderer: Renderer) { }
-
- setFontSize(fontSize) {
- if (this.isVisible() && this.done === false) {
- if (fontSize < this.minFontSize) {
- // force that font size will never be lower than minimal allowed font size
- fontSize = this.minFontSize;
- }
-
- this.fontSize = fontSize;
- return this.el.nativeElement.style.setProperty('font-size', (fontSize).toString() + 'px');
- }
- }
-
- calculateFontSize(fontSize, speed) {
- // TODO Do with Gauss
- return Math.floor(fontSize / speed);
- }
-
- checkOverflow(parent: any, children: any) {
- let overflowX = children.scrollWidth - parent.clientWidth;
- let overflowY = children.clientHeight - parent.clientHeight;
- return (overflowX > 1 || overflowY > 1);
- }
-
- @HostListener('window:resize', ['$event'])
- onResize() {
- this.done = false;
- if (this.activateOnResize && this.fittext) {
- if (this.activateOnInputEvents && this.fittext) {
- this.setFontSize(this.getStartFontSizeFromHeight());
- } else {
- this.setFontSize(this.getStartFontSizeFromWeight());
- }
-
- this.ngAfterViewInit();
- }
- }
-
- @HostListener('input', ['$event'])
- onInputEvents() {
- this.done = false;
- if (this.activateOnInputEvents && this.fittext) {
- this.setFontSize(this.getStartFontSizeFromHeight());
- this.ngAfterViewInit();
- }
- }
-
- ngOnInit() {
- this.done = false;
- if (this.useMaxFontSize) {
- this.maxFontSize = parseInt(this.getComputetStyle().fontSize, null);
- }
-
- if (this.fittext) {
- this.setFontSize(this.maxFontSize);
- }
-
- this.el.nativeElement.style.setProperty('will-change', 'content');
- }
-
- ngAfterViewInit() {
- if (this.isVisible() && this.done === false) {
- if (this.fittext) {
- let overflow = this.container ? this.checkOverflow(this.container, this.el.nativeElement)
- : this.checkOverflow(this.el.nativeElement.parentElement, this.el.nativeElement);
- if (overflow) {
- if (this.fontSize > this.minFontSize) {
- // iterate only until font size is bigger than minimal value
- this.setFontSize(this.calculateFontSize(this.fontSize, this.speed));
- this.ngAfterViewInit();
- }
- } else {
- if (this.useMaxFontSize) {
- if (this.fontSize > this.maxFontSize) {
- this.maxFontSize = parseInt(this.getComputetStyle().fontSize, null);
- this.setFontSize(this.maxFontSize);
- }
- }
- this.done = true;
- }
- }
- }
- }
-
- ngOnChanges(changes: any): void {
- if (changes.modelToWatch) {
- // change of model to watch - call ngAfterViewInit where is implemented logic to change size
- setTimeout(_ => this.ngAfterViewInit() );
- }
- }
-
- ngAfterViewChecked() {
- if (this.fontSize > this.minFontSize) {
- this.setFontSize(this.getStartFontSizeFromHeight());
- this.ngAfterViewInit();
- }
- }
-
- private getComputetStyle(): CSSStyleDeclaration {
- return window.getComputedStyle(this.container ? this.container : this.el.nativeElement.parentElement);
- }
-
- private getStartFontSizeFromHeight(): number {
- return this.container ? this.container.clientHeight : this.el.nativeElement.parentElement.clientHeight;
- }
-
- private getStartFontSizeFromWeight(): number {
- return this.container ? this.container.clientWidth : this.el.nativeElement.parentElement.clientWidth;
- }
-
- private isVisible(): boolean {
- return this.getStartFontSizeFromHeight() > 0;
- }
-}
diff --git a/src/ng2fittext.module.ts b/src/ng2fittext.module.ts
deleted file mode 100644
index 0313c74..0000000
--- a/src/ng2fittext.module.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { NgModule } from '@angular/core';
-import { Ng2FittextDirective } from "./ng2fittext.directive";
-
-@NgModule({
- declarations: [
- Ng2FittextDirective
- ],
- exports: [Ng2FittextDirective]
-})
-
-export class Ng2FittextModule {};
diff --git a/src/test.js b/src/test.js
new file mode 100644
index 0000000..4b9a800
--- /dev/null
+++ b/src/test.js
@@ -0,0 +1,24 @@
+require('core-js/es6');
+require('core-js/es7');
+require('zone.js/dist/zone');
+require('zone.js/dist/long-stack-trace-zone');
+require('zone.js/dist/async-test');
+require('zone.js/dist/fake-async-test');
+require('zone.js/dist/sync-test');
+require('zone.js/dist/proxy');
+require('zone.js/dist/jasmine-patch');
+
+const browserTesting = require('@angular/platform-browser-dynamic/testing');
+const coreTesting = require('@angular/core/testing');
+const context = require.context('./', true, /\.spec\.ts$/);
+
+Error.stackTraceLimit = Infinity;
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
+
+coreTesting.TestBed.resetTestEnvironment();
+coreTesting.TestBed.initTestEnvironment(
+ browserTesting.BrowserDynamicTestingModule,
+ browserTesting.platformBrowserDynamicTesting()
+);
+
+context.keys().forEach(context);
\ No newline at end of file
diff --git a/src/vendor.ts b/src/vendor.ts
new file mode 100644
index 0000000..fb8fe9a
--- /dev/null
+++ b/src/vendor.ts
@@ -0,0 +1,19 @@
+// polyfills Angular 2 requires to be loaded BEFORE the application
+// only used in library development--not packaged
+import 'core-js/es6/symbol';
+import 'core-js/es6/object';
+import 'core-js/es6/function';
+import 'core-js/es6/parse-int';
+import 'core-js/es6/parse-float';
+import 'core-js/es6/number';
+import 'core-js/es6/math';
+import 'core-js/es6/string';
+import 'core-js/es6/date';
+import 'core-js/es6/array';
+import 'core-js/es6/regexp';
+import 'core-js/es6/map';
+import 'core-js/es6/set';
+import 'core-js/es6/reflect';
+
+import 'core-js/es7/reflect';
+import 'zone.js/dist/zone';
diff --git a/tasks/build.js b/tasks/build.js
new file mode 100644
index 0000000..1c42a61
--- /dev/null
+++ b/tasks/build.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const fs = require('fs-extra');
+const ngc = require('@angular/compiler-cli/src/main').main;
+const librarianUtils = require('angular-librarian/commands/utilities');
+const path = require('path');
+
+const copyGlobs = require('./copy-globs');
+const copyToBuild = require('./copy-build');
+const inlineResources = require('./inline-resources');
+const rollup = require('./rollup');
+
+const colorize = librarianUtils.colorize;
+const rootDir = path.resolve(__dirname, '..');
+const buildDir = path.resolve(rootDir, 'build');
+const distDir = path.resolve(rootDir, 'dist');
+const libName = require(path.resolve(rootDir, 'package.json')).name;
+const srcDir = path.resolve(rootDir, 'src');
+const tscDir = path.resolve(rootDir, 'out-tsc');
+const es5Dir = path.resolve(tscDir, 'lib-es5');
+const es2015Dir = path.resolve(tscDir, 'lib-es2015');
+
+const runPromise = (message, fn) => {
+ return function() {
+ console.info(colorize.colorize(message, 'cyan'));
+ return fn().then(complete);
+ };
+};
+
+const complete = (depth = 0) => {
+ const spaces = ' '.repeat(depth);
+ console.info(colorize.colorize(`${ spaces }> Complete`, 'green'));
+};
+const compileCode = () => Promise.all([2015, 5].map((type) =>
+ ngc({ project: path.resolve(rootDir, `tsconfig.es${ type }.json`)})
+ .then((exitCode) =>
+ exitCode === 0 ? Promise.resolve() : Promise.reject()
+ )
+));
+const copyMetadata = () =>
+ copyGlobs(['**/*.d.ts', '**/*.metadata.json'], es2015Dir, distDir);
+const copyPackageFiles = () =>
+ copyGlobs(['.npmignore', 'package.json', 'README.md'], rootDir, distDir)
+ .then(() => {
+ const contents = fs.readFileSync(path.resolve(distDir, 'package.json'), 'utf8');
+
+ return fs.writeFileSync(path.resolve(distDir, 'package.json'), contents.replace('"dependencies":', '"peerDependencies":'));
+ });
+const copySource = () => copyGlobs('**/*', srcDir, buildDir);
+const doInlining = () => inlineResources(buildDir, 'src');
+const rollupBundles = () => rollup(libName, {
+ dist: distDir,
+ es2015: es2015Dir,
+ es5: es5Dir,
+ root: rootDir
+});
+
+return Promise.resolve()
+ .then(runPromise('Copying `src` files into `build`', copySource))
+ .then(runPromise('Inlining resources', doInlining))
+ .then(runPromise('Compiling code', compileCode))
+ .then(runPromise('Copying typings + metadata to `dist`', copyMetadata))
+ .then(runPromise('Generating bundles via rollup', rollupBundles))
+ .then(runPromise('Copying package files to `dist`', copyPackageFiles))
+ .catch((error) => {
+ console.error('\x1b[31m%s\x1b[0m', '> Build failed\n');
+ console.error(error);
+ process.exit(1);
+ });
diff --git a/tasks/copy-build.js b/tasks/copy-build.js
new file mode 100644
index 0000000..92a729a
--- /dev/null
+++ b/tasks/copy-build.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const fs = require('fs-extra');
+
+// copy all src files -> build
+const copyToBuild = (buildDir, sourceDir) => {
+ fs.ensureDirSync(buildDir);
+ fs.emptyDirSync(buildDir);
+ fs.copySync(sourceDir, buildDir);
+};
+
+module.exports = copyToBuild;
+
+if (!module.parent) {
+ copyToBuild('./build', './src');
+}
diff --git a/tasks/copy-globs.js b/tasks/copy-globs.js
new file mode 100644
index 0000000..413adc8
--- /dev/null
+++ b/tasks/copy-globs.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const fs = require('fs-extra');
+const glob = require('glob');
+const path = require('path');
+
+const copy = (globs, from, to) => {
+ if (typeof globs === 'string') {
+ globs = [globs];
+ }
+
+ fs.ensureDir(to);
+ return Promise.all(
+ globs.map((fileGlob) => copyGlob(fileGlob, from, to))
+ );
+};
+
+const copyGlob = (fileGlob, from, to) => new Promise((resolve, reject) => {
+ glob(fileGlob, { cwd: from, nodir: true }, (error, files) => {
+ if (error) reject(error);
+
+ files.forEach((file) => {
+ const origin = path.resolve(from, file);
+ const destination = path.resolve(to, file);
+ const contents = fs.readFileSync(origin, 'utf8');
+
+ fs.ensureDirSync(path.dirname(destination));
+ fs.writeFileSync(destination, contents);
+ });
+
+ resolve();
+ });
+});
+
+module.exports = copy;
diff --git a/tasks/inline-resources.js b/tasks/inline-resources.js
new file mode 100644
index 0000000..0776d06
--- /dev/null
+++ b/tasks/inline-resources.js
@@ -0,0 +1,186 @@
+'use strict';
+// original code by the Angular Material 2 team
+
+const fs = require('fs');
+const glob = require('glob');
+const path = require('path');
+const sass = require('node-sass');
+
+const inlineResources = (globs, sourcePrefix) => {
+ if (typeof globs === 'string') {
+ globs = [globs];
+ }
+
+ return Promise.all(
+ globs.map((pattern) => replaceSource(pattern, sourcePrefix))
+ );
+};
+
+const replaceSource = (pattern, sourcePrefix) => {
+ // pattern is a directory
+ if (pattern.indexOf('*') === -1) {
+ pattern = path.join(pattern, '**', '*');
+ }
+
+ return new Promise((resolve, reject) => {
+ glob(pattern, {}, (error, files) => {
+ if (error) reject(Error);
+
+ files.filter((name) => /\.ts$/.test(name)).forEach((filePath) => {
+ try {
+ inlineFileResources(filePath, sourcePrefix);
+ } catch (readError) {
+ reject(readError);
+ }
+ });
+
+ resolve();
+ });
+ });
+};
+
+const inlineFileResources = (filePath, sourcePrefix) => {
+ const content = fs.readFileSync(filePath, 'utf8');
+ const inlineContents = inlineResourcesFromString(content, sourcePrefix, (url) =>
+ path.join(path.dirname(filePath), url)
+ );
+
+ fs.writeFileSync(filePath, inlineContents);
+};
+
+const inlineResourcesFromString = (content, sourcePrefix, callback) => [
+ inlineTemplate, inlineStyle, removeModuleId
+].reduce((final, method) => method(final, sourcePrefix, callback), content);
+
+const inlineTemplate = (content, sourcePrefix, callback) =>
+ content.replace(/templateUrl:\s*'([^']+?\.html)'/g, (match, url) => {
+ const mini = getMiniContents(url, sourcePrefix, callback);
+
+ return `template: "${mini}"`;
+ });
+
+const inlineStyle = (content, sourcePrefix, callback) =>
+ content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, (match, styleUrls) => {
+ const urls = eval(styleUrls); // string -> array
+ return 'styles: [' + urls.map((url) => {
+ const mini = getMiniContents(url, sourcePrefix, callback);
+
+ return `"${mini}"`;
+ }).join(',\n') + ']';
+ });
+
+const getMiniContents = (url, sourcePrefix, callback) => {
+ const srcFile = callback(url);
+ const file = srcFile.replace(/^dist/, sourcePrefix)
+ const srcDir = file.slice(0, file.lastIndexOf(path.sep));
+ let template = '';
+
+ if (file.match(/\.s(a|c)ss$/)) {
+ // convert SASS -> CSS
+ template = sass.renderSync({
+ file,
+ importer: (url) => handleSassImport(url, srcDir)
+ });
+ template = template.css.toString();
+ } else {
+ template = fs.readFileSync(file, 'utf8');
+ }
+
+ return minifyText(template);
+};
+
+const handleSassImport = (url, srcDir) => {
+ const fullUrl = getFullSassUrl(url, srcDir);
+ let isPartial = false;
+ let validUrls = getSassUrls(fullUrl);
+
+ // if we can't find the file, try to
+ // see find it as a partial (underscore-prefixed)
+ if (validUrls.length === 0) {
+ validUrls = getSassUrls(fullUrl, true);
+ isPartial = true;
+ }
+
+ const file = getSassImportUrl(validUrls);
+
+ // CSS files don't get compiled in
+ return /\.css$/.test(file) ?
+ { contents: fs.readFileSync(file, 'utf8') } :
+ { file };
+};
+
+const getSassUrls = (url, partial) => {
+ let extensions = ['sass', 'scss'];
+
+ if (!partial) {
+ extensions = extensions.concat('', 'css');
+ } else {
+ const lastSlash = url.lastIndexOf(path.sep);
+ const urlDir = url.slice(0, lastSlash);
+ const fileName = url.slice(lastSlash + 1);
+
+ if (fileName[0] !== '_') {
+ url = urlDir + path.sep + '_' + fileName;
+ }
+ }
+
+ return extensions.reduce((valid, extension) => {
+ const extensionUrl = verifyUrl(url, extension);
+
+ if (extensionUrl) {
+ valid = valid.concat(extensionUrl);
+ }
+
+ return valid;
+ }, []);
+};
+
+const verifyUrl = (url, extension) => {
+ if (extension) {
+ url = url + `.${ extension }`;
+ }
+
+ if (!fs.existsSync(url)) {
+ url = null;
+ }
+
+ return url;
+}
+
+// convert ~-prefixed filenames to node_modules-prefixed
+// make all others relative to srcDir
+const getFullSassUrl = (url, srcDir) =>
+ /^~/.test(url) ?
+ path.resolve('node_modules', url.slice(1)) :
+ path.resolve(srcDir, url);
+
+const getSassImportUrl = (validUrls) => {
+ if (validUrls.length !== 1) {
+ let error = 'Cannot determine Sass/CSS file to process. ';
+
+ if (validUrls.length === 0) {
+ error = error + `\n There are no files matching ${ url }`;
+ } else {
+ error = error + 'Candidates:\n ' + validUrls.join('\n ')
+ + '\nPlease delete or rename all but one of these files or specify the extension to use.';
+ }
+
+ throw new Error(error);
+ }
+
+ return validUrls[0];
+};
+
+
+const minifyText = (text) => text
+ .replace(/([\n\r]\s*)+/gm, ' ')
+ .replace(/"/g, '\\"');
+
+const removeModuleId = (content) =>
+ content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
+
+module.exports = inlineResources;
+
+if (!module.parent) {
+ inlineResources('./build', 'src');
+}
diff --git a/tasks/rollup.js b/tasks/rollup.js
new file mode 100644
index 0000000..a22c322
--- /dev/null
+++ b/tasks/rollup.js
@@ -0,0 +1,134 @@
+'use strict';
+
+const erectorUtils = require('erector-set/src/utils');
+const fs = require('fs-extra');
+const librarianUtils = require('angular-librarian/commands/utilities');
+const path = require('path');
+const rollup = require('rollup');
+const rollupCommon = require('rollup-plugin-commonjs');
+const rollupNodeResolve = require('rollup-plugin-node-resolve');
+const rollupSourcemaps = require('rollup-plugin-sourcemaps');
+const rollupUglify = require('rollup-plugin-uglify');
+
+const doRollup = (libName, dirs) => {
+ const nameParts = extractName(libName);
+ const es5Entry = path.resolve(dirs.es5, `${ nameParts.package }.js`);
+ const es2015Entry = path.resolve(dirs.es2015, `${ nameParts.package }.js`);
+ const destinations = generateDestinations(dirs.dist, nameParts);
+ const baseConfig = generateConfig({
+ entry: es5Entry,
+ external: [
+ '@angular/common',
+ '@angular/core'
+ ],
+ globals: {
+ '@angular/common': 'ng.common',
+ '@angular/core': 'ng.core'
+ },
+ moduleName: librarianUtils.caseConvert.dashToCamel(nameParts.package),
+ onwarn: function rollupOnWarn(warning) {
+ // keeps TypeScript this errors down
+ if (warning.code !== 'THIS_IS_UNDEFINED') {
+ console.warn(warning.message);
+ }
+ },
+ plugins: [
+ rollupNodeResolve({
+ jsnext: true,
+ module: true
+ }),
+ rollupSourcemaps()
+ ],
+ sourceMap: true
+ }, dirs.root);
+ const fesm2015Config = Object.assign({}, baseConfig, {
+ entry: es2015Entry,
+ dest: destinations.fesm2015,
+ format: 'es'
+ });
+ const fesm5Config = Object.assign({}, baseConfig, {
+ dest: destinations.fesm5,
+ format: 'es'
+ });
+ const minUmdConfig = Object.assign({}, baseConfig, {
+ dest: destinations.minUmd,
+ format: 'umd',
+ plugins: baseConfig.plugins.concat([rollupUglify({})])
+ });
+ const umdConfig = Object.assign({}, baseConfig, {
+ dest: destinations.umd,
+ format: 'umd'
+ });
+
+ const bundles = [
+ fesm2015Config,
+ fesm5Config,
+ minUmdConfig,
+ umdConfig
+ ].map((config) =>
+ rollup.rollup(config).then((bundle) =>
+ bundle.write(config)
+ )
+ );
+
+ return Promise.all(bundles);
+};
+
+const extractName = (libName) => {
+ const isScoped = librarianUtils.checkIsScopedName(libName);
+ const nameParts = {
+ package: libName,
+ scope: undefined
+ };
+
+ if (isScoped) {
+ const parts = libName.split('/', 2);
+
+ nameParts.package = parts[1];
+ nameParts.scope = parts[0];
+ }
+
+ return nameParts;
+};
+
+const generateDestinations = (dist, nameParts) => {
+ const bundleDest = path.resolve(dist, 'bundles');
+ let fesmDest = path.resolve(dist);
+
+ if (nameParts.scope) {
+ fesmDest = path.resolve(fesmDest, nameParts.scope);
+ fs.ensureDirSync(fesmDest);
+ }
+
+ return Object.freeze({
+ fesm2015: path.resolve(fesmDest,`${ nameParts.package }.js`),
+ fesm5: path.resolve(fesmDest,`${ nameParts.package }.es5.js`),
+ minUmd: path.resolve(bundleDest, `${ nameParts.package }.umd.min.js`),
+ umd: path.resolve(bundleDest, `${ nameParts.package }.umd.js`)
+ });
+};
+
+const generateConfig = (base, rootDir) => {
+ let commonjsIncludes = ['node_modules/rxjs/**'];
+ const customLocation = path.resolve(rootDir, 'configs', 'rollup.config.js');
+
+ if (fs.existsSync(customLocation)) {
+ const custom = require(customLocation);
+ const external = (custom.external || []).filter((external) => base.external.indexOf(external) === -1);
+ const includes = (custom.commonjs || []).filter((include) => commonjsIncludes.indexOf(include) === -1);
+
+ base.external = base.external.concat(external);
+ base.globals = erectorUtils.mergeDeep(custom.globals, base.globals);
+ commonjsIncludes = commonjsIncludes.concat(includes);
+ }
+
+ base.plugins.unshift(
+ rollupCommon({
+ include: commonjsIncludes
+ })
+ );
+
+ return base;
+};
+
+module.exports = doRollup;
diff --git a/tasks/test.js b/tasks/test.js
new file mode 100644
index 0000000..74ae43c
--- /dev/null
+++ b/tasks/test.js
@@ -0,0 +1,66 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const Server = require('karma').Server;
+
+function run(type) {
+ const config = getConfig(type);
+ const server = new Server(config, function(exitCode) {
+ process.exit(exitCode);
+ });
+
+ server.start();
+}
+
+function getConfig(type) {
+ switch (type) {
+ case 'headless':
+ case 'hl':
+ case 'h':
+ return getHeadlessConfig();
+ case 'all':
+ case 'a':
+ return getAllConfig();
+ case 'watch':
+ case 'w':
+ return getWatchConfig();
+ default:
+ return getSingleConfig();
+ }
+}
+
+function getSingleConfig() {
+ let config = getHeadlessConfig();
+
+ config.singleRun = true;
+
+ return config;
+}
+
+function getHeadlessConfig() {
+ let config = getAllConfig();
+
+ config.browsers = ['PhantomJS'];
+
+ return config;
+}
+
+function getWatchConfig() {
+ let config = getAllConfig(true);
+
+ config.browsers = ['Chrome'];
+
+ return config;
+}
+
+const getAllConfig = (watch) => ({
+ configFile: path.resolve(__dirname, '..', 'karma.conf.js'),
+ webpack: require(path.resolve(__dirname, '..', 'webpack', 'webpack.test.js'))(watch),
+});
+
+module.exports = run;
+
+if (!module.parent) {
+ run(process.argv[2]);
+}
diff --git a/tsconfig.es2015.json b/tsconfig.es2015.json
new file mode 100644
index 0000000..652dfa4
--- /dev/null
+++ b/tsconfig.es2015.json
@@ -0,0 +1,34 @@
+{
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "flatModuleId": "ng2-fittext",
+ "flatModuleOutFile": "ng2-fittext.js",
+ "genDir": "../out-tsc/lib-gen-dir/",
+ "strictMetadataEmit": true,
+ "skipTemplateCodegen": true
+ },
+ "compilerOptions": {
+ "baseUrl": "",
+ "declaration": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": ["es2015", "dom"],
+ "module": "es2015",
+ "moduleResolution": "node",
+ "outDir": "./out-tsc/lib-es2015/",
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "stripInternal": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "target": "es2015",
+ "typeRoots": [
+ "./node_modules/@types"
+ ]
+ },
+ "files": [
+ "./build/index.ts"
+ ],
+ "awesomeTypescriptLoaderOptions": {
+ "forkChecker": true
+ }
+}
diff --git a/tsconfig.es5.json b/tsconfig.es5.json
new file mode 100644
index 0000000..d310921
--- /dev/null
+++ b/tsconfig.es5.json
@@ -0,0 +1,34 @@
+{
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "flatModuleId": "ng2-fittext",
+ "flatModuleOutFile": "ng2-fittext.js",
+ "genDir": "../out-tsc/lib-gen-dir/",
+ "strictMetadataEmit": true,
+ "skipTemplateCodegen": true
+ },
+ "compilerOptions": {
+ "baseUrl": "",
+ "declaration": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": ["es2015", "dom"],
+ "module": "es2015",
+ "moduleResolution": "node",
+ "outDir": "./out-tsc/lib-es5/",
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "stripInternal": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "target": "es5",
+ "typeRoots": [
+ "./node_modules/@types"
+ ]
+ },
+ "files": [
+ "./build/index.ts"
+ ],
+ "awesomeTypescriptLoaderOptions": {
+ "forkChecker": true
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index a088c86..413ea1a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,19 +1,28 @@
{
- "compilerOptions": {
- "module": "commonjs",
- "moduleResolution": "node",
- "target": "es5",
- "sourceMap": true,
- "inlineSources": false,
- "declaration": false,
- "experimentalDecorators": true,
- "emitDecoratorMetadata": true,
- "stripInternal": true,
- "skipLibCheck": true
- },
- "files": [
- "ng2fittext.ts",
- "ng2fittext.d.ts",
- "index.ts"
- ]
-}
\ No newline at end of file
+ "angularCompilerOptions": {
+ "strictMetadataEmit": true,
+ "skipTemplateCodegen": true
+ },
+ "compilerOptions": {
+ "baseUrl": "",
+ "declaration": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": ["es2015", "dom"],
+ "mapRoot": "./",
+ "module": "es2015",
+ "moduleResolution": "node",
+ "outDir": "./dist",
+ "sourceMap": true,
+ "target": "es5",
+ "typeRoots": [
+ "./node_modules/@types"
+ ]
+ },
+ "files": [
+ "./src/index.ts"
+ ],
+ "awesomeTypescriptLoaderOptions": {
+ "forkChecker": true
+ }
+}
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
index 0000000..1dce4db
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,20 @@
+{
+ "buildOnSave": false,
+ "compilerOptions": {
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "lib": ["es2015", "dom"],
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "removeComments": false,
+ "sourceMap": true,
+ "target": "es5",
+ "typeRoots": [
+ "./node_modules/@types"
+ ]
+ },
+ "compileOnSave": false,
+ "files": [
+ "./src/ng2-fittext.module.ts"
+ ]
+}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..288a655
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,116 @@
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [true, "rxjs"],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ "static-before-instance",
+ "variables-before-functions"
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-variable": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "typeof-compare": true,
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+
+ "directive-selector": [true, "attribute", "", "camelCase"],
+ "component-selector": [true, "element", "", "kebab-case"],
+ "use-input-property-decorator": true,
+ "use-output-property-decorator": true,
+ "use-host-property-decorator": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-life-cycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true,
+ "no-access-missing-member": true,
+ "templates-use-public": true,
+ "invoke-injectable": true
+ }
+}
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
new file mode 100644
index 0000000..ec373f4
--- /dev/null
+++ b/webpack/webpack.common.js
@@ -0,0 +1,70 @@
+'use strict';
+
+const fs = require('fs');
+const webpack = require('webpack');
+const webpackMerge = require('webpack-merge');
+
+const ExtractTextPlugin = require("extract-text-webpack-plugin");
+const ContextReplacementPlugin = webpack.ContextReplacementPlugin;
+const LoaderOptionsPlugin = webpack.LoaderOptionsPlugin;
+
+const webpackUtils = require('./webpack.utils');
+
+const getCommonConfig = (type) => {
+ const tsconfigType = type !== 'dev' ? `.${ type }` : '';
+
+ return {
+ module: {
+ rules: [
+ {
+ exclude: /node_modules/,
+ test: /\.ts$/,
+ use: [
+ 'awesome-typescript-loader?configFileName=' + webpackUtils.rootPath(`tsconfig${ tsconfigType }.json`),
+ 'angular2-template-loader?keepUrl=true'
+ ]
+ },
+ { test: /\.html$/, use: 'raw-loader' },
+ {
+ use: ['url-loader?limit=10000'],
+ test: /\.(woff2?|ttf|eot|svg|jpg|jpeg|json|gif|png)(\?v=\d+\.\d+\.\d+)?$/
+ }
+ ]
+ },
+ performance: { hints: false },
+ plugins: [
+ new ContextReplacementPlugin(
+ /angular(\\|\/)core(\\|\/)@angular/,
+ __dirname
+ ),
+ new LoaderOptionsPlugin({
+ debug: true,
+ options: {
+ emitErrors: true
+ }
+ }),
+ new ExtractTextPlugin("*.css")
+ ],
+ resolve: {
+ extensions: [ '.js', '.ts' ],
+ modules: [ webpackUtils.rootPath('node_modules') ]
+ }
+ };
+};
+
+module.exports = (type, typeConfig) => {
+ const configs = [getCommonConfig(type), typeConfig];
+ const customConfigPath = webpackUtils.rootPath('configs', `webpack.${ type }.js`);
+
+ if (fs.existsSync(customConfigPath)) {
+ let customConfig = require(customConfigPath);
+
+ if (Object.prototype.toString.call(customConfig) === '[object Function]') {
+ customConfig = customConfig();
+ }
+
+ configs.push(customConfig);
+ }
+
+ return webpackMerge.apply(null, configs);
+};
diff --git a/webpack/webpack.dev.js b/webpack/webpack.dev.js
new file mode 100644
index 0000000..70ad913
--- /dev/null
+++ b/webpack/webpack.dev.js
@@ -0,0 +1,77 @@
+'use strict';
+
+const HtmlWebpack = require('html-webpack-plugin');
+const webpack = require('webpack');
+
+const ChunkWebpack = webpack.optimize.CommonsChunkPlugin;
+const webpackCommon = require('./webpack.common');
+const webpackUtils = require('./webpack.utils');
+
+const entryPoints = [
+ 'vendor',
+ 'scripts',
+ 'styles',
+ 'app'
+];
+const examplePath = function examples() {
+ return webpackUtils.relayArguments(
+ webpackUtils.rootPath,
+ 'examples',
+ arguments
+ );
+};
+
+module.exports = webpackCommon('dev', {
+ devServer: {
+ contentBase: webpackUtils.rootPath('dist'),
+ port: 9000
+ },
+ devtool: 'cheap-module-eval-source-map',
+ entry: {
+ app: [ examplePath('example.main') ],
+ scripts: [],
+ vendor: [ webpackUtils.srcPath('vendor') ],
+ styles: [ examplePath('styles.scss') ]
+ },
+ module: {
+ rules: webpackUtils.buildRules({
+ cssExtract: examplePath(),
+ sassLoader: examplePath('styles.scss')
+ }, {
+ include: examplePath(),
+ test: /styles\.scss$/,
+ use: ['style-loader', 'css-loader', 'sass-loader']
+ })
+ },
+ output: {
+ filename: '[name].bundle.js',
+ path: webpackUtils.rootPath('dist')
+ },
+ plugins: [
+ new ChunkWebpack({
+ filename: 'vendor.bundle.js',
+ minChunks: Infinity,
+ name: 'vendor'
+ }),
+ new HtmlWebpack({
+ // shameless/shamefully stolen from Angular CLI
+ chunksSortMode: function(left, right) {
+ const leftIndex = entryPoints.indexOf(left.names[0]);
+ const rightIndex = entryPoints.indexOf(right.names[0]);
+ let direction = 0;
+
+ if (leftIndex > rightIndex) {
+ direction = 1;
+ } else if (leftIndex < rightIndex) {
+ direction = -1;
+ }
+
+ return direction;
+ },
+ filename: 'index.html',
+ inject: 'body',
+ template: examplePath('index.html')
+ })
+ ]
+});
+
diff --git a/webpack/webpack.test.js b/webpack/webpack.test.js
new file mode 100644
index 0000000..0766656
--- /dev/null
+++ b/webpack/webpack.test.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const webpack = require('webpack');
+
+const SourceMapDevToolPlugin = webpack.SourceMapDevToolPlugin;
+const webpackCommon = require('./webpack.common');
+const webpackUtils = require('./webpack.utils');
+
+module.exports = (watch) => {
+ return webpackCommon('test', {
+ devtool: watch ? 'inline-source-map' : 'cheap-module-eval-source-map',
+ module: {
+ rules: [
+ {
+ test: /\.s?css$/,
+ use: ['raw-loader', 'css-loader', 'sass-loader']
+ },
+ {
+ enforce: 'pre',
+ exclude: /node_modules/,
+ test: /\.ts$/,
+ use: 'tslint-loader'
+ },
+ {
+ enforce: 'post',
+ exclude: [
+ /node_modules/,
+ /\.(e2e|spec\.)ts$/
+ ],
+ test: /\.ts$/,
+ use: 'istanbul-instrumenter-loader?esModules=true'
+ }
+ ]
+ },
+ plugins: [
+ new SourceMapDevToolPlugin({
+ filename: null,
+ test: /\.ts$/
+ })
+ ],
+ resolve: {
+ modules: [ webpackUtils.srcPath() ],
+ moduleExtensions: ['-loader']
+ }
+ });
+};
diff --git a/webpack/webpack.utils.js b/webpack/webpack.utils.js
new file mode 100644
index 0000000..02bebb8
--- /dev/null
+++ b/webpack/webpack.utils.js
@@ -0,0 +1,66 @@
+'use strict';
+
+const ExtractText = require('extract-text-webpack-plugin');
+const path = require('path');
+
+function rootPath() {
+ const rootDir = path.resolve(__dirname, '..');
+ return relayArguments(path.resolve, rootDir, arguments);
+}
+exports.rootPath = rootPath;
+
+function srcPath() {
+ return relayArguments(rootPath, 'src', arguments);
+};
+exports.srcPath = srcPath;
+
+function relayArguments(method, prefix, args) {
+ const fullArguments = [prefix].concat(
+ Array.prototype.slice.apply(args)
+ );
+
+ return method.apply(null, fullArguments);
+}
+exports.relayArguments = relayArguments;
+
+exports.buildRules = (excludes, extraRules) => {
+ let cssExtractExcludes = [srcPath()];
+ let sassLoaderExcludes = [/node_modules/];
+ let rules;
+
+ excludes = excludes || {};
+ if (excludes.cssExtract) {
+ cssExtractExcludes = cssExtractExcludes.concat(excludes.cssExtract);
+ }
+
+ if (excludes.sassLoader) {
+ sassLoaderExcludes = sassLoaderExcludes.concat(excludes.sassLoader);
+ }
+
+ rules = [
+ {
+ exclude: cssExtractExcludes,
+ test: /\.css$/,
+ use: ExtractText.extract({
+ fallback: 'style-loader',
+ use: 'css-loader?sourceMap'
+ })
+ },
+ {
+ exclude: /node_modules/,
+ test: /\.css$/,
+ use: ['css-to-string-loader', 'css-loader']
+ },
+ {
+ exclude: sassLoaderExcludes,
+ use: ['css-to-string-loader', 'css-loader', 'sass-loader'],
+ test: /\.scss$/
+ }
+ ];
+
+ if (extraRules) {
+ rules = rules.concat(extraRules);
+ }
+
+ return rules;
+};