Skip to content

Commit

Permalink
fix: not working when target elements contain comment nodes (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukePeavey authored Jun 24, 2022
1 parent 6050c0c commit 14f11c4
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 58 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ name: 'Chromatic'

# Event for the workflow
on:
pull_request:
branches: [master]
push:
branches-ignore:
- 'gh-pages'

# List of jobs
jobs:
Expand Down
91 changes: 91 additions & 0 deletions __stories__/12-with-html-comments.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import getTemplate from './helpers/getTemplate'
import count from './helpers/count'
import { baseArgTypes } from './constants'

const children = `
<!-- COMMENT -->
<!-- COMMENT -->
Typography is the art and technique of arranging
<!-- COMMENT -->
<!-- COMMENT -->
type to make written language legible, readable,
<!-- COMMENT -->
<!-- COMMENT -->
and appealing when displayed.
<!-- COMMENT -->
<!-- COMMENT -->
`

const lineCount = 3
const { words, chars } = count(children)

export default {
title: 'Tests/With HTML Comments',
argTypes: { ...baseArgTypes },
}

const Template = getTemplate({ children })

export const NotSplit = Template.bind({})
NotSplit.args = { types: 'none' }

export const SplitLinesWordsAndChars = Template.bind({})
SplitLinesWordsAndChars.args = { types: 'lines, words, chars' }
SplitLinesWordsAndChars.parameters = {
async puppeteerTest(page) {
expect((await page.$$('.target > .line')).length).toEqual(lineCount)
expect((await page.$$('.line > .word')).length).toEqual(words)
expect((await page.$$('.word > .char')).length).toEqual(chars)
},
}

export const SplitLinesAndWords = Template.bind({})
SplitLinesAndWords.args = { types: 'lines, words' }
SplitLinesAndWords.parameters = {
async puppeteerTest(page) {
expect((await page.$$('.target > .line')).length).toEqual(lineCount)
expect((await page.$$('.line > .word')).length).toEqual(words)
expect((await page.$$('.char')).length).toEqual(0)
},
}

export const SplitLinesAndChars = Template.bind({})
SplitLinesAndChars.args = { types: 'lines, chars', absolute: false }
SplitLinesAndChars.parameters = {
async puppeteerTest(page) {
expect((await page.$$('.target > .line')).length).toEqual(lineCount)
expect((await page.$$('.line > .char')).length).toEqual(chars)
},
}

export const SplitWordsAndChars = Template.bind({})
SplitWordsAndChars.args = { types: 'words, chars' }
SplitWordsAndChars.parameters = {
async puppeteerTest(page) {
// Should not contain any line elements
expect((await page.$$('.line')).length).toEqual(0)
//
expect((await page.$$('.target > .word')).length).toEqual(words)
expect((await page.$$('.word > .char')).length).toEqual(chars)
},
}

export const SplitLines = Template.bind({})
SplitLines.args = { types: 'lines' }
SplitLines.parameters = {
async puppeteerTest(page) {
expect((await page.$$('.target > .line')).length).toEqual(lineCount)
expect((await page.$$('.word')).length).toEqual(0)
expect((await page.$$('.char')).length).toEqual(0)
},
}

export const SplitWords = Template.bind({})
SplitWords.args = { types: 'words' }
SplitWords.parameters = {
async puppeteerTest(page) {
expect((await page.$$('.line')).length).toEqual(0)
expect((await page.$$('.target > .word')).length).toEqual(words)
expect((await page.$$('.char')).length).toEqual(0)
},
}
14 changes: 0 additions & 14 deletions __tests__/utils/getTextContent.test.js

This file was deleted.

49 changes: 28 additions & 21 deletions lib/split.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,53 @@ import { Data } from './Data'
* The function is recursive, it will also split the text content of any child
* elements into words/characters, while preserving the nested elements.
*
* @param {Node} element an HTML Element or Text Node
* @param {Node} node an HTML Element or Text Node
* @param {Object} setting splitType settings
*/
export default function split(element, settings) {
// A) If `element` is a text node...
export default function split(node, settings) {
const type = node.nodeType
// Arrays of split words and characters
const wordsAndChars = { words: [], chars: [] }

// Only proceed if `node` is an `Element`, `Fragment`, or `Text`
if (!/(1|3|11)/.test(type)) {
return wordsAndChars
}

// A) IF `node` is TextNode that contains characters other than white space...
// Split the text content of the node into words and/or characters
// returns an object containing the split word and characters
if (element.nodeType === 3) {
return splitWordsAndChars(element, settings)
// return an object containing the split word and character elements
if (type === 3 && /\S/.test(node.nodeValue)) {
return splitWordsAndChars(node, settings)
}

// B) if not, element is an HTML element or fragment
// We will iterate through the child nodes of the element,
// calling the `split` function recursively for each child.
const childNodes = toArray(element.childNodes)
// B) ELSE `node` is an 'Element'
// Iterate through its child nodes, calling the `split` function
// recursively for each child node.
const childNodes = toArray(node.childNodes)

if (childNodes.length) {
Data(element).isSplit = true
Data(node).isSplit = true
// we need to set a few styles on nested html elements
if (!Data(element).isRoot) {
element.style.display = 'inline-block'
element.style.position = 'relative'
if (!Data(node).isRoot) {
node.style.display = 'inline-block'
node.style.position = 'relative'
// To maintain original spacing around nested elements when we are
// splitting text into lines, we need to check if the element should
// have a space before and after, and store that value for later.
// Note: this was necessary to maintain the correct spacing when nested
// elements do not align with word boundaries. For example, a nested
// element only wraps part of a word.
const nextSibling = element.nextSibling
const prevSibling = element.previousSibling
const text = element.textContent || ''
const nextSibling = node.nextSibling
const prevSibling = node.previousSibling
const text = node.textContent || ''
const textAfter = nextSibling ? nextSibling.textContent : ' '
const textBefore = prevSibling ? prevSibling.textContent : ' '
Data(element).isWordEnd = /\s$/.test(text) || /^\s/.test(textAfter)
Data(element).isWordStart = /^\s/.test(text) || /\s$/.test(textBefore)
Data(node).isWordEnd = /\s$/.test(text) || /^\s/.test(textAfter)
Data(node).isWordStart = /^\s/.test(text) || /\s$/.test(textBefore)
}
}

const wordsAndChars = { words: [], chars: [] }

// Iterate through child nodes, calling `split` recursively
// Returns an object containing all split words and chars
return childNodes.reduce((result, child) => {
Expand Down
11 changes: 5 additions & 6 deletions lib/splitWordsAndChars.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import toChars from './utils/toChars'
import createElement from './utils/createElement'
import parseTypes from './utils/parseTypes'
import extend from './utils/extend'
import getTextContent from './utils/getTextContent'
import defaults from './defaults'
import { Data } from './Data'

Expand All @@ -24,21 +23,21 @@ export default function splitWordsAndChars(textNode, settings) {
const types = parseTypes(settings.types)
// the tag name for split text nodes
const TAG_NAME = settings.tagName
// text content of the element (trims white space)
const TEXT_CONTENT = getTextContent(textNode)
// value of the text node
const VALUE = textNode.nodeValue
// `splitText` is a wrapper to hold the HTML structure
const splitText = document.createDocumentFragment()

// Arrays of split word and character elements
let words = []
let chars = []

if (/^\s/.test(textNode.nodeValue)) {
if (/^\s/.test(VALUE)) {
splitText.append(' ')
}

// Create an array of wrapped word elements.
words = toWords(TEXT_CONTENT).reduce((result, WORD, idx, arr) => {
words = toWords(VALUE).reduce((result, WORD, idx, arr) => {
// Let `wordElement` be the wrapped element for the current word
let wordElement
let characterElementsForCurrentWord
Expand Down Expand Up @@ -93,7 +92,7 @@ export default function splitWordsAndChars(textNode, settings) {
}, []) // END LOOP;

// Add a trailing white space to maintain word spacing
if (/\s$/.test(textNode.nodeValue)) {
if (/\s$/.test(VALUE)) {
splitText.append(' ')
}
textNode.replaceWith(splitText)
Expand Down
12 changes: 0 additions & 12 deletions lib/utils/getTextContent.js

This file was deleted.

6 changes: 3 additions & 3 deletions lib/utils/toWords.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @param {string | RegExp} [separator = ' ']
* @return {string[]} Array of words
*/
export default function toWords(string, separator = ' ') {
string = string ? String(string) : ''
return string.split(separator)
export default function toWords(value, separator = ' ') {
const string = value ? String(value) : ''
return string.trim().replace(/\s+/g, ' ').split(separator)
}

0 comments on commit 14f11c4

Please sign in to comment.