@frontfactory/readmore 1.0.1 → 1.1.0

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 CHANGED
@@ -1,11 +1,15 @@
1
- # @frontfactory/readmore
1
+ # Readmore JS
2
2
 
3
- Lightweight, framework-agnostic plugin to clamp text to N lines with an ellipsis and a `Read more` / `Read less` toggle
4
- button.
3
+ [![Version](https://flat.badgen.net/npm/v/@frontfactory/readmore)](https://www.npmjs.com/package/@frontfactory/readmore)
4
+ [![Downloads](https://flat.badgen.net/npm/dt/@frontfactory/readmore)](https://www.npmjs.com/package/@frontfactory/readmore)
5
+ [![License](https://flat.badgen.net/npm/license/@frontfactory/readmore)](https://www.npmjs.com/package/@frontfactory/readmore)
6
+
7
+ Lightweight, framework-agnostic plugin to clamp text to N lines (or a fixed pixel height) with an ellipsis and a
8
+ `Read more` / `Read less` toggle button.
5
9
 
6
10
  - Zero dependencies, ~1 KB
7
11
  - TypeScript types included
8
- - CSS-driven clamping via `-webkit-line-clamp` and a CSS custom property (`--readmore-lines`)
12
+ - CSS-driven clamping via `-webkit-line-clamp` or `max-height`
9
13
  - The toggle button is only created when the text actually overflows
10
14
 
11
15
  ## Installation
@@ -32,9 +36,15 @@ npm run dev
32
36
  import { ReadMore } from '@frontfactory/readmore';
33
37
  import '@frontfactory/readmore/style.css';
34
38
 
39
+ // clamp to 3 lines
35
40
  ReadMore.init('.excerpt', {
36
41
  lines: 3
37
42
  });
43
+
44
+ // clamp to a fixed pixel height
45
+ ReadMore.init('.excerpt', {
46
+ height: 80
47
+ });
38
48
  ```
39
49
 
40
50
  Or instantiate against a single element:
@@ -49,13 +59,16 @@ if (el) new ReadMore(el, {
49
59
 
50
60
  ## Options
51
61
 
52
- | Option | Type | Default | Description |
53
- |-----------------|----------|------------------|-----------------------------------------------------|
54
- | `lines` | `number` | `3` | Number of lines to clamp to. |
55
- | `moreText` | `string` | `'Read more'` | Label of the button when the text is collapsed. |
56
- | `lessText` | `string` | `'Read less'` | Label of the button when the text is expanded. |
57
- | `buttonClass` | `string` | `'readmore-btn'` | Class applied to the toggle button. |
58
- | `expandedClass` | `string` | `'is-expanded'` | Class applied to the target element while expanded. |
62
+ | Option | Type | Default | Description |
63
+ |-----------------|----------|------------------|--------------------------------------------------------------------|
64
+ | `lines` | `number` | `3` | Number of lines to clamp to. Ignored if `height` is set. |
65
+ | `height` | `number` | — | Max height in pixels. Takes precedence over `lines` when provided. |
66
+ | `moreText` | `string` | `'Read more'` | Label of the button when the text is collapsed. |
67
+ | `lessText` | `string` | `'Read less'` | Label of the button when the text is expanded. |
68
+ | `buttonClass` | `string` | `'readmore-btn'` | Class applied to the toggle button. |
69
+ | `expandedClass` | `string` | `'is-expanded'` | Class applied to the target element while expanded. |
70
+ | `openingClass` | `string` | `'is-opening'` | Transient class applied while expanding, removed after transition. |
71
+ | `closingClass` | `string` | `'is-closing'` | Transient class applied while collapsing, removed after transition.|
59
72
 
60
73
  ## API
61
74
 
@@ -71,7 +84,25 @@ ReadMore.init(selector, options); // returns ReadMore[]
71
84
 
72
85
  ## How it works
73
86
 
74
- The plugin sets the CSS custom property `--readmore-lines` on the target element and toggles the `.readmore-clamp`
75
- class. The bundled stylesheet uses that variable in a `-webkit-line-clamp` rule, so the ellipsis appears at the end of
76
- the requested line. The toggle button is mounted right after the element only when the content actually overflows, and
77
- unmounted again on resize if the text ends up fitting.
87
+ The plugin sets a CSS custom property on the target element and toggles `.readmore-clamp`. In **lines mode** (default),
88
+ it sets `--readmore-lines` and the bundled stylesheet applies `-webkit-line-clamp`, producing an ellipsis at the end of
89
+ the last visible line. In **height mode** (`height` option), it sets `--readmore-height` and applies `max-height` via
90
+ the `.readmore-clamp--height` modifier class instead. The toggle button is mounted right after the element only when the
91
+ content actually overflows, and unmounted again on resize if the text ends up fitting.
92
+
93
+ ### Transient classes for animations
94
+
95
+ In addition to `expandedClass` (which stays applied for the entire expanded state), the plugin applies short-lived
96
+ `openingClass` and `closingClass` markers on the target element at the moment of expand and collapse respectively.
97
+ Each is added when the toggle starts and removed automatically once the CSS transition on the element ends — or on the
98
+ next tick if no transition is declared. Use them as hooks to drive enter/leave animations:
99
+
100
+ ```css
101
+ .is-opening {
102
+ /* expand animation */
103
+ }
104
+
105
+ .is-closing {
106
+ /* collapse animation */
107
+ }
108
+ ```
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ .readmore-clamp{-webkit-line-clamp:var(--readmore-lines,3);line-clamp:var(--readmore-lines,3);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.readmore-clamp--height{max-height:var(--readmore-height);-webkit-line-clamp:none;line-clamp:none;display:block}
@@ -1,11 +1,16 @@
1
1
  export interface ReadMoreOptions {
2
2
  lines?: number;
3
+ height?: number;
3
4
  moreText?: string;
4
5
  lessText?: string;
5
6
  buttonClass?: string;
6
7
  expandedClass?: string;
8
+ openingClass?: string;
9
+ closingClass?: string;
7
10
  }
8
- type ResolvedOptions = Required<ReadMoreOptions>;
11
+ type ResolvedOptions = Required<Omit<ReadMoreOptions, 'height'>> & {
12
+ height?: number;
13
+ };
9
14
  export declare class ReadMore {
10
15
  #private;
11
16
  readonly el: HTMLElement;
package/dist/readmore.js CHANGED
@@ -1,39 +1,43 @@
1
1
  //#region src/readmore.ts
2
- var e = "readmore-clamp", t = {
2
+ var e = "readmore-clamp", t = "readmore-clamp--height", n = {
3
3
  lines: 3,
4
4
  moreText: "Read more",
5
5
  lessText: "Read less",
6
6
  buttonClass: "readmore-btn",
7
- expandedClass: "is-expanded"
8
- }, n = class n {
7
+ expandedClass: "is-expanded",
8
+ openingClass: "is-opening",
9
+ closingClass: "is-closing"
10
+ }, r = class r {
9
11
  el;
10
12
  options;
11
13
  #e = null;
12
14
  #t = !1;
13
15
  #n = () => this.#o();
14
16
  #r = () => this.toggle();
15
- constructor(e, n = {}) {
17
+ constructor(e, t = {}) {
16
18
  if (!(e instanceof HTMLElement)) throw TypeError("ReadMore: an HTMLElement is required.");
17
19
  this.el = e, this.options = {
18
- ...t,
19
- ...n
20
+ ...n,
21
+ ...t
20
22
  }, this.#i();
21
23
  }
22
24
  static init(e, t) {
23
- let r = typeof e == "string" ? document.querySelectorAll(e) : e;
24
- return Array.from(r).map((e) => new n(e, t));
25
+ let n = typeof e == "string" ? document.querySelectorAll(e) : e;
26
+ return Array.from(n).map((e) => new r(e, t));
25
27
  }
26
28
  get expanded() {
27
29
  return this.#t;
28
30
  }
29
31
  toggle() {
30
- this.#t = !this.#t, this.el.classList.toggle(e, !this.#t), this.el.classList.toggle(this.options.expandedClass, this.#t), this.#e && (this.#e.textContent = this.#t ? this.options.lessText : this.options.moreText), this.#t || this.#o();
32
+ this.#t = !this.#t, this.el.classList.toggle(e, !this.#t), this.options.height != null && this.el.classList.toggle(t, !this.#t), this.el.classList.toggle(this.options.expandedClass, this.#t), this.#e && (this.#e.textContent = this.#t ? this.options.lessText : this.options.moreText);
33
+ let n = this.#t ? this.options.openingClass : this.options.closingClass, r = this.#t ? this.options.closingClass : this.options.openingClass;
34
+ this.el.classList.remove(r), this.#s(n), this.#t || this.#o();
31
35
  }
32
36
  destroy() {
33
- window.removeEventListener("resize", this.#n), this.#c(), this.el.classList.remove(e, this.options.expandedClass), this.el.style.removeProperty("--readmore-lines");
37
+ window.removeEventListener("resize", this.#n), this.#l(), this.el.classList.remove(e, t, this.options.expandedClass, this.options.openingClass, this.options.closingClass), this.el.style.removeProperty("--readmore-lines"), this.el.style.removeProperty("--readmore-height");
34
38
  }
35
39
  #i() {
36
- this.el.style.setProperty("--readmore-lines", String(this.options.lines)), this.el.classList.add(e), window.addEventListener("resize", this.#n), this.#o();
40
+ this.options.height == null ? (this.el.style.setProperty("--readmore-lines", String(this.options.lines)), this.el.classList.add(e)) : (this.el.style.setProperty("--readmore-height", `${this.options.height}px`), this.el.classList.add(e, t)), window.addEventListener("resize", this.#n), this.#o();
37
41
  }
38
42
  #a() {
39
43
  return this.el.scrollHeight - this.el.clientHeight > 1;
@@ -41,17 +45,29 @@ var e = "readmore-clamp", t = {
41
45
  #o() {
42
46
  if (this.#t) return;
43
47
  let e = this.#a();
44
- e && !this.#e ? this.#s() : !e && this.#e && this.#c();
48
+ e && !this.#e ? this.#c() : !e && this.#e && this.#l();
45
49
  }
46
- #s() {
50
+ #s(e) {
51
+ this.el.classList.add(e);
52
+ let t = () => this.el.classList.remove(e);
53
+ if (!getComputedStyle(this.el).transitionDuration.split(",").some((e) => parseFloat(e) > 0)) {
54
+ setTimeout(t, 0);
55
+ return;
56
+ }
57
+ let n = (e) => {
58
+ e.target === this.el && (this.el.removeEventListener("transitionend", n), t());
59
+ };
60
+ this.el.addEventListener("transitionend", n);
61
+ }
62
+ #c() {
47
63
  let e = document.createElement("button");
48
64
  e.type = "button", e.className = this.options.buttonClass, e.textContent = this.options.moreText, e.addEventListener("click", this.#r), this.el.insertAdjacentElement("afterend", e), this.#e = e;
49
65
  }
50
- #c() {
66
+ #l() {
51
67
  this.#e &&= (this.#e.removeEventListener("click", this.#r), this.#e.remove(), null);
52
68
  }
53
69
  };
54
70
  //#endregion
55
- export { n as ReadMore };
71
+ export { r as ReadMore };
56
72
 
57
73
  //# sourceMappingURL=readmore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readmore.js","names":["#refresh","#init","#expanded","#button","#onResize","#unmountButton","#isOverflowing","#mountButton","#onClick"],"sources":["../src/readmore.ts"],"sourcesContent":["export interface ReadMoreOptions {\n lines?: number;\n moreText?: string;\n lessText?: string;\n buttonClass?: string;\n expandedClass?: string;\n}\n\ntype ResolvedOptions = Required<ReadMoreOptions>;\n\nconst CLAMP_CLASS = 'readmore-clamp';\n\nconst DEFAULTS: ResolvedOptions = {\n lines: 3,\n moreText: 'Read more',\n lessText: 'Read less',\n buttonClass: 'readmore-btn',\n expandedClass: 'is-expanded'\n};\n\nexport class ReadMore {\n readonly el: HTMLElement;\n readonly options: ResolvedOptions;\n\n #button: HTMLButtonElement | null = null;\n #expanded = false;\n #onResize = (): void => this.#refresh();\n #onClick = (): void => this.toggle();\n\n constructor(element: HTMLElement, options: ReadMoreOptions = {}) {\n if (!(element instanceof HTMLElement)) {\n throw new TypeError('ReadMore: an HTMLElement is required.');\n }\n this.el = element;\n this.options = { ...DEFAULTS, ...options };\n this.#init();\n }\n\n static init(\n target: string | NodeListOf<HTMLElement> | HTMLElement[],\n options?: ReadMoreOptions\n ): ReadMore[] {\n const nodes =\n typeof target === 'string'\n ? document.querySelectorAll<HTMLElement>(target)\n : target;\n return Array.from(nodes).map((n) => new ReadMore(n, options));\n }\n\n get expanded(): boolean {\n return this.#expanded;\n }\n\n toggle(): void {\n this.#expanded = !this.#expanded;\n this.el.classList.toggle(CLAMP_CLASS, !this.#expanded);\n this.el.classList.toggle(this.options.expandedClass, this.#expanded);\n if (this.#button) {\n this.#button.textContent = this.#expanded\n ? this.options.lessText\n : this.options.moreText;\n }\n if (!this.#expanded) this.#refresh();\n }\n\n destroy(): void {\n window.removeEventListener('resize', this.#onResize);\n this.#unmountButton();\n this.el.classList.remove(CLAMP_CLASS, this.options.expandedClass);\n this.el.style.removeProperty('--readmore-lines');\n }\n\n #init(): void {\n this.el.style.setProperty('--readmore-lines', String(this.options.lines));\n this.el.classList.add(CLAMP_CLASS);\n window.addEventListener('resize', this.#onResize);\n this.#refresh();\n }\n\n #isOverflowing(): boolean {\n return this.el.scrollHeight - this.el.clientHeight > 1;\n }\n\n #refresh(): void {\n if (this.#expanded) return;\n const overflowing = this.#isOverflowing();\n if (overflowing && !this.#button) {\n this.#mountButton();\n } else if (!overflowing && this.#button) {\n this.#unmountButton();\n }\n }\n\n #mountButton(): void {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = this.options.buttonClass;\n btn.textContent = this.options.moreText;\n btn.addEventListener('click', this.#onClick);\n this.el.insertAdjacentElement('afterend', btn);\n this.#button = btn;\n }\n\n #unmountButton(): void {\n if (!this.#button) return;\n this.#button.removeEventListener('click', this.#onClick);\n this.#button.remove();\n this.#button = null;\n }\n}\n"],"mappings":";AAUA,IAAM,IAAc,kBAEd,IAA4B;CAC9B,OAAO;CACP,UAAU;CACV,UAAU;CACV,aAAa;CACb,eAAe;CAClB,EAEY,IAAb,MAAa,EAAS;CAClB;CACA;CAEA,KAAoC;CACpC,KAAY;CACZ,WAAwB,KAAKA,IAAU;CACvC,WAAuB,KAAK,QAAQ;CAEpC,YAAY,GAAsB,IAA2B,EAAE,EAAE;EAC7D,IAAI,EAAE,aAAmB,cACrB,MAAU,UAAU,wCAAwC;EAIhE,AAFA,KAAK,KAAK,GACV,KAAK,UAAU;GAAE,GAAG;GAAU,GAAG;GAAS,EAC1C,KAAKC,IAAO;;CAGhB,OAAO,KACH,GACA,GACU;EACV,IAAM,IACF,OAAO,KAAW,WACZ,SAAS,iBAA8B,EAAO,GAC9C;EACV,OAAO,MAAM,KAAK,EAAM,CAAC,KAAK,MAAM,IAAI,EAAS,GAAG,EAAQ,CAAC;;CAGjE,IAAI,WAAoB;EACpB,OAAO,KAAKC;;CAGhB,SAAe;EASX,AARA,KAAKA,KAAY,CAAC,KAAKA,IACvB,KAAK,GAAG,UAAU,OAAO,GAAa,CAAC,KAAKA,GAAU,EACtD,KAAK,GAAG,UAAU,OAAO,KAAK,QAAQ,eAAe,KAAKA,GAAU,EAChE,KAAKC,OACL,KAAKA,GAAQ,cAAc,KAAKD,KAC1B,KAAK,QAAQ,WACb,KAAK,QAAQ,WAElB,KAAKA,MAAW,KAAKF,IAAU;;CAGxC,UAAgB;EAIZ,AAHA,OAAO,oBAAoB,UAAU,KAAKI,GAAU,EACpD,KAAKC,IAAgB,EACrB,KAAK,GAAG,UAAU,OAAO,GAAa,KAAK,QAAQ,cAAc,EACjE,KAAK,GAAG,MAAM,eAAe,mBAAmB;;CAGpD,KAAc;EAIV,AAHA,KAAK,GAAG,MAAM,YAAY,oBAAoB,OAAO,KAAK,QAAQ,MAAM,CAAC,EACzE,KAAK,GAAG,UAAU,IAAI,EAAY,EAClC,OAAO,iBAAiB,UAAU,KAAKD,GAAU,EACjD,KAAKJ,IAAU;;CAGnB,KAA0B;EACtB,OAAO,KAAK,GAAG,eAAe,KAAK,GAAG,eAAe;;CAGzD,KAAiB;EACb,IAAI,KAAKE,IAAW;EACpB,IAAM,IAAc,KAAKI,IAAgB;EACzC,AAAI,KAAe,CAAC,KAAKH,KACrB,KAAKI,IAAc,GACZ,CAAC,KAAe,KAAKJ,MAC5B,KAAKE,IAAgB;;CAI7B,KAAqB;EACjB,IAAM,IAAM,SAAS,cAAc,SAAS;EAM5C,AALA,EAAI,OAAO,UACX,EAAI,YAAY,KAAK,QAAQ,aAC7B,EAAI,cAAc,KAAK,QAAQ,UAC/B,EAAI,iBAAiB,SAAS,KAAKG,GAAS,EAC5C,KAAK,GAAG,sBAAsB,YAAY,EAAI,EAC9C,KAAKL,KAAU;;CAGnB,KAAuB;EACd,AAGL,KAAKA,QAFL,KAAKA,GAAQ,oBAAoB,SAAS,KAAKK,GAAS,EACxD,KAAKL,GAAQ,QAAQ,EACN"}
1
+ {"version":3,"file":"readmore.js","names":["#refresh","#init","#expanded","#button","#applyTransientClass","#onResize","#unmountButton","#isOverflowing","#mountButton","#onClick"],"sources":["../src/readmore.ts"],"sourcesContent":["export interface ReadMoreOptions {\n lines?: number;\n height?: number;\n moreText?: string;\n lessText?: string;\n buttonClass?: string;\n expandedClass?: string;\n openingClass?: string;\n closingClass?: string;\n}\n\ntype ResolvedOptions = Required<Omit<ReadMoreOptions, 'height'>> & { height?: number };\n\nconst CLAMP_CLASS = 'readmore-clamp';\nconst CLAMP_HEIGHT_CLASS = 'readmore-clamp--height';\n\nconst DEFAULTS: ResolvedOptions = {\n lines: 3,\n moreText: 'Read more',\n lessText: 'Read less',\n buttonClass: 'readmore-btn',\n expandedClass: 'is-expanded',\n openingClass: 'is-opening',\n closingClass: 'is-closing'\n};\n\nexport class ReadMore {\n readonly el: HTMLElement;\n readonly options: ResolvedOptions;\n\n #button: HTMLButtonElement | null = null;\n #expanded = false;\n #onResize = (): void => this.#refresh();\n #onClick = (): void => this.toggle();\n\n constructor(element: HTMLElement, options: ReadMoreOptions = {}) {\n if (!(element instanceof HTMLElement)) {\n throw new TypeError('ReadMore: an HTMLElement is required.');\n }\n this.el = element;\n this.options = { ...DEFAULTS, ...options };\n this.#init();\n }\n\n static init(\n target: string | NodeListOf<HTMLElement> | HTMLElement[],\n options?: ReadMoreOptions\n ): ReadMore[] {\n const nodes =\n typeof target === 'string'\n ? document.querySelectorAll<HTMLElement>(target)\n : target;\n return Array.from(nodes).map((n) => new ReadMore(n, options));\n }\n\n get expanded(): boolean {\n return this.#expanded;\n }\n\n toggle(): void {\n this.#expanded = !this.#expanded;\n this.el.classList.toggle(CLAMP_CLASS, !this.#expanded);\n if (this.options.height != null) {\n this.el.classList.toggle(CLAMP_HEIGHT_CLASS, !this.#expanded);\n }\n this.el.classList.toggle(this.options.expandedClass, this.#expanded);\n if (this.#button) {\n this.#button.textContent = this.#expanded\n ? this.options.lessText\n : this.options.moreText;\n }\n const stateClass = this.#expanded ? this.options.openingClass : this.options.closingClass;\n const otherClass = this.#expanded ? this.options.closingClass : this.options.openingClass;\n this.el.classList.remove(otherClass);\n this.#applyTransientClass(stateClass);\n if (!this.#expanded) this.#refresh();\n }\n\n destroy(): void {\n window.removeEventListener('resize', this.#onResize);\n this.#unmountButton();\n this.el.classList.remove(\n CLAMP_CLASS,\n CLAMP_HEIGHT_CLASS,\n this.options.expandedClass,\n this.options.openingClass,\n this.options.closingClass\n );\n this.el.style.removeProperty('--readmore-lines');\n this.el.style.removeProperty('--readmore-height');\n }\n\n #init(): void {\n if (this.options.height != null) {\n this.el.style.setProperty('--readmore-height', `${ this.options.height }px`);\n this.el.classList.add(CLAMP_CLASS, CLAMP_HEIGHT_CLASS);\n } else {\n this.el.style.setProperty('--readmore-lines', String(this.options.lines));\n this.el.classList.add(CLAMP_CLASS);\n }\n window.addEventListener('resize', this.#onResize);\n this.#refresh();\n }\n\n #isOverflowing(): boolean {\n return this.el.scrollHeight - this.el.clientHeight > 1;\n }\n\n #refresh(): void {\n if (this.#expanded) return;\n const overflowing = this.#isOverflowing();\n if (overflowing && !this.#button) {\n this.#mountButton();\n } else if (!overflowing && this.#button) {\n this.#unmountButton();\n }\n }\n\n #applyTransientClass(stateClass: string): void {\n this.el.classList.add(stateClass);\n const remove = (): void => this.el.classList.remove(stateClass);\n const duration = getComputedStyle(this.el).transitionDuration;\n const hasTransition = duration\n .split(',')\n .some((d) => parseFloat(d) > 0);\n if (!hasTransition) {\n setTimeout(remove, 0);\n return;\n }\n const onEnd = (e: TransitionEvent): void => {\n if (e.target !== this.el) return;\n this.el.removeEventListener('transitionend', onEnd);\n remove();\n };\n this.el.addEventListener('transitionend', onEnd);\n }\n\n #mountButton(): void {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = this.options.buttonClass;\n btn.textContent = this.options.moreText;\n btn.addEventListener('click', this.#onClick);\n this.el.insertAdjacentElement('afterend', btn);\n this.#button = btn;\n }\n\n #unmountButton(): void {\n if (!this.#button) return;\n this.#button.removeEventListener('click', this.#onClick);\n this.#button.remove();\n this.#button = null;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontfactory/readmore",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Lightweight plugin to clamp text to N lines with ellipsis and a read more action.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -8,6 +8,9 @@
8
8
  "build": "vite build",
9
9
  "preview": "vite preview",
10
10
  "typecheck": "tsc --noEmit",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "test:coverage": "vitest run --coverage",
11
14
  "prepare": "husky && npm run build",
12
15
  "release": "semantic-release"
13
16
  },
@@ -31,13 +34,16 @@
31
34
  "@semantic-release/changelog": "^6.0.3",
32
35
  "@semantic-release/git": "^10.0.1",
33
36
  "@semantic-release/github": "^12.0.6",
37
+ "@vitest/coverage-v8": "^4.1.5",
34
38
  "conventional-changelog-conventionalcommits": "^9.3.1",
35
39
  "husky": "^9.1.7",
40
+ "jsdom": "^29.1.1",
36
41
  "semantic-release": "^25.0.3",
37
42
  "typescript": "^5.4.0",
38
43
  "vite": "^8.0.11",
39
44
  "vite-plugin-dts": "^5.0.0",
40
- "vite-plugin-static-copy": "^4.1.0"
45
+ "vite-plugin-static-copy": "^4.1.0",
46
+ "vitest": "^4.1.5"
41
47
  },
42
48
  "files": [
43
49
  "dist"
@@ -58,6 +64,7 @@
58
64
  "url": "git+https://github.com/front-factory/readmore.git"
59
65
  },
60
66
  "publishConfig": {
61
- "access": "public"
67
+ "access": "public",
68
+ "provenance": true
62
69
  }
63
70
  }
@@ -1,7 +0,0 @@
1
- .readmore-clamp {
2
- display: -webkit-box;
3
- -webkit-box-orient: vertical;
4
- overflow: hidden;
5
- -webkit-line-clamp: var(--readmore-lines, 3);
6
- line-clamp: var(--readmore-lines, 3);
7
- }