Compare commits

..

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

44 changed files with 320 additions and 14465 deletions

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

21
.gitignore vendored
View file

@ -1,10 +1,11 @@
build
coverage
dist
debug.log
node_modules
out-tsc
/.vs
/.angular
.idea
.DS_Store
# Node generated files
node_modules
npm-debug.log
# OS generated files
Thumbs.db
.DS_Store
# Ignored files
*.js
*.map
*.d.ts
.idea

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

7
.npmignore Normal file
View file

@ -0,0 +1,7 @@
# Node generated files
node_modules
npm-debug.log
# OS generated files
Thumbs.db
.DS_Store
# Ignored files

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
}

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.

196
README.md
View file

@ -1,141 +1,133 @@
<h1 align="center">
ng2-fittext
</h1>
# ng2-fittext
<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)
![Angular](https://img.shields.io/badge/Angular-18-green.svg)
![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)
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!
How you can say it? ng2-fittext!
## 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>
### Demo
## Installation
http://plnkr.co/edit/v0TQaYepV4E2Heur02j5?p=preview
Install the library using npm according to your angular version (see table below):
| 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``` |
### Installation
Install the library
```sh
$ npm install --save ng2-fittext
```
### Usage
1. Import the module in your Angular application:
```ts
import { Ng2FittextModule } from "ng2-fittext";
Import it in your Angular2 project like a module
@NgModule({
imports: [Ng2FittextModule]
})
```
1) Declare it in your module
```sh
import {Ng2FittextModule} from "ng2-fittext/ng2fittext";
@NgModule({
imports: [
Ng2FittextModule
]
})
2. Use the directive in your components:
```ts
import { Component } from '@angular/core';
```
@Component({
selector: 'label',
template: `
<div #container>
<div [fittext]="true" [activateOnResize]="true" [container]="container">Bla bla bla...</div>
</div>
`
})
export class LabelComponent {}
```
2) Use it in your components
### Examples
Fit with specified container (can be the parent or a deeper ancestor)
```sh
import {Component} from '@angular/core';
Fit to the parent element (works if you have a variable number of elements between your element and its parent):
@Component({
selector: 'label',
template: `<div #container>
<div [fittext]="true" [activateOnResize]="true" [container]="container" [useMaxFontSize]="false">Bla bla bla...</div>
</div>`
})
```ts
import { Component } from '@angular/core';
export class LabelComponent {}
```
@Component({
selector: 'label',
template: `
<div>
<div [fittext]="true" [activateOnResize]="true">Bla bla bla...</div>
</div>
`
})
export class LabelComponent {}
```
**NEW! Support for auto-resize input box:**
Fit with the parent element (this works if you have a variable number of element between element and parent)
```sh
import {Component} from '@angular/core';
```ts
import { Component } from '@angular/core';
@Component({
selector: 'label',
template: `<div>
<div [fittext]="true" [activateOnResize]="true" [useMaxFontSize]="false">Bla bla bla...</div>
</div>`
})
@Component({
selector: 'inputbox',
template: `
<div #container>
<input [fittext]="true" [activateOnResize]="true" [container]="container" [activateOnInputEvents]="true">
</div>
`
})
export class InputBoxComponent {}
```
export class LabelComponent {}
```
**NEW! Support for maxFontSize:**
```ts
import { Component } from '@angular/core';
@Component({
selector: 'inputbox',
template: `
<div>
<input [fittext]="true" [activateOnResize]="true" [activateOnInputEvents]="true" [minFontSize]="40" [maxFontSize]="100">
</div>
`
})
export class InputBoxComponent {}
```
**NEW! Support for autoresize input box!**
Input Parameters:
```sh
import {Component} from '@angular/core';
| Parameter | Description | Values |
| ------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------- |
| fittext | Directive selector | true/false |
| container | The container to fit (if not present it fits to the parent container by default) | ElementRef |
| activateOnResize | Enable/disable the autofit on 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) |
| maxFontSize | Maximum font size | Number (default is 1000) |
| **!deprecated!** useMaxFontSize | Use max font size if is true | Deprecated! |
| 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 |
@Component({
selector: 'inputbox',
template: `<div #container>
<input [fittext]="true" [activateOnResize]="true" [container]="container" [activateOnInputEvents]="true" [useMaxFontSize]="false">`,
</div>`
})
Output Parameters:
export class InputBoxComponent {}
```
**NEW! Support for maxFontSize!**
```sh
import {Component} from '@angular/core';
@Component({
selector: 'inputbox',
template: `<div>
<input [fittext]="true" [activateOnResize]="true" [activateOnInputEvents]="true" [useMaxFontSize]="true">`,
</div>`
})
export class InputBoxComponent {}
```
Parameters:
| Parameter | Description | Values |
| --- | --- | --- |
| fittext | is the selector of the directive | true/false
| container | the container to fit (if not present it fit default to parent container)| ElementRef
| activateOnResize | enable/disable the autofit in case of window resize | true or false (default false)
| activateOnInputEvents | enbale/disable the autofit in case of input box events (keydown, keyup etc..) | true or false (default false)
| useMaxFontSize | Use font-size from element as maximum font-size | enable/disable the usage of max font-size of the lement
| minFontSize | minimal font size | number, default is 7
| modelToWatch | pass model to watch, when this model changes -> font size is automatically recalculated | any type of model
| Parameter | Description | Values |
| --------------- | ----------------- | ------ |
| fontSizeChanged | Current font size | string |
### Development
Want to contribute? Great!
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+.
While it might not be the best implementation, it gets the job done.
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.
For sure is not a good implementation, maybe is not the best way to do it, but, it do the job.
### Todos
- Write tests
- Find a performant algorithm to find the font size that fits the container better
- Write tests
- Find a better algorithm to find the font-size who fits better the container
## License
License
----
ISC
MIT
**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
}
}

1
index.ts Normal file
View file

@ -0,0 +1 @@
export {Ng2FittextModule} from './src/ng2fittext.module';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

1
ng2fittext.ts Normal file
View file

@ -0,0 +1 @@
export {Ng2FittextModule} from './src/ng2fittext.module';

13258
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,43 @@
{
"name": "dev",
"version": "0.0.0",
"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",
"scripts": {
"ng": "./node_modules/.bin/ng",
"start": "node node_modules/@angular/cli/bin/ng serve",
"build": "node node_modules/@angular/cli/bin/ng build --configuration production",
"test": "node node_modules/@angular/cli/bin/ng test lib",
"test:headless": "node node_modules/@angular/cli/bin/ng test --browsers=ChromeHeadless",
"test:headless:singleRun": "node node_modules/@angular/cli/bin/ng test --no-watch --no-progress --browsers=ChromeHeadless lib",
"release:minor": "cd ./projects/lib/ && npm version minor",
"release:major": "cd ./projects/lib/ && npm version major",
"release:patch": "cd ./projects/lib/ && npm version patch",
"pack": "npm run build lib && cp ./README.md ./dist/lib/README.md"
"prepublish": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.0.6",
"@angular/common": "^18.0.6",
"@angular/compiler": "^18.0.6",
"@angular/core": "^18.0.6",
"@angular/forms": "^18.0.6",
"@angular/platform-browser": "^18.0.6",
"@angular/platform-browser-dynamic": "^18.0.6",
"@angular/router": "^18.0.6",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
"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",
"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"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.0.7",
"@angular/cli": "^18.0.7",
"@angular/compiler-cli": "^18.0.6",
"@types/jasmine": "~5.1.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^18.18.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"ng-packagr": "^18.0.0",
"typescript": "5.4.5"
"typescript": "~2.2.2"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"@angular/core": "^4.3.4"
}
}

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

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>

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

View file

@ -0,0 +1,8 @@
import { FittextDirective } from './fittext.directive';
describe('FittextDirective', () => {
it('should create an instance', () => {
const directive = new FittextDirective();
expect(directive).toBeTruthy();
});
});

134
src/ng2fittext.directive.ts Normal file
View file

@ -0,0 +1,134 @@
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;
}
}

11
src/ng2fittext.module.ts Normal file
View file

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { Ng2FittextDirective } from "./ng2fittext.directive";
@NgModule({
declarations: [
Ng2FittextDirective
],
exports: [Ng2FittextDirective]
})
export class Ng2FittextModule {};

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

View file

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

View file

@ -1,41 +1,19 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"paths": {
"lib": [
"./dist/lib"
]
},
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"sourceMap": true,
"inlineSources": false,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
],
"emitDecoratorMetadata": true,
"stripInternal": true,
"skipLibCheck": true
},
"files": [
"./src/main.ts"
],
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
"ng2fittext.ts",
"ng2fittext.d.ts",
"index.ts"
]
}

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