Comparing version 0.6.3 to 0.7.0
406
clamp.js
/*! | ||
* Clamp.js 0.6.2-bw | ||
* Clamp.js 0.7.0 | ||
* | ||
@@ -9,212 +9,264 @@ * Copyright 2011-2013, Joseph Schmitt http://joe.sh | ||
(function(){ | ||
/** | ||
* Clamps a text node. | ||
* @param {HTMLElement} element. Element containing the text node to clamp. | ||
* @param {Object} options. Options to pass to the clamper. | ||
*/ | ||
function clamp(element, options) { | ||
options = options || {}; | ||
(function(root, factory) { | ||
if (typeof define === 'function' && define.amd) { | ||
// AMD | ||
define([], factory); | ||
} else if (typeof exports === 'object') { | ||
// Node, CommonJS-like | ||
module.exports = factory(); | ||
} else { | ||
// Browser globals | ||
root.$clamp = factory(); | ||
} | ||
}(this, function() { | ||
/** | ||
* Clamps a text node. | ||
* @param {HTMLElement} element. Element containing the text node to clamp. | ||
* @param {Object} options. Options to pass to the clamper. | ||
*/ | ||
function clamp(element, options) { | ||
options = options || {}; | ||
var self = this, | ||
win = window, | ||
opt = { | ||
clamp: options.clamp || 2, | ||
useNativeClamp: typeof(options.useNativeClamp) != 'undefined' ? options.useNativeClamp : true, | ||
truncationChar: options.truncationChar || '…', | ||
removeTrailingChars: options.removeTrailingChars || ',.;:!?-' | ||
}, | ||
var self = this, | ||
win = window, | ||
opt = { | ||
clamp: options.clamp || 2, | ||
useNativeClamp: typeof(options.useNativeClamp) != 'undefined' ? options.useNativeClamp : true, | ||
splitOnChars: options.splitOnChars || ['.', '-', '–', '—', ' '], //Split on sentences (periods), hypens, en-dashes, em-dashes, and words (spaces). | ||
animate: options.animate || false, | ||
truncationChar: options.truncationChar || '…', | ||
truncationHTML: options.truncationHTML | ||
}, | ||
sty = element.style, | ||
originalText = element.innerHTML, | ||
sty = element.style, | ||
originalText = element.innerHTML, | ||
supportsNativeClamp = typeof(element.style.webkitLineClamp) != 'undefined', | ||
clampValue = opt.clamp, | ||
isCSSValue = clampValue.indexOf && (clampValue.indexOf('px') > -1 || clampValue.indexOf('em') > -1); | ||
supportsNativeClamp = typeof(element.style.webkitLineClamp) != 'undefined', | ||
clampValue = opt.clamp, | ||
isCSSValue = clampValue.indexOf && (clampValue.indexOf('px') > -1 || clampValue.indexOf('em') > -1), | ||
truncationHTMLContainer; | ||
// UTILITY FUNCTIONS __________________________________________________________ | ||
if (opt.truncationHTML) { | ||
truncationHTMLContainer = document.createElement('span'); | ||
truncationHTMLContainer.innerHTML = opt.truncationHTML; | ||
} | ||
/** | ||
* Return the current style for an element. | ||
* @param {HTMLElement} elem The element to compute. | ||
* @param {string} prop The style property. | ||
* @returns {number} | ||
*/ | ||
function computeStyle(elem, prop) { | ||
var getComputedStyle; | ||
if (win.getComputedStyle) { | ||
getComputedStyle = win.getComputedStyle; | ||
} else { | ||
getComputedStyle = function (el, pseudo) { | ||
this.el = el; | ||
this.getPropertyValue = function (prop) { | ||
var re = /(\-([a-z]){1})/g; | ||
if (prop == 'float') prop = 'styleFloat'; | ||
if (re.test(prop)) { | ||
prop = prop.replace(re, function () { | ||
return arguments[2].toUpperCase(); | ||
}); | ||
} | ||
return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null; | ||
}; | ||
return this; | ||
}; | ||
} | ||
return getComputedStyle(elem, null).getPropertyValue(prop); | ||
} | ||
// UTILITY FUNCTIONS __________________________________________________________ | ||
/** | ||
* Returns the maximum number of lines of text that should be rendered based | ||
* on the current height of the element and the line-height of the text. | ||
*/ | ||
function getMaxLines(height) { | ||
var availHeight = height || getElemHeight(element), | ||
lineHeight = getLineHeight(element); | ||
return Math.max(Math.floor(availHeight/lineHeight), 0); | ||
} | ||
/** | ||
* Returns the maximum height a given element should have based on the line- | ||
* height of the text and the given clamp value. | ||
*/ | ||
function getMaxHeight(clmp) { | ||
var lineHeight = getLineHeight(element); | ||
return lineHeight * clmp; | ||
} | ||
/** | ||
* Returns the line-height of an element as an integer. | ||
*/ | ||
function getLineHeight(elem) { | ||
var lh = computeStyle(elem, 'line-height'); | ||
if (lh == 'normal') { | ||
// Normal line heights vary from browser to browser. The spec recommends | ||
// a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff. | ||
lh = parseInt(computeStyle(elem, 'font-size'), 10) * 1.2; | ||
/** | ||
* Return the current style for an element. | ||
* @param {HTMLElement} elem The element to compute. | ||
* @param {string} prop The style property. | ||
* @returns {number} | ||
*/ | ||
function computeStyle(elem, prop) { | ||
if (!win.getComputedStyle) { | ||
win.getComputedStyle = function(el, pseudo) { | ||
this.el = el; | ||
this.getPropertyValue = function(prop) { | ||
var re = /(\-([a-z]){1})/g; | ||
if (prop == 'float') prop = 'styleFloat'; | ||
if (re.test(prop)) { | ||
prop = prop.replace(re, function() { | ||
return arguments[2].toUpperCase(); | ||
}); | ||
} | ||
return parseInt(lh, 10); | ||
} | ||
return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null; | ||
}; | ||
return this; | ||
}; | ||
} | ||
/** | ||
* Returns the height of an element as an integer (max of scroll/offset/client). | ||
* Note: inline elements return 0 for scrollHeight and clientHeight | ||
*/ | ||
function getElemHeight(elem) { | ||
return Math.max(elem.scrollHeight, elem.offsetHeight, elem.clientHeight); | ||
} | ||
return win.getComputedStyle(elem, null).getPropertyValue(prop); | ||
} | ||
/** | ||
* Gets an element's last text node. This will remove empty elements from the end. | ||
*/ | ||
function getLastTextNode(elem) { | ||
// if we have children, search inside the last one | ||
if (elem.lastChild) { | ||
return getLastTextNode(elem.lastChild); | ||
} | ||
/** | ||
* Returns the maximum number of lines of text that should be rendered based | ||
* on the current height of the element and the line-height of the text. | ||
*/ | ||
function getMaxLines(height) { | ||
var availHeight = height || element.clientHeight, | ||
lineHeight = getLineHeight(element); | ||
//we don't have children, and this is the root => we can't find anything here | ||
if (elem == element) { | ||
return null; | ||
} | ||
return Math.max(Math.floor(availHeight / lineHeight), 0); | ||
} | ||
// we don't have children, but this is not a text node, or it is empty => remove it and try again | ||
if (elem.nodeType !== 3 || !elem.nodeValue.trim() || elem.nodeValue == opt.truncationChar) { | ||
var parent = elem.parentNode; | ||
parent.removeChild(elem); | ||
return getLastTextNode(parent); | ||
} | ||
/** | ||
* Returns the maximum height a given element should have based on the line- | ||
* height of the text and the given clamp value. | ||
*/ | ||
function getMaxHeight(clmp) { | ||
var lineHeight = getLineHeight(element); | ||
return lineHeight * clmp; | ||
} | ||
//we found a child of type text with actual content | ||
return elem; | ||
} | ||
/** | ||
* Returns the line-height of an element as an integer. | ||
*/ | ||
function getLineHeight(elem) { | ||
var lh = computeStyle(elem, 'line-height'); | ||
if (lh == 'normal') { | ||
// Normal line heights vary from browser to browser. The spec recommends | ||
// a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff. | ||
lh = parseInt(computeStyle(elem, 'font-size')) * 1.2; | ||
} | ||
return parseInt(lh); | ||
} | ||
// MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________ | ||
var splitOnChars = opt.splitOnChars.slice(0), | ||
splitChar = splitOnChars[0], | ||
chunks, | ||
lastChunk; | ||
// MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________ | ||
/** | ||
* Gets an element's last child. That may be another node or a node's contents. | ||
*/ | ||
function getLastChild(elem) { | ||
//Current element has children, need to go deeper and get last child as a text node | ||
if (elem.lastChild.children && elem.lastChild.children.length > 0) { | ||
return getLastChild(Array.prototype.slice.call(elem.children).pop()); | ||
} | ||
//This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying | ||
else if (!elem.lastChild || !elem.lastChild.nodeValue || elem.lastChild.nodeValue === '' || elem.lastChild.nodeValue == opt.truncationChar) { | ||
elem.lastChild.parentNode.removeChild(elem.lastChild); | ||
return getLastChild(element); | ||
} | ||
//This is the last child we want, return it | ||
else { | ||
return elem.lastChild; | ||
} | ||
} | ||
/** | ||
* Removes one character at a time from the text until its width or | ||
* height is beneath the passed-in max param. | ||
*/ | ||
function truncate(target, maxHeight) { | ||
if (!maxHeight) { | ||
return; | ||
} | ||
/** | ||
* Does a binary search over the words in the elements text until it finds the last one that fits in the maximum height | ||
*/ | ||
function truncate(target, maxHeight) { | ||
if (!target || !maxHeight) {return;} | ||
/** | ||
* Resets global variables. | ||
*/ | ||
function reset() { | ||
splitOnChars = opt.splitOnChars.slice(0); | ||
splitChar = splitOnChars[0]; | ||
chunks = null; | ||
lastChunk = null; | ||
} | ||
var original = target.nodeValue.replace(opt.truncationChar, ''); | ||
var nodeValue = target.nodeValue.replace(opt.truncationChar, ''); | ||
var words = original.split(' '); | ||
//Grab the next chunks | ||
if (!chunks) { | ||
//If there are more characters to try, grab the next one | ||
if (splitOnChars.length > 0) { | ||
splitChar = splitOnChars.shift(); | ||
} | ||
//No characters to chunk by. Go character-by-character | ||
else { | ||
splitChar = ''; | ||
} | ||
var start=0, end = words.length-1, mid=-1, m; | ||
chunks = nodeValue.split(splitChar); | ||
} | ||
while (start <= end && end > 0) { | ||
m = Math.floor((start+end)/2); | ||
if (m == mid) { | ||
break; | ||
} | ||
mid= m; | ||
//If there are chunks left to remove, remove the last one and see if | ||
// the nodeValue fits. | ||
if (chunks.length > 1) { | ||
// console.log('chunks', chunks); | ||
lastChunk = chunks.pop(); | ||
// console.log('lastChunk', lastChunk); | ||
applyEllipsis(target, chunks.join(splitChar)); | ||
} | ||
//No more chunks can be removed using this character | ||
else { | ||
chunks = null; | ||
} | ||
applyEllipsis(target, words.slice(0, mid+1).join(' ')); | ||
//Insert the custom HTML before the truncation character | ||
if (truncationHTMLContainer) { | ||
target.nodeValue = target.nodeValue.replace(opt.truncationChar, ''); | ||
element.innerHTML = target.nodeValue + ' ' + truncationHTMLContainer.innerHTML + opt.truncationChar; | ||
} | ||
height = getElemHeight(element); | ||
//Search produced valid chunks | ||
if (chunks) { | ||
//It fits | ||
if (element.clientHeight <= maxHeight) { | ||
//There's still more characters to try splitting on, not quite done yet | ||
if (splitOnChars.length >= 0 && splitChar !== '') { | ||
applyEllipsis(target, chunks.join(splitChar) + splitChar + lastChunk); | ||
chunks = null; | ||
} | ||
//Finished! | ||
else { | ||
return element.innerHTML; | ||
} | ||
} | ||
} | ||
//No valid chunks produced | ||
else { | ||
//No valid chunks even when splitting by letter, time to move | ||
//on to the next node | ||
if (splitChar === '') { | ||
applyEllipsis(target, ''); | ||
target = getLastChild(element); | ||
if (height <= maxHeight) { | ||
start = mid; | ||
} else { | ||
end = mid; | ||
} | ||
} | ||
if (height > maxHeight) { | ||
target.parentNode.removeChild(target); | ||
truncate(getLastTextNode(element), maxHeight); | ||
} | ||
reset(); | ||
} | ||
} | ||
function applyEllipsis(elem, str) { | ||
while (str.length && opt.removeTrailingChars.indexOf(str[str.length-1]) != -1) { | ||
str = str.substring(0, str.length -1); | ||
} | ||
//If you get here it means still too big, let's keep truncating | ||
if (opt.animate) { | ||
setTimeout(function() { | ||
truncate(target, maxHeight); | ||
}, opt.animate === true ? 10 : opt.animate); | ||
} else { | ||
return truncate(target, maxHeight); | ||
} | ||
} | ||
elem.nodeValue = str + opt.truncationChar; | ||
} | ||
function applyEllipsis(elem, str) { | ||
elem.nodeValue = str + opt.truncationChar; | ||
} | ||
// CONSTRUCTOR ________________________________________________________________ | ||
// CONSTRUCTOR ________________________________________________________________ | ||
if (clampValue == 'auto') { | ||
clampValue = getMaxLines(); | ||
} | ||
else if (isCSSValue) { | ||
clampValue = getMaxLines(parseInt(clampValue, 10)); | ||
} | ||
if (clampValue == 'auto') { | ||
clampValue = getMaxLines(); | ||
} else if (isCSSValue) { | ||
clampValue = getMaxLines(parseInt(clampValue)); | ||
} | ||
var clampedText; | ||
if (supportsNativeClamp && opt.useNativeClamp) { | ||
sty.overflow = 'hidden'; | ||
sty.textOverflow = 'ellipsis'; | ||
sty.webkitBoxOrient = 'vertical'; | ||
sty.display = '-webkit-box'; | ||
sty.webkitLineClamp = clampValue; | ||
var clampedText; | ||
if (supportsNativeClamp && opt.useNativeClamp) { | ||
sty.overflow = 'hidden'; | ||
sty.textOverflow = 'ellipsis'; | ||
sty.webkitBoxOrient = 'vertical'; | ||
sty.display = '-webkit-box'; | ||
sty.webkitLineClamp = clampValue; | ||
if (isCSSValue) { | ||
sty.height = opt.clamp + 'px'; | ||
} | ||
} | ||
else { | ||
var height = getMaxHeight(clampValue); | ||
if (height < getElemHeight(element)) { | ||
truncate(getLastTextNode(element), height); | ||
clampedText = element.innerHTML; | ||
} | ||
} | ||
return { | ||
'original': originalText, | ||
'clamped': clampedText | ||
}; | ||
if (isCSSValue) { | ||
sty.height = opt.clamp + 'px'; | ||
} | ||
} else { | ||
var height = getMaxHeight(clampValue); | ||
if (height <= element.clientHeight) { | ||
clampedText = truncate(getLastChild(element), height); | ||
} | ||
} | ||
window.$clamp = clamp; | ||
})(); | ||
return { | ||
'original': originalText, | ||
'clamped': clampedText | ||
}; | ||
} | ||
return clamp; | ||
})); |
{ | ||
"name": "clamp-js", | ||
"version": "0.6.3", | ||
"version": "0.7.0", | ||
"license": "WTFPL", | ||
"description": "Clamps (ie. cuts off) an HTML element's content by adding ellipsis to it if the content inside is too long.", | ||
"author": { | ||
"name": "Javier López", | ||
"email": "xavi160@gmail.com" | ||
}, | ||
"keywords": [ | ||
"clamp", | ||
"ellipsis", | ||
"multiline", | ||
"browser" | ||
], | ||
"main": "clamp.js", | ||
"devDependencies": {} | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/xavi160/Clamp.js.git" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No License Found
License(Experimental) License information could not be found.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
13662
0
237
2
63
0
4
1