@azure/communication-react
Version:
React library for building modern communication user experiences utilizing Azure Communication Services
86 lines • 4.13 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import React, { useLayoutEffect, useRef, useState } from 'react';
/**
* An SVG element component that wraps inner text to fit the width of the SVG.
* @private
*/
export const SvgWithWordWrapping = (props) => {
const { width, text, lineHeightPx, bufferHeightPx, role } = props;
const svgRef = useRef(null);
const calculationTextElement = useRef(null);
const visibleTextElement = useRef(null);
const [height, setHeight] = useState(0);
// useLayoutEffect ensures that the calculationTextElement is rendered before being used for calculations.
// Using useLayoutEffect over useEffect ensures we do not get a layout shift when the visibleTextElement is rendered
// and the height is updated. This is because useLayoutEffect runs synchronously after DOM mutations but
// before the browser has a chance to paint. See https://reactjs.org/docs/hooks-reference.html#uselayouteffect
// for more details.
useLayoutEffect(() => {
if (text && calculationTextElement.current && visibleTextElement.current) {
const numLines = convertTextToWrappedText(calculationTextElement.current, visibleTextElement.current, width, lineHeightPx);
setHeight(numLines * lineHeightPx);
}
}, [width, lineHeightPx, text]);
return React.createElement("svg", { "aria-level": 1, role: role, width: width, height: height + bufferHeightPx, ref: svgRef, xmlns: "http://www.w3.org/2000/svg" },
React.createElement("text", { height: 0, ref: calculationTextElement, style: {
visibility: 'hidden'
} }, text),
React.createElement("text", { ref: visibleTextElement, x: "0", y: bufferHeightPx / 4, role: "heading", "aria-level": 1 }));
};
/**
* Wrap text in tspan elements to fit the width of the SVG
* @param baseTextElement The text element to create the wrapped text from.
* @param outputTextElement The text element to insert the wrapped text into.
* @param maxWidth The maximum width of the text element.
* @param lineHeightPx The height of each line in pixels.
* @returns The number of lines of text.
*/
const convertTextToWrappedText = (inputTextElement, outputTextElement, maxWidth, lineHeightPx) => {
var _a, _b;
const words = (_b = (_a = inputTextElement.textContent) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0 ? _b : [];
if (words.length === 0 || words[0] === '') {
throw new Error('Text element must contain text');
}
// The current line being built.
let line = '';
// Running total of the number of lines.
let numLines = 0;
// First, clear the output text element.
outputTextElement.textContent = '';
// Iterate through each word and create a tspan element for each line.
for (let i = 0; i < words.length; i++) {
const testLine = line + words[i] + ' ';
const testWidth = inputTextElement.getSubStringLength(0, testLine.length);
if (testWidth > maxWidth && i > 0) {
const newLine = constructTSpanLine(line, lineHeightPx);
outputTextElement.appendChild(newLine);
line = words[i] + ' ';
numLines++;
}
else {
line = testLine;
}
}
// Add the last line.
const newLine = constructTSpanLine(line, lineHeightPx);
outputTextElement.appendChild(newLine);
numLines++;
// Return the number of lines to calculate the height of the SVG.
return numLines;
};
/**
* Create a tspan element for a line of text, with text set to be centered.
* @param line The line of text.
* @param lineHeightPx The height of each line in pixels.
* @returns The tspan element.
*/
const constructTSpanLine = (line, lineHeightPx) => {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.textContent = line;
tspan.setAttribute('x', '50%');
tspan.setAttribute('dy', `${lineHeightPx}px`);
tspan.setAttribute('text-anchor', 'middle');
return tspan;
};
//# sourceMappingURL=SvgWithWordWrapping.js.map