shave
Version: 
Shave is a javascript plugin that truncates multi-line text within a html element based on set max height
1 lines • 7.74 kB
Source Map (JSON)
{"version":3,"sources":["../src/shave.ts"],"sourcesContent":["export type Link = {\n  [key: string]: string | number | boolean\n}\n\nexport type Opts = {\n  character?: string\n  classname?: string\n  spaces?: boolean\n  charclassname?: string\n  link?: Link\n  delimiter?: string\n}\n\nfunction generateArrayOfNodes(target: string | NodeList | Node): Array<Node> {\n  if (typeof target === 'string') {\n    return [...document.querySelectorAll(target)]\n  } else if ('length' in target) {\n    return [...target]\n  } else {\n    return [target]\n  }\n}\n\nexport default function shave(target: string | NodeList | Node, maxHeight: number, opts: Opts = {}): void {\n  if (typeof maxHeight === 'undefined' || isNaN(maxHeight)) {\n    throw Error('maxHeight is required')\n  }\n  const els = generateArrayOfNodes(target)\n\n  if (!els.length) {\n    return\n  }\n\n  const {\n    character = '…',\n    classname = 'js-shave',\n    spaces: initialSpaces = true,\n    charclassname = 'js-shave-char',\n    link = {},\n    delimiter,\n  } = opts\n\n  /**\n   * @notes\n   * the initialSpaces + spaces variable definition below fixes\n   * a previous bug where spaces being a boolean type wasn't clear\n   * meaning people were using (a string, in example—which is truthy)\n   * hence, doing it this way is a non-breaking change\n   */\n  const spaces = typeof initialSpaces === 'boolean' ? initialSpaces : true\n\n  /**\n   * @notes\n   * - create a span or anchor element and assign properties to it\n   * - JSON.stringify is used to support IE8+\n   * - if link.href is not provided, link object properties are ignored\n   */\n  const isLink = link && JSON.stringify(link) !== '{}' && link.href\n  const shavedTextElType = isLink ? 'a' : 'span'\n\n  for (let i = 0; i < els.length; i += 1) {\n    const el = els[i] as HTMLElement\n    const styles = el.style\n    const span = el.querySelector('.' + classname)\n    const textProp = el.textContent === undefined ? 'innerText' : 'textContent'\n    if (span) {\n      el.removeChild(el.querySelector('.' + charclassname))\n      el[textProp] = el[textProp] // eslint-disable-line\n    }\n\n    const fullText = el[textProp]\n    let words: string | string[]\n\n    if (!delimiter) words = spaces ? fullText.split(' ') : fullText\n    else words = fullText.split(delimiter)\n\n    if (words.length < 2) continue\n\n    const heightStyle = styles.height\n    styles.height = 'auto'\n    const maxHeightStyle = styles.maxHeight\n    styles.maxHeight = 'none'\n\n    if (el.offsetHeight <= maxHeight) {\n      styles.height = heightStyle\n      styles.maxHeight = maxHeightStyle\n      continue\n    }\n\n    const textContent = isLink && link.textContent ? link.textContent : character\n    const shavedTextEl = document.createElement(shavedTextElType)\n    const shavedTextElAttributes = {\n      className: charclassname,\n      textContent,\n    }\n\n    for (const property in shavedTextElAttributes) {\n      shavedTextEl[property] = shavedTextElAttributes[property]\n      shavedTextEl.textContent = character;\n    }\n\n    if (isLink) {\n      for (const linkProperty in link) {\n        shavedTextEl[linkProperty] = link[linkProperty]\n      }\n    }\n\n    let max = words.length - 1\n    let min = 0\n    let pivot\n    while (min < max) {\n      pivot = (min + max + 1) >> 1 // eslint-disable-line no-bitwise\n      const wordItems = words.slice(0, pivot);\n      el[textProp] = updateTextProp(delimiter, spaces, wordItems)\n      el.insertAdjacentElement('beforeend', shavedTextEl)\n      if (el.offsetHeight > maxHeight) {\n        max = pivot - 1\n      } else {\n        min = pivot\n      }\n    }\n    const wordeItems = words.slice(0, max)\n    el[textProp] = updateTextProp(delimiter, spaces, wordeItems)\n    el.insertAdjacentElement('beforeend', shavedTextEl)\n    const diffItems = words.slice(max);\n    const isArray = Array.isArray(diffItems)\n    let diff = '';\n    if (delimiter && isArray) diff = delimiter + diffItems.join(delimiter)\n    else if (spaces && isArray) diff = ' ' + diffItems.join(' ')\n    else if (isArray) diff = diffItems.join('')\n    else diff = diffItems\n    const shavedText = document.createTextNode(diff)\n    const elWithShavedText = document.createElement('span')\n    elWithShavedText.classList.add(classname)\n    elWithShavedText.style.display = 'none'\n    elWithShavedText.appendChild(shavedText)\n    el.insertAdjacentElement('beforeend', elWithShavedText)\n    styles.height = heightStyle\n    styles.maxHeight = maxHeightStyle\n  }\n}\n\nexport function updateTextProp (delimiter: string, spaces: boolean, wordItems: string | string[]): string {\n  const isArray = Array.isArray(wordItems)\n  if (delimiter && isArray) return (wordItems).join(delimiter)\n  if (spaces && isArray) return (wordItems).join(' ')\n  if (isArray) return wordItems.join('')\n  return wordItems\n}\n"],"mappings":"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","names":["generateArrayOfNodes","target","shave","maxHeight","opts","els","character","classname","initialSpaces","charclassname","link","delimiter","spaces","isLink","shavedTextElType","i","el","styles","span","textProp","fullText","words","heightStyle","maxHeightStyle","textContent","shavedTextEl","shavedTextElAttributes","property","linkProperty","max","min","pivot","wordItems","updateTextProp","wordeItems","diffItems","isArray","diff","shavedText","elWithShavedText"]}