@chitovas/ngx-clamp 0.1.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -25
- package/fesm2022/chitovas-ngx-clamp.mjs +15 -9
- package/fesm2022/chitovas-ngx-clamp.mjs.map +1 -1
- package/index.d.ts +4 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,48 +1,209 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @chitovas/ngx-clamp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
[](https://www.npmjs.com/package/@chitovas/ngx-clamp)
|
|
5
|
+

|
|
6
|
+
[](https://www.npmjs.com/package/@chitovas/ngx-clamp)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
An Angular library that provides elegant text overflow management with support for legacy browsers. Clamp text content to a specific number of lines or maximum height, with customizable truncation indicators.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
## Why ngx-clamp?
|
|
11
|
+
|
|
12
|
+
Modern CSS properties like `-webkit-line-clamp` and `line-clamp` aren't supported in older browsers. This library provides a cross-browser solution that works consistently everywhere, with additional features like custom truncation text and nested element support.
|
|
8
13
|
|
|
9
14
|
## Features
|
|
10
15
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
+
- ✨ **Simple Integration** - Single directive, no complex setup required
|
|
17
|
+
- 🎯 **Flexible Clamping** - Clamp by line count or maximum height
|
|
18
|
+
- 🎨 **Customizable Truncation** - Use ellipsis or custom text (e.g., "Read more...")
|
|
19
|
+
- 🏗️ **Nested HTML Support** - Works seamlessly with complex nested structures
|
|
20
|
+
- 🌐 **Universal Browser Support** - Reliable fallback for legacy browsers
|
|
21
|
+
- 📦 **Lightweight** - Minimal bundle size impact
|
|
16
22
|
|
|
17
23
|
## Installation
|
|
18
24
|
|
|
19
|
-
Install the package using npm:
|
|
20
|
-
|
|
21
25
|
```bash
|
|
22
|
-
npm install @
|
|
26
|
+
npm install @chitovas/ngx-clamp
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
##
|
|
29
|
+
## Quick Start
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
### 1. Import the Directive
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Component } from '@angular/core';
|
|
35
|
+
import { NgxClamp } from '@chitovas/ngx-clamp';
|
|
29
36
|
|
|
30
37
|
@Component({
|
|
31
|
-
selector: '
|
|
38
|
+
selector: 'app-example',
|
|
39
|
+
standalone: true,
|
|
40
|
+
imports: [NgxClamp],
|
|
41
|
+
template: ` <div ngxClamp [lines]="3">Your long text content here...</div> `,
|
|
42
|
+
})
|
|
43
|
+
export class ExampleComponent {}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Basic Usage Examples
|
|
47
|
+
|
|
48
|
+
#### Clamp by Line Count
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<div ngxClamp [lines]="3">
|
|
52
|
+
This text will be clamped to 3 lines. Any content exceeding this limit will be truncated and replaced with an ellipsis (...)
|
|
53
|
+
</div>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Clamp by Maximum Height
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<div ngxClamp [maxHeight]="100">
|
|
60
|
+
This text will be clamped when it exceeds 100 pixels in height. The overflow content will be hidden with an ellipsis.
|
|
61
|
+
</div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Custom Truncation Text
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<div ngxClamp [lines]="3" truncationText=" Read more...">
|
|
68
|
+
This text will show "Read more..." instead of the default ellipsis when the content is clamped.
|
|
69
|
+
</div>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### With Nested Elements
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<div ngxClamp [lines]="4" truncationText="...">
|
|
76
|
+
<h3>Article Title</h3>
|
|
77
|
+
<p>First paragraph with some content...</p>
|
|
78
|
+
<p>Second paragraph that might get clamped...</p>
|
|
79
|
+
<span>Additional nested content</span>
|
|
80
|
+
</div>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API Reference
|
|
84
|
+
|
|
85
|
+
### Directive: `ngxClamp`
|
|
86
|
+
|
|
87
|
+
| Input | Type | Default | Description |
|
|
88
|
+
| ---------------- | -------- | ------- | ------------------------------------------ |
|
|
89
|
+
| `lines` | `number` | - | Number of lines to display before clamping |
|
|
90
|
+
| `maxHeight` | `number` | - | Maximum height in pixels before clamping |
|
|
91
|
+
| `truncationText` | `string` | `'...'` | Text to display when content is clamped |
|
|
92
|
+
|
|
93
|
+
**Note**: Use either `lines` or `maxHeight`, not both. If both are provided, `lines` takes precedence.
|
|
94
|
+
|
|
95
|
+
## Advanced Examples
|
|
96
|
+
|
|
97
|
+
### Article Preview Card
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
@Component({
|
|
101
|
+
selector: 'app-article-card',
|
|
102
|
+
standalone: true,
|
|
103
|
+
imports: [NgxClamp],
|
|
32
104
|
template: `
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</div>
|
|
105
|
+
<article class="card">
|
|
106
|
+
<h2>{{ article.title }}</h2>
|
|
107
|
+
<div ngxClamp [lines]="3" truncationText=" [Read more]">
|
|
108
|
+
{{ article.content }}
|
|
109
|
+
</div>
|
|
110
|
+
</article>
|
|
40
111
|
`,
|
|
112
|
+
})
|
|
113
|
+
export class ArticleCardComponent {
|
|
114
|
+
article = {
|
|
115
|
+
title: 'Understanding Angular',
|
|
116
|
+
content: 'Very long article content...',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Product Description
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<div class="product-info">
|
|
125
|
+
<div ngxClamp [maxHeight]="120" truncationText="... See full description">
|
|
126
|
+
<h4>Product Features</h4>
|
|
127
|
+
<ul>
|
|
128
|
+
<li>Feature one with detailed description</li>
|
|
129
|
+
<li>Feature two with more information</li>
|
|
130
|
+
<li>Feature three that might be hidden</li>
|
|
131
|
+
</ul>
|
|
132
|
+
<p>Additional product details and specifications...</p>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Dynamic Content
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
@Component({
|
|
141
|
+
selector: 'app-dynamic-clamp',
|
|
142
|
+
standalone: true,
|
|
41
143
|
imports: [NgxClamp],
|
|
144
|
+
template: `
|
|
145
|
+
<div ngxClamp [lines]="maxLines" [truncationText]="truncText">
|
|
146
|
+
{{ dynamicContent }}
|
|
147
|
+
</div>
|
|
148
|
+
<button (click)="toggleExpand()">
|
|
149
|
+
{{ expanded ? 'Show Less' : 'Show More' }}
|
|
150
|
+
</button>
|
|
151
|
+
`,
|
|
42
152
|
})
|
|
43
|
-
export class
|
|
153
|
+
export class DynamicClampComponent {
|
|
154
|
+
maxLines = 3;
|
|
155
|
+
expanded = false;
|
|
156
|
+
truncText = ' ...more';
|
|
157
|
+
dynamicContent = 'Your long dynamic content...';
|
|
158
|
+
|
|
159
|
+
toggleExpand() {
|
|
160
|
+
this.maxLines = this.expanded ? 3 : 999;
|
|
161
|
+
this.expanded = !this.expanded;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
44
164
|
```
|
|
45
165
|
|
|
166
|
+
## Browser Support
|
|
167
|
+
|
|
168
|
+
- ✅ Chrome (all versions)
|
|
169
|
+
- ✅ Firefox (all versions)
|
|
170
|
+
- ✅ Safari (all versions)
|
|
171
|
+
- ✅ Edge (all versions)
|
|
172
|
+
- ✅ IE11 and older legacy browsers
|
|
173
|
+
|
|
174
|
+
## Migration Guide
|
|
175
|
+
|
|
176
|
+
### From CSS line-clamp
|
|
177
|
+
|
|
178
|
+
**Before** (CSS only):
|
|
179
|
+
|
|
180
|
+
```css
|
|
181
|
+
.text {
|
|
182
|
+
display: -webkit-box;
|
|
183
|
+
-webkit-line-clamp: 3;
|
|
184
|
+
-webkit-box-orient: vertical;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**After** (with ngx-clamp):
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<div ngxClamp [lines]="3" class="text">Your content here</div>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
198
|
+
|
|
46
199
|
## License
|
|
47
200
|
|
|
48
|
-
|
|
201
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
202
|
+
|
|
203
|
+
## Support
|
|
204
|
+
|
|
205
|
+
- 🐛 [Report Issues](https://github.com/Chitova263/ngx-clamp/issues)
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
Made with ❤️ by [@chitovas](https://github.com/Chitova263)
|
|
@@ -3,10 +3,11 @@ import { Input, Directive } from '@angular/core';
|
|
|
3
3
|
|
|
4
4
|
class NgxClamp {
|
|
5
5
|
htmlElementRef;
|
|
6
|
-
maxHeight =
|
|
7
|
-
|
|
6
|
+
maxHeight = null;
|
|
7
|
+
lines = 0;
|
|
8
8
|
maxLines = 0;
|
|
9
9
|
splitOnWordsCharacter = ' ';
|
|
10
|
+
truncationCharacters = '...';
|
|
10
11
|
constructor(htmlElementRef) {
|
|
11
12
|
this.htmlElementRef = htmlElementRef;
|
|
12
13
|
}
|
|
@@ -14,14 +15,17 @@ class NgxClamp {
|
|
|
14
15
|
this.clamp();
|
|
15
16
|
}
|
|
16
17
|
ngOnChanges(changes) {
|
|
17
|
-
if (changes['
|
|
18
|
+
if (changes['maxHeight'] || changes['lines']) {
|
|
18
19
|
this.clamp();
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
clamp() {
|
|
22
|
-
this.
|
|
23
|
+
if (!this.maxHeight && !this.lines) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.maxLines = this.lines ? this.lines : this.getMaxLines();
|
|
23
27
|
const hostHtmlElement = this.htmlElementRef.nativeElement;
|
|
24
|
-
const maxRequiredHeight = Math.max(this.maxHeight, this.getMaxHeight(this.maxLines, hostHtmlElement));
|
|
28
|
+
const maxRequiredHeight = Math.max(this.maxHeight ?? 0, this.getMaxHeight(this.maxLines, hostHtmlElement));
|
|
25
29
|
if (maxRequiredHeight < hostHtmlElement.clientHeight) {
|
|
26
30
|
const lastChild = this.getLastChild(hostHtmlElement);
|
|
27
31
|
if (lastChild) {
|
|
@@ -31,7 +35,7 @@ class NgxClamp {
|
|
|
31
35
|
}
|
|
32
36
|
// Recursively removes words from the text until its width or height is beneath maximum required height.
|
|
33
37
|
truncate(maxRequiredHeight, node, words = undefined, isCurrentNodeValueSplitIntoWords = false) {
|
|
34
|
-
// Removes truncation
|
|
38
|
+
// Removes truncation character from node text
|
|
35
39
|
const nodeValue = node.nodeValue?.replace(this.truncationCharacters, '');
|
|
36
40
|
if (!words && nodeValue) {
|
|
37
41
|
words = nodeValue.split(this.splitOnWordsCharacter);
|
|
@@ -109,16 +113,18 @@ class NgxClamp {
|
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: NgxClamp, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
112
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: NgxClamp, isStandalone: true, selector: "[ngxClamp]", inputs: { maxHeight: "maxHeight", truncationCharacters: "truncationCharacters" }, usesOnChanges: true, ngImport: i0 });
|
|
116
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: NgxClamp, isStandalone: true, selector: "[ngxClamp]", inputs: { maxHeight: "maxHeight", lines: "lines", truncationCharacters: "truncationCharacters" }, usesOnChanges: true, ngImport: i0 });
|
|
113
117
|
}
|
|
114
118
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: NgxClamp, decorators: [{
|
|
115
119
|
type: Directive,
|
|
116
120
|
args: [{
|
|
117
121
|
selector: '[ngxClamp]',
|
|
122
|
+
standalone: true,
|
|
118
123
|
}]
|
|
119
124
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { maxHeight: [{
|
|
120
|
-
type: Input
|
|
121
|
-
|
|
125
|
+
type: Input
|
|
126
|
+
}], lines: [{
|
|
127
|
+
type: Input
|
|
122
128
|
}], truncationCharacters: [{
|
|
123
129
|
type: Input
|
|
124
130
|
}] } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chitovas-ngx-clamp.mjs","sources":["../../../../projects/chitova/ngx-clamp/src/lib/ngx-clamp.ts","../../../../projects/chitova/ngx-clamp/src/chitovas-ngx-clamp.ts"],"sourcesContent":["import { AfterViewInit, Directive, ElementRef, Input, OnChanges, SimpleChanges } from '@angular/core';\n\n@Directive({\n selector: '[ngxClamp]',\n})\nexport class NgxClamp implements AfterViewInit, OnChanges {\n @Input({ required: true })\n public maxHeight: number = 0;\n\n @Input()\n public truncationCharacters: string = '...';\n\n public maxLines: number = 0;\n\n private readonly splitOnWordsCharacter: string = ' ';\n\n constructor(private readonly htmlElementRef: ElementRef<HTMLElement>) {}\n\n public ngAfterViewInit(): void {\n this.clamp();\n }\n\n public ngOnChanges(changes: SimpleChanges): void {\n if (changes['height']) {\n this.clamp();\n }\n }\n\n private clamp(): void {\n this.maxLines = this.getMaxLines();\n const hostHtmlElement: HTMLElement = this.htmlElementRef.nativeElement;\n const maxRequiredHeight: number = Math.max(this.maxHeight, this.getMaxHeight(this.maxLines, hostHtmlElement));\n if (maxRequiredHeight < hostHtmlElement.clientHeight) {\n const lastChild: ChildNode = this.getLastChild(hostHtmlElement);\n if (lastChild) {\n this.truncate(maxRequiredHeight, lastChild);\n }\n }\n }\n\n // Recursively removes words from the text until its width or height is beneath maximum required height.\n private truncate(\n maxRequiredHeight: number,\n node: ChildNode,\n words: string[] | undefined = undefined,\n isCurrentNodeValueSplitIntoWords: boolean = false\n ): void {\n // Removes truncation characters from node text\n const nodeValue: string | undefined = node.nodeValue?.replace(this.truncationCharacters, '');\n\n if (!words && nodeValue) {\n words = nodeValue.split(this.splitOnWordsCharacter);\n isCurrentNodeValueSplitIntoWords = true;\n }\n\n if (words) {\n const isTexFits: boolean = this.htmlElementRef.nativeElement.clientHeight <= maxRequiredHeight;\n node.nodeValue = words.join(this.splitOnWordsCharacter) + this.truncationCharacters;\n if (isTexFits) {\n return;\n }\n }\n\n // If there are words left to remove, remove the last one and see if the nodeValue fits.\n if (words && words.length > 1) {\n words.pop();\n node.nodeValue = words.join(this.splitOnWordsCharacter) + this.truncationCharacters;\n }\n // No more words can be removed using this character\n else {\n words = undefined;\n }\n\n if (words) {\n const isTexFits: boolean = this.htmlElementRef.nativeElement.clientHeight <= maxRequiredHeight;\n if (isTexFits) {\n return;\n }\n }\n\n // No valid words produced\n else if (isCurrentNodeValueSplitIntoWords) {\n // No valid words even when splitting by letter, time to move on to the next node\n\n // Set the current node value to the truncation character\n node.nodeValue = this.truncationCharacters;\n node = this.getLastChild(this.htmlElementRef.nativeElement);\n return this.truncate(maxRequiredHeight, node);\n }\n return this.truncate(maxRequiredHeight, node, words, isCurrentNodeValueSplitIntoWords);\n }\n\n private getMaxHeight(maxLines: number, element: HTMLElement): number {\n const lineHeight: number = this.getLineHeight(element);\n return lineHeight * maxLines;\n }\n\n private getLineHeight(element: HTMLElement): number {\n const cssStyleDeclaration: CSSStyleDeclaration = getComputedStyle(element, 'line-height');\n const lineHeight: string = cssStyleDeclaration.lineHeight;\n if (cssStyleDeclaration.lineHeight === 'normal') {\n // Normal line heights vary from browser to browser. The spec recommends a value between 1.0 and 1.2 of the font size.\n return Math.ceil(parseInt(getComputedStyle(element, 'font-size').fontSize, 10) * 1.187);\n }\n return Math.ceil(parseInt(lineHeight, 10));\n }\n\n private getMaxLines(): number {\n // Returns the maximum number of lines of text that should be rendered based on the current height of the element and the line-height of the text.\n const hostHtmlElement: HTMLElement = this.htmlElementRef.nativeElement;\n const availableHeight: number = this.maxHeight ?? hostHtmlElement.clientHeight;\n return Math.max(Math.floor(availableHeight / this.getLineHeight(hostHtmlElement)), 0);\n }\n\n private getLastChild(node: ChildNode): ChildNode {\n // Gets an element's last child. That may be another node or a node's contents.\n if (!node.lastChild) {\n return node;\n }\n // Current element has children, need to go deeper and get last child as a text node\n if (node.lastChild.childNodes && node.lastChild.childNodes.length > 0) {\n return this.getLastChild(node.childNodes.item(node.childNodes.length - 1));\n }\n // This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying\n else if (\n node.lastChild.parentNode &&\n (!node.lastChild?.nodeValue || node.lastChild.nodeValue === '' || node.lastChild.nodeValue === this.truncationCharacters)\n ) {\n node.lastChild.parentNode.removeChild(node.lastChild);\n return this.getLastChild(this.htmlElementRef.nativeElement);\n }\n // This is the last child we want, return it\n else {\n return node.lastChild;\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chitovas-ngx-clamp.mjs","sources":["../../../../projects/chitova/ngx-clamp/src/lib/ngx-clamp.ts","../../../../projects/chitova/ngx-clamp/src/chitovas-ngx-clamp.ts"],"sourcesContent":["import { AfterViewInit, Directive, ElementRef, Input, OnChanges, SimpleChanges } from '@angular/core';\n\n@Directive({\n selector: '[ngxClamp]',\n standalone: true,\n})\nexport class NgxClamp implements AfterViewInit, OnChanges {\n @Input()\n public maxHeight: number | null = null;\n\n @Input()\n public lines: number = 0;\n\n public maxLines: number = 0;\n\n private readonly splitOnWordsCharacter: string = ' ';\n\n @Input()\n public truncationCharacters: string = '...';\n\n constructor(private readonly htmlElementRef: ElementRef<HTMLElement>) {}\n\n public ngAfterViewInit(): void {\n this.clamp();\n }\n\n public ngOnChanges(changes: SimpleChanges): void {\n if (changes['maxHeight'] || changes['lines']) {\n this.clamp();\n }\n }\n\n private clamp(): void {\n if (!this.maxHeight && !this.lines) {\n return;\n }\n this.maxLines = this.lines ? this.lines : this.getMaxLines();\n const hostHtmlElement: HTMLElement = this.htmlElementRef.nativeElement;\n const maxRequiredHeight: number = Math.max(this.maxHeight ?? 0, this.getMaxHeight(this.maxLines, hostHtmlElement));\n if (maxRequiredHeight < hostHtmlElement.clientHeight) {\n const lastChild: ChildNode = this.getLastChild(hostHtmlElement);\n if (lastChild) {\n this.truncate(maxRequiredHeight, lastChild);\n }\n }\n }\n\n // Recursively removes words from the text until its width or height is beneath maximum required height.\n private truncate(\n maxRequiredHeight: number,\n node: ChildNode,\n words: string[] | undefined = undefined,\n isCurrentNodeValueSplitIntoWords: boolean = false\n ): void {\n // Removes truncation character from node text\n const nodeValue: string | undefined = node.nodeValue?.replace(this.truncationCharacters, '');\n\n if (!words && nodeValue) {\n words = nodeValue.split(this.splitOnWordsCharacter);\n isCurrentNodeValueSplitIntoWords = true;\n }\n\n if (words) {\n const isTexFits: boolean = this.htmlElementRef.nativeElement.clientHeight <= maxRequiredHeight;\n node.nodeValue = words.join(this.splitOnWordsCharacter) + this.truncationCharacters;\n if (isTexFits) {\n return;\n }\n }\n\n // If there are words left to remove, remove the last one and see if the nodeValue fits.\n if (words && words.length > 1) {\n words.pop();\n node.nodeValue = words.join(this.splitOnWordsCharacter) + this.truncationCharacters;\n }\n // No more words can be removed using this character\n else {\n words = undefined;\n }\n\n if (words) {\n const isTexFits: boolean = this.htmlElementRef.nativeElement.clientHeight <= maxRequiredHeight;\n if (isTexFits) {\n return;\n }\n }\n\n // No valid words produced\n else if (isCurrentNodeValueSplitIntoWords) {\n // No valid words even when splitting by letter, time to move on to the next node\n\n // Set the current node value to the truncation character\n node.nodeValue = this.truncationCharacters;\n node = this.getLastChild(this.htmlElementRef.nativeElement);\n return this.truncate(maxRequiredHeight, node);\n }\n return this.truncate(maxRequiredHeight, node, words, isCurrentNodeValueSplitIntoWords);\n }\n\n private getMaxHeight(maxLines: number, element: HTMLElement): number {\n const lineHeight: number = this.getLineHeight(element);\n return lineHeight * maxLines;\n }\n\n private getLineHeight(element: HTMLElement): number {\n const cssStyleDeclaration: CSSStyleDeclaration = getComputedStyle(element, 'line-height');\n const lineHeight: string = cssStyleDeclaration.lineHeight;\n if (cssStyleDeclaration.lineHeight === 'normal') {\n // Normal line heights vary from browser to browser. The spec recommends a value between 1.0 and 1.2 of the font size.\n return Math.ceil(parseInt(getComputedStyle(element, 'font-size').fontSize, 10) * 1.187);\n }\n return Math.ceil(parseInt(lineHeight, 10));\n }\n\n private getMaxLines(): number {\n // Returns the maximum number of lines of text that should be rendered based on the current height of the element and the line-height of the text.\n const hostHtmlElement: HTMLElement = this.htmlElementRef.nativeElement;\n const availableHeight: number = this.maxHeight ?? hostHtmlElement.clientHeight;\n return Math.max(Math.floor(availableHeight / this.getLineHeight(hostHtmlElement)), 0);\n }\n\n private getLastChild(node: ChildNode): ChildNode {\n // Gets an element's last child. That may be another node or a node's contents.\n if (!node.lastChild) {\n return node;\n }\n // Current element has children, need to go deeper and get last child as a text node\n if (node.lastChild.childNodes && node.lastChild.childNodes.length > 0) {\n return this.getLastChild(node.childNodes.item(node.childNodes.length - 1));\n }\n // This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying\n else if (\n node.lastChild.parentNode &&\n (!node.lastChild?.nodeValue || node.lastChild.nodeValue === '' || node.lastChild.nodeValue === this.truncationCharacters)\n ) {\n node.lastChild.parentNode.removeChild(node.lastChild);\n return this.getLastChild(this.htmlElementRef.nativeElement);\n }\n // This is the last child we want, return it\n else {\n return node.lastChild;\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/index.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { AfterViewInit, OnChanges, ElementRef, SimpleChanges } from '@angular/co
|
|
|
3
3
|
|
|
4
4
|
declare class NgxClamp implements AfterViewInit, OnChanges {
|
|
5
5
|
private readonly htmlElementRef;
|
|
6
|
-
maxHeight: number;
|
|
7
|
-
|
|
6
|
+
maxHeight: number | null;
|
|
7
|
+
lines: number;
|
|
8
8
|
maxLines: number;
|
|
9
9
|
private readonly splitOnWordsCharacter;
|
|
10
|
+
truncationCharacters: string;
|
|
10
11
|
constructor(htmlElementRef: ElementRef<HTMLElement>);
|
|
11
12
|
ngAfterViewInit(): void;
|
|
12
13
|
ngOnChanges(changes: SimpleChanges): void;
|
|
@@ -17,7 +18,7 @@ declare class NgxClamp implements AfterViewInit, OnChanges {
|
|
|
17
18
|
private getMaxLines;
|
|
18
19
|
private getLastChild;
|
|
19
20
|
static ɵfac: i0.ɵɵFactoryDeclaration<NgxClamp, never>;
|
|
20
|
-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxClamp, "[ngxClamp]", never, { "maxHeight": { "alias": "maxHeight"; "required":
|
|
21
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgxClamp, "[ngxClamp]", never, { "maxHeight": { "alias": "maxHeight"; "required": false; }; "lines": { "alias": "lines"; "required": false; }; "truncationCharacters": { "alias": "truncationCharacters"; "required": false; }; }, {}, never, never, true, never>;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export { NgxClamp };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chitovas/ngx-clamp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"author": "Nigel Mukandi",
|
|
5
5
|
"description": "angular library designed to elegantly manage text overflow within HTML elements. This library allows you to clamp text, adding ellipsis or a truncation text of your choice when the content exceeds a specified height.",
|
|
6
6
|
"repository": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@angular/common": "
|
|
23
|
-
"@angular/core": "
|
|
22
|
+
"@angular/common": ">=18.0.0",
|
|
23
|
+
"@angular/core": ">=18.0.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"tslib": "^2.3.0"
|