Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@fawazahmed/react-native-read-more

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fawazahmed/react-native-read-more - npm Package Compare versions

Comparing version 2.1.0 to 2.1.1

357

example/src/ReadMore.js

@@ -52,4 +52,2 @@ import React, {memo, useState, useEffect, useCallback} from 'react';

const [textWidth, setTextWidth] = useState(0);
// lineOfImpact comes from hidden component four
// const [lineOfImpact, setLineOfImpact] = useState({});
const [truncatedLineOfImpact, setTruncatedLineOfImpact] = useState('');

@@ -110,27 +108,26 @@ const [truncatedLineOfImpactWidth, setTruncatedLineOfImpactWidth] = useState(

},
[setHiddenTextLinesWithSeeLess, setMountHiddenTextTwo],
[
setHiddenTextLinesWithSeeLess,
setMountHiddenTextTwo,
setMountHiddenTextFour,
],
);
const onLayoutActualTextComponent = useCallback(
({
nativeEvent: {
layout: {width},
},
}) => {
setTextWidth(width);
(event) => {
const _event = event; // clone event
const _width = _event?.nativeEvent?.layout?.width || 0;
setTextWidth(_width);
setMountHiddenTextFour(false);
if (Platform.OS === 'android') {
setMountHiddenTextSix(true);
}
},
[setTextWidth],
[setTextWidth, setMountHiddenTextFour],
);
const onTextLayoutActualTextComponent = useCallback(
({nativeEvent: {lines: _lines}}) => {
if (!collapsed) {
return;
(event) => {
const _event = event; // clone event
if (collapsed) {
const _lines = _event?.nativeEvent?.lines || [];
setCollapsedLines(_lines);
}
setCollapsedLines(_lines);
},

@@ -181,2 +178,29 @@ [setCollapsedLines, collapsed],

const updateLineOfImpact = useCallback(
(_text = '', resetCollapsedChildren = true) => {
setHideEllipsis(!_text?.length);
setTruncatedLineOfImpact(_text || '');
if (!_text?.length) {
// reset width if no text
// otherwise an effect will update the width
setTruncatedLineOfImpactWidth(0);
setReconciledLineOfImpactWidth(0);
setSeeMoreRightPadding(0);
setIsMeasured(true);
}
if (resetCollapsedChildren) {
setCollapsedChildren(null);
}
},
[
setHideEllipsis,
setTruncatedLineOfImpact,
setTruncatedLineOfImpactWidth,
setCollapsedChildren,
setIsMeasured,
],
);
const measureSeeMoreLine = useCallback(() => {

@@ -187,3 +211,4 @@ if (

!collapsedLines.length ||
!seeMore
!seeMore ||
!seeMoreWidth
) {

@@ -212,2 +237,3 @@ return;

const availableWidth = textWidth - seeMoreWidth;
const _trimmedText = _lineOfImpact?.text?.trimEnd?.();

@@ -218,100 +244,90 @@ // calculate how many characters to cut off if any

// case 1
// if no text after right trim
// hide ellipsis
// move see more to beginning
if (_lineOfImpact.text.trim().length === 0) {
_lineOfImpact.width = 0;
_lineOfImpact.text = '';
setHideEllipsis(true);
} else {
setHideEllipsis(false);
if (!_trimmedText?.length) {
return updateLineOfImpact(_trimmedText);
}
// todo
// right trim and width adjustment
// case 2
// text is there but no need to put \n
// enough space for see more text on right side
if (_lineOfImpact.width < availableWidth) {
return updateLineOfImpact(_trimmedText);
}
// setLineOfImpact(_lineOfImpact);
const seeMoreTextLength =
`${ellipsis} ${seeMoreText}`.length + seeMoreOverlapCount;
if (_lineOfImpact.width < availableWidth) {
// if no need to cutoff, simply calculate see more right padding
const _seeMoreRightPadding =
textWidth - _lineOfImpact.width - seeMoreWidth;
if (_seeMoreRightPadding > 0) {
setTruncatedLineOfImpact('');
setTruncatedLineOfImpactWidth(0);
setReconciledLineOfImpact('');
setReconciledLineOfImpactWidth(0);
setSeeMoreRightPadding(_seeMoreRightPadding);
// todo: remove this
if (animate) {
LayoutAnimation.configureNext(readmoreAnimation);
}
}
} else {
// todo
// determine point, traverse through nodes
// create collapsed children with spaces at the point
const seeMoreTextLength =
`${ellipsis} ${seeMoreText}`.length + seeMoreOverlapCount;
const linesTillImpact = Array(_lineOfImpact.index + 1)
.fill({})
.map((_e, index) => lines[index]);
const charactersBeforeSeeMore = linesToCharacters(linesTillImpact);
const charactersLengthTillSeeMore = charactersBeforeSeeMore.length;
// text break position for collapsed text
const textBreakPosition = charactersLengthTillSeeMore - seeMoreTextLength;
// case 3
// many spaces at the end of text
// so still no need to cutoff the text at end with \n
const spaceDifference = _lineOfImpact?.text?.length - _trimmedText?.length;
if (spaceDifference >= seeMoreTextLength) {
return updateLineOfImpact(_trimmedText);
}
const _truncatedText = _lineOfImpact.text.substr(
0,
_lineOfImpact.text.length - seeMoreTextLength,
);
// case 4
// create collapsed children with \n at the point
const linesTillImpact = Array(_lineOfImpact.index + 1)
.fill({})
.map((_e, index) => lines[index]);
const charactersBeforeSeeMore = linesToCharacters(linesTillImpact);
const charactersLengthTillSeeMore =
charactersBeforeSeeMore?.trimEnd?.()?.length || 0;
// text break position for collapsed text
const textBreakPosition = charactersLengthTillSeeMore - seeMoreTextLength;
if (truncatedLineOfImpact !== _truncatedText) {
setTruncatedLineOfImpact(_truncatedText);
}
const _truncatedText =
_trimmedText
?.substr(0, _trimmedText.length - seeMoreTextLength)
?.trimEnd?.() || '';
// go to this position and insert spaces
let charactersToTraverse = textBreakPosition;
let nodeFound = false;
const modifiedChildrenObjects = getText(children, TextComponent, true)
?.map((_childObject) => {
if (nodeFound) {
return _childObject;
}
if (_childObject.content.length > charactersToTraverse) {
// this node is the one
nodeFound = true;
const childContent = insertAt(
_childObject.content,
'\n',
charactersToTraverse,
);
return {
type: _childObject?.type,
content: childContent,
child:
_childObject?.type === 'string'
? childContent
: React.cloneElement(
_childObject,
_childObject.props,
childContent,
),
};
}
charactersToTraverse =
charactersToTraverse - _childObject.content.length;
// go to this position and insert \n
let charactersToTraverse = textBreakPosition;
let nodeFound = false;
const modifiedChildrenObjects = getText(children, TextComponent, true)
?.map((_childObject) => {
if (nodeFound) {
return _childObject;
})
?.map((_updatedObjects) => {
return _updatedObjects.child;
});
}
if (_childObject.content.length > charactersToTraverse) {
// this node is the one
nodeFound = true;
const childContent = insertAt(
_childObject.content,
'\n',
charactersToTraverse,
);
return {
type: _childObject?.type,
content: childContent,
child:
_childObject?.type === 'string'
? childContent
: React.cloneElement(
_childObject,
_childObject.props,
childContent,
),
};
}
charactersToTraverse =
charactersToTraverse - _childObject.content.length;
if (nodeFound) {
setCollapsedChildren(modifiedChildrenObjects);
}
return _childObject;
})
?.map((_updatedObjects) => {
return _updatedObjects.child;
});
if (nodeFound) {
setCollapsedChildren(modifiedChildrenObjects);
return updateLineOfImpact(_truncatedText, false);
}
setIsMeasured(true);
// todo: inform user
// error case
return updateLineOfImpact(_trimmedText);
}, [

@@ -324,9 +340,8 @@ numberOfLines,

seeMoreWidth,
animate,
ellipsis,
seeMoreText,
seeMoreOverlapCount,
truncatedLineOfImpact,
children,
TextComponent,
updateLineOfImpact,
]);

@@ -359,8 +374,8 @@

const seeMoreTextHidingStyle = !isMeasured
? {color: 'transparent'}
: {
backgroundColor: 'transparent',
};
? styles.transparentColor
: styles.transparentBackground;
const seeMoreContainerStyle = [
styles.seeMoreContainer,
hideEllipsis
? styles.seeMoreContainerEllpisisHidden
: styles.seeMoreContainer,
{

@@ -381,3 +396,2 @@ marginRight: seeMoreRightPadding,

const callback = collapsed ? onCollapse : onExpand;
setAfterCollapsed(collapsed);
if (animate) {

@@ -388,2 +402,3 @@ LayoutAnimation.configureNext(readmoreAnimation, callback);

}
setAfterCollapsed(collapsed);
// eslint-disable-next-line react-hooks/exhaustive-deps

@@ -394,2 +409,5 @@ }, [collapsed]);

const handle = setTimeout(() => {
// to commence measurement chain
// we should mount component 1
// also reset isMeasured
setMountHiddenTextOne(true);

@@ -413,6 +431,6 @@ }, debounceSeeMoreCalc);

// a map of additional props to be passed down
// in hidden text components other than style
// for accurate measurements
useEffect(() => {
// a map of additional props to be passed down
// in hidden text components other than style
// for accurate measurements
const _additionalProps = {};

@@ -429,17 +447,13 @@

useEffect(() => {
if (mountHiddenTextOne || mountHiddenTextTwo || mountHiddenTextFour) {
if (mountHiddenTextTwo && !seeMoreWidth && collapsedLines?.length) {
return;
}
// only start measurement after component 2 is unmounted and see more width is calculated
// since component 1 mounts -> unmounts -> mounts component 2
// then component 2 unmounts itself
// and then all measurement params are available
const handle = setTimeout(measureSeeMoreLine, debounceSeeMoreCalc);
return () => clearTimeout(handle);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
lines,
numberOfLines,
collapsedLines,
seeMore,
mountHiddenTextOne,
mountHiddenTextTwo,
mountHiddenTextFour,
]);
}, [mountHiddenTextTwo, seeMoreWidth, collapsedLines]);

@@ -452,29 +466,41 @@ useEffect(() => {

setMountHiddenTextThree(true);
if (Platform.OS === 'android') {
setMountHiddenTextSix(true);
}
}, [truncatedLineOfImpact]);
useEffect(() => {
if (!truncatedLineOfImpactWidth) {
if (
!(truncatedLineOfImpactWidth || reconciledLineOfImpactWidth) ||
!seeMoreWidth ||
!textWidth
) {
return;
}
const padding = textWidth - truncatedLineOfImpactWidth - seeMoreWidth;
setSeeMoreRightPadding(padding);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [truncatedLineOfImpactWidth]);
const _width =
reconciledLineOfImpactWidth || truncatedLineOfImpactWidth || 0;
useEffect(() => {
if (!reconciledLineOfImpactWidth || !seeMoreWidth || !textWidth) {
// setSeeMoreRightPadding(0);
return;
let _seeMoreRightPadding = textWidth - _width - seeMoreWidth;
_seeMoreRightPadding = _seeMoreRightPadding < 0 ? 0 : _seeMoreRightPadding;
setSeeMoreRightPadding(_seeMoreRightPadding);
/*
// shut down animation while measuring
// so that it doesn't effects other UI animations
if (animate && isMeasured) {
LayoutAnimation.configureNext(readmoreAnimation);
}
const _seeMoreRightPadding =
textWidth - reconciledLineOfImpactWidth - seeMoreWidth;
if (_seeMoreRightPadding > 0) {
setSeeMoreRightPadding(_seeMoreRightPadding);
if (animate) {
LayoutAnimation.configureNext(readmoreAnimation);
}
}
*/
setIsMeasured(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reconciledLineOfImpactWidth, seeMoreWidth, textWidth]);
}, [
truncatedLineOfImpactWidth,
reconciledLineOfImpactWidth,
seeMoreWidth,
textWidth,
]);

@@ -519,3 +545,3 @@ return (

)}
{/* extract line of impact with measured children */}
{/* extract line of impact with collapsed children for remeasurement of right padding on android */}
{mountHiddenTextSix && (

@@ -562,17 +588,22 @@ <TextComponent

<View style={seeMoreContainerStyle} onLayout={onSeeMoreViewLayout}>
{!hideEllipsis && (
<TextComponent
key={`${isMeasured}-${hideEllipsis}`}
{...additionalProps}
{...restProps}
onPress={toggle}
style={[
style,
seeMoreTextHidingStyle,
hideEllipsis ? styles.transparentColor : {},
]}>
{`${ellipsis} `}
</TextComponent>
)}
<TextComponent
key={`${isMeasured}`}
{...additionalProps}
{...restProps}
onPress={toggle}
style={[style, seeMoreTextHidingStyle]}>
{`${hideEllipsis ? '' : ellipsis}`}
</TextComponent>
<TextComponent
{...additionalProps}
{...restProps}
onPress={toggle}
style={[style, seeMoreStyle, seeMoreTextHidingStyle]}>
{` ${seeMoreText}`}
{hideEllipsis ? ' ' : ''}
{seeMoreText}
</TextComponent>

@@ -611,2 +642,8 @@ </View>

},
seeMoreContainerEllpisisHidden: {
position: 'absolute',
left: 0,
bottom: 0,
flexDirection: 'row',
},
seeMoreButton: {

@@ -624,2 +661,12 @@ flexDirection: 'row',

},
transparentBackground: {
backgroundColor: 'transparent',
},
transparentColor: {
color: 'transparent',
},
hiddenEllpisisText: {
color: 'transparent',
position: 'absolute',
},
});

@@ -649,2 +696,4 @@

debounceSeeMoreCalc: PropTypes.number,
onLayout: PropTypes.func,
onTextLayout: PropTypes.func,
};

@@ -667,3 +716,3 @@

expandOnly: false,
seeMoreOverlapCount: 1,
seeMoreOverlapCount: 2,
debounceSeeMoreCalc: 300,

@@ -670,0 +719,0 @@ allowFontScaling: Platform.select({

{
"name": "@fawazahmed/react-native-read-more",
"version": "2.1.0",
"version": "2.1.1",
"description": "A simple react native library to show large blocks of text in a condensed manner with the ability to collapse and expand.",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -73,3 +73,3 @@ ![NPM Downloads](https://img.shields.io/npm/dw/@fawazahmed/react-native-read-more) ![NPM License](https://img.shields.io/npm/l/@fawazahmed/react-native-read-more) ![NPM Version](https://img.shields.io/npm/v/@fawazahmed/react-native-read-more)

| `animate` | `bool` | no | defaults to `true` => applies a subtle animation to see more and see less text, not the complete text itself
| `backgroundColor` | `string` | no | (deprecated) defaults to `white` => supply `backgroundColor` if your background color is something other than white
| `backgroundColor` | `string` | no | (removed in v2.1.0 and above) defaults to `white` => supply `backgroundColor` if your background color is something other than white
| `customTextComponent` | `React component` | no | defaults to `Text`

@@ -79,3 +79,3 @@ | `expandOnly` | `bool` | no | defaults to `false` => hide see less option similar to a linkedIn post

| `onCollapse` | `func` | no | optional callback executed when collapsed
| `preserveLinebreaks` | `bool` | no | (deprecated) defaults to `false` => preserves `\n` in the content while in the collapsed state. This prop is in experimental stage.
| `preserveLinebreaks` | `bool` | no | (removed in v2.1.0 and above) defaults to `false` => preserves `\n` in the content while in the collapsed state. This prop is in experimental stage.

@@ -117,1 +117,5 @@ Any additional props are passed down to underlying `Text` component.

refer to jest docs [here](https://jestjs.io/docs/en/tutorial-react-native#transformignorepatterns-customization) and github [issue](https://github.com/fawaz-ahmed/react-native-read-more/issues/19)
### Known issues
`Android only` if `numberOfLines` with a value of `1` is passed down as a prop, text in android devices will overlap at the end of line. This is an issue in `react-native` where text from other lines concatenates into the first one even if we add `\n` to the first line, where the lines returned from `onTextLayout` indicates a different response.
To overcome this issue, use `numberOfLines` greater than `1`.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc