Compare commits

..

No commits in common. "master" and "1.1.0" have entirely different histories.

71 changed files with 1957 additions and 14463 deletions

1
.erector Normal file
View file

@ -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"}]

View file

@ -1,21 +0,0 @@
name: Mirror to Forgejo
on:
push:
branches: ["**"]
tags: ["**"]
delete:
jobs:
mirror:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push to Forgejo
run: |
git remote add forgejo https://loke:${{ secrets.FORGEJO_TOKEN }}@git.lorenzoiovino.com/loke/ng2-fittext.git
git push forgejo --all --force
git push forgejo --tags --force

View file

@ -1,22 +0,0 @@
name: Publish to NPM
on:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest]
node-version: [21.x]
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run pack
- run: cp README.md ./dist/lib/README.md
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
package: './dist/lib/package.json'

View file

@ -1,21 +0,0 @@
name: Run CI Tests
on:
push:
branches:
- '*'
- '!master'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest]
node-version: [21.x]
steps:
- uses: actions/checkout@v2
- run: npm install
- run: npm run test:headless:singleRun

4
.gitignore vendored
View file

@ -4,7 +4,3 @@ dist
debug.log debug.log
node_modules node_modules
out-tsc out-tsc
/.vs
/.angular
.idea
.DS_Store

12
.idea/fittext2.iml generated Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="TsLint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fittext2.iml" filepath="$PROJECT_DIR$/.idea/fittext2.iml" />
</modules>
</component>
</project>

249
.idea/workspace.xml generated Normal file
View file

@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="6d9e6998-eced-4f47-8fb4-3257db3e18e7" name="Default" comment="" />
<ignored path="$PROJECT_DIR$/.tmp/" />
<ignored path="$PROJECT_DIR$/temp/" />
<ignored path="$PROJECT_DIR$/tmp/" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="ng2-fittext.directive.ts" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/directives/ng2-fittext.directive.ts">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-270">
<caret line="6" column="0" lean-forward="false" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="README.md" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="2430">
<caret line="120" column="9" lean-forward="false" selection-start-line="120" selection-start-column="9" selection-end-line="120" selection-end-column="9" />
<folding />
</first_editor>
<second_editor />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="package.json" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="76" column="24" lean-forward="true" selection-start-line="76" selection-start-column="24" selection-end-line="76" selection-end-column="24" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="tsconfig.json" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/tsconfig.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="ng2-fittext.module.ts" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/src/ng2-fittext.module.ts">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="108">
<caret line="6" column="22" lean-forward="false" selection-start-line="6" selection-start-column="22" selection-end-line="6" selection-end-column="22" />
<folding>
<element signature="e#0#47#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/src/ng2-fittext.module.ts" />
<option value="$PROJECT_DIR$/src/directives/ng2-fittext.directive.ts" />
<option value="$PROJECT_DIR$/README.md" />
<option value="$PROJECT_DIR$/package.json" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER">
<package-json value="$PROJECT_DIR$/package.json" />
</component>
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="NodeModulesDirectoryManager">
<handled-path value="$PROJECT_DIR$/node_modules" />
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="285" />
<option name="width" value="1598" />
<option name="height" value="1038" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="fittext2" type="b2602c69:ProjectViewProjectNode" />
<item name="fittext2" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scratches" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="settings.editor.selected.configurable" value="configurable.group.appearance" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="nodejs_interpreter_path" value="C:/Program Files/nodejs/node" />
<property name="HbShouldOpenHtmlAsHb" value="" />
<property name="node.js.path.for.package.tslint" value="project" />
<property name="node.js.detected.package.tslint" value="true" />
<property name="node.js.selected.package.tslint" value="C:\Users\loke\Work\mylib\fittext2\node_modules\tslint" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="6d9e6998-eced-4f47-8fb4-3257db3e18e7" name="Default" comment="" />
<created>1510652992413</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1510652992413</updated>
<workItem from="1510652993603" duration="653000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="653000" />
</component>
<component name="ToolWindowManager">
<frame x="-9" y="-9" width="1938" height="1048" extended-state="6" />
<layout>
<window_info id="TypeScript" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.20465362" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="npm" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="true" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.32915717" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/src/ng2-fittext.module.ts">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="108">
<caret line="6" column="22" lean-forward="false" selection-start-line="6" selection-start-column="22" selection-end-line="6" selection-end-column="22" />
<folding>
<element signature="e#0#47#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/directives/ng2-fittext.directive.ts">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-270">
<caret line="6" column="0" lean-forward="false" selection-start-line="6" selection-start-column="0" selection-end-line="6" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/tsconfig.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;markdown-preview-editor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="2430">
<caret line="120" column="9" lean-forward="false" selection-start-line="120" selection-start-column="9" selection-end-line="120" selection-end-column="9" />
<folding />
</first_editor>
<second_editor />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/package.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="76" column="24" lean-forward="true" selection-start-line="76" selection-start-column="24" selection-end-line="76" selection-end-column="24" />
<folding />
</state>
</provider>
</entry>
</component>
</project>

24
.npmignore Normal file
View file

@ -0,0 +1,24 @@
*.spec.ts
*.tgz
.erector
.gitignore
.npmignore
.vscode
build
coverage
debug.log
DEVELOPMENT.md
dist
index.ts
karma.conf.js
node_modules
out-tsc
src
tasks
test.ts
tsconfig.*json
tslint.json
typings
typings.json
vendor.ts
webpack

20
.vscode/launch.json vendored
View file

@ -1,20 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "attach",
"name": "Unit tests",
"address": "localhost",
"port": 9333,
"sourceMaps": true,
"webRoot": "${workspaceFolder}",
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}"
}
}
]
}

View file

@ -1,5 +0,0 @@
{
"prettier.configPath": "./src/.prettierrc",
"editor.formatOnSave": true,
"javascript.implicitProjectConfig.experimentalDecorators": true
}

120
DEVELOPMENT.md Normal file
View file

@ -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 <type>`)
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
});
};
```
## <a name="unit"></a>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 <type>
```
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.
## <a name="pack"></a>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.

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Lorenzo Iovino
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

142
README.md
View file

@ -1,141 +1,133 @@
<h1 align="center"> # ng2-fittext
ng2-fittext
</h1>
<p align="center">An Angular 2+ directive, written in pure TypeScript (without jQuery!), to automatically scale the font size of an element so that it fits within its parent container.</p> An Angular2 directive written in pure typescript (and without jquery!), for autoscale the font size of an element to fit an upper level container.
[![Download via NPM](https://img.shields.io/npm/v/ng2-fittext.svg)](https://www.npmjs.com/package/ng2-fittext) How many times your font doesn't scale automatically to fit the size of the container? Always, if you don't say it to do that!
![Angular](https://img.shields.io/badge/Angular-18-green.svg) How you can say it? ng2-fittext!
![Build Status](https://github.com/thisloke/ng2-fittext/actions/workflows/tests.yml/badge.svg)
![Contributions welcome](https://img.shields.io/badge/contributions-welcome-green.svg)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
## Demo ### Demo
Check out the live demo: [ng2-fittext Demo](https://stackblitz.com/edit/stackblitz-starters-7ghzat?file=src%2Fmain.ts)
<div>
<img src="ng2-fittext-example.gif" alt="ng2 fittext example" border="0" style="width: 70%" align="center"/>
</div>
## Installation http://plnkr.co/edit/v0TQaYepV4E2Heur02j5?p=preview
Install the library using npm according to your angular version (see table below): ### Installation
| Angular | ng2-fittext | command |
|---------|-------------|-------------------------------|
| <= v17 | 1.4.3 | ```npm i ng2-fittext@1.4.3``` |
| >= v18 | 2.0.0 | ```npm i ng2-fittext@2.0.0``` |
Install the library
```sh
$ npm install --save ng2-fittext
```
### Usage ### Usage
1. Import the module in your Angular application: Import it in your Angular2 project like a module
```ts
import { Ng2FittextModule } from "ng2-fittext";
1) Declare it in your module
```sh
import {Ng2FittextModule} from "ng2-fittext/ng2fittext";
@NgModule({ @NgModule({
imports: [Ng2FittextModule] imports: [
Ng2FittextModule
]
}) })
``` ```
2. Use the directive in your components: 2) Use it in your components
```ts
Fit with specified container (can be the parent or a deeper ancestor)
```sh
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'label', selector: 'label',
template: ` template: `<div #container>
<div #container> <div [fittext]="true" [activateOnResize]="true" [container]="container" [useMaxFontSize]="false">Bla bla bla...</div>
<div [fittext]="true" [activateOnResize]="true" [container]="container">Bla bla bla...</div> </div>`
</div>
`
}) })
export class LabelComponent {} export class LabelComponent {}
``` ```
### Examples
Fit to the parent element (works if you have a variable number of elements between your element and its parent): Fit with the parent element (this works if you have a variable number of element between element and parent)
```sh
```ts
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'label', selector: 'label',
template: ` template: `<div>
<div> <div [fittext]="true" [activateOnResize]="true" [useMaxFontSize]="false">Bla bla bla...</div>
<div [fittext]="true" [activateOnResize]="true">Bla bla bla...</div> </div>`
</div>
`
}) })
export class LabelComponent {} export class LabelComponent {}
``` ```
**NEW! Support for auto-resize input box:**
```ts
**NEW! Support for autoresize input box!**
```sh
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'inputbox', selector: 'inputbox',
template: ` template: `<div #container>
<div #container> <input [fittext]="true" [activateOnResize]="true" [container]="container" [activateOnInputEvents]="true" [useMaxFontSize]="false">`,
<input [fittext]="true" [activateOnResize]="true" [container]="container" [activateOnInputEvents]="true"> </div>`
</div>
`
}) })
export class InputBoxComponent {} export class InputBoxComponent {}
``` ```
**NEW! Support for maxFontSize:**
```ts
**NEW! Support for maxFontSize!**
```sh
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'inputbox', selector: 'inputbox',
template: ` template: `<div>
<div> <input [fittext]="true" [activateOnResize]="true" [activateOnInputEvents]="true" [useMaxFontSize]="true">`,
<input [fittext]="true" [activateOnResize]="true" [activateOnInputEvents]="true" [minFontSize]="40" [maxFontSize]="100"> </div>`
</div>
`
}) })
export class InputBoxComponent {} export class InputBoxComponent {}
``` ```
Input Parameters:
Parameters:
| Parameter | Description | Values | | Parameter | Description | Values |
| ------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------- | | --- | --- | --- |
| fittext | Directive selector | true/false | | fittext | is the selector of the directive | true/false
| container | The container to fit (if not present it fits to the parent container by default) | ElementRef | | container | the container to fit (if not present it fit default to parent container)| ElementRef
| activateOnResize | Enable/disable the autofit on window resize | true or false (default false) | | activateOnResize | enable/disable the autofit in case of window resize | true or false (default false)
| activateOnInputEvents | Enable/disable the auto-fit in case of input box events (keydown, keyup etc..) | true or false (default false) | | activateOnInputEvents | enbale/disable the autofit in case of input box events (keydown, keyup etc..) | true or false (default false)
| maxFontSize | Maximum font size | Number (default is 1000) | | useMaxFontSize | Use font-size from element as maximum font-size | enable/disable the usage of max font-size of the lement
| **!deprecated!** useMaxFontSize | Use max font size if is true | Deprecated! | | minFontSize | minimal font size | number, default is 7
| minFontSize | Minimum font size | Number (default is 7) | | modelToWatch | pass model to watch, when this model changes -> font size is automatically recalculated | any type of model
| modelToWatch | Pass model to watch; when this model changes, font size is automatically recalculated | Any type of model |
Output Parameters:
| Parameter | Description | Values |
| --------------- | ----------------- | ------ |
| fontSizeChanged | Current font size | string |
### Development ### Development
Want to contribute? Great! Want to contribute? Great!
Simply, clone the repository! Simply, clone the repository!
I created this library because I always spent too much time solving this problem and didn't find anything on the web (as of 13/03/2017) that does this without jQuery and is easily integrable in Angular 2+. I created this library because I always spended too much time for solve this problem and because i didn't find nothing on the web (13/03/2017) that do this without jquery and easily integrable in angular2.
While it might not be the best implementation, it gets the job done. For sure is not a good implementation, maybe is not the best way to do it, but, it do the job.
### Todos ### Todos
- Write tests - Write tests
- Find a performant algorithm to find the font size that fits the container better - Find a better algorithm to find the font-size who fits better the container
## License License
----
ISC
MIT
**Lorenzo I.** **Lorenzo I.**

View file

@ -1,140 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"dev": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"prefix": "app"
},
"@schematics/angular:directive": {
"prefix": "app"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "./src/index.html",
"main": "./src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all",
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"buildTarget": "dev:build"
},
"configurations": {
"production": {
"buildTarget": "dev:build:production"
},
"development": {
"buildTarget": "dev:build:development"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
],
"scripts": []
}
}
}
},
"lib": {
"projectType": "library",
"root": "projects/lib",
"sourceRoot": "projects/lib/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "projects/lib/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/lib/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "projects/lib/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "projects/lib/tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
}
},
"cli": {
"analytics": false
}
}

View file

@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'example-root',
templateUrl: './example.component.html',
styleUrls: []
})
export class ExampleComponent { }

4
examples/example.main.ts Normal file
View file

@ -0,0 +1,4 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { ExampleModule } from './example.module';
platformBrowserDynamic().bootstrapModule(ExampleModule);

View file

@ -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 { }

12
examples/index.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<title>Ng2FittextModule Tutorial</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<example-root>Loading...</example-root>
</body>
</html>

0
examples/styles.scss Normal file
View file

1
index.ts Normal file
View file

@ -0,0 +1 @@
export * from './src';

113
karma.conf.js Normal file
View file

@ -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;
}, {});
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

13258
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,85 @@
{ {
"name": "dev", "name": "ng2-fittext",
"version": "0.0.0", "version": "1.0.24",
"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": { "scripts": {
"ng": "./node_modules/.bin/ng", "build": "node ./tasks/build",
"start": "node node_modules/@angular/cli/bin/ng serve", "g": "node ./node_modules/angular-librarian",
"build": "node node_modules/@angular/cli/bin/ng build --configuration production", "lint": "tslint ./src/**/*.ts",
"test": "node node_modules/@angular/cli/bin/ng test lib", "postbuild": "rimraf build",
"test:headless": "node node_modules/@angular/cli/bin/ng test --browsers=ChromeHeadless", "posttagVersion": "npm run build && npm publish dist",
"test:headless:singleRun": "node node_modules/@angular/cli/bin/ng test --no-watch --no-progress --browsers=ChromeHeadless lib", "prebuild": "rimraf dist out-tsc",
"release:minor": "cd ./projects/lib/ && npm version minor", "start": "webpack-dev-server --open --config ./webpack/webpack.dev.js",
"release:major": "cd ./projects/lib/ && npm version major", "tagVersion": "np --no-publish",
"release:patch": "cd ./projects/lib/ && npm version patch", "test": "node ./tasks/test"
"pack": "npm run build lib && cp ./README.md ./dist/lib/README.md"
}, },
"private": true, "keywords": [],
"author": "",
"license": "ISC",
"dependencies": { "dependencies": {
"@angular/animations": "^18.0.6", "@angular/common": "^4.0.0",
"@angular/common": "^18.0.6", "@angular/compiler": "^4.0.0",
"@angular/compiler": "^18.0.6", "@angular/core": "^4.0.0",
"@angular/core": "^18.0.6", "@angular/platform-browser": "^4.0.0",
"@angular/forms": "^18.0.6", "@angular/platform-browser-dynamic": "^4.0.0",
"@angular/platform-browser": "^18.0.6", "core-js": "^2.4.1",
"@angular/platform-browser-dynamic": "^18.0.6", "rxjs": "^5.0.1",
"@angular/router": "^18.0.6", "zone.js": "0.8.12"
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^18.0.7", "@angular/compiler-cli": "^4.0.0",
"@angular/cli": "^18.0.7", "@types/jasmine": "2.5.38",
"@angular/compiler-cli": "^18.0.6", "@types/node": "^6.0.42",
"@types/jasmine": "~5.1.0", "angular-librarian": "1.0.0-beta.14",
"@types/jasminewd2": "~2.0.3", "angular2-template-loader": "0.6.0",
"@types/node": "^18.18.0", "awesome-typescript-loader": "^3.0.0",
"jasmine-core": "~5.1.0", "codelyzer": "~3.0.0",
"karma": "~6.4.0", "css-loader": "^0.26.1",
"karma-chrome-launcher": "~3.2.0", "css-to-string-loader": "^0.1.3",
"karma-coverage": "~2.2.0", "extract-text-webpack-plugin": "^2.1.0",
"karma-jasmine": "~5.1.0", "file-loader": "^0.8.5",
"karma-jasmine-html-reporter": "~2.1.0", "fs-extra": "^2.1.2",
"ng-packagr": "^18.0.0", "html-webpack-plugin": "^2.19.0",
"typescript": "5.4.5" "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"
},
"repository": {
"url": "https://github.com/lokenxo/ng2-fittext.git"
} }
} }

View file

@ -1,24 +0,0 @@
# Lib
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.0.
## Code scaffolding
Run `ng generate component component-name --project lib` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project lib`.
> Note: Don't forget to add `--project lib` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build lib` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build lib`, go to the dist folder `cd dist/lib` and run `npm publish`.
## Running unit tests
Run `ng test lib` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View file

@ -1,7 +0,0 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/lib",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View file

@ -1,43 +0,0 @@
{
"name": "ng2-fittext",
"version": "2.0.0",
"description": "Ng2-fittext: An Angular2+ directive that change the font size until it fit the upper level container dimension.",
"keywords": [
"ng2-fittext",
"fittext",
"angular",
"angular 2",
"angular 4",
"angular 5",
"angular 6",
"angular 7",
"angular 8",
"angular 9",
"angular 10",
"angular 11",
"angular 12",
"angular 13",
"angular 14",
"angular 15",
"angular 16",
"angular 17",
"angular 18",
"fit text",
"responsive text",
"responsive font size",
"font size"
],
"author": "Lorenzo Iovino",
"license": "MIT",
"peerDependencies": {
"@angular/common": ">=18.0.0",
"@angular/core": ">=18.0.0"
},
"dependencies": {
"tslib": "^2.3.0"
},
"repository": {
"url": "https://github.com/thisloke/ng2-fittext.git"
},
"sideEffects": false
}

View file

@ -1,310 +0,0 @@
import { Ng2FittextDirective } from './ng2-fittext.directive';
import { Renderer2, ElementRef } from '@angular/core';
describe('Class: Ng2FittextDirective', () => {
let ng2FittextDirective: Ng2FittextDirective;
let elMock: ElementRef;
beforeEach(() => {
elMock = {} as ElementRef;
ng2FittextDirective = new Ng2FittextDirective(elMock);
});
describe('Method: setFontSize', () => {
let newFontSize: number;
let isVisibleSpy: jasmine.Spy;
let isDoneSpy: jasmine.Spy;
beforeEach(() => {
newFontSize = 100;
isVisibleSpy = spyOn(ng2FittextDirective, 'isVisible').and.returnValue(
true
);
isDoneSpy = spyOn(ng2FittextDirective, 'isDone').and.returnValue(false);
elMock.nativeElement = {
style: {
setProperty: () => {},
},
};
});
it('Should not change the font size if the element is not visible', () => {
isVisibleSpy.and.returnValue(false);
const previousFontSize: number = ng2FittextDirective.getFontSize();
ng2FittextDirective.setFontSize(newFontSize);
expect(ng2FittextDirective.getFontSize()).toEqual(previousFontSize);
});
it('Should not change the font size if the fitting operation is done', () => {
isDoneSpy.and.returnValue(true);
const previousFontSize: number = ng2FittextDirective.getFontSize();
ng2FittextDirective.setFontSize(newFontSize);
expect(ng2FittextDirective.getFontSize()).toEqual(previousFontSize);
});
it('Should use the minFontSize property value if the specified font size is smaller', () => {
const minFontSize: number = ng2FittextDirective.minFontSize;
newFontSize = 5;
ng2FittextDirective.setFontSize(newFontSize);
const currentFontSize: number = ng2FittextDirective.getFontSize();
expect(currentFontSize).toEqual(minFontSize);
});
it('Should use the maxFontSize property value if the specified font size is bigger', () => {
const maxFontSize: number = ng2FittextDirective.maxFontSize;
newFontSize = 1001;
ng2FittextDirective.setFontSize(newFontSize);
const currentFontSize: number = ng2FittextDirective.getFontSize();
expect(currentFontSize).toEqual(maxFontSize);
});
it('Should set a new fontSize value', () => {
newFontSize = 500;
ng2FittextDirective.setFontSize(newFontSize);
const currentFontSize: number = ng2FittextDirective.getFontSize();
expect(currentFontSize).toEqual(newFontSize);
});
it('Should emit the font size change', () => {
newFontSize = 500;
spyOn(ng2FittextDirective.fontSizeChanged, 'emit');
ng2FittextDirective.setFontSize(newFontSize);
expect(ng2FittextDirective.fontSizeChanged.emit).toHaveBeenCalledWith(
newFontSize
);
});
it('Should update the nativeElement with the new font size', () => {
newFontSize = 500;
spyOn(ng2FittextDirective.el.nativeElement.style, 'setProperty');
ng2FittextDirective.setFontSize(newFontSize);
expect(
ng2FittextDirective.el.nativeElement.style.setProperty
).toHaveBeenCalledWith('font-size', `${newFontSize}px`);
});
});
describe('Method: getFontSize', () => {
it('Should return the current font size', () => {
expect(ng2FittextDirective.getFontSize()).toEqual(1000);
});
});
describe('Method: calculateFontSize', () => {
it('Should return the font size rounded down', () => {
expect(ng2FittextDirective.calculateFontSize(10, 3)).toEqual(3);
expect(ng2FittextDirective.calculateFontSize(9, 3)).toEqual(3);
expect(ng2FittextDirective.calculateFontSize(8, 3)).toEqual(2);
});
});
describe('Method: checkOverflow', () => {
let parentElementMock: HTMLElement;
let childrenElementMock: HTMLElement;
let hasXAxisOverflowSpy: jasmine.Spy;
let hasYAxisOverflowSpy: jasmine.Spy;
beforeEach(() => {
parentElementMock = {} as HTMLElement;
childrenElementMock = {} as HTMLElement;
hasXAxisOverflowSpy = spyOn(
ng2FittextDirective,
'hasXAxisOverflow'
).and.returnValue(false);
hasYAxisOverflowSpy = spyOn(
ng2FittextDirective,
'hasYAxisOverflow'
).and.returnValue(false);
});
it('Should return false if no overflow is present', () => {
expect(
ng2FittextDirective.checkOverflow(
parentElementMock,
childrenElementMock
)
).toBe(false);
});
it('Should return true if x axis has overflow', () => {
hasXAxisOverflowSpy.and.returnValue(true);
expect(
ng2FittextDirective.checkOverflow(
parentElementMock,
childrenElementMock
)
).toBe(true);
});
it('Should return true if y axis has overflow', () => {
hasYAxisOverflowSpy.and.returnValue(true);
expect(
ng2FittextDirective.checkOverflow(
parentElementMock,
childrenElementMock
)
).toBe(true);
});
});
describe('Method: getStartFontSizeFromHeight', () => {
it('Should return the container clientHeight value if the container is present', () => {
const containerClientHeight = 10;
ng2FittextDirective.container = {
clientHeight: containerClientHeight,
} as HTMLElement;
expect(ng2FittextDirective.getStartFontSizeFromHeight()).toEqual(
containerClientHeight
);
});
it('Should return the parentElement clientHeight value if no container is present', () => {
const parentlientHeight = 11;
elMock.nativeElement = {
parentElement: {
clientHeight: parentlientHeight,
},
} as HTMLElement;
expect(ng2FittextDirective.getStartFontSizeFromHeight()).toEqual(
parentlientHeight
);
});
});
describe('Method: isDone', () => {
it('Should return the done property value', () => {
const defaultDoneValue = false;
expect(ng2FittextDirective.isDone()).toBe(defaultDoneValue);
});
});
describe('Method: isVisible', () => {
it('Should return the true if getStartFontSizeFromHeight() is greater than zero', () => {
spyOn(ng2FittextDirective, 'getStartFontSizeFromHeight').and.returnValue(
1
);
expect(ng2FittextDirective.isVisible()).toBe(true);
});
it('Should return the false if getStartFontSizeFromHeight() is smaller or equal to zero', () => {
const spy = spyOn(
ng2FittextDirective,
'getStartFontSizeFromHeight'
).and.returnValue(0);
expect(ng2FittextDirective.isVisible()).toBe(false);
spy.and.returnValue(-1);
expect(ng2FittextDirective.isVisible()).toBe(false);
});
});
describe('Method: hasXAxisOverflow', () => {
let parentElementMock: HTMLElement;
let childrenElementMock: HTMLElement;
beforeEach(() => {
parentElementMock = {
clientWidth: 0,
} as HTMLElement;
childrenElementMock = {
scrollWidth: 0,
} as HTMLElement;
});
it('Should return false if no overflow is present on the x axis', () => {
expect(
ng2FittextDirective.hasXAxisOverflow(
parentElementMock,
childrenElementMock
)
).toBe(false);
});
it('Should return true if overflow is present on the x axis', () => {
childrenElementMock = {
scrollWidth: 2,
} as HTMLElement;
expect(
ng2FittextDirective.hasXAxisOverflow(
parentElementMock,
childrenElementMock
)
).toBe(true);
});
});
describe('Method: hasYAxisOverflow', () => {
let parentElementMock: HTMLElement;
let childrenElementMock: HTMLElement;
beforeEach(() => {
parentElementMock = {
clientHeight: 0,
} as HTMLElement;
childrenElementMock = {
clientHeight: 0,
} as HTMLElement;
});
it('Should return false if no overflow is present on the x axis', () => {
expect(
ng2FittextDirective.hasYAxisOverflow(
parentElementMock,
childrenElementMock
)
).toBe(false);
});
it('Should return true if overflow is present on the x axis', () => {
childrenElementMock = {
clientHeight: 2,
} as HTMLElement;
expect(
ng2FittextDirective.hasYAxisOverflow(
parentElementMock,
childrenElementMock
)
).toBe(true);
});
});
describe('Method: hasOverflow', () => {
let containerMock: any;
let parentElementMock: any;
beforeEach(() => {
containerMock = {
isContainer: true,
};
parentElementMock = {
isParentElement: true,
};
ng2FittextDirective.container = { ...containerMock };
ng2FittextDirective.el.nativeElement = {
parentElement: { ...parentElementMock },
} as HTMLElement;
});
it('Should calculate the overflow using the container if is present', () => {
spyOn(ng2FittextDirective, 'checkOverflow').and.callFake(
(parentElement: any, childrenElement: any) => {
expect(parentElement).toEqual(containerMock);
return true;
}
);
expect(ng2FittextDirective.hasOverflow()).toBe(true);
});
it('Should calculate the overflow using the parent element if the container is not present', () => {
if(ng2FittextDirective) {
delete (ng2FittextDirective as any).container;
}
spyOn(ng2FittextDirective, 'checkOverflow').and.callFake(
(parentElement: any, childrenElement: any) => {
expect(parentElement).toEqual(parentElementMock);
return true;
}
);
expect(ng2FittextDirective.hasOverflow()).toBe(true);
});
});
});

View file

@ -1,170 +0,0 @@
import {
AfterViewChecked,
AfterViewInit,
Directive,
ElementRef,
HostListener,
Input,
Output,
EventEmitter,
OnChanges,
OnInit,
} from '@angular/core';
@Directive({
selector: '[fittext]',
})
export class Ng2FittextDirective
implements AfterViewInit, OnInit, OnChanges, AfterViewChecked {
@Input('fittext') fittext: any;
@Input('activateOnResize') activateOnResize: boolean = true;
@Input('container') container: HTMLElement | null = null;
@Input('activateOnInputEvents') activateOnInputEvents: boolean = false;
@Input('minFontSize') minFontSize = 7;
@Input('maxFontSize') maxFontSize = 1000;
/* Deprecated */
@Input('useMaxFontSize') useMaxFontSize = true;
@Input('modelToWatch') modelToWatch: any;
@Output() fontSizeChanged: EventEmitter<number> = new EventEmitter();
private fontSize = 1000;
private speed = 1.05;
private done = false;
constructor(public el: ElementRef<HTMLElement>) {}
setFontSize(fontSize: number): void {
if (this.isVisible() && !this.isDone()) {
if (fontSize < this.minFontSize) {
fontSize = this.minFontSize;
}
if (fontSize > this.maxFontSize) {
fontSize = this.maxFontSize;
}
this.fontSize = fontSize;
this.fontSizeChanged.emit(fontSize);
this.el.nativeElement.style.setProperty(
'font-size',
fontSize.toString() + 'px'
);
}
}
getFontSize(): number {
return this.fontSize;
}
calculateFontSize(fontSize: number, speed: number): number {
return Math.floor(fontSize / speed);
}
checkOverflow(parent: HTMLElement, children: HTMLElement): boolean {
return (
this.hasXAxisOverflow(parent, children) ||
this.hasYAxisOverflow(parent, children)
);
}
hasXAxisOverflow(parent: HTMLElement, children: HTMLElement): boolean {
return children.scrollWidth - parent.clientWidth > 0;
}
hasYAxisOverflow(parent: HTMLElement, children: HTMLElement): boolean {
return children.clientHeight - parent.clientHeight > 0;
}
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
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(event: Event) {
this.done = false;
if (this.activateOnInputEvents && this.fittext) {
this.setFontSize(this.getStartFontSizeFromHeight());
this.ngAfterViewInit();
}
}
ngOnInit() {
this.done = false;
this.el.nativeElement.style.setProperty('will-change', 'content');
this.ngAfterViewInit();
}
ngAfterViewInit() {
if (this.isVisible() && !this.isDone()) {
if (this.fittext) {
if (this.hasOverflow()) {
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 {
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.done = false;
this.setFontSize(this.maxFontSize);
this.ngAfterViewInit();
});
}
}
ngAfterViewChecked() {
if (this.fontSize > this.minFontSize) {
this.setFontSize(this.getStartFontSizeFromHeight());
this.ngAfterViewInit();
}
}
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;
}
isDone(): boolean {
return this.done;
}
isVisible(): boolean {
return this.getStartFontSizeFromHeight() > 0;
}
hasOverflow(): boolean {
return this.container
? this.checkOverflow(this.container, this.el.nativeElement)
: this.checkOverflow(
this.el.nativeElement.parentElement!,
this.el.nativeElement
);
}
}

View file

@ -1,19 +0,0 @@
import { Ng2FittextDirective } from './ng2-fittext.directive';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
export { Ng2FittextDirective } from './ng2-fittext.directive';
@NgModule({
declarations: [Ng2FittextDirective],
exports: [Ng2FittextDirective],
imports: [CommonModule],
})
export class Ng2FittextModule {
static forRoot() {
return {
ngModule: Ng2FittextModule,
providers: [],
};
}
}

View file

@ -1,6 +0,0 @@
/*
* Public API Surface of lib
*/
export * from './ng2-fittext.directive';
export * from './ng2-fittext.module';

View file

@ -1,14 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"**/*.spec.ts"
]
}

View file

@ -1,10 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

View file

@ -1,14 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View file

@ -1,7 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"arrowParens": "always"
}

View file

@ -1,30 +0,0 @@
<h2>Add text or remove text from div (max and min font size)</h2>
<div style="border: 2px solid; overflow:hidden; width:100%; height:300px; font-size:520px;">
<div [fittext]="true"
[modelToWatch]="title"
[maxFontSize]="50"
[minFontSize]="30"
[activateOnResize]="true">{{title}}</div>
</div>
<h2>Add text or remove text from div (no max and no min font size)</h2>
<div style="border: 2px solid; overflow:hidden; width:100%; height:300px; font-size:520px;">
<div [fittext]="true"
[modelToWatch]="title"
[activateOnResize]="true">{{title}}</div>
</div>
<button (click)="click('add')"> ADD text</button>
<button (click)="click('remove')"> REMOVE text</button>
<br/>
<br/>
<h2>Write text in input box (text fit inside without overflowing)</h2>
<div #cont2 style="border: 2px solid; height:101px; width:300px;">
<input [fittext]="true"
style="width:100%; height:100px"
[activateOnResize]="true"
[activateOnInputEvents]="true"
[container]="cont2">
</div>

View file

@ -1,15 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'sdf fsd fsd fsdfsdfsdfsdfsfdf sdfsdf sdfs df fsdfsdfsdfsdfsfdf fsdfsdfsdfsdfsfdf fsdfsdfsdfsdfsfdf ' +
'fsdfsdfsdfsdfsfdf fsdfsdfsdfsdfsfdf';
click(par: string) {
this.title = par === 'add' ? (this.title + this.title) : (this.title.substring(0, this.title.length / 2 + 10));
}
}

View file

@ -1,17 +0,0 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { Ng2FittextModule } from '../../projects/lib/src/ng2-fittext.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
Ng2FittextModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View file

@ -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();
});
});

View file

@ -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;
}
}

View file

@ -1,3 +0,0 @@
export const environment = {
production: true
};

View file

@ -1,15 +0,0 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* In development mode, to ignore zone related error stack frames such as
* `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
* import the following file, but please comment it out in production mode
* because it will have performance impact when throw error
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -1,14 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dev</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

1
src/index.ts Normal file
View file

@ -0,0 +1 @@
export * from './ng2-fittext.module';

View file

@ -1,12 +0,0 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

23
src/ng2-fittext.module.ts Normal file
View file

@ -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: []
};
}
}

24
src/test.js Normal file
View file

@ -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);

View file

@ -1,14 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);

19
src/vendor.ts Normal file
View file

@ -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';

69
tasks/build.js Normal file
View file

@ -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);
});

16
tasks/copy-build.js Normal file
View file

@ -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');
}

35
tasks/copy-globs.js Normal file
View file

@ -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;

186
tasks/inline-resources.js Normal file
View file

@ -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');
}

134
tasks/rollup.js Normal file
View file

@ -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;

66
tasks/test.js Normal file
View file

@ -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]);
}

View file

@ -1,15 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": [
"node"
]
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

34
tsconfig.es2015.json Normal file
View file

@ -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
}
}

34
tsconfig.es5.json Normal file
View file

@ -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
}
}

View file

@ -1,41 +1,28 @@
{ {
"compileOnSave": false, "angularCompilerOptions": {
"strictMetadataEmit": true,
"skipTemplateCodegen": true
},
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "",
"outDir": "./dist/out-tsc", "declaration": true,
"forceConsistentCasingInFileNames": true, "emitDecoratorMetadata": true,
"strict": true, "experimentalDecorators": true,
"noImplicitOverride": true, "lib": ["es2015", "dom"],
"noPropertyAccessFromIndexSignature": true, "mapRoot": "./",
"paths": { "module": "es2015",
"lib": [ "moduleResolution": "node",
"./dist/lib" "outDir": "./dist",
"sourceMap": true,
"target": "es5",
"typeRoots": [
"./node_modules/@types"
] ]
}, },
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
],
},
"files": [ "files": [
"./src/main.ts" "./src/index.ts"
], ],
"angularCompilerOptions": { "awesomeTypescriptLoaderOptions": {
"enableI18nLegacyMessageIdFormat": false, "forkChecker": true
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
} }
} }

View file

@ -1,14 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

20
tsconfig.test.json Normal file
View file

@ -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"
]
}

116
tslint.json Normal file
View file

@ -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
}
}

70
webpack/webpack.common.js Normal file
View file

@ -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);
};

77
webpack/webpack.dev.js Normal file
View file

@ -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')
})
]
});

46
webpack/webpack.test.js Normal file
View file

@ -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']
}
});
};

66
webpack/webpack.utils.js Normal file
View file

@ -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;
};