use-clamp-text
Advanced tools
Comparing version 1.0.1 to 1.1.0
@@ -0,1 +1,6 @@ | ||
# 1.1.0 | ||
- Support for custom ellipsis | ||
- Allow more flexible usage through `charWidth` | ||
# 1.0.1 | ||
@@ -2,0 +7,0 @@ |
/// <reference types="react" /> | ||
export declare function useClampText({ text, ellipsis, lines, expanded, debounceTime, }: { | ||
text: any; | ||
ellipsis?: string; | ||
interface ClampTextConfig { | ||
text: string; | ||
ellipsis: string | number; | ||
lines?: number; | ||
expanded?: boolean; | ||
debounceTime?: number; | ||
}): readonly [import("react").MutableRefObject<HTMLElement>, { | ||
charWidth?: number; | ||
} | ||
export declare function useClampText({ text, ellipsis, lines, expanded, debounceTime, charWidth, }: ClampTextConfig): readonly [import("react").MutableRefObject<HTMLElement>, { | ||
readonly noClamp: boolean; | ||
readonly clampedText: string; | ||
readonly key: string; | ||
}]; | ||
export {}; |
import { useRef, useState, useCallback } from 'react'; | ||
import { useDebounce, useOnWindowResize, useDidMount, useDidUpdate, } from 'rooks'; | ||
var defaultEllipsis = '...'; | ||
var key = 0; | ||
var getNewKey = function () { return "__clamp_text_key__".concat(key++); }; | ||
export function useClampText(_a) { | ||
var text = _a.text, _b = _a.ellipsis, ellipsis = _b === void 0 ? defaultEllipsis : _b, _c = _a.lines, lines = _c === void 0 ? 3 : _c, _d = _a.expanded, expanded = _d === void 0 ? false : _d, _e = _a.debounceTime, debounceTime = _e === void 0 ? 300 : _e; | ||
var _f = useState(function () { return ({ | ||
var text = _a.text, _b = _a.ellipsis, ellipsis = _b === void 0 ? defaultEllipsis : _b, _c = _a.lines, lines = _c === void 0 ? 3 : _c, _d = _a.expanded, expanded = _d === void 0 ? false : _d, _e = _a.debounceTime, debounceTime = _e === void 0 ? 300 : _e, _f = _a.charWidth, charWidth = _f === void 0 ? 1.2 : _f; | ||
var _g = useState(function () { return ({ | ||
noClamp: false, | ||
clampedText: '.', | ||
}); }), _g = _f[0], noClamp = _g.noClamp, clampedText = _g.clampedText, setState = _f[1]; | ||
key: getNewKey(), | ||
}); }), _h = _g[0], noClamp = _h.noClamp, clampedText = _h.clampedText, key = _h.key, setState = _g[1]; | ||
var nodeRef = useRef(); | ||
var lineHeightRef = useRef(0); | ||
var clampLines = useCallback(function (_a) { | ||
var lineHeight = _a.lineHeight, originalText = _a.originalText, expanded = _a.expanded, ellipsis = _a.ellipsis, lines = _a.lines; | ||
var lineHeight = _a.lineHeight, originalText = _a.originalText, expanded = _a.expanded, ellipsis = _a.ellipsis, lines = _a.lines, charWidth = _a.charWidth; | ||
var node = nodeRef.current; | ||
@@ -22,2 +25,3 @@ if (!node) { | ||
clampedText: originalText, | ||
key: getNewKey(), | ||
}); | ||
@@ -27,5 +31,12 @@ return; | ||
var maxHeight = lineHeight * lines + 1; | ||
var ellipsisLength = defaultEllipsis | ||
? 5 | ||
: Math.ceil(ellipsis.length * 1.2); | ||
var ellipsisLength = 0; | ||
if (typeof ellipsis === 'string') { | ||
ellipsisLength = | ||
ellipsis === defaultEllipsis | ||
? 5 | ||
: Math.ceil(ellipsis.length * charWidth); | ||
} | ||
else if (typeof ellipsis === 'number') { | ||
ellipsisLength = Math.ceil(ellipsis * charWidth); | ||
} | ||
var start = 0; | ||
@@ -38,3 +49,4 @@ var middle = 0; | ||
function moveMarkers() { | ||
var clientHeight = node.clientHeight; | ||
var _a; | ||
var clientHeight = (_a = node === null || node === void 0 ? void 0 : node.clientHeight) !== null && _a !== void 0 ? _a : 1; | ||
if (clientHeight <= maxHeight) { | ||
@@ -51,3 +63,7 @@ start = middle + 1; | ||
if (middle === originalText.length) { | ||
setState({ clampedText: originalText, noClamp: true }); | ||
setState({ | ||
clampedText: originalText, | ||
noClamp: true, | ||
key: getNewKey(), | ||
}); | ||
return; | ||
@@ -58,3 +74,3 @@ } | ||
var clampedText = originalText.slice(0, Math.max(middle - ellipsisLength, 0)).trim() + | ||
ellipsis; | ||
(typeof ellipsis === 'string' ? ellipsis : ''); | ||
node.innerText = clampedText; | ||
@@ -64,2 +80,3 @@ setState({ | ||
clampedText: clampedText, | ||
key: getNewKey(), | ||
}); | ||
@@ -75,8 +92,9 @@ }, []); | ||
lines: lines, | ||
charWidth: charWidth, | ||
}); | ||
}); | ||
useDidMount(function () { | ||
var _a; | ||
var _a, _b; | ||
if (text && !lineHeightRef.current) { | ||
var lineHeight = ((_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight) + 1; | ||
var lineHeight = ((_b = (_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 1) + 1; | ||
lineHeightRef.current = lineHeight; | ||
@@ -89,2 +107,3 @@ clampLines({ | ||
lines: lines, | ||
charWidth: charWidth, | ||
}); | ||
@@ -100,4 +119,5 @@ } | ||
lines: lines, | ||
charWidth: charWidth, | ||
}); | ||
}, [expanded, text]); | ||
}, [expanded, text, charWidth]); | ||
return [ | ||
@@ -108,4 +128,5 @@ nodeRef, | ||
clampedText: clampedText, | ||
key: key, | ||
}, | ||
]; | ||
} |
/// <reference types="react" /> | ||
export declare function useClampText({ text, ellipsis, lines, expanded, debounceTime, }: { | ||
text: any; | ||
ellipsis?: string; | ||
interface ClampTextConfig { | ||
text: string; | ||
ellipsis: string | number; | ||
lines?: number; | ||
expanded?: boolean; | ||
debounceTime?: number; | ||
}): readonly [import("react").MutableRefObject<HTMLElement>, { | ||
charWidth?: number; | ||
} | ||
export declare function useClampText({ text, ellipsis, lines, expanded, debounceTime, charWidth, }: ClampTextConfig): readonly [import("react").MutableRefObject<HTMLElement>, { | ||
readonly noClamp: boolean; | ||
readonly clampedText: string; | ||
readonly key: string; | ||
}]; | ||
export {}; |
@@ -7,12 +7,15 @@ "use strict"; | ||
var defaultEllipsis = '...'; | ||
var key = 0; | ||
var getNewKey = function () { return "__clamp_text_key__".concat(key++); }; | ||
function useClampText(_a) { | ||
var text = _a.text, _b = _a.ellipsis, ellipsis = _b === void 0 ? defaultEllipsis : _b, _c = _a.lines, lines = _c === void 0 ? 3 : _c, _d = _a.expanded, expanded = _d === void 0 ? false : _d, _e = _a.debounceTime, debounceTime = _e === void 0 ? 300 : _e; | ||
var _f = (0, react_1.useState)(function () { return ({ | ||
var text = _a.text, _b = _a.ellipsis, ellipsis = _b === void 0 ? defaultEllipsis : _b, _c = _a.lines, lines = _c === void 0 ? 3 : _c, _d = _a.expanded, expanded = _d === void 0 ? false : _d, _e = _a.debounceTime, debounceTime = _e === void 0 ? 300 : _e, _f = _a.charWidth, charWidth = _f === void 0 ? 1.2 : _f; | ||
var _g = (0, react_1.useState)(function () { return ({ | ||
noClamp: false, | ||
clampedText: '.', | ||
}); }), _g = _f[0], noClamp = _g.noClamp, clampedText = _g.clampedText, setState = _f[1]; | ||
key: getNewKey(), | ||
}); }), _h = _g[0], noClamp = _h.noClamp, clampedText = _h.clampedText, key = _h.key, setState = _g[1]; | ||
var nodeRef = (0, react_1.useRef)(); | ||
var lineHeightRef = (0, react_1.useRef)(0); | ||
var clampLines = (0, react_1.useCallback)(function (_a) { | ||
var lineHeight = _a.lineHeight, originalText = _a.originalText, expanded = _a.expanded, ellipsis = _a.ellipsis, lines = _a.lines; | ||
var lineHeight = _a.lineHeight, originalText = _a.originalText, expanded = _a.expanded, ellipsis = _a.ellipsis, lines = _a.lines, charWidth = _a.charWidth; | ||
var node = nodeRef.current; | ||
@@ -26,2 +29,3 @@ if (!node) { | ||
clampedText: originalText, | ||
key: getNewKey(), | ||
}); | ||
@@ -31,5 +35,12 @@ return; | ||
var maxHeight = lineHeight * lines + 1; | ||
var ellipsisLength = defaultEllipsis | ||
? 5 | ||
: Math.ceil(ellipsis.length * 1.2); | ||
var ellipsisLength = 0; | ||
if (typeof ellipsis === 'string') { | ||
ellipsisLength = | ||
ellipsis === defaultEllipsis | ||
? 5 | ||
: Math.ceil(ellipsis.length * charWidth); | ||
} | ||
else if (typeof ellipsis === 'number') { | ||
ellipsisLength = Math.ceil(ellipsis * charWidth); | ||
} | ||
var start = 0; | ||
@@ -42,3 +53,4 @@ var middle = 0; | ||
function moveMarkers() { | ||
var clientHeight = node.clientHeight; | ||
var _a; | ||
var clientHeight = (_a = node === null || node === void 0 ? void 0 : node.clientHeight) !== null && _a !== void 0 ? _a : 1; | ||
if (clientHeight <= maxHeight) { | ||
@@ -55,3 +67,7 @@ start = middle + 1; | ||
if (middle === originalText.length) { | ||
setState({ clampedText: originalText, noClamp: true }); | ||
setState({ | ||
clampedText: originalText, | ||
noClamp: true, | ||
key: getNewKey(), | ||
}); | ||
return; | ||
@@ -62,3 +78,3 @@ } | ||
var clampedText = originalText.slice(0, Math.max(middle - ellipsisLength, 0)).trim() + | ||
ellipsis; | ||
(typeof ellipsis === 'string' ? ellipsis : ''); | ||
node.innerText = clampedText; | ||
@@ -68,2 +84,3 @@ setState({ | ||
clampedText: clampedText, | ||
key: getNewKey(), | ||
}); | ||
@@ -79,8 +96,9 @@ }, []); | ||
lines: lines, | ||
charWidth: charWidth, | ||
}); | ||
}); | ||
(0, rooks_1.useDidMount)(function () { | ||
var _a; | ||
var _a, _b; | ||
if (text && !lineHeightRef.current) { | ||
var lineHeight = ((_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight) + 1; | ||
var lineHeight = ((_b = (_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 1) + 1; | ||
lineHeightRef.current = lineHeight; | ||
@@ -93,2 +111,3 @@ clampLines({ | ||
lines: lines, | ||
charWidth: charWidth, | ||
}); | ||
@@ -104,4 +123,5 @@ } | ||
lines: lines, | ||
charWidth: charWidth, | ||
}); | ||
}, [expanded, text]); | ||
}, [expanded, text, charWidth]); | ||
return [ | ||
@@ -112,2 +132,3 @@ nodeRef, | ||
clampedText: clampedText, | ||
key: key, | ||
}, | ||
@@ -114,0 +135,0 @@ ]; |
{ | ||
"name": "use-clamp-text", | ||
"description": "react hook to clamp multiline text to a given height in a responsive way (in < 2.5kb)", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"main": "lib/index.js", | ||
@@ -70,5 +70,5 @@ "module": "esm/index.js", | ||
"path": "esm/index.js", | ||
"limit": "2.5 KB" | ||
"limit": "3 KB" | ||
} | ||
] | ||
} |
@@ -48,10 +48,28 @@ # use-clamp-text | ||
| prop | type | required | default | description | | ||
| -------- | --------- | -------- | ------- | -------------------------------------------------------- | | ||
| text | `string` | `true` | | Text you wish to clamp | | ||
| ellipsis | `string` | `false` | `'…'` | String displayed after the clamped `text` | | ||
| expanded | `boolean` | `false` | `false` | To control whether the string should be truncated or not | | ||
| lines | `number` | `false` | `3` | Number of visible lines | | ||
| debounce | `number` | `false` | `300` | Time in milliseconds used for debounce | | ||
### Arguments | ||
The hook accepts only a single object argument is accepted with the following properties: | ||
| property | type | required | default | description | | ||
| --------- | ------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||
| text | `string` | `true` | | Text you wish to clamp | | ||
| ellipsis | `string \| number` | `false` | `'…'` | String displayed after the clamped text or number of characters to be trimmed off the string (useful for adding inline custom ellipsis like a `<a>` or `<button>`) | | ||
| expanded | `boolean` | `false` | `false` | To control whether the string should be truncated or not | | ||
| lines | `number` | `false` | `3` | Number of visible lines | | ||
| debounce | `number` | `false` | `300` | Time in milliseconds used for debounce | | ||
| charWidth | `number` | `false` | `1.2` | Character width to be assumed for calculating clamped string length (an average depending on your font size should work well enough) | | ||
### Return | ||
The hook returns a tuple - | ||
- [0] (first element) - `React.MutableRefObject<HTMLElement | null>` - a ref to attach to the element where the clamped text will be rendered | ||
- [1] (second element) - `Object` - The properties of the object are documented in the table below | ||
| property | type | description | | ||
| ----------- | --------- | ---------------------------------------------------------------------------------------------------------------------- | | ||
| noClamp | `boolean` | Whether the text is clamped or not. Will return true if not clamped | | ||
| clampedText | `string` | The string to be rendered | | ||
| key | `string` | A key to attach to the element that contains the string to be rendered (only needed when using custom inline ellipsis) | | ||
## Prior Art (packages I ~~copied~~ adapted code from) | ||
@@ -58,0 +76,0 @@ |
@@ -11,2 +11,23 @@ import { useRef, useState, useCallback } from 'react'; | ||
let key = 0; | ||
const getNewKey = () => `__clamp_text_key__${key++}`; | ||
interface ClampTextConfig { | ||
text: string; | ||
ellipsis: string | number; | ||
lines?: number; | ||
expanded?: boolean; | ||
debounceTime?: number; | ||
charWidth?: number; | ||
} | ||
interface ClampLineParams { | ||
lineHeight: number; | ||
originalText: string; | ||
expanded: boolean; | ||
ellipsis?: string | number; | ||
lines: number; | ||
charWidth: number; | ||
} | ||
export function useClampText({ | ||
@@ -18,6 +39,8 @@ text, | ||
debounceTime = 300, | ||
}) { | ||
const [{ noClamp, clampedText }, setState] = useState(() => ({ | ||
charWidth = 1.2, | ||
}: ClampTextConfig) { | ||
const [{ noClamp, clampedText, key }, setState] = useState(() => ({ | ||
noClamp: false, | ||
clampedText: '.', | ||
key: getNewKey(), | ||
})); | ||
@@ -29,3 +52,10 @@ | ||
const clampLines = useCallback( | ||
({ lineHeight, originalText, expanded, ellipsis, lines }) => { | ||
({ | ||
lineHeight, | ||
originalText, | ||
expanded, | ||
ellipsis, | ||
lines, | ||
charWidth, | ||
}: ClampLineParams) => { | ||
const node = nodeRef.current; | ||
@@ -39,2 +69,3 @@ if (!node) { | ||
clampedText: originalText, | ||
key: getNewKey(), | ||
}); | ||
@@ -45,5 +76,11 @@ return; | ||
const maxHeight = lineHeight * lines + 1; | ||
const ellipsisLength = defaultEllipsis | ||
? 5 | ||
: Math.ceil(ellipsis.length * 1.2); | ||
let ellipsisLength = 0; | ||
if (typeof ellipsis === 'string') { | ||
ellipsisLength = | ||
ellipsis === defaultEllipsis | ||
? 5 | ||
: Math.ceil(ellipsis.length * charWidth); | ||
} else if (typeof ellipsis === 'number') { | ||
ellipsisLength = Math.ceil(ellipsis * charWidth); | ||
} | ||
@@ -59,3 +96,3 @@ let start = 0; | ||
function moveMarkers() { | ||
const clientHeight = node.clientHeight; | ||
const clientHeight = node?.clientHeight ?? 1; | ||
if (clientHeight <= maxHeight) { | ||
@@ -73,3 +110,7 @@ start = middle + 1; | ||
if (middle === originalText.length) { | ||
setState({ clampedText: originalText, noClamp: true }); | ||
setState({ | ||
clampedText: originalText, | ||
noClamp: true, | ||
key: getNewKey(), | ||
}); | ||
return; | ||
@@ -83,3 +124,3 @@ } | ||
originalText.slice(0, Math.max(middle - ellipsisLength, 0)).trim() + | ||
ellipsis; | ||
(typeof ellipsis === 'string' ? ellipsis : ''); | ||
@@ -90,2 +131,3 @@ node.innerText = clampedText; | ||
clampedText, | ||
key: getNewKey(), | ||
}); | ||
@@ -103,2 +145,3 @@ }, | ||
lines, | ||
charWidth, | ||
}) | ||
@@ -109,3 +152,3 @@ ); | ||
if (text && !lineHeightRef.current) { | ||
const lineHeight = nodeRef.current?.clientHeight + 1; | ||
const lineHeight = (nodeRef.current?.clientHeight ?? 1) + 1; | ||
lineHeightRef.current = lineHeight; | ||
@@ -118,2 +161,3 @@ clampLines({ | ||
lines, | ||
charWidth, | ||
}); | ||
@@ -129,4 +173,5 @@ } | ||
lines, | ||
charWidth, | ||
}); | ||
}, [expanded, text]); | ||
}, [expanded, text, charWidth]); | ||
@@ -138,4 +183,5 @@ return [ | ||
clampedText, | ||
key, | ||
}, | ||
] as const; | ||
} |
21749
433
78