/**
* Utility functions (static methods) used to implement the XUI module.
* <p>Part of the XUI module which, for now, has an undocumented API.
*/
export class Util {
// -----------------------------------------------------------------------
// Text
// -----------------------------------------------------------------------
static escapeHTML(text) {
return text.replaceAll('&', "&")
.replaceAll('<', "<")
.replaceAll('>', ">")
.replaceAll('"', """)
.replaceAll("'", "'"); // &apos, only as of HTML 5.0.
}
static shortenText(text, maxLength) {
if (maxLength < 4) {
maxLength = 4;
}
if (text.length > maxLength) {
const half = maxLength / 2;
text = text.substring(0, half - 1) + "\u2026" + // Ellipsis
text.substring(text.length - half);
}
return text;
}
static wordWrap(text, lineMaxLength, maxLines=-1) {
let wrapped = "";
let lineCount = 0;
let lines = text.split('\n', /*limit*/ maxLines); // -1 means: no limit.
for (let i = 0; i < lines.length; ++i) {
let line = lines[i];
if (i > 0) {
wrapped += '\n';
}
if (line.length <= lineMaxLength || line.indexOf(' ') < 0) {
wrapped += line;
++lineCount;
} else {
let lineLength = 0;
let words = line.split(' ');
for (let word of words) {
let wordLength = word.length;
if (lineLength + wordLength > lineMaxLength) {
if (lineLength > 0) {
wrapped += '\n';
lineLength = 0;
++lineCount;
if (maxLines > 0 && lineCount >= maxLines) {
break;
}
}
} else {
if (lineLength > 0) {
wrapped += ' ';
++lineLength;
}
}
wrapped += word;
lineLength += wordLength;
}
if (lineLength > 0) {
++lineCount;
}
}
if (maxLines > 0 && lineCount >= maxLines) {
break;
}
}
return wrapped;
}
// -----------------------------------------------------------------------
// DOM
// -----------------------------------------------------------------------
static pageContains(node) {
// Needed because document.body.contains(node) returns false if node
// is contained in a ShadowRoot.
let foundDoc = false;
while (node) {
if (node === document) {
foundDoc = true;
break;
}
const parent = node.parentNode;
if (parent instanceof DocumentFragment) {
// A ShadowRoot is a DocumentFragment having a host property.
node = parent.host;
} else {
node = parent;
}
}
return foundDoc;
}
static removeAllChildren(parent) {
let child = parent.firstChild;
while (child !== null) {
let next = child.nextSibling;
parent.removeChild(child);
child = next;
}
}
static addStylesheetLink(tree, css=null) {
let link = document.createElement("link");
link.setAttribute("rel", "stylesheet");
link.setAttribute("type", "text/css");
if (!css) {
css = "xui.css";
}
let url = new URL(css, import.meta.url);
link.setAttribute("href", url.toString());
tree.appendChild(link);
return link;
}
// -----------------------------------------------------------------------
// CSSOM
// -----------------------------------------------------------------------
static getPxProperty(element, propName) {
let propValue =
window.getComputedStyle(element).getPropertyValue(propName);
if (!propValue || !propValue.endsWith("px")) {
return NaN;
} else {
// Note that parseFloat would have ignored the trailing "px"!
return parseFloat(propValue.substring(0, propValue.length-2));
}
}
// -----------------------------------------------------------------------
// Events
// -----------------------------------------------------------------------
static consumeEvent(event) {
// Prevent default behavior if any.
event.preventDefault();
// Prevent capturing and bubbling
// (but, unlike, stopImmediatePropagation, NOT other listeners are
// attached to the same element for the same event type).
event.stopPropagation();
}
static modKey(event) {
return ((Util.PLATFORM_IS_MAC_OS && event.metaKey) ||
(!Util.PLATFORM_IS_MAC_OS && event.ctrlKey));
}
// -----------------------------------------------------------------------
// Dialogs
// -----------------------------------------------------------------------
static badTextField(textField) {
textField.select();
textField.focus();
}
static rememberDatalistItem(localStorageKey, value) {
let valueList = [];
let values = window.localStorage.getItem(localStorageKey);
if (values !== null) {
valueList = values.split('\n');
}
if (value) {
while (valueList.length >= 10) {
valueList.pop();
}
let index = valueList.indexOf(value);
if (index < 0) {
valueList.unshift(value);
} else if (index > 0) {
valueList.splice(index, 1);
valueList.unshift(value);
}
// Otherwise, already first item. Nothing to do.
}
if (valueList.length > 0) {
window.localStorage.setItem(localStorageKey, valueList.join('\n'));
} else {
window.localStorage.removeItem(localStorageKey);
}
return valueList;
}
static attachDatalist(textField, valueList, form) {
if (textField.hasAttribute("list") ||
!Array.isArray(valueList) || valueList.length === 0) {
// Nothing to do.
return;
}
let datalistId = Util.uid();
Util.appendDatalist(datalistId, valueList, form);
textField.setAttribute("list", datalistId);
}
static uid() {
return Date.now().toString(36) +
Math.random().toString(36).substring(2); /*Skip leading "0."*/
}
static appendDatalist(id, items, parent) {
let list = document.createElement("datalist");
list.id = id;
for (let item of items) {
let option = document.createElement("option");
option.value = item;
list.appendChild(option);
}
parent.appendChild(list);
return list;
}
static prependDatalistItem(list, item, maxItems=20) {
if (item === null || item.length === 0) {
return;
}
let option = list.firstElementChild;
if (option !== null) {
if (option.value === item) {
// Nothing to do.
return;
}
while (option !== null) {
if (option.value === item) {
list.removeChild(option);
break;
}
option = option.nextElementSibling;
}
}
// ---
option = document.createElement("option");
option.value = item;
list.insertBefore(option, list.firstElementChild);
if (maxItems < 2) {
maxItems = 2;
}
let itemCount = list.childElementCount;
while (itemCount > maxItems) {
list.removeChild(list.lastElementChild);
--itemCount;
}
}
// -----------------------------------------------------------------------
// Miscellaneous
// -----------------------------------------------------------------------
static intersects(r1, r2) {
return (r1.right > r2.left) && (r1.left < r2.right) &&
(r1.bottom > r2.top) && (r1.top < r2.bottom);
}
}
Util.PLATFORM_IS_MAC_OS = window.navigator.platform.match(/mac/i);