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 1.1.5 to 2.0.0

example/src/helper.js

397

example/src/ReadMore.js

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

} from 'react-native';
import {
childrenToTextChildren,
getText,
insertAt,
linesToCharacters,
childrenObjectsToChildren,
} from './helper';

@@ -21,3 +28,3 @@ if (Platform.OS === 'android') {

300,
LayoutAnimation.Types.easeInEaseOut,
LayoutAnimation.Types.easeOut,
LayoutAnimation.Properties.opacity,

@@ -43,6 +50,10 @@ );

expandOnly,
seeMoreOverlapCount,
debounceSeeMoreCalc,
...restProps
}) => {
const [textHeight, setTextHeight] = useState(0);
const [hiddenTextHeight, setHiddenTextHeight] = useState(0);
const [additionalProps, setAdditionalProps] = useState({});
// hiddenTextHeightOne comes from hidden component one
const [hiddenTextHeightOne, setHiddenTextHeightOne] = useState(0);
// hiddenTextHeightWithSeeLess comes from hidden component two
const [

@@ -52,20 +63,85 @@ hiddenTextHeightWithSeeLess,

] = useState(0);
// textHeight and textWidth comes from hidden component three
const [textHeight, setTextHeight] = useState(0);
const [textWidth, setTextWidth] = useState(0);
// lineOfImpact comes from hidden component four
const [lineOfImpact, setLineOfImpact] = useState({});
const [lines, setLines] = useState([]);
const [truncatedLineOfImpact, setTruncatedLineOfImpact] = useState('');
const [truncatedLineOfImpactWidth, setTruncatedLineOfImpactWidth] = useState(
0,
);
const [seeMoreRightPadding, setSeeMoreRightPadding] = useState(0);
// mount or unmount hidden components
const [mountHiddenTextOne, setMountHiddenTextOne] = useState(true);
const [mountHiddenTextTwo, setMountHiddenTextTwo] = useState(true);
const [mountHiddenTextThree, setMountHiddenTextThree] = useState(true);
const [mountHiddenTextFour, setMountHiddenTextFour] = useState(true);
const [mountHiddenTextFive, setMountHiddenTextFive] = useState(false);
// initial measurement is in progress
const [isMeasuring, setIsMeasuring] = useState(true);
// logic decisioning params
const [seeMore, setSeeMore] = useState(false);
const [collapsed, setCollapsed] = useState(true);
const [afterCollapsed, setAfterCollapsed] = useState(true);
// copy of children with only text
const [collapsedChildren, setCollapsedChildren] = useState(
childrenToTextChildren(children, TextComponent),
);
const [measuredCollapsedChildren, setMeasuredCollapsedChildren] = useState(
null,
);
// width of see more component
const [seeMoreWidth, setSeeMoreWidth] = useState(0);
const onTextLayout = useCallback(
const onSeeMoreViewLayout = useCallback(
({
nativeEvent: {
layout: {height},
layout: {width},
},
}) => {
setSeeMoreWidth(width);
},
[setSeeMoreWidth],
);
const onLayoutHiddenTextFive = useCallback(
({
nativeEvent: {
layout: {width},
},
}) => {
setMountHiddenTextFive(false);
setTruncatedLineOfImpactWidth(width);
},
[setTruncatedLineOfImpactWidth, setMountHiddenTextFive],
);
const onTextLayoutHiddenTextFour = useCallback(
({nativeEvent: {lines: _lines}}) => {
const _lineOfImpact = _lines[numberOfLines - 1];
setLineOfImpact(_lineOfImpact);
setLines(_lines);
},
[numberOfLines, setLineOfImpact, setLines],
);
const onLayoutHiddenTextFour = useCallback(() => {
setMountHiddenTextFour(false);
}, [setMountHiddenTextFour]);
const onLayoutHiddenTextThree = useCallback(
({
nativeEvent: {
layout: {height, width},
},
}) => {
setTextHeight(height);
setTextWidth(width);
setMountHiddenTextThree(false);
},
[setTextHeight],
[setTextHeight, setTextWidth, setMountHiddenTextThree],
);
const onHiddenTextLayout = useCallback(
const onHiddenTextLayoutOne = useCallback(
({

@@ -76,6 +152,6 @@ nativeEvent: {

}) => {
setHiddenTextHeight(height);
setHiddenTextHeightOne(height);
setMountHiddenTextOne(false);
},
[setHiddenTextHeight, setMountHiddenTextOne],
[setHiddenTextHeightOne, setMountHiddenTextOne],
);

@@ -99,9 +175,131 @@

const measureSeeMoreLine = useCallback(() => {
if (
!seeMore ||
!textWidth ||
!numberOfLines ||
!seeMoreWidth ||
!lineOfImpact?.text
) {
setTruncatedLineOfImpact('');
setTruncatedLineOfImpactWidth(0);
return setMeasuredCollapsedChildren(null);
}
// if line of impact
// use number fo lines - 1 lines with wrap text and clip ellipsis
// another text component with line of impact
// width will be total width - see more width
// show this ^^ on collapsed state
const linesTillImpact = Array(numberOfLines)
.fill({})
.map((_e, index) => lines[index]);
const charactersBeforeSeeMore = linesToCharacters(linesTillImpact);
const charactersLengthTillSeeMore = charactersBeforeSeeMore.trim().length;
const seeMoreTextLength =
`${ellipsis} ${seeMoreText}`.length + seeMoreOverlapCount;
const textBreakPosition = charactersLengthTillSeeMore - seeMoreTextLength;
const trimmedLineOfImpact = lineOfImpact.text.trim();
const _truncatedLineOfImpact = trimmedLineOfImpact.substring(
0,
trimmedLineOfImpact.length - seeMoreTextLength,
);
setTruncatedLineOfImpact(_truncatedLineOfImpact);
// go to this position and insert a line break
let charactersToTraverse = textBreakPosition;
let nodeFound = false;
const modifiedChildrenObjects = getText(children, TextComponent).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;
return _childObject;
},
);
if (nodeFound) {
return setMeasuredCollapsedChildren(
childrenObjectsToChildren(modifiedChildrenObjects),
);
}
return setMeasuredCollapsedChildren(null);
}, [
children,
TextComponent,
textWidth,
numberOfLines,
seeMore,
seeMoreWidth,
lineOfImpact,
lines,
ellipsis,
seeMoreText,
seeMoreOverlapCount,
]);
const textProps = afterCollapsed
? {
numberOfLines,
ellipsizeMode: 'clip',
}
: {};
const commonHiddenComponentProps = {
...additionalProps,
style: StyleSheet.flatten([
Array.isArray(style) ? StyleSheet.flatten(style) : style,
styles.hiddenTextAbsolute,
]),
};
const hiddenComponentPropsLineOfImpact = {
...additionalProps,
style: StyleSheet.flatten([
Array.isArray(style) ? StyleSheet.flatten(style) : style,
styles.hiddenTextAbsoluteCompact,
]),
};
const seeMoreBackgroundStyle =
isMeasuring || lineOfImpact?.text ? {} : {backgroundColor};
const seeMoreContainerStyle = [
styles.seeMoreContainer,
seeMoreBackgroundStyle,
{
marginRight: seeMoreRightPadding,
},
];
useEffect(() => {
if (!hiddenTextHeight || !textHeight) {
if (!hiddenTextHeightOne || !textHeight) {
return;
}
setSeeMore(hiddenTextHeight > textHeight);
}, [textHeight, hiddenTextHeight]);
setSeeMore(hiddenTextHeightOne > textHeight);
}, [textHeight, hiddenTextHeightOne]);

@@ -124,5 +322,12 @@ useEffect(() => {

useEffect(() => {
setMountHiddenTextOne(true);
setMountHiddenTextTwo(true);
const handle = setTimeout(() => {
setMountHiddenTextOne(true);
setMountHiddenTextTwo(true);
setMountHiddenTextThree(true);
setMountHiddenTextFour(true);
}, debounceSeeMoreCalc);
return () => clearTimeout(handle);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
// re calc if any of these params change
numberOfLines,

@@ -137,18 +342,81 @@ style,

ellipsis,
allowFontScaling,
additionalProps,
]);
const textProps = afterCollapsed
? {
onLayout: onTextLayout,
numberOfLines,
ellipsizeMode: 'tail',
useEffect(() => {
const checkIfStillMeasuring = () => {
if (
!mountHiddenTextOne &&
!mountHiddenTextTwo &&
!mountHiddenTextThree &&
!mountHiddenTextFour
) {
setIsMeasuring(false);
if (animate) {
LayoutAnimation.configureNext(readmoreAnimation);
}
}
: {};
};
const additionalProps = {};
if (allowFontScaling !== undefined) {
additionalProps.allowFontScaling = allowFontScaling;
}
const handler = setTimeout(checkIfStillMeasuring, debounceSeeMoreCalc);
return () => clearTimeout(handler);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
mountHiddenTextOne,
mountHiddenTextTwo,
mountHiddenTextThree,
mountHiddenTextFour,
]);
// a map of additional props to be passed down
// in hidden text components other than style
// for accurate measurements
useEffect(() => {
const _additionalProps = {};
// pick selected params
if (allowFontScaling !== undefined) {
_additionalProps.allowFontScaling = allowFontScaling;
}
setAdditionalProps(_additionalProps);
}, [allowFontScaling]);
useEffect(() => {
const handle = setTimeout(measureSeeMoreLine, debounceSeeMoreCalc);
return () => clearTimeout(handle);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [measureSeeMoreLine]);
useEffect(() => {
const _textChildren = childrenToTextChildren(children, TextComponent);
setCollapsedChildren(_textChildren);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children]);
useEffect(() => {
if (!truncatedLineOfImpact) {
return;
}
setMountHiddenTextFive(true);
}, [truncatedLineOfImpact]);
useEffect(() => {
console.log('padding', truncatedLineOfImpactWidth, seeMoreWidth, textWidth);
if (!truncatedLineOfImpactWidth || !seeMoreWidth || !textWidth) {
setSeeMoreRightPadding(0);
return;
}
const _seeMoreRightPadding =
textWidth - truncatedLineOfImpactWidth - seeMoreWidth;
if (_seeMoreRightPadding > 0) {
setSeeMoreRightPadding(_seeMoreRightPadding);
if (animate) {
LayoutAnimation.configureNext(readmoreAnimation);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [truncatedLineOfImpactWidth, seeMoreWidth, textWidth]);
return (

@@ -159,9 +427,5 @@ <View style={wrapperStyle}>

<TextComponent
{...additionalProps}
style={StyleSheet.flatten([
Array.isArray(style) ? StyleSheet.flatten(style) : style,
styles.hiddenTextAbsolute,
])}
{...commonHiddenComponentProps}
ellipsizeMode={'clip'}
onLayout={onHiddenTextLayout}>
onLayout={onHiddenTextLayoutOne}>
{children || ''}

@@ -173,12 +437,39 @@ </TextComponent>

<TextComponent
{...additionalProps}
style={StyleSheet.flatten([
Array.isArray(style) ? StyleSheet.flatten(style) : style,
styles.hiddenTextAbsolute,
])}
{...commonHiddenComponentProps}
onLayout={onHiddenSeeLessTextLayoutTwo}>
{children || ''}
{` ${seeLessText}`}
{/* 3 spaces before see less are intentional */}
{` ${seeLessText}`}
</TextComponent>
)}
{/* to remove all flickers add another hidden component with collapsed children to get seeMore and all hidden components to use collapsed children only */}
{mountHiddenTextThree && (
<TextComponent
{...commonHiddenComponentProps}
numberOfLines={numberOfLines}
onLayout={onLayoutHiddenTextThree}>
{collapsedChildren || ''}
{/* no see less here since it's in collapsed state replicating original component */}
</TextComponent>
)}
{/* extract line of impact -> see more line */}
{mountHiddenTextFour && (
<TextComponent
{...commonHiddenComponentProps}
numberOfLines={numberOfLines + 1}
onLayout={onLayoutHiddenTextFour}
onTextLayout={onTextLayoutHiddenTextFour}>
{collapsedChildren || ''}
{/* no see less here since it's in collapsed state replicating original component */}
</TextComponent>
)}
{/* extract width of line of impact without see more line */}
{mountHiddenTextFive && (
<TextComponent
{...hiddenComponentPropsLineOfImpact}
onLayout={onLayoutHiddenTextFive}>
{truncatedLineOfImpact}
</TextComponent>
)}
{/* actual text component */}
<TextComponent

@@ -189,3 +480,5 @@ {...additionalProps}

{...textProps}>
{children || ''}
{isMeasuring || (seeMore && collapsed)
? measuredCollapsedChildren || collapsedChildren || ''
: children || ''}
{seeMore && !collapsed && !expandOnly && (

@@ -197,3 +490,3 @@ <TextComponent

style={seeLessStyle}>
{hiddenTextHeightWithSeeLess > hiddenTextHeight ? '\n' : ' '}
{hiddenTextHeightWithSeeLess > hiddenTextHeightOne ? '\n' : ' '}
{seeLessText}

@@ -203,4 +496,5 @@ </TextComponent>

</TextComponent>
{seeMore && collapsed && afterCollapsed && (
<View style={[styles.seeMoreContainer, {backgroundColor}]}>
{/* See more component */}
{seeMore && collapsed && !!collapsedChildren && !isMeasuring && (
<View style={seeMoreContainerStyle} onLayout={onSeeMoreViewLayout}>
<TextComponent

@@ -210,4 +504,11 @@ {...additionalProps}

onPress={toggle}
style={style}>
{`${ellipsis}`}
</TextComponent>
<TextComponent
{...additionalProps}
{...restProps}
onPress={toggle}
style={[style, seeMoreStyle]}>
{`${ellipsis} ${seeMoreText}`}
{` ${seeMoreText}`}
</TextComponent>

@@ -230,3 +531,11 @@ </View>

color: 'transparent',
display: 'none',
},
hiddenTextAbsoluteCompact: {
position: 'absolute',
left: 0,
top: 0,
color: 'transparent',
display: 'none',
},
seeMoreContainer: {

@@ -273,2 +582,4 @@ position: 'absolute',

expandOnly: PropTypes.bool,
seeMoreOverlapCount: PropTypes.number,
debounceSeeMoreCalc: PropTypes.number,
};

@@ -292,4 +603,6 @@

expandOnly: false,
seeMoreOverlapCount: 1,
debounceSeeMoreCalc: 300,
};
export default memo(ReadMore);

2

package.json
{
"name": "@fawazahmed/react-native-read-more",
"version": "1.1.5",
"version": "2.0.0",
"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",

![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)
### Imp: v2.0.0 is exprimental, if you see issues please report as gituhb issue and downgrade to v1.1.5
#### Please :star: it, thanks :thumbsup:

@@ -4,0 +6,0 @@ # react-native-read-more

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