├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── example
├── App.css
├── App.jsx
├── MultiClampWithTooltip.jsx
├── index.html
├── main.jsx
├── reset.css
├── sample1.png
├── sample2.png
└── sample3.png
├── index.d.ts
├── lib
└── MultiClamp.js
├── package.json
├── site
├── bundle.js
├── bundle.js.map
└── index.html
├── src
└── MultiClamp.jsx
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ],
6 | "plugins": ["@babel/plugin-proposal-class-properties"]
7 | }
8 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | example/
3 | site/
4 | lib/
5 | .babelrc
6 | .eslintignore
7 | .eslintrc
8 | .gitignore
9 | .npmignore
10 | webpack.config.js
11 | *.d.ts
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | "extends": ["eslint:recommended", "plugin:react/recommended"],
6 | "parser": "babel-eslint",
7 | "rules": {
8 | "strict": 0,
9 | "indent": [
10 | "error",
11 | 2
12 | ],
13 | "quotes": [
14 | "error",
15 | "single"
16 | ],
17 | "semi": [
18 | "error",
19 | "always"
20 | ],
21 | "no-console": [
22 | "warn"
23 | ]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | .DS_Store
4 | build/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | build/
2 | example/
3 | site/
4 | .babelrc
5 | .eslintignore
6 | .eslintrc
7 | .gitignore
8 | .npmignore
9 | webpack.config.js
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 jackyr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-multi-clamp
2 |
3 | 
4 | 
5 | 
6 | [](https://www.npmjs.com/package/react-multi-clamp)
7 |
8 | Simple, efficient and easy-to-use multiline text clamp react component. (supports reverse clamp)
9 |
10 | 简单、高效、易用的多行文本裁剪react组件。(支持反向裁剪)
11 |
12 | Encapsulation based on [multi-clamp](https://github.com/jackyr/multi-clamp) module
13 | [](https://www.npmjs.com/package/multi-clamp)
14 |
15 | 本组件基于[multi-clamp](https://github.com/jackyr/multi-clamp)模块封装
16 |
17 | ## Samples
18 | Default multiline text clamp:
19 |
20 | 默认多行文本裁剪效果:
21 |
22 | 
23 |
24 | Custom ellipsis:
25 |
26 | 自定义省略符号:
27 |
28 | 
29 |
30 | Reverse clamp:
31 |
32 | 反向裁剪:
33 |
34 | 
35 |
36 | [Demo Page](https://jackyr.github.io/react-multi-clamp/site/)
37 |
38 | ## Browser compatibility
39 | Supports IE9+ / Android4.4+ / etc. ES5 environment.
40 |
41 | 支持PC/移动设备所有兼容ES5环境的浏览器。
42 |
43 | ## Installation
44 | You can install react-multi-clamp from npm.
45 |
46 | 你可以从npm安装react-multi-clamp组件。
47 |
48 | ```sh
49 | npm install react-multi-clamp --save
50 | ```
51 |
52 | ## Usage
53 | Import react-multi-clamp.
54 |
55 | 引入react-multi-clamp组件。
56 |
57 | ```js
58 | import MultiClamp from 'react-multi-clamp';
59 | ```
60 |
61 | Just wrap the content(**must be pure text**) in react-multi-clamp component.
62 |
63 | 将要裁减的内容(**必须为纯文本**)包裹起来即可。
64 |
65 | ```html
66 | {longText}
67 | ```
68 |
69 | ## Options
70 | #### `ellipsis`: PropTypes.string || PropTypes.element
71 | Ellipsis can be string or react element. default: '...'
72 |
73 | 超出最大行数裁剪后的符号,可以为字符串或任意react元素。默认为:'...'
74 |
75 | #### `clamp`: PropTypes.number || 'auto'
76 | The max number of lines to show. It will try to fill up the available space when set to string 'auto', and at this point you should set a static height on the text container element. default: 3
77 |
78 | 最大行数。设置为字符串'auto'时会根据最大高度自适应裁剪,此时文本容器需要定义高度。默认为:3
79 |
80 | #### `reverse`: PropTypes.bool
81 | You can clamp the content from back to front, the ellipsis will be in the front. default: false
82 |
83 | 是否反向裁剪。反向将从后往前裁剪,ellipsis符号会显示在最前面。默认为:false
84 |
85 | #### `splitByWords`: PropTypes.bool
86 | The default behavior is to split by letters. If you want to split by words, set splitByWords to true. default: false
87 |
88 | 组件对于英文文本默认按字符进行裁剪。如果希望按单词裁剪,请将splitByWords设置为true。默认为:false
89 |
90 | #### `disableCssClamp`: PropTypes.bool
91 | React-multi-clamp will use native css clamp(-webkit-line-clamp) in supported browser when the ellipsis is set to '...'. If you don't want to use css clamp, set disableCssClamp to true. default: false
92 |
93 | 当ellipsis被设置为'...'时,组件会默认优先使用webkit的原生css裁剪(-webkit-line-clamp),如果想禁用css裁减,请将disableCssClamp设置为true。默认为:false
94 |
95 | #### `onClampStart`: function({ needClamp: boolean }): void || false
96 | This callback function will be executed when clamp starts, and will not be executed when use native css clamp. Clamp will be prevented when return value is false. default: function() {}
97 |
98 | 该回调函数在clamp开始时触发,使用原生css裁剪时不会触发。返回值为false时强制不进行clamp。默认为:function() {}
99 |
100 | #### `onClampEnd`: function({ didClamp: boolean }): void
101 | This callback function will be executed when clamp ends, and will not be executed when use native css clamp. default: function() {}
102 |
103 | 该回调函数在clamp结束时触发,使用原生css裁剪时不会触发。默认为:function() {}
104 |
105 | ## Testing
106 | ```sh
107 | git clone git@github.com:jackyr/react-multi-clamp.git
108 | cd react-multi-clamp
109 | npm install
110 | npm start
111 | ```
112 |
113 | ## Changelog
114 | #### v2.0.6
115 | - Bugfix when text changes immediately after mount. [#12](https://github.com/jackyr/react-multi-clamp/issues/12)
116 | - mount之后文本立即发生改变时bug修正。 [#12](https://github.com/jackyr/react-multi-clamp/issues/12)
117 |
118 | #### v2.0.5
119 | - Typescript definitions bugfix. [#11](https://github.com/jackyr/react-multi-clamp/pull/11)
120 | - ts类型定义bug修正。 [#11](https://github.com/jackyr/react-multi-clamp/pull/11)
121 |
122 | #### v2.0.4
123 | - Typescript definitions bugfix. [#9](https://github.com/jackyr/react-multi-clamp/issues/9)
124 | - ts类型定义bug修正。 [#9](https://github.com/jackyr/react-multi-clamp/issues/9)
125 |
126 | #### v2.0.3
127 | - Add typescript definitions. [#8](https://github.com/jackyr/react-multi-clamp/pull/8)
128 | - 增加ts类型定义。 [#8](https://github.com/jackyr/react-multi-clamp/pull/8)
129 |
130 | #### v2.0.2
131 | - Bugfix when passing element object to option ellipsis. [#5](https://github.com/jackyr/react-multi-clamp/issues/5)
132 | - 裁剪符号为react元素时bug修正。[#5](https://github.com/jackyr/react-multi-clamp/issues/5)
133 |
134 | #### v2.0.1
135 | - Bugfix when passing element object to option ellipsis. [#3](https://github.com/jackyr/react-multi-clamp/issues/3)
136 | - 裁剪符号为react元素时bug修正。[#3](https://github.com/jackyr/react-multi-clamp/issues/3)
137 |
138 | #### v2.0.0
139 | - Dependency [multi-clamp](https://github.com/jackyr/multi-clamp) update to v2.0, refactoring. [multi-clamp#3](https://github.com/jackyr/multi-clamp/issues/3)
140 | - 依赖[multi-clamp](https://github.com/jackyr/multi-clamp)升级至v2.0,其内部实现重构。[multi-clamp#3](https://github.com/jackyr/multi-clamp/issues/3)
141 |
142 | ## License
143 | MIT
--------------------------------------------------------------------------------
/example/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | width: 460px;
3 | margin: 0 auto;
4 | font-family: 'Helvetica';
5 | font-size: 16px;
6 | line-height: 1.5;
7 | }
8 |
--------------------------------------------------------------------------------
/example/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import MultiClamp from '../src/MultiClamp.jsx';
3 | import MultiClampWithTooltip from './MultiClampWithTooltip.jsx';
4 | import './App.css';
5 |
6 | const text1 = 'React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.';
7 | const text2 = "2009年12月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在2013年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。";
8 |
9 | class App extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | text: text1,
14 | style: { height: 48 },
15 | }
16 | }
17 | componentDidMount() {
18 | setTimeout(() => {
19 | this.setState({
20 | text: 'When text changes example: ' + this.state.text,
21 | });
22 | }, 3000);
23 | setTimeout(() => {
24 | this.setState({
25 | style: { height: 72 },
26 | }, () => {
27 | this.instance.multiClamp.reload({ useOriginalText: true });
28 | });
29 | }, 3000);
30 | }
31 | onClampStart(e) {
32 | console.log(e);
33 | }
34 | onClampEnd(e) {
35 | console.log(e);
36 | }
37 | handleClick = () => {
38 | this.instance2.multiClamp.reload({
39 | clamp: 'auto',
40 | useOriginalText: true,
41 | });
42 | }
43 | render() {
44 | return (
45 |
46 | {text1}
47 |
48 | {text1}
49 |
50 | more>>
52 | } splitByWords ref={ref => this.instance2 = ref}>{text1}
53 |
54 | {text2}
55 |
56 | {text2}
57 |
58 | {this.state.text}
59 |
60 | this.instance = ref}>{'When style changes example: ' + text1}
61 |
62 | {'Hover on me: ' + text1}
63 |
64 | );
65 | }
66 | }
67 |
68 | export default App;
69 |
--------------------------------------------------------------------------------
/example/MultiClampWithTooltip.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Tooltip from 'rc-tooltip';
3 | import 'rc-tooltip/assets/bootstrap.css';
4 | import MultiClamp from '../src/MultiClamp.jsx';
5 |
6 | export default class MultiClampWithToolTip extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | withToolTip: false,
11 | };
12 | }
13 | onClampStart = (e) => {
14 | if (!this.state.withToolTip && e.needClamp) {
15 | this.setState({
16 | withToolTip: true,
17 | });
18 | return false;
19 | }
20 | }
21 | render() {
22 | const clamp =
23 | {this.props.children}
24 | ;
25 |
26 | if (this.state.withToolTip) {
27 | return {this.props.children}}
30 | >{clamp};
31 | }
32 |
33 | return clamp;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | react-multi-clamp examples
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './reset.css';
4 | import App from './App.jsx';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/example/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
--------------------------------------------------------------------------------
/example/sample1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackyr/react-multi-clamp/4504418c7274e1764a2ae6da9d652e8663a19880/example/sample1.png
--------------------------------------------------------------------------------
/example/sample2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackyr/react-multi-clamp/4504418c7274e1764a2ae6da9d652e8663a19880/example/sample2.png
--------------------------------------------------------------------------------
/example/sample3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jackyr/react-multi-clamp/4504418c7274e1764a2ae6da9d652e8663a19880/example/sample3.png
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "react-multi-clamp" {
2 | import React from "react";
3 |
4 | export type ReloadOptions = {
5 | /**
6 | * Ellipsis can be string, HTML string or element object.
7 | *
8 | * 超出最大行数裁剪后的符号,可以为字符串,html字符串或任意html元素。
9 | *
10 | * @default "..."
11 | */
12 | ellipsis?: string | HTMLElement;
13 |
14 | /**
15 | * The max number of lines to show. It will try to fill up the available space when set to string 'auto', and at this point you should set a static height on the text container element.
16 | *
17 | * 最大行数。设置为字符串'auto'时会根据最大高度自适应裁剪,此时文本容器需要定义高度。
18 | *
19 | * @default 3
20 | */
21 | clamp?: string | number;
22 |
23 | /**
24 | * You can clamp the content from back to front, the ellipsis will be in the front.
25 | *
26 | * 是否反向裁剪。反向将从后往前裁剪,ellipsis符号会显示在最前面。
27 | *
28 | * @default false
29 | */
30 | reverse?: boolean;
31 |
32 | /**
33 | * The default behavior is to split by letters. If you want to split by words, set splitByWords to true.
34 | *
35 | * 组件对于英文文本默认按字符进行裁剪。如果希望按单词裁剪,请将splitByWords设置为true。
36 | *
37 | * @default false
38 | */
39 | splitByWords?: boolean;
40 |
41 | /**
42 | * React-multi-clamp will use native css clamp(-webkit-line-clamp) in supported browser when the ellipsis is set to '...'. If you don't want to use css clamp, set `disableCssClamp` to `true`.
43 | *
44 | * 当ellipsis被设置为'...'时,组件会默认优先使用webkit的原生css裁剪(-webkit-line-clamp),如果想禁用css裁减,请将disableCssClamp设置为true。
45 | *
46 | * @default false
47 | */
48 | disableCssClamp?: boolean;
49 |
50 | lineTextLen?: string | number;
51 |
52 | /**
53 | * This callback function will be executed when clamp starts, and will not be executed when use native css clamp. Clamp will be prevented when return value is false.
54 | *
55 | * 该回调函数在clamp开始时触发,使用原生css裁剪时不会触发。返回值为false时强制不进行clamp。
56 | *
57 | * @default function() {}
58 | */
59 | onClampStart?: (result: { needClamp: boolean }) => void | false;
60 |
61 | /**
62 | * This callback function will be executed when clamp ends, and will not be executed when use native css clamp.
63 | *
64 | * 该回调函数在clamp结束时触发,使用原生css裁剪时不会触发。
65 | *
66 | * @default function() {}
67 | */
68 | onClampEnd?: (result: { didClamp: boolean }) => void;
69 |
70 | /**
71 | * Use the original text to re-clamp when options.useOriginalText set to true.
72 | *
73 | * 是否使用原始文本重新裁剪。
74 | *
75 | * @default false
76 | */
77 | useOriginalText?: boolean;
78 | };
79 |
80 | export type MultiClampHTMLDivElement = HTMLDivElement & {
81 | multiClamp: {
82 | reload(options?: ReloadOptions): void;
83 | };
84 | };
85 |
86 | export type ClampProps = {
87 | /**
88 | * Ellipsis can be string or react element.
89 | *
90 | * 超出最大行数裁剪后的符号,可以为字符串或任意react元素。
91 | *
92 | * @default "..."
93 | */
94 | ellipsis?: string | JSX.Element;
95 |
96 | /**
97 | * The max number of lines to show. It will try to fill up the available space when set to string 'auto', and at this point you should set a static height on the text container element.
98 | *
99 | * 最大行数。设置为字符串'auto'时会根据最大高度自适应裁剪,此时文本容器需要定义高度。
100 | *
101 | * @default 3
102 | */
103 | clamp?: string | number;
104 |
105 | /**
106 | * You can clamp the content from back to front, the ellipsis will be in the front.
107 | *
108 | * 是否反向裁剪。反向将从后往前裁剪,ellipsis符号会显示在最前面。
109 | *
110 | * @default false
111 | */
112 | reverse?: boolean;
113 |
114 | /**
115 | * The default behavior is to split by letters. If you want to split by words, set splitByWords to true.
116 | *
117 | * 组件对于英文文本默认按字符进行裁剪。如果希望按单词裁剪,请将splitByWords设置为true。
118 | *
119 | * @default false
120 | */
121 | splitByWords?: boolean;
122 |
123 | /**
124 | * React-multi-clamp will use native css clamp(-webkit-line-clamp) in supported browser when the ellipsis is set to '...'. If you don't want to use css clamp, set `disableCssClamp` to `true`.
125 | *
126 | * 当ellipsis被设置为'...'时,组件会默认优先使用webkit的原生css裁剪(-webkit-line-clamp),如果想禁用css裁减,请将disableCssClamp设置为true。
127 | *
128 | * @default false
129 | */
130 | disableCssClamp?: boolean;
131 |
132 | lineTextLen?: string | number;
133 |
134 | /**
135 | * This callback function will be executed when clamp starts, and will not be executed when use native css clamp. Clamp will be prevented when return value is false.
136 | *
137 | * 该回调函数在clamp开始时触发,使用原生css裁剪时不会触发。返回值为false时强制不进行clamp。
138 | *
139 | * @default function() {}
140 | */
141 | onClampStart?: (result: { needClamp: boolean }) => void | false;
142 |
143 | /**
144 | * This callback function will be executed when clamp ends, and will not be executed when use native css clamp.
145 | *
146 | * 该回调函数在clamp结束时触发,使用原生css裁剪时不会触发。
147 | *
148 | * @default function() {}
149 | */
150 | onClampEnd?: (result: { didClamp: boolean }) => void;
151 |
152 | ref?: React.Ref;
153 |
154 | children?: React.ReactNode;
155 | };
156 |
157 | const Clamp: React.FC;
158 |
159 | export default Clamp;
160 | }
161 |
--------------------------------------------------------------------------------
/lib/MultiClamp.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _propTypes = _interopRequireDefault(require("prop-types"));
11 |
12 | var _multiClamp = _interopRequireDefault(require("multi-clamp"));
13 |
14 | var _class, _temp;
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
17 |
18 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
19 |
20 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
21 |
22 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
23 |
24 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
25 |
26 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
27 |
28 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
29 |
30 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
31 |
32 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
33 |
34 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
35 |
36 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
37 |
38 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
39 |
40 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
41 |
42 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
43 |
44 | var Clamp = (_temp = _class =
45 | /*#__PURE__*/
46 | function (_React$Component) {
47 | _inherits(Clamp, _React$Component);
48 |
49 | function Clamp(props) {
50 | var _this;
51 |
52 | _classCallCheck(this, Clamp);
53 |
54 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Clamp).call(this, props));
55 | var ellipsisElementMode = _react["default"].isValidElement(_this.props.ellipsis) && _react["default"].Children.count(_this.props.ellipsis) === 1;
56 | _this.state = {
57 | ellipsisInit: !ellipsisElementMode,
58 | ellipsisElementMode: ellipsisElementMode
59 | };
60 | return _this;
61 | }
62 |
63 | _createClass(Clamp, [{
64 | key: "componentDidMount",
65 | value: function componentDidMount() {
66 | var _this2 = this;
67 |
68 | var ellipsis;
69 |
70 | if (this.state.ellipsisElementMode) {
71 | ellipsis = this.wrapper.previousElementSibling || this.wrapper.previousSibling;
72 | } else {
73 | ellipsis = this.ellipsis.innerHTML;
74 | }
75 |
76 | this.setState({
77 | ellipsisInit: false
78 | }, function () {
79 | var self = _this2;
80 | _this2.multiClamp = new _multiClamp["default"](_this2.wrapper, _objectSpread({}, _this2.props, {
81 | ellipsis: ellipsis,
82 | onClampEnd: function onClampEnd() {
83 | if (ellipsis === (self.wrapper.previousElementSibling || self.wrapper.previousSibling)) {
84 | self.wrapper.parentNode.removeChild(ellipsis);
85 | }
86 |
87 | if (self.props.onClampEnd) {
88 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
89 | args[_key] = arguments[_key];
90 | }
91 |
92 | self.props.onClampEnd.apply(this, args);
93 | }
94 | }
95 | }));
96 | });
97 | }
98 | }, {
99 | key: "componentDidUpdate",
100 | value: function componentDidUpdate(prevProps) {
101 | if (this.multiClamp && this.diffChildren(this.props, prevProps)) {
102 | this.multiClamp.reload();
103 | }
104 | }
105 | }, {
106 | key: "diffChildren",
107 | value: function diffChildren(nextProps, prevProps) {
108 | var nextChildren = nextProps.children;
109 | var prevChildren = prevProps.children;
110 |
111 | if (Array.isArray(prevChildren)) {
112 | return !Array.isArray(nextChildren) || nextChildren.join('') !== prevChildren.join('');
113 | }
114 |
115 | return prevChildren !== nextChildren;
116 | }
117 | }, {
118 | key: "render",
119 | value: function render() {
120 | var _this3 = this;
121 |
122 | var _this$props = this.props,
123 | children = _this$props.children,
124 | ellipsis = _this$props.ellipsis;
125 |
126 | var props = _objectSpread({}, this.props);
127 |
128 | 'key|ellipsis|clamp|reverse|splitByWords|disableCssClamp|lineTextLen|onClampStart|onClampEnd'.split('|').forEach(function (v) {
129 | return delete props[v];
130 | });
131 | return _react["default"].createElement(_react["default"].Fragment, null, this.state.ellipsisElementMode && ellipsis, _react["default"].createElement("div", _extends({}, props, {
132 | ref: function ref(_ref2) {
133 | _this3.wrapper = _ref2;
134 | }
135 | }), this.state.ellipsisInit ? _react["default"].createElement("div", {
136 | style: {
137 | display: 'none'
138 | },
139 | ref: function ref(_ref) {
140 | _this3.ellipsis = _ref;
141 | }
142 | }, ellipsis) : children));
143 | }
144 | }]);
145 |
146 | return Clamp;
147 | }(_react["default"].Component), _defineProperty(_class, "propTypes", {
148 | children: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].arrayOf(_propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]))]),
149 | ellipsis: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].element]),
150 | clamp: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]),
151 | reverse: _propTypes["default"].bool,
152 | splitByWords: _propTypes["default"].bool,
153 | disableCssClamp: _propTypes["default"].bool,
154 | lineTextLen: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].number]),
155 | onClampStart: _propTypes["default"].func,
156 | onClampEnd: _propTypes["default"].func
157 | }), _defineProperty(_class, "defaultProps", {
158 | ellipsis: '...',
159 | clamp: 3,
160 | reverse: false,
161 | splitByWords: false,
162 | disableCssClamp: false
163 | }), _temp);
164 | var _default = Clamp;
165 | exports["default"] = _default;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-multi-clamp",
3 | "version": "2.0.6",
4 | "description": "Simple, efficient and easy-to-use multiline text clamp react component. (supports reverse clamp)",
5 | "main": "lib/MultiClamp.js",
6 | "scripts": {
7 | "build": "babel src -d lib",
8 | "clean": "rm -rf ./lib",
9 | "dev": "webpack-dev-server",
10 | "lint": "eslint --ext .js,.jsx src/",
11 | "prepublish": "npm run lint && npm run clean && npm run build",
12 | "site": "webpack --output-path site",
13 | "start": "npm run dev",
14 | "test": "echo \"Error: no test specified\" && exit 1"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/jackyr/react-multi-clamp.git"
19 | },
20 | "keywords": [
21 | "react",
22 | "multi",
23 | "multiline",
24 | "clamp",
25 | "ellipsis"
26 | ],
27 | "author": "jackyr",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/jackyr/react-multi-clamp/issues"
31 | },
32 | "homepage": "https://github.com/jackyr/react-multi-clamp#readme",
33 | "devDependencies": {
34 | "@babel/cli": "^7.7.0",
35 | "@babel/core": "^7.7.2",
36 | "@babel/plugin-proposal-class-properties": "^7.7.0",
37 | "@babel/polyfill": "^7.7.0",
38 | "@babel/preset-env": "^7.7.1",
39 | "@babel/preset-react": "^7.7.0",
40 | "babel-eslint": "^10.0.3",
41 | "babel-loader": "^8.0.6",
42 | "css-loader": "^3.2.0",
43 | "eslint": "^6.6.0",
44 | "eslint-plugin-react": "^7.16.0",
45 | "rc-tooltip": "^4.0.0-alpha.2",
46 | "react": "^16.11.0",
47 | "react-dom": "^16.11.0",
48 | "style-loader": "^1.0.0",
49 | "webpack": "^4.41.2",
50 | "webpack-cli": "^3.3.10",
51 | "webpack-dev-server": "^3.9.0"
52 | },
53 | "dependencies": {
54 | "multi-clamp": "^2.0.2",
55 | "prop-types": "^15.7.2"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/site/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | react-multi-clamp examples
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/MultiClamp.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MultiClamp from 'multi-clamp';
4 |
5 | const Clamp = class extends React.Component {
6 | static propTypes = {
7 | children: PropTypes.oneOfType([
8 | PropTypes.string,
9 | PropTypes.arrayOf(PropTypes.oneOfType([
10 | PropTypes.string,
11 | PropTypes.number,
12 | ])),
13 | ]),
14 | ellipsis: PropTypes.oneOfType([
15 | PropTypes.string,
16 | PropTypes.element,
17 | ]),
18 | clamp: PropTypes.oneOfType([
19 | PropTypes.string,
20 | PropTypes.number,
21 | ]),
22 | reverse: PropTypes.bool,
23 | splitByWords: PropTypes.bool,
24 | disableCssClamp: PropTypes.bool,
25 | lineTextLen: PropTypes.oneOfType([
26 | PropTypes.string,
27 | PropTypes.number,
28 | ]),
29 | onClampStart: PropTypes.func,
30 | onClampEnd: PropTypes.func,
31 | }
32 | static defaultProps = {
33 | ellipsis: '...',
34 | clamp: 3,
35 | reverse: false,
36 | splitByWords: false,
37 | disableCssClamp: false,
38 | }
39 | constructor(props) {
40 | super(props);
41 | const ellipsisElementMode = React.isValidElement(this.props.ellipsis) && React.Children.count(this.props.ellipsis) === 1;
42 | this.state = {
43 | ellipsisInit: !ellipsisElementMode,
44 | ellipsisElementMode,
45 | };
46 | }
47 | componentDidMount() {
48 | let ellipsis;
49 |
50 | if (this.state.ellipsisElementMode) {
51 | ellipsis = this.wrapper.previousElementSibling || this.wrapper.previousSibling;
52 | } else {
53 | ellipsis = this.ellipsis.innerHTML;
54 | }
55 |
56 | this.setState({
57 | ellipsisInit: false,
58 | }, () => {
59 | const self = this;
60 | this.multiClamp = new MultiClamp(this.wrapper, {
61 | ...this.props,
62 | ellipsis,
63 | onClampEnd(...args) {
64 | if (ellipsis === (self.wrapper.previousElementSibling || self.wrapper.previousSibling)) {
65 | self.wrapper.parentNode.removeChild(ellipsis);
66 | }
67 | if (self.props.onClampEnd) {
68 | self.props.onClampEnd.apply(this, args);
69 | }
70 | },
71 | });
72 | });
73 | }
74 | componentDidUpdate(prevProps) {
75 | if (this.multiClamp && this.diffChildren(this.props, prevProps)) {
76 | this.multiClamp.reload();
77 | }
78 | }
79 | diffChildren(nextProps, prevProps) {
80 | const nextChildren = nextProps.children;
81 | const prevChildren = prevProps.children;
82 | if (Array.isArray(prevChildren)) {
83 | return !Array.isArray(nextChildren) || nextChildren.join('') !== prevChildren.join('');
84 | }
85 | return prevChildren !== nextChildren;
86 | }
87 | render() {
88 | const { children, ellipsis } = this.props;
89 | const props = { ...this.props };
90 |
91 | 'key|ellipsis|clamp|reverse|splitByWords|disableCssClamp|lineTextLen|onClampStart|onClampEnd'
92 | .split('|').forEach(v => delete props[v]);
93 |
94 | return (<>
95 | {this.state.ellipsisElementMode && ellipsis}
96 | { this.wrapper = ref; }}
99 | >
100 | {
101 | this.state.ellipsisInit ?
{ this.ellipsis = ref; }}
104 | >{ellipsis}
: children
105 | }
106 |
107 | >);
108 | }
109 | };
110 |
111 | export default Clamp;
112 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 |
3 | module.exports = {
4 | devtool: 'source-map',
5 | entry: ['@babel/polyfill', path.resolve(__dirname, "example/main.jsx")],
6 | output: {
7 | path: path.resolve(__dirname, "build"),
8 | publicPath: "/assets/",
9 | filename: "bundle.js"
10 | },
11 | devServer: {
12 | port: 3000,
13 | contentBase: path.resolve(__dirname, "example"),
14 | historyApiFallback: true,
15 | inline: true,
16 | open: true
17 | },
18 | module: {
19 | rules: [
20 | { test: /(\.jsx|\.js)$/, use: ["babel-loader"] },
21 | { test: /\.css$/, use: ["style-loader","css-loader"] }
22 | ]
23 | }
24 | };
25 |
--------------------------------------------------------------------------------