mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-27 19:15:26 +03:00
170 lines
4.2 KiB
JavaScript
170 lines
4.2 KiB
JavaScript
|
const HIDDEN_TEXTAREA_STYLE = {
|
||
|
"min-height": "0",
|
||
|
"max-height": "none",
|
||
|
height: "0",
|
||
|
visibility: "hidden",
|
||
|
overflow: "hidden",
|
||
|
position: "absolute",
|
||
|
"z-index": "-1000",
|
||
|
top: "0",
|
||
|
right: "0",
|
||
|
};
|
||
|
|
||
|
const SIZING_STYLE = [
|
||
|
"letter-spacing",
|
||
|
"line-height",
|
||
|
"font-family",
|
||
|
"font-weight",
|
||
|
"font-size",
|
||
|
"font-style",
|
||
|
"tab-size",
|
||
|
"text-rendering",
|
||
|
"text-transform",
|
||
|
"width",
|
||
|
"text-indent",
|
||
|
"padding-top",
|
||
|
"padding-right",
|
||
|
"padding-bottom",
|
||
|
"padding-left",
|
||
|
"border-top-width",
|
||
|
"border-right-width",
|
||
|
"border-bottom-width",
|
||
|
"border-left-width",
|
||
|
"box-sizing",
|
||
|
];
|
||
|
|
||
|
let computedStyleCache = {};
|
||
|
const hiddenTextarea = process.browser && document.createElement("textarea");
|
||
|
|
||
|
const forceHiddenStyles = (node) => {
|
||
|
Object.keys(HIDDEN_TEXTAREA_STYLE).forEach((key) => {
|
||
|
node.style.setProperty(key, HIDDEN_TEXTAREA_STYLE[key], "important");
|
||
|
});
|
||
|
};
|
||
|
|
||
|
if (process.browser) {
|
||
|
hiddenTextarea.setAttribute("tab-index", "-1");
|
||
|
hiddenTextarea.setAttribute("aria-hidden", "true");
|
||
|
forceHiddenStyles(hiddenTextarea);
|
||
|
}
|
||
|
|
||
|
export default function calculateNodeHeight(
|
||
|
uiTextNode,
|
||
|
uid,
|
||
|
useCache = false,
|
||
|
minRows = null,
|
||
|
maxRows = null
|
||
|
) {
|
||
|
if (hiddenTextarea.parentNode === null) {
|
||
|
document.body.appendChild(hiddenTextarea);
|
||
|
}
|
||
|
|
||
|
// Copy all CSS properties that have an impact on the height of the content in
|
||
|
// the textbox
|
||
|
const nodeStyling = calculateNodeStyling(uiTextNode, uid, useCache);
|
||
|
|
||
|
if (nodeStyling === null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const { paddingSize, borderSize, boxSizing, sizingStyle } = nodeStyling;
|
||
|
|
||
|
// Need to have the overflow attribute to hide the scrollbar otherwise
|
||
|
// text-lines will not calculated properly as the shadow will technically be
|
||
|
// narrower for content
|
||
|
Object.keys(sizingStyle).forEach((key) => {
|
||
|
hiddenTextarea.style[key] = sizingStyle[key];
|
||
|
});
|
||
|
forceHiddenStyles(hiddenTextarea);
|
||
|
hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || "x";
|
||
|
|
||
|
let minHeight = -Infinity;
|
||
|
let maxHeight = Infinity;
|
||
|
let height = hiddenTextarea.scrollHeight;
|
||
|
|
||
|
if (boxSizing === "border-box") {
|
||
|
// border-box: add border, since height = content + padding + border
|
||
|
height = height + borderSize;
|
||
|
} else if (boxSizing === "content-box") {
|
||
|
// remove padding, since height = content
|
||
|
height = height - paddingSize;
|
||
|
}
|
||
|
|
||
|
// measure height of a textarea with a single row
|
||
|
hiddenTextarea.value = "x";
|
||
|
const singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
|
||
|
|
||
|
// Stores the value's rows count rendered in `hiddenTextarea`,
|
||
|
// regardless if `maxRows` or `minRows` props are passed
|
||
|
const valueRowCount = Math.floor(height / singleRowHeight);
|
||
|
|
||
|
if (minRows !== null) {
|
||
|
minHeight = singleRowHeight * minRows;
|
||
|
if (boxSizing === "border-box") {
|
||
|
minHeight = minHeight + paddingSize + borderSize;
|
||
|
}
|
||
|
height = Math.max(minHeight, height);
|
||
|
}
|
||
|
|
||
|
if (maxRows !== null) {
|
||
|
maxHeight = singleRowHeight * maxRows;
|
||
|
if (boxSizing === "border-box") {
|
||
|
maxHeight = maxHeight + paddingSize + borderSize;
|
||
|
}
|
||
|
height = Math.min(maxHeight, height);
|
||
|
}
|
||
|
|
||
|
const rowCount = Math.floor(height / singleRowHeight);
|
||
|
|
||
|
return { height, minHeight, maxHeight, rowCount, valueRowCount };
|
||
|
}
|
||
|
|
||
|
function calculateNodeStyling(node, uid, useCache = false) {
|
||
|
if (useCache && computedStyleCache[uid]) {
|
||
|
return computedStyleCache[uid];
|
||
|
}
|
||
|
|
||
|
const style = window.getComputedStyle(node);
|
||
|
|
||
|
if (style === null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
let sizingStyle = SIZING_STYLE.reduce((obj, name) => {
|
||
|
obj[name] = style.getPropertyValue(name);
|
||
|
return obj;
|
||
|
}, {});
|
||
|
|
||
|
const boxSizing = sizingStyle["box-sizing"];
|
||
|
|
||
|
// probably node is detached from DOM, can't read computed dimensions
|
||
|
if (boxSizing === "") {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const paddingSize =
|
||
|
parseFloat(sizingStyle["padding-bottom"]) +
|
||
|
parseFloat(sizingStyle["padding-top"]);
|
||
|
|
||
|
const borderSize =
|
||
|
parseFloat(sizingStyle["border-bottom-width"]) +
|
||
|
parseFloat(sizingStyle["border-top-width"]);
|
||
|
|
||
|
const nodeInfo = {
|
||
|
sizingStyle,
|
||
|
paddingSize,
|
||
|
borderSize,
|
||
|
boxSizing,
|
||
|
};
|
||
|
|
||
|
if (useCache) {
|
||
|
computedStyleCache[uid] = nodeInfo;
|
||
|
}
|
||
|
|
||
|
return nodeInfo;
|
||
|
}
|
||
|
|
||
|
export const purgeCache = (uid) => {
|
||
|
delete computedStyleCache[uid];
|
||
|
};
|