agent: a11y — add bionic reading Svelte action using safe DOM manipulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
src/lib/actions/bionicReading.js
Normal file
70
src/lib/actions/bionicReading.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// src/lib/actions/bionicReading.js
|
||||||
|
|
||||||
|
function applyBionic(node) {
|
||||||
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
|
||||||
|
const textNodes = [];
|
||||||
|
let current;
|
||||||
|
while ((current = walker.nextNode())) textNodes.push(current);
|
||||||
|
|
||||||
|
for (const textNode of textNodes) {
|
||||||
|
const words = textNode.textContent.split(/(\s+)/);
|
||||||
|
if (words.length <= 1 && !words[0].trim()) continue;
|
||||||
|
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
for (const word of words) {
|
||||||
|
if (!word.trim() || word.length < 2) {
|
||||||
|
fragment.appendChild(document.createTextNode(word));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const boldLen = Math.min(3, Math.ceil(word.length / 2));
|
||||||
|
const b = document.createElement('b');
|
||||||
|
b.textContent = word.slice(0, boldLen);
|
||||||
|
fragment.appendChild(b);
|
||||||
|
fragment.appendChild(document.createTextNode(word.slice(boldLen)));
|
||||||
|
}
|
||||||
|
textNode.parentNode.replaceChild(fragment, textNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripBionic(node) {
|
||||||
|
const bolds = node.querySelectorAll('b');
|
||||||
|
for (const b of bolds) {
|
||||||
|
const text = document.createTextNode(b.textContent);
|
||||||
|
b.parentNode.replaceChild(text, b);
|
||||||
|
}
|
||||||
|
node.normalize(); // merge adjacent text nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bionicReading(node, enabled) {
|
||||||
|
let observer = null;
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
// Disconnect observer while we mutate, to avoid infinite loop
|
||||||
|
if (observer) observer.disconnect();
|
||||||
|
stripBionic(node);
|
||||||
|
if (enabled) applyBionic(node);
|
||||||
|
if (observer) observe();
|
||||||
|
}
|
||||||
|
|
||||||
|
function observe() {
|
||||||
|
observer.observe(node, { childList: true, characterData: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutationObserver catches Svelte re-rendering DOM content underneath us
|
||||||
|
// (new transcript segments, task name edits, etc.)
|
||||||
|
observer = new MutationObserver(refresh);
|
||||||
|
|
||||||
|
if (enabled) applyBionic(node);
|
||||||
|
observe();
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(newEnabled) {
|
||||||
|
enabled = newEnabled;
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
if (observer) observer.disconnect();
|
||||||
|
stripBionic(node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user