diff --git a/.eslintrc.js b/.eslintrc.js index 8ceadd6..e922b65 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -61,6 +61,7 @@ module.exports = { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, ], + 'react/jsx-props-no-spreading': OFF, 'func-names': OFF, 'lines-between-class-members': OFF, 'max-classes-per-file': OFF, @@ -73,7 +74,20 @@ module.exports = { 'no-unused-expressions': OFF, 'no-useless-constructor': OFF, 'no-empty-function': OFF, + 'unicorn/no-null': OFF, 'unicorn/prevent-abbreviations': OFF, + 'unicorn/no-process-exit': OFF, + 'unicorn/filename-case': [ + ERROR, + { + cases: { + kebabCase: false, + camelCase: true, + snakeCase: false, + pascalCase: true, + }, + }, + ], '@typescript-eslint/no-empty-function': OFF, '@typescript-eslint/no-useless-constructor': ERROR, }, diff --git a/example/ClampText.d.ts b/example/ClampText.d.ts new file mode 100644 index 0000000..56f3e25 --- /dev/null +++ b/example/ClampText.d.ts @@ -0,0 +1,11 @@ +import React from 'react'; +export interface ClampTextProps { + autoresize?: boolean; + maxLines: number; + maxHeight: number | string; + ellipsis?: string; + expanded?: boolean; + content: string; +} +declare const ClampText: React.FC; +export default ClampText; diff --git a/example/clamp.d.ts b/example/clamp.d.ts new file mode 100644 index 0000000..d1a373f --- /dev/null +++ b/example/clamp.d.ts @@ -0,0 +1,13 @@ +import React from 'react'; +export interface ReactSimpleClampProps { + autoresize?: boolean; + maxLines: number; + maxHeight: number | string; + ellipsis?: string; + expanded?: boolean; + content: T; + renderContent: () => React.ReactElement; + renderClampedContent: (offset: number, ellipsis: string) => React.ReactElement; +} +declare const ReactSimpleClamp: React.FC>>; +export default ReactSimpleClamp; diff --git a/example/index.d.ts b/example/index.d.ts index dfb0172..6657561 100644 --- a/example/index.d.ts +++ b/example/index.d.ts @@ -1,11 +1,2 @@ -import React from 'react'; -export interface ReactSimpleClampProps { - autoresize?: boolean; - maxLines: number; - maxHeight: number | string; - ellipsis?: string; - expanded: boolean; - content: string; -} -declare const ReactSimpleClamp: React.FC; -export default ReactSimpleClamp; +import ClampText from './ClampText'; +export default ClampText; diff --git a/example/index.js b/example/index.js index 07be124..3644dea 100644 --- a/example/index.js +++ b/example/index.js @@ -600,6 +600,21 @@ } }; + var nativeSymbol = + !!Object.getOwnPropertySymbols && + !fails(function () { + // Chrome 38 Symbol has incorrect toString conversion + // eslint-disable-next-line no-undef + return !String(Symbol()); + }); + + var useSymbolAsUid = + nativeSymbol && + // eslint-disable-next-line no-undef + !Symbol.sham && + // eslint-disable-next-line no-undef + typeof Symbol.iterator == 'symbol'; + // `IsArray` abstract operation // https://tc39.github.io/ecma262/#sec-isarray var isArray = @@ -614,26 +629,132 @@ return Object(requireObjectCoercible(argument)); }; - var createProperty = function (object, key, value) { - var propertyKey = toPrimitive(key); - if (propertyKey in object) objectDefineProperty.f(object, propertyKey, createPropertyDescriptor(0, value)); - else object[propertyKey] = value; + // `Object.keys` method + // https://tc39.github.io/ecma262/#sec-object.keys + var objectKeys = + Object.keys || + function keys(O) { + return objectKeysInternal(O, enumBugKeys); + }; + + // `Object.defineProperties` method + // https://tc39.github.io/ecma262/#sec-object.defineproperties + var objectDefineProperties = descriptors + ? Object.defineProperties + : function defineProperties(O, Properties) { + anObject(O); + var keys = objectKeys(Properties); + var length = keys.length; + var index = 0; + var key; + while (length > index) objectDefineProperty.f(O, (key = keys[index++]), Properties[key]); + return O; + }; + + var html = getBuiltIn('document', 'documentElement'); + + var GT = '>'; + var LT = '<'; + var PROTOTYPE = 'prototype'; + var SCRIPT = 'script'; + var IE_PROTO = sharedKey('IE_PROTO'); + + var EmptyConstructor = function () { + /* empty */ }; - var nativeSymbol = - !!Object.getOwnPropertySymbols && - !fails(function () { - // Chrome 38 Symbol has incorrect toString conversion - // eslint-disable-next-line no-undef - return !String(Symbol()); - }); + var scriptTag = function (content) { + return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT; + }; - var useSymbolAsUid = - nativeSymbol && - // eslint-disable-next-line no-undef - !Symbol.sham && - // eslint-disable-next-line no-undef - typeof Symbol.iterator == 'symbol'; + // Create object with fake `null` prototype: use ActiveX Object with cleared prototype + var NullProtoObjectViaActiveX = function (activeXDocument) { + activeXDocument.write(scriptTag('')); + activeXDocument.close(); + var temp = activeXDocument.parentWindow.Object; + activeXDocument = null; // avoid memory leak + return temp; + }; + + // Create object with fake `null` prototype: use iframe Object with cleared prototype + var NullProtoObjectViaIFrame = function () { + // Thrash, waste and sodomy: IE GC bug + var iframe = documentCreateElement('iframe'); + var JS = 'java' + SCRIPT + ':'; + var iframeDocument; + iframe.style.display = 'none'; + html.appendChild(iframe); + // https://github.com/zloirock/core-js/issues/475 + iframe.src = String(JS); + iframeDocument = iframe.contentWindow.document; + iframeDocument.open(); + iframeDocument.write(scriptTag('document.F=Object')); + iframeDocument.close(); + return iframeDocument.F; + }; + + // Check for document.domain and active x support + // No need to use active x approach when document.domain is not set + // see https://github.com/es-shims/es5-shim/issues/150 + // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 + // avoid IE GC bug + var activeXDocument; + var NullProtoObject = function () { + try { + /* global ActiveXObject */ + activeXDocument = document.domain && new ActiveXObject('htmlfile'); + } catch (error) { + /* ignore */ + } + NullProtoObject = activeXDocument ? NullProtoObjectViaActiveX(activeXDocument) : NullProtoObjectViaIFrame(); + var length = enumBugKeys.length; + while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]]; + return NullProtoObject(); + }; + + hiddenKeys[IE_PROTO] = true; + + // `Object.create` method + // https://tc39.github.io/ecma262/#sec-object.create + var objectCreate = + Object.create || + function create(O, Properties) { + var result; + if (O !== null) { + EmptyConstructor[PROTOTYPE] = anObject(O); + result = new EmptyConstructor(); + EmptyConstructor[PROTOTYPE] = null; + // add "__proto__" for Object.getPrototypeOf polyfill + result[IE_PROTO] = O; + } else result = NullProtoObject(); + return Properties === undefined ? result : objectDefineProperties(result, Properties); + }; + + var nativeGetOwnPropertyNames = objectGetOwnPropertyNames.f; + + var toString$1 = {}.toString; + + var windowNames = + typeof window == 'object' && window && Object.getOwnPropertyNames ? Object.getOwnPropertyNames(window) : []; + + var getWindowNames = function (it) { + try { + return nativeGetOwnPropertyNames(it); + } catch (error) { + return windowNames.slice(); + } + }; + + // fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window + var f$5 = function getOwnPropertyNames(it) { + return windowNames && toString$1.call(it) == '[object Window]' + ? getWindowNames(it) + : nativeGetOwnPropertyNames(toIndexedObject(it)); + }; + + var objectGetOwnPropertyNamesExternal = { + f: f$5, + }; var WellKnownSymbolsStore = shared('wks'); var Symbol$1 = global_1.Symbol; @@ -647,6 +768,66 @@ return WellKnownSymbolsStore[name]; }; + var f$6 = wellKnownSymbol; + + var wellKnownSymbolWrapped = { + f: f$6, + }; + + var defineProperty = objectDefineProperty.f; + + var defineWellKnownSymbol = function (NAME) { + var Symbol = path.Symbol || (path.Symbol = {}); + if (!has(Symbol, NAME)) + defineProperty(Symbol, NAME, { + value: wellKnownSymbolWrapped.f(NAME), + }); + }; + + var defineProperty$1 = objectDefineProperty.f; + + var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + + var setToStringTag = function (it, TAG, STATIC) { + if (it && !has((it = STATIC ? it : it.prototype), TO_STRING_TAG)) { + defineProperty$1(it, TO_STRING_TAG, { configurable: true, value: TAG }); + } + }; + + var aFunction$1 = function (it) { + if (typeof it != 'function') { + throw TypeError(String(it) + ' is not a function'); + } + return it; + }; + + // optional / simple context binding + var functionBindContext = function (fn, that, length) { + aFunction$1(fn); + if (that === undefined) return fn; + switch (length) { + case 0: + return function () { + return fn.call(that); + }; + case 1: + return function (a) { + return fn.call(that, a); + }; + case 2: + return function (a, b) { + return fn.call(that, a, b); + }; + case 3: + return function (a, b, c) { + return fn.call(that, a, b, c); + }; + } + return function (/* ...args */) { + return fn.apply(that, arguments); + }; + }; + var SPECIES = wellKnownSymbol('species'); // `ArraySpeciesCreate` abstract operation @@ -665,6 +846,405 @@ return new (C === undefined ? Array : C)(length === 0 ? 0 : length); }; + var push = [].push; + + // `Array.prototype.{ forEach, map, filter, some, every, find, findIndex }` methods implementation + var createMethod$1 = function (TYPE) { + var IS_MAP = TYPE == 1; + var IS_FILTER = TYPE == 2; + var IS_SOME = TYPE == 3; + var IS_EVERY = TYPE == 4; + var IS_FIND_INDEX = TYPE == 6; + var NO_HOLES = TYPE == 5 || IS_FIND_INDEX; + return function ($this, callbackfn, that, specificCreate) { + var O = toObject($this); + var self = indexedObject(O); + var boundFunction = functionBindContext(callbackfn, that, 3); + var length = toLength(self.length); + var index = 0; + var create = specificCreate || arraySpeciesCreate; + var target = IS_MAP ? create($this, length) : IS_FILTER ? create($this, 0) : undefined; + var value, result; + for (; length > index; index++) + if (NO_HOLES || index in self) { + value = self[index]; + result = boundFunction(value, index, O); + if (TYPE) { + if (IS_MAP) target[index] = result; + // map + else if (result) + switch (TYPE) { + case 3: + return true; // some + case 5: + return value; // find + case 6: + return index; // findIndex + case 2: + push.call(target, value); // filter + } + else if (IS_EVERY) return false; // every + } + } + return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target; + }; + }; + + var arrayIteration = { + // `Array.prototype.forEach` method + // https://tc39.github.io/ecma262/#sec-array.prototype.foreach + forEach: createMethod$1(0), + // `Array.prototype.map` method + // https://tc39.github.io/ecma262/#sec-array.prototype.map + map: createMethod$1(1), + // `Array.prototype.filter` method + // https://tc39.github.io/ecma262/#sec-array.prototype.filter + filter: createMethod$1(2), + // `Array.prototype.some` method + // https://tc39.github.io/ecma262/#sec-array.prototype.some + some: createMethod$1(3), + // `Array.prototype.every` method + // https://tc39.github.io/ecma262/#sec-array.prototype.every + every: createMethod$1(4), + // `Array.prototype.find` method + // https://tc39.github.io/ecma262/#sec-array.prototype.find + find: createMethod$1(5), + // `Array.prototype.findIndex` method + // https://tc39.github.io/ecma262/#sec-array.prototype.findIndex + findIndex: createMethod$1(6), + }; + + var $forEach = arrayIteration.forEach; + + var HIDDEN = sharedKey('hidden'); + var SYMBOL = 'Symbol'; + var PROTOTYPE$1 = 'prototype'; + var TO_PRIMITIVE = wellKnownSymbol('toPrimitive'); + var setInternalState = internalState.set; + var getInternalState = internalState.getterFor(SYMBOL); + var ObjectPrototype = Object[PROTOTYPE$1]; + var $Symbol = global_1.Symbol; + var $stringify = getBuiltIn('JSON', 'stringify'); + var nativeGetOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f; + var nativeDefineProperty$1 = objectDefineProperty.f; + var nativeGetOwnPropertyNames$1 = objectGetOwnPropertyNamesExternal.f; + var nativePropertyIsEnumerable$1 = objectPropertyIsEnumerable.f; + var AllSymbols = shared('symbols'); + var ObjectPrototypeSymbols = shared('op-symbols'); + var StringToSymbolRegistry = shared('string-to-symbol-registry'); + var SymbolToStringRegistry = shared('symbol-to-string-registry'); + var WellKnownSymbolsStore$1 = shared('wks'); + var QObject = global_1.QObject; + // Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173 + var USE_SETTER = !QObject || !QObject[PROTOTYPE$1] || !QObject[PROTOTYPE$1].findChild; + + // fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687 + var setSymbolDescriptor = + descriptors && + fails(function () { + return ( + objectCreate( + nativeDefineProperty$1({}, 'a', { + get: function () { + return nativeDefineProperty$1(this, 'a', { value: 7 }).a; + }, + }), + ).a != 7 + ); + }) + ? function (O, P, Attributes) { + var ObjectPrototypeDescriptor = nativeGetOwnPropertyDescriptor$1(ObjectPrototype, P); + if (ObjectPrototypeDescriptor) delete ObjectPrototype[P]; + nativeDefineProperty$1(O, P, Attributes); + if (ObjectPrototypeDescriptor && O !== ObjectPrototype) { + nativeDefineProperty$1(ObjectPrototype, P, ObjectPrototypeDescriptor); + } + } + : nativeDefineProperty$1; + + var wrap = function (tag, description) { + var symbol = (AllSymbols[tag] = objectCreate($Symbol[PROTOTYPE$1])); + setInternalState(symbol, { + type: SYMBOL, + tag: tag, + description: description, + }); + if (!descriptors) symbol.description = description; + return symbol; + }; + + var isSymbol = useSymbolAsUid + ? function (it) { + return typeof it == 'symbol'; + } + : function (it) { + return Object(it) instanceof $Symbol; + }; + + var $defineProperty = function defineProperty(O, P, Attributes) { + if (O === ObjectPrototype) $defineProperty(ObjectPrototypeSymbols, P, Attributes); + anObject(O); + var key = toPrimitive(P, true); + anObject(Attributes); + if (has(AllSymbols, key)) { + if (!Attributes.enumerable) { + if (!has(O, HIDDEN)) nativeDefineProperty$1(O, HIDDEN, createPropertyDescriptor(1, {})); + O[HIDDEN][key] = true; + } else { + if (has(O, HIDDEN) && O[HIDDEN][key]) O[HIDDEN][key] = false; + Attributes = objectCreate(Attributes, { enumerable: createPropertyDescriptor(0, false) }); + } + return setSymbolDescriptor(O, key, Attributes); + } + return nativeDefineProperty$1(O, key, Attributes); + }; + + var $defineProperties = function defineProperties(O, Properties) { + anObject(O); + var properties = toIndexedObject(Properties); + var keys = objectKeys(properties).concat($getOwnPropertySymbols(properties)); + $forEach(keys, function (key) { + if (!descriptors || $propertyIsEnumerable.call(properties, key)) $defineProperty(O, key, properties[key]); + }); + return O; + }; + + var $create = function create(O, Properties) { + return Properties === undefined ? objectCreate(O) : $defineProperties(objectCreate(O), Properties); + }; + + var $propertyIsEnumerable = function propertyIsEnumerable(V) { + var P = toPrimitive(V, true); + var enumerable = nativePropertyIsEnumerable$1.call(this, P); + if (this === ObjectPrototype && has(AllSymbols, P) && !has(ObjectPrototypeSymbols, P)) return false; + return enumerable || !has(this, P) || !has(AllSymbols, P) || (has(this, HIDDEN) && this[HIDDEN][P]) + ? enumerable + : true; + }; + + var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(O, P) { + var it = toIndexedObject(O); + var key = toPrimitive(P, true); + if (it === ObjectPrototype && has(AllSymbols, key) && !has(ObjectPrototypeSymbols, key)) return; + var descriptor = nativeGetOwnPropertyDescriptor$1(it, key); + if (descriptor && has(AllSymbols, key) && !(has(it, HIDDEN) && it[HIDDEN][key])) { + descriptor.enumerable = true; + } + return descriptor; + }; + + var $getOwnPropertyNames = function getOwnPropertyNames(O) { + var names = nativeGetOwnPropertyNames$1(toIndexedObject(O)); + var result = []; + $forEach(names, function (key) { + if (!has(AllSymbols, key) && !has(hiddenKeys, key)) result.push(key); + }); + return result; + }; + + var $getOwnPropertySymbols = function getOwnPropertySymbols(O) { + var IS_OBJECT_PROTOTYPE = O === ObjectPrototype; + var names = nativeGetOwnPropertyNames$1(IS_OBJECT_PROTOTYPE ? ObjectPrototypeSymbols : toIndexedObject(O)); + var result = []; + $forEach(names, function (key) { + if (has(AllSymbols, key) && (!IS_OBJECT_PROTOTYPE || has(ObjectPrototype, key))) { + result.push(AllSymbols[key]); + } + }); + return result; + }; + + // `Symbol` constructor + // https://tc39.github.io/ecma262/#sec-symbol-constructor + if (!nativeSymbol) { + $Symbol = function Symbol() { + if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor'); + var description = !arguments.length || arguments[0] === undefined ? undefined : String(arguments[0]); + var tag = uid(description); + var setter = function (value) { + if (this === ObjectPrototype) setter.call(ObjectPrototypeSymbols, value); + if (has(this, HIDDEN) && has(this[HIDDEN], tag)) this[HIDDEN][tag] = false; + setSymbolDescriptor(this, tag, createPropertyDescriptor(1, value)); + }; + if (descriptors && USE_SETTER) setSymbolDescriptor(ObjectPrototype, tag, { configurable: true, set: setter }); + return wrap(tag, description); + }; + + redefine($Symbol[PROTOTYPE$1], 'toString', function toString() { + return getInternalState(this).tag; + }); + + redefine($Symbol, 'withoutSetter', function (description) { + return wrap(uid(description), description); + }); + + objectPropertyIsEnumerable.f = $propertyIsEnumerable; + objectDefineProperty.f = $defineProperty; + objectGetOwnPropertyDescriptor.f = $getOwnPropertyDescriptor; + objectGetOwnPropertyNames.f = objectGetOwnPropertyNamesExternal.f = $getOwnPropertyNames; + objectGetOwnPropertySymbols.f = $getOwnPropertySymbols; + + wellKnownSymbolWrapped.f = function (name) { + return wrap(wellKnownSymbol(name), name); + }; + + if (descriptors) { + // https://github.com/tc39/proposal-Symbol-description + nativeDefineProperty$1($Symbol[PROTOTYPE$1], 'description', { + configurable: true, + get: function description() { + return getInternalState(this).description; + }, + }); + { + redefine(ObjectPrototype, 'propertyIsEnumerable', $propertyIsEnumerable, { unsafe: true }); + } + } + } + + _export( + { global: true, wrap: true, forced: !nativeSymbol, sham: !nativeSymbol }, + { + Symbol: $Symbol, + }, + ); + + $forEach(objectKeys(WellKnownSymbolsStore$1), function (name) { + defineWellKnownSymbol(name); + }); + + _export( + { target: SYMBOL, stat: true, forced: !nativeSymbol }, + { + // `Symbol.for` method + // https://tc39.github.io/ecma262/#sec-symbol.for + for: function (key) { + var string = String(key); + if (has(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string]; + var symbol = $Symbol(string); + StringToSymbolRegistry[string] = symbol; + SymbolToStringRegistry[symbol] = string; + return symbol; + }, + // `Symbol.keyFor` method + // https://tc39.github.io/ecma262/#sec-symbol.keyfor + keyFor: function keyFor(sym) { + if (!isSymbol(sym)) throw TypeError(sym + ' is not a symbol'); + if (has(SymbolToStringRegistry, sym)) return SymbolToStringRegistry[sym]; + }, + useSetter: function () { + USE_SETTER = true; + }, + useSimple: function () { + USE_SETTER = false; + }, + }, + ); + + _export( + { target: 'Object', stat: true, forced: !nativeSymbol, sham: !descriptors }, + { + // `Object.create` method + // https://tc39.github.io/ecma262/#sec-object.create + create: $create, + // `Object.defineProperty` method + // https://tc39.github.io/ecma262/#sec-object.defineproperty + defineProperty: $defineProperty, + // `Object.defineProperties` method + // https://tc39.github.io/ecma262/#sec-object.defineproperties + defineProperties: $defineProperties, + // `Object.getOwnPropertyDescriptor` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptors + getOwnPropertyDescriptor: $getOwnPropertyDescriptor, + }, + ); + + _export( + { target: 'Object', stat: true, forced: !nativeSymbol }, + { + // `Object.getOwnPropertyNames` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertynames + getOwnPropertyNames: $getOwnPropertyNames, + // `Object.getOwnPropertySymbols` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertysymbols + getOwnPropertySymbols: $getOwnPropertySymbols, + }, + ); + + // Chrome 38 and 39 `Object.getOwnPropertySymbols` fails on primitives + // https://bugs.chromium.org/p/v8/issues/detail?id=3443 + _export( + { + target: 'Object', + stat: true, + forced: fails(function () { + objectGetOwnPropertySymbols.f(1); + }), + }, + { + getOwnPropertySymbols: function getOwnPropertySymbols(it) { + return objectGetOwnPropertySymbols.f(toObject(it)); + }, + }, + ); + + // `JSON.stringify` method behavior with symbols + // https://tc39.github.io/ecma262/#sec-json.stringify + if ($stringify) { + var FORCED_JSON_STRINGIFY = + !nativeSymbol || + fails(function () { + var symbol = $Symbol(); + // MS Edge converts symbol values to JSON as {} + return ( + $stringify([symbol]) != '[null]' || + // WebKit converts symbol values to JSON as null + $stringify({ a: symbol }) != '{}' || + // V8 throws on boxed symbols + $stringify(Object(symbol)) != '{}' + ); + }); + + _export( + { target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, + { + // eslint-disable-next-line no-unused-vars + stringify: function stringify(it, replacer, space) { + var args = [it]; + var index = 1; + var $replacer; + while (arguments.length > index) args.push(arguments[index++]); + $replacer = replacer; + if ((!isObject(replacer) && it === undefined) || isSymbol(it)) return; // IE8 returns string on undefined + if (!isArray(replacer)) + replacer = function (key, value) { + if (typeof $replacer == 'function') value = $replacer.call(this, key, value); + if (!isSymbol(value)) return value; + }; + args[1] = replacer; + return $stringify.apply(null, args); + }, + }, + ); + } + + // `Symbol.prototype[@@toPrimitive]` method + // https://tc39.github.io/ecma262/#sec-symbol.prototype-@@toprimitive + if (!$Symbol[PROTOTYPE$1][TO_PRIMITIVE]) { + createNonEnumerableProperty($Symbol[PROTOTYPE$1], TO_PRIMITIVE, $Symbol[PROTOTYPE$1].valueOf); + } + // `Symbol.prototype[@@toStringTag]` property + // https://tc39.github.io/ecma262/#sec-symbol.prototype-@@tostringtag + setToStringTag($Symbol, SYMBOL); + + hiddenKeys[HIDDEN] = true; + + var createProperty = function (object, key, value) { + var propertyKey = toPrimitive(key); + if (propertyKey in object) objectDefineProperty.f(object, propertyKey, createPropertyDescriptor(0, value)); + else object[propertyKey] = value; + }; + var engineUserAgent = getBuiltIn('navigator', 'userAgent') || ''; var process = global_1.process; @@ -758,7 +1338,7 @@ }, ); - var defineProperty = Object.defineProperty; + var defineProperty$2 = Object.defineProperty; var cache = {}; var thrower = function (it) { @@ -779,15 +1359,74 @@ if (ACCESSORS && !descriptors) return true; var O = { length: -1 }; - if (ACCESSORS) defineProperty(O, 1, { enumerable: true, get: thrower }); + if (ACCESSORS) defineProperty$2(O, 1, { enumerable: true, get: thrower }); else O[1] = 1; method.call(O, argument0, argument1); })); }; - var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('slice'); - var USES_TO_LENGTH = arrayMethodUsesToLength('slice', { ACCESSORS: true, 0: 0, 1: 2 }); + var $filter = arrayIteration.filter; + + var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('filter'); + // Edge 14- issue + var USES_TO_LENGTH = arrayMethodUsesToLength('filter'); + + // `Array.prototype.filter` method + // https://tc39.github.io/ecma262/#sec-array.prototype.filter + // with adding support of @@species + _export( + { target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT || !USES_TO_LENGTH }, + { + filter: function filter(callbackfn /* , thisArg */) { + return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); + }, + }, + ); + + var arrayMethodIsStrict = function (METHOD_NAME, argument) { + var method = [][METHOD_NAME]; + return ( + !!method && + fails(function () { + // eslint-disable-next-line no-useless-call,no-throw-literal + method.call( + null, + argument || + function () { + throw 1; + }, + 1, + ); + }) + ); + }; + + var $forEach$1 = arrayIteration.forEach; + + var STRICT_METHOD = arrayMethodIsStrict('forEach'); + var USES_TO_LENGTH$1 = arrayMethodUsesToLength('forEach'); + + // `Array.prototype.forEach` method implementation + // https://tc39.github.io/ecma262/#sec-array.prototype.foreach + var arrayForEach = + !STRICT_METHOD || !USES_TO_LENGTH$1 + ? function forEach(callbackfn /* , thisArg */) { + return $forEach$1(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); + } + : [].forEach; + + // `Array.prototype.forEach` method + // https://tc39.github.io/ecma262/#sec-array.prototype.foreach + _export( + { target: 'Array', proto: true, forced: [].forEach != arrayForEach }, + { + forEach: arrayForEach, + }, + ); + + var HAS_SPECIES_SUPPORT$1 = arrayMethodHasSpeciesSupport('slice'); + var USES_TO_LENGTH$2 = arrayMethodUsesToLength('slice', { ACCESSORS: true, 0: 0, 1: 2 }); var SPECIES$2 = wellKnownSymbol('species'); var nativeSlice = [].slice; @@ -797,7 +1436,7 @@ // https://tc39.github.io/ecma262/#sec-array.prototype.slice // fallback for not array-like ES3 strings and DOM objects _export( - { target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT || !USES_TO_LENGTH }, + { target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$1 || !USES_TO_LENGTH$2 }, { slice: function slice(start, end) { var O = toIndexedObject(this); @@ -827,6 +1466,163 @@ }, ); + var nativeGetOwnPropertyDescriptor$2 = objectGetOwnPropertyDescriptor.f; + + var FAILS_ON_PRIMITIVES = fails(function () { + nativeGetOwnPropertyDescriptor$2(1); + }); + var FORCED$1 = !descriptors || FAILS_ON_PRIMITIVES; + + // `Object.getOwnPropertyDescriptor` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptor + _export( + { target: 'Object', stat: true, forced: FORCED$1, sham: !descriptors }, + { + getOwnPropertyDescriptor: function getOwnPropertyDescriptor(it, key) { + return nativeGetOwnPropertyDescriptor$2(toIndexedObject(it), key); + }, + }, + ); + + // `Object.getOwnPropertyDescriptors` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptors + _export( + { target: 'Object', stat: true, sham: !descriptors }, + { + getOwnPropertyDescriptors: function getOwnPropertyDescriptors(object) { + var O = toIndexedObject(object); + var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f; + var keys = ownKeys(O); + var result = {}; + var index = 0; + var key, descriptor; + while (keys.length > index) { + descriptor = getOwnPropertyDescriptor(O, (key = keys[index++])); + if (descriptor !== undefined) createProperty(result, key, descriptor); + } + return result; + }, + }, + ); + + var FAILS_ON_PRIMITIVES$1 = fails(function () { + objectKeys(1); + }); + + // `Object.keys` method + // https://tc39.github.io/ecma262/#sec-object.keys + _export( + { target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$1 }, + { + keys: function keys(it) { + return objectKeys(toObject(it)); + }, + }, + ); + + // iterable DOM collections + // flag - `iterable` interface - 'entries', 'keys', 'values', 'forEach' methods + var domIterables = { + CSSRuleList: 0, + CSSStyleDeclaration: 0, + CSSValueList: 0, + ClientRectList: 0, + DOMRectList: 0, + DOMStringList: 0, + DOMTokenList: 1, + DataTransferItemList: 0, + FileList: 0, + HTMLAllCollection: 0, + HTMLCollection: 0, + HTMLFormElement: 0, + HTMLSelectElement: 0, + MediaList: 0, + MimeTypeArray: 0, + NamedNodeMap: 0, + NodeList: 1, + PaintRequestList: 0, + Plugin: 0, + PluginArray: 0, + SVGLengthList: 0, + SVGNumberList: 0, + SVGPathSegList: 0, + SVGPointList: 0, + SVGStringList: 0, + SVGTransformList: 0, + SourceBufferList: 0, + StyleSheetList: 0, + TextTrackCueList: 0, + TextTrackList: 0, + TouchList: 0, + }; + + for (var COLLECTION_NAME in domIterables) { + var Collection = global_1[COLLECTION_NAME]; + var CollectionPrototype = Collection && Collection.prototype; + // some Chrome versions have non-configurable methods on DOMTokenList + if (CollectionPrototype && CollectionPrototype.forEach !== arrayForEach) + try { + createNonEnumerableProperty(CollectionPrototype, 'forEach', arrayForEach); + } catch (error) { + CollectionPrototype.forEach = arrayForEach; + } + } + + 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; + } + + var defineProperty$3 = _defineProperty; + + function _objectWithoutPropertiesLoose(source, excluded) { + if (source == null) return {}; + var target = {}; + var sourceKeys = Object.keys(source); + var key, i; + + for (i = 0; i < sourceKeys.length; i++) { + key = sourceKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + target[key] = source[key]; + } + + return target; + } + + var objectWithoutPropertiesLoose = _objectWithoutPropertiesLoose; + + function _objectWithoutProperties(source, excluded) { + if (source == null) return {}; + var target = objectWithoutPropertiesLoose(source, excluded); + var key, i; + + if (Object.getOwnPropertySymbols) { + var sourceSymbolKeys = Object.getOwnPropertySymbols(source); + + for (i = 0; i < sourceSymbolKeys.length; i++) { + key = sourceSymbolKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; + target[key] = source[key]; + } + } + + return target; + } + + var objectWithoutProperties = _objectWithoutProperties; + function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } @@ -903,7 +1699,7 @@ var _this = undefined; - var _jsxFileName = '/Users/albertaz/Documents/github/react-simple-clamp/src/index.tsx'; + var _jsxFileName = '/Users/albertaz/Documents/github/react-simple-clamp/src/clamp.tsx'; var RENDER_STATE = { START: 'START', DONE: 'DONE', @@ -957,8 +1753,10 @@ return screenMaxHeight; } - function useScreenContent(content, offset, contentLength, ellipsis) { - var _useState3 = React.useState(content), + function useScreenContent(content, renderContent, renderClampedContent, offset, contentLength, ellipsis) { + var _useState3 = React.useState(function () { + return renderContent(); + }), _useState4 = slicedToArray(_useState3, 2), screenContent = _useState4[0], setScreenContent = _useState4[1]; @@ -966,12 +1764,12 @@ React.useEffect( function () { if (!contentLength) { - setScreenContent(content); + setScreenContent(renderContent()); } else if (offset !== contentLength) { - setScreenContent(''.concat(content.slice(0, offset)).concat(ellipsis)); + setScreenContent(renderClampedContent(offset, ellipsis)); } }, - [content, offset, contentLength, ellipsis], + [content, renderContent, renderClampedContent, offset, contentLength, ellipsis], ); return screenContent; } @@ -982,7 +1780,10 @@ content = properties.content, maxHeight = properties.maxHeight, maxLines = properties.maxLines, - expanded = properties.expanded; + _properties$expanded = properties.expanded, + expanded = _properties$expanded === void 0 ? false : _properties$expanded, + renderContent = properties.renderContent, + renderClampedContent = properties.renderClampedContent; var tagRef = React.useRef(); var contentRef = React.useRef(); var contentLength = content.length || 0; @@ -1012,16 +1813,24 @@ renderFillState = _useState14[0], setRenderFillState = _useState14[1]; - var screenContent = useScreenContent(content, offset, contentLength, ellipsis); + var screenContent = useScreenContent(content, renderContent, renderClampedContent, offset, contentLength, ellipsis); var screenMaxHeight = useScreenMaxHeight(internalExpanded, maxHeight); + /** start rendering * */ + React.useEffect( function () { - if (!internalExpanded && isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) && renderState === 'done') { + if ( + !internalExpanded && + isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) && + renderState === RENDER_STATE.DONE + ) { setRenderState(RENDER_STATE.START); } }, [maxLines, maxHeight, ellipsis, internalExpanded, renderState, screenMaxHeight], ); + /** start locating * */ + React.useEffect( function () { if (renderState === RENDER_STATE.START) { @@ -1030,6 +1839,8 @@ }, [renderState, contentLength], ); + /** locating process,find the locate position before clamp position as soon as posible * */ + React.useEffect( function () { var contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; @@ -1053,6 +1864,8 @@ }, [renderLocateState, maxLines, contentRef, screenMaxHeight, screenContent], ); + /** filling process, fill the gap between locate position and clamp position * */ + React.useEffect( function () { var contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; @@ -1085,7 +1898,7 @@ __self: _this, __source: { fileName: _jsxFileName, - lineNumber: 142, + lineNumber: 168, }, }, screenContent, @@ -1100,7 +1913,7 @@ __self: _this, __source: { fileName: _jsxFileName, - lineNumber: 144, + lineNumber: 170, }, }, contentWrapper, @@ -1115,7 +1928,7 @@ __self: _this, __source: { fileName: _jsxFileName, - lineNumber: 148, + lineNumber: 175, }, }, linesWrapper, @@ -1124,59 +1937,149 @@ var _this$1 = undefined; - var _jsxFileName$1 = '/Users/albertaz/Documents/github/react-simple-clamp/example/demo.jsx'; + function ownKeys$1(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; + } + + function _objectSpread(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + if (i % 2) { + ownKeys$1(Object(source), true).forEach(function (key) { + defineProperty$3(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys$1(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + return target; + } + + var _jsxFileName$1 = '/Users/albertaz/Documents/github/react-simple-clamp/src/ClampText.tsx'; + + var ClampText = function ClampText(properties) { + var content = properties.content, + restProps = objectWithoutProperties(properties, ['content']); + + var renderClampedContent = function renderClampedContent(offset, ellipsis) { + return /*#__PURE__*/ React__default.createElement( + 'span', + { + __self: _this$1, + __source: { + fileName: _jsxFileName$1, + lineNumber: 23, + }, + }, + ''.concat(content.slice(0, offset)).concat(ellipsis), + ); + }; + + var renderContent = function renderContent() { + return /*#__PURE__*/ React__default.createElement( + 'span', + { + __self: _this$1, + __source: { + fileName: _jsxFileName$1, + lineNumber: 27, + }, + }, + content, + ); + }; + + return /*#__PURE__*/ React__default.createElement( + ReactSimpleClamp, + _objectSpread( + _objectSpread( + { + content: content, + renderContent: renderContent, + renderClampedContent: renderClampedContent, + }, + restProps, + ), + {}, + { + __self: _this$1, + __source: { + fileName: _jsxFileName$1, + lineNumber: 31, + }, + }, + ), + ); + }; + + var _this$2 = undefined; + + var _jsxFileName$2 = '/Users/albertaz/Documents/github/react-simple-clamp/example/demo.jsx'; var App = function App() { return /*#__PURE__*/ React__default.createElement( 'div', { - __self: _this$1, + __self: _this$2, __source: { - fileName: _jsxFileName$1, + fileName: _jsxFileName$2, lineNumber: 7, }, }, /*#__PURE__*/ React__default.createElement( 'div', { - __self: _this$1, + __self: _this$2, __source: { - fileName: _jsxFileName$1, + fileName: _jsxFileName$2, lineNumber: 8, }, }, 'test 1', ), - /*#__PURE__*/ React__default.createElement(ReactSimpleClamp, { + /*#__PURE__*/ React__default.createElement(ClampText, { content: '你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好', maxHeight: 40, maxLines: 2, - __self: _this$1, + __self: _this$2, __source: { - fileName: _jsxFileName$1, + fileName: _jsxFileName$2, lineNumber: 9, }, }), /*#__PURE__*/ React__default.createElement( 'div', { - __self: _this$1, + __self: _this$2, __source: { - fileName: _jsxFileName$1, + fileName: _jsxFileName$2, lineNumber: 10, }, }, 'test 2', ), - /*#__PURE__*/ React__default.createElement(ReactSimpleClamp, { + /*#__PURE__*/ React__default.createElement(ClampText, { content: '你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好', maxHeight: 40, maxLines: 5, - __self: _this$1, + __self: _this$2, __source: { - fileName: _jsxFileName$1, + fileName: _jsxFileName$2, lineNumber: 11, }, }), @@ -1187,7 +2090,7 @@ /*#__PURE__*/ React__default.createElement(App, { __self: undefined, __source: { - fileName: _jsxFileName$1, + fileName: _jsxFileName$2, lineNumber: 16, }, }), diff --git a/src/ClampText.tsx b/src/ClampText.tsx new file mode 100644 index 0000000..6c9a8b1 --- /dev/null +++ b/src/ClampText.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import Clamp from './clamp'; + +export interface ClampTextProps { + autoresize?: boolean; + maxLines: number; + maxHeight: number | string; + ellipsis?: string; + expanded?: boolean; + content: string; +} + +const ClampText: React.FC = (properties) => { + const { content, ...restProps } = properties; + + const renderClampedContent = (offset: number, ellipsis: string) => { + return {`${content.slice(0, offset)}${ellipsis}`}; + }; + + const renderContent = () => { + return {content}; + }; + + return ( + + ); +}; + +export default ClampText; diff --git a/src/clamp.tsx b/src/clamp.tsx new file mode 100644 index 0000000..b14ad93 --- /dev/null +++ b/src/clamp.tsx @@ -0,0 +1,179 @@ +import React, { useState, useEffect, useRef } from 'react'; + +export interface ReactSimpleClampProps { + autoresize?: boolean; + maxLines: number; + maxHeight: number | string; + ellipsis?: string; + expanded?: boolean; + content: T; + renderContent: () => React.ReactElement; + renderClampedContent: (offset: number, ellipsis: string) => React.ReactElement; +} + +const RENDER_STATE = { + START: 'START', + DONE: 'DONE', +}; + +const RENDER_LOCATE_STATE = { + START: 'START', + DONE: 'DONE', +}; + +const RENDER_FILL_STATE = { + ASCEND: 'ASCEND ', + DESCEND: 'DESCEND ', + DONE: 'DONE', +}; + +function isOverFlow( + maxLines: number, + screenMaxHeight: string, + tagRef: React.RefObject, + contentRef: React.RefObject, +): boolean { + const contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; + if (!maxLines && !screenMaxHeight) { + return false; + } + if (maxLines && contentLines > maxLines) { + return true; + } + if (screenMaxHeight && tagRef.current && tagRef.current.scrollHeight > tagRef.current.offsetHeight) { + return true; + } + return false; +} + +function useScreenMaxHeight(internalExpanded: boolean, maxHeight: number | string): string { + const [screenMaxHeight, setScreenMaxHeight] = useState(''); + + useEffect(() => { + if (internalExpanded) { + setScreenMaxHeight(''); + } else if (!maxHeight) { + setScreenMaxHeight(''); + } else { + setScreenMaxHeight(typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight); + } + }, [internalExpanded, maxHeight]); + + return screenMaxHeight; +} + +function useScreenContent( + content: string | string[], + renderContent: () => React.ReactElement, + renderClampedContent: (offset: number, ellipsis: string) => React.ReactElement, + offset: number, + contentLength: number, + ellipsis: string, +): React.ReactElement { + const [screenContent, setScreenContent] = useState(() => renderContent()); + + useEffect(() => { + if (!contentLength) { + setScreenContent(renderContent()); + } else if (offset !== contentLength) { + setScreenContent(renderClampedContent(offset, ellipsis)); + } + }, [content, renderContent, renderClampedContent, offset, contentLength, ellipsis]); + + return screenContent; +} + +const ReactSimpleClamp: React.FC>> = (properties) => { + const { + ellipsis = '...', + content, + maxHeight, + maxLines, + expanded = false, + renderContent, + renderClampedContent, + } = properties; + const tagRef = useRef() as React.RefObject; + const contentRef = useRef() as React.RefObject; + const contentLength = content.length || 0; + + const [offset, setOffset] = useState(contentLength); + const [internalExpanded, setInternalExpanded] = useState(expanded); + const [renderState, setRenderState] = useState(RENDER_STATE.DONE); + const [renderLocateState, setRenderLocateState] = useState(RENDER_LOCATE_STATE.DONE); + const [renderFillState, setRenderFillState] = useState(RENDER_FILL_STATE.DONE); + + const screenContent = useScreenContent(content, renderContent, renderClampedContent, offset, contentLength, ellipsis); + const screenMaxHeight = useScreenMaxHeight(internalExpanded, maxHeight); + + /** start rendering * */ + useEffect(() => { + if ( + !internalExpanded && + isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) && + renderState === RENDER_STATE.DONE + ) { + setRenderState(RENDER_STATE.START); + } + }, [maxLines, maxHeight, ellipsis, internalExpanded, renderState, screenMaxHeight]); + + /** start locating * */ + useEffect(() => { + if (renderState === RENDER_STATE.START) { + setRenderLocateState(RENDER_LOCATE_STATE.START); + } + }, [renderState, contentLength]); + + /** locating process,find the locate position before clamp position as soon as posible * */ + useEffect(() => { + const contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; + + if (renderLocateState === RENDER_LOCATE_STATE.START) { + if (isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef)) { + // need dec + setOffset((prevOffset) => ~~(prevOffset / 2)); + } else if (contentLines !== maxLines) { + // need add + setOffset((prevOffset) => ~~(prevOffset + prevOffset / 2)); + } else { + setRenderLocateState(RENDER_LOCATE_STATE.DONE); + setRenderFillState(RENDER_FILL_STATE.ASCEND); + } + } + }, [renderLocateState, maxLines, contentRef, screenMaxHeight, screenContent]); + + /** filling process, fill the gap between locate position and clamp position * */ + useEffect(() => { + const contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; + if (renderFillState === RENDER_FILL_STATE.ASCEND) { + if ((!isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) || contentLines < 2) && offset < contentLength) { + setOffset(offset + 1); + console.log(RENDER_FILL_STATE.ASCEND); + } else { + setRenderFillState(RENDER_FILL_STATE.DESCEND); + } + } else if (renderFillState === RENDER_FILL_STATE.DESCEND) { + if (isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) && contentLines > 1 && offset > 0) { + setOffset(offset - 1); + console.log(RENDER_FILL_STATE.DESCEND); + } else { + setRenderFillState(RENDER_FILL_STATE.DONE); + setRenderState(RENDER_STATE.DONE); + } + } + }, [renderFillState, contentLength, offset, contentRef, maxLines, screenMaxHeight]); + + const contentWrapper = {screenContent}; + const linesWrapper = ( + + {contentWrapper} + + ); + return ( +
+ {linesWrapper} +
+ ); +}; + +export default ReactSimpleClamp; diff --git a/src/index.tsx b/src/index.tsx index e671775..b44ba9e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,154 +1,3 @@ -import React, { useState, useEffect, useRef } from 'react'; +import ClampText from './ClampText'; -export interface ReactSimpleClampProps { - autoresize?: boolean; - maxLines: number; - maxHeight: number | string; - ellipsis?: string; - expanded: boolean; - content: string; -} - -const RENDER_STATE = { - START: 'START', - DONE: 'DONE', -}; - -const RENDER_LOCATE_STATE = { - START: 'START', - DONE: 'DONE', -}; - -const RENDER_FILL_STATE = { - ASCEND: 'ASCEND ', - DESCEND: 'DESCEND ', - DONE: 'DONE', -}; - -function isOverFlow( - maxLines: number, - screenMaxHeight: string, - tagRef: React.RefObject, - contentRef: React.RefObject, -): boolean { - const contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; - if (!maxLines && !screenMaxHeight) { - return false; - } - if (maxLines && contentLines > maxLines) { - return true; - } - if (screenMaxHeight && tagRef.current && tagRef.current.scrollHeight > tagRef.current.offsetHeight) { - return true; - } - return false; -} - -function useScreenMaxHeight(internalExpanded: boolean, maxHeight: number | string): string { - const [screenMaxHeight, setScreenMaxHeight] = useState(''); - - useEffect(() => { - if (internalExpanded) { - setScreenMaxHeight(''); - } else if (!maxHeight) { - setScreenMaxHeight(''); - } else { - setScreenMaxHeight(typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight); - } - }, [internalExpanded, maxHeight]); - - return screenMaxHeight; -} - -function useScreenContent(content: string, offset: number, contentLength: number, ellipsis: string): string { - const [screenContent, setScreenContent] = useState(content); - - useEffect(() => { - if (!contentLength) { - setScreenContent(content); - } else if (offset !== contentLength) { - setScreenContent(`${content.slice(0, offset)}${ellipsis}`); - } - }, [content, offset, contentLength, ellipsis]); - - return screenContent; -} - -const ReactSimpleClamp: React.FC = (properties) => { - const { ellipsis = '...', content, maxHeight, maxLines, expanded } = properties; - const tagRef = useRef() as React.RefObject; - const contentRef = useRef() as React.RefObject; - const contentLength = content.length || 0; - - const [offset, setOffset] = useState(contentLength); - const [internalExpanded, setInternalExpanded] = useState(expanded); - const [renderState, setRenderState] = useState(RENDER_STATE.DONE); - const [renderLocateState, setRenderLocateState] = useState(RENDER_LOCATE_STATE.DONE); - const [renderFillState, setRenderFillState] = useState(RENDER_FILL_STATE.DONE); - - const screenContent = useScreenContent(content, offset, contentLength, ellipsis); - const screenMaxHeight = useScreenMaxHeight(internalExpanded, maxHeight); - - useEffect(() => { - if (!internalExpanded && isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) && renderState === 'done') { - setRenderState(RENDER_STATE.START); - } - }, [maxLines, maxHeight, ellipsis, internalExpanded, renderState, screenMaxHeight]); - - useEffect(() => { - if (renderState === RENDER_STATE.START) { - setRenderLocateState(RENDER_LOCATE_STATE.START); - } - }, [renderState, contentLength]); - - useEffect(() => { - const contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; - - if (renderLocateState === RENDER_LOCATE_STATE.START) { - if (isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef)) { - // need dec - setOffset((prevOffset) => ~~(prevOffset / 2)); - } else if (contentLines !== maxLines) { - // need add - setOffset((prevOffset) => ~~(prevOffset + prevOffset / 2)); - } else { - setRenderLocateState(RENDER_LOCATE_STATE.DONE); - setRenderFillState(RENDER_FILL_STATE.ASCEND); - } - } - }, [renderLocateState, maxLines, contentRef, screenMaxHeight, screenContent]); - - useEffect(() => { - const contentLines = contentRef.current ? contentRef.current.getClientRects().length : 0; - if (renderFillState === RENDER_FILL_STATE.ASCEND) { - if ((!isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) || contentLines < 2) && offset < contentLength) { - setOffset(offset + 1); - console.log(RENDER_FILL_STATE.ASCEND); - } else { - setRenderFillState(RENDER_FILL_STATE.DESCEND); - } - } else if (renderFillState === RENDER_FILL_STATE.DESCEND) { - if (isOverFlow(maxLines, screenMaxHeight, tagRef, contentRef) && contentLines > 1 && offset > 0) { - setOffset(offset - 1); - console.log(RENDER_FILL_STATE.DESCEND); - } else { - setRenderFillState(RENDER_FILL_STATE.DONE); - setRenderState(RENDER_STATE.DONE); - } - } - }, [renderFillState, contentLength, offset, contentRef, maxLines, screenMaxHeight]); - - const contentWrapper = {screenContent}; - const linesWrapper = ( - - {contentWrapper} - - ); - return ( -
- {linesWrapper} -
- ); -}; - -export default ReactSimpleClamp; +export default ClampText;