Blame Raw
· · · 69 lines (2.5 KB)
0 contributors
1import { morphdom } from '../../lib.js';
2
3/**
4 * Check if the current browser supports native segmentation function.
5 * @returns {boolean} True if the Segmenter is supported by the current browser.
6 */
7export function isSegmenterSupported() {
8 return typeof Intl.Segmenter === 'function';
9}
10
11/**
12 * Segment text in the given HTML content using Intl.Segmenter.
13 * @param {HTMLElement} htmlElement Target HTML element
14 * @param {string} htmlContent HTML content to segment
15 * @param {'word'|'grapheme'|'sentence'} [granularity='word'] Text split granularity
16 */
17export function segmentTextInElement(htmlElement, htmlContent, granularity = 'word') {
18 htmlElement.innerHTML = htmlContent;
19
20 if (!isSegmenterSupported()) {
21 return;
22 }
23
24 // TODO: Support more locales, make granularity configurable.
25 const segmenter = new Intl.Segmenter('en-US', { granularity });
26 const textNodes = [];
27 const walker = document.createTreeWalker(htmlElement, NodeFilter.SHOW_TEXT);
28 while (walker.nextNode()) {
29 const textNode = /** @type {Text} */ (walker.currentNode);
30
31 // Skip ancestors of code/pre
32 if (textNode.parentElement && textNode.parentElement.closest('pre, code')) {
33 continue;
34 }
35
36 // Skip text nodes that are empty or only whitespace
37 if (/^\s*$/.test(textNode.data)) {
38 continue;
39 }
40
41 textNodes.push(textNode);
42 }
43
44 // Split every text node into segments using spans
45 for (const textNode of textNodes) {
46 const fragment = document.createDocumentFragment();
47 const segments = segmenter.segment(textNode.data);
48 for (const segment of segments) {
49 // TODO: Apply a different class for different segment length/content?
50 // For now, just use a single class for all segments.
51 const span = document.createElement('span');
52 span.innerText = segment.segment;
53 span.className = 'text_segment';
54 fragment.appendChild(span);
55 }
56 textNode.replaceWith(fragment);
57 }
58}
59
60/**
61 * Apply stream fade-in effect to the given message text element by morphing its content.
62 * @param {HTMLElement} messageTextElement Message text element
63 * @param {string} htmlContent New HTML content to apply
64 */
65export function applyStreamFadeIn(messageTextElement, htmlContent) {
66 const targetElement = /** @type {HTMLElement} */ (messageTextElement.cloneNode());
67 segmentTextInElement(targetElement, htmlContent);
68 morphdom(messageTextElement, targetElement);
69}