Skip to content

Commit 2922d38

Browse files
fix(a11y): resolve playwright tests issues
1 parent 1170d09 commit 2922d38

File tree

3 files changed

+94
-54
lines changed

3 files changed

+94
-54
lines changed

demo/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ <h1>Split Text</h1>
3838
<input type="checkbox" id="input-handle-cjt" />
3939
<label for="input-handle-cjt">Handle CJT</label>
4040
</div>
41+
<div>
42+
<input type="range" id="input-balance-ratio" step="0.01" min="0" max="1" value="1" />
43+
<label for="input-balance-ratio">Balance ratio</label>
44+
</div>
45+
<div>
46+
<input type="number" id="input-min-lines" min="1" value="1" />
47+
<label for="input-min-lines">Min lines</label>
48+
</div>
49+
<div>
50+
<input type="range" id="input-line-threshold" step="0.01" min="0" max="1" value="0.2" />
51+
<label for="input-line-threshold">Line threshold</label>
52+
</div>
4153
</fieldset>
4254
<fieldset>
4355
<legend>Styles</legend>

demo/index.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ document.addEventListener('DOMContentLoaded', () => {
1616
const noBalance = document.getElementById('input-no-balance');
1717
const handleCJT = document.getElementById('input-handle-cjt');
1818
const cssBalance = document.getElementById('input-balance');
19+
const balanceRatio = document.getElementById('input-balance-ratio');
20+
const minLines = document.getElementById('input-min-lines');
21+
const lineThreshold = document.getElementById('input-line-threshold');
22+
1923
// Get font size input
2024
const fontSize = document.getElementById('input-font-size');
2125

@@ -33,6 +37,9 @@ document.addEventListener('DOMContentLoaded', () => {
3337
noAriaLabel: noAriaLabel.checked,
3438
noBalance: noBalance.checked,
3539
handleCJT: handleCJT.checked,
40+
balanceRatio: Number(balanceRatio.value),
41+
minLines: Number(minLines.value),
42+
lineThreshold: Number(lineThreshold.value),
3643
};
3744
};
3845

@@ -67,7 +74,7 @@ document.addEventListener('DOMContentLoaded', () => {
6774
// Event listeners
6875
inputText.addEventListener('input', updateSplit);
6976
fontSize.addEventListener('input', updateFontSize);
70-
[typeLines, typeWords, typeChars, noAriaLabel, noBalance, handleCJT, cssBalance].forEach((checkbox) => {
77+
[typeLines, typeWords, typeChars, noAriaLabel, noBalance, handleCJT, cssBalance, balanceRatio, minLines, lineThreshold].forEach((checkbox) => {
7178
checkbox.addEventListener('change', updateSplit);
7279
});
7380

@@ -86,15 +93,16 @@ document.addEventListener('DOMContentLoaded', () => {
8693
});
8794

8895
// Initial text
89-
inputText.value = [
90-
/*html*/ `<h1>split anything 🐳 🍔 🍕 into words, chars, lines</h1>`,
91-
/*html*/ `<p>Try typing some text <a href="https://www.google.com">here</a> to see it split into lines, words, and characters!</p>`,
92-
/*html*/ `<ul>`,
93-
/*html*/ ` <li>pizza <b>margherita</b></li>`,
94-
/*html*/ ` <li>hamburger</li>`,
95-
/*html*/ ` <li>taco</li>`,
96-
/*html*/ `</ul>`,
97-
].join('\n');
96+
inputText.value = /* html */ `
97+
<h1>split anything 🐳 🍔 🍕 into words, chars, lines</h1>
98+
<p>Try typing some text to see it split into lines, words, and characters!</p>
99+
<p> Link <a href="https://www.google.com">here</a></p>
100+
<ul>
101+
<li>pizza <b>margherita</b></li>
102+
<li>hamburger</li>
103+
<li>taco</li>
104+
</ul>
105+
`.trim();
98106

99107
updateSplit();
100108
});

src/index.js

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ function toArray(e, parent) {
66

77
const NBSP = String.fromCharCode(160);
88
const NNBSP = String.fromCharCode(8239);
9-
const NBSPACES = [NBSP, NNBSP];
109
const SPACES = [' ', NBSP, NNBSP];
1110
const BLOCK_TAGS = ['DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'UL', 'OL', 'LI'];
1211

@@ -60,6 +59,8 @@ export default class SplitText {
6059
const byChars = ~by.indexOf('chars');
6160

6261
this.elements.forEach((element, i) => {
62+
element.__isParent = true;
63+
6364
this.originals[i] = element.innerHTML.trim();
6465

6566
// replace all zero width space with <wbr>
@@ -98,20 +99,32 @@ export default class SplitText {
9899
}
99100

100101
if (!this.options.noAriaLabel) {
101-
const blockTags = toArray(element.childNodes).filter((child) => BLOCK_TAGS.includes(child.tagName));
102-
if (blockTags.length) {
103-
blockTags.forEach((child) => {
104-
this.createAriaLabel(child);
105-
});
106-
} else {
107-
element.appendChild(this.createAriaLabel(element));
108-
}
102+
this.recursiveAriaLabel(element);
103+
104+
// Handle A tags
105+
const aTags = toArray(element.getElementsByTagName('A'));
106+
aTags.forEach((aTag) => {
107+
if (!aTag.getAttribute('aria-label')) {
108+
aTag.setAttribute('aria-label', aTag.textContent);
109+
}
110+
});
109111
}
110112
});
111113

112114
this.isSplit = true;
113115
}
114116

117+
recursiveAriaLabel(element) {
118+
const blockTags = toArray(element.childNodes).filter((child) => BLOCK_TAGS.includes(child.tagName));
119+
if (blockTags.length) {
120+
blockTags.forEach((child) => {
121+
this.recursiveAriaLabel(child);
122+
});
123+
} else {
124+
this.createAriaLabel(element);
125+
}
126+
}
127+
115128
createAriaLabel(element) {
116129
const span = document.createElement('span');
117130
span.classList.add('sr-only');
@@ -160,11 +173,20 @@ export default class SplitText {
160173
this.isSplit = false;
161174
}
162175

163-
recursiveBalance(e, lineParents) {
176+
recursiveBalance(e) {
164177
e.normalize();
165178
toArray(e.childNodes).forEach((next) => {
166179
next.normalize();
167-
next.__lineParent = next.tagName && next.hasChildNodes() && BLOCK_TAGS.includes(next.tagName);
180+
next.__lineParent = Boolean(next.tagName && next.hasChildNodes() && BLOCK_TAGS.includes(next.tagName));
181+
if (next.__lineParent && e?.__lineParent && !e.__isParent) {
182+
e.__lineParent = false;
183+
}
184+
this.recursiveBalance(next);
185+
});
186+
}
187+
188+
recursiveCheckLineParent(e, lineParents) {
189+
toArray(e.childNodes).forEach((next) => {
168190
if (next.__lineParent) {
169191
next.__idx = null;
170192
// check if the __lineParent element has a valid text node to split
@@ -173,15 +195,17 @@ export default class SplitText {
173195
lineParents.push(next);
174196
}
175197
}
176-
this.recursiveBalance(next, lineParents);
198+
this.recursiveCheckLineParent(next, lineParents);
177199
});
178200
}
179201

180202
balance(el) {
181203
// save all line parents
182204
this.lineParents = [];
183205

184-
this.recursiveBalance(el, this.lineParents);
206+
this.recursiveBalance(el);
207+
208+
this.recursiveCheckLineParent(el, this.lineParents);
185209

186210
let useParent = true;
187211
if (!this.lineParents.length) {
@@ -300,13 +324,20 @@ export default class SplitText {
300324
}
301325

302326
handleRawElement(parentEl, el, key, splitOn, preserveWhitespace, elements, allElements) {
303-
// Get the text to split, trimming out the whitespace
327+
// Get the text to split
304328
const wholeText = el.wholeText || '';
305-
let contents = wholeText.trim();
329+
let contents = wholeText;
306330

307-
// If there's no text left after trimming whitespace, continue the loop
308-
if (contents.length) {
309-
// insert leading space if there was one and preserve &nbsp;
331+
// If there's no text after removing all whitespace, preserve the original whitespace
332+
if (!contents.trim().length) {
333+
allElements.push(document.createTextNode(wholeText));
334+
return;
335+
}
336+
337+
// If we're splitting into words/chars, trim the content but preserve spaces
338+
if (key === 'word' || key === 'char') {
339+
contents = wholeText.trim();
340+
// Preserve leading whitespace
310341
if (SPACES.includes(wholeText[0])) {
311342
allElements.push(document.createTextNode(wholeText[0]));
312343
}
@@ -371,31 +402,19 @@ export default class SplitText {
371402
allElements.push(splitEl);
372403
});
373404
} else {
374-
const words = contents.split(splitOn);
375-
let i = 0,
376-
splitText;
377-
378-
const recursiveSupportNBSpaces = () => {
379-
if (key === 'char') return;
380-
let matched = false;
381-
const charAt = contents.charAt(contents.indexOf(splitText) + splitText.length);
382-
const space = NBSPACES.find((s) => s === charAt);
383-
if (space) {
384-
splitText = splitText.concat(space).concat(words[++i]);
385-
matched = true;
405+
// Split content preserving all whitespace
406+
const parts = contents.split(/([\s\u00A0\u202F]+)/);
407+
parts.forEach((part, i) => {
408+
if (i % 2 === 1) {
409+
// Odd indices are whitespace - preserve them exactly
410+
allElements.push(document.createTextNode(part));
411+
} else if (part) {
412+
// Even indices are words - process them
413+
const splitEl = this.createElement(parentEl, key, part);
414+
elements.push(splitEl);
415+
allElements.push(splitEl);
386416
}
387-
contents = contents.substring(contents.indexOf(splitText));
388-
if (matched) return recursiveSupportNBSpaces();
389-
};
390-
391-
for (; i < words.length; i++) {
392-
splitText = words[i];
393-
if (i && preserveWhitespace) allElements.push(document.createTextNode(' '));
394-
recursiveSupportNBSpaces();
395-
const splitEl = this.createElement(parentEl, key, splitText);
396-
elements.push(splitEl);
397-
allElements.push(splitEl);
398-
}
417+
});
399418
}
400419
}
401420

@@ -482,8 +501,9 @@ export default class SplitText {
482501
});
483502

484503
let globalLineIndex = 0;
485-
this.lineParents.forEach((lp) => {
504+
this.lineParents.forEach((lp, i) => {
486505
let lineIndex = 0;
506+
if (i > 0) globalLineIndex++;
487507
toArray(lp.childNodes).forEach((next) => {
488508
if (next.tagName === 'BR') {
489509
globalLineIndex++;
@@ -511,7 +531,7 @@ export default class SplitText {
511531
const line = document.createElement('span');
512532
line.style.setProperty('display', 'block');
513533
line.className = 'line';
514-
line.setAttribute('aria-hidden', true);
534+
// line.setAttribute('aria-hidden', true);
515535
return parent ? parent.appendChild(line) : line;
516536
}
517537

0 commit comments

Comments
 (0)