/*
----------------------------------------------------------------------------
GENERATED HTML EXPECTATIONS:
Element tree view:
data-e="" id=UID
+ data-be=UID (contains an <xxe-collapser>)
+ CHILDREN
+ data-ae=UID
----------------------------------------------------------------------------
*/
/**
* A collapser/expander button for an element tree view.
*/
class TreeCollapser extends HTMLElement {
constructor() {
super();
this._settingCollapsed = false;
let shadow = this.attachShadow({mode: "open"});
shadow.appendChild(this.getTemplate().content.cloneNode(true));
this._iconSpan = shadow.lastElementChild;
const blockEvent = (event) => {
XUI.Util.consumeEvent(event);
};
for (const eventName of ["mousedown", "mousemove", "mouseup",
"auxclick"]) {
this.addEventListener(eventName, blockEvent);
}
this.addEventListener("click", (e) => {
if (!this.disabled) {
e.preventDefault();
e.stopPropagation();
switch (e.detail) {
case 1: // First click.
this.setCollapsed(!this.collapsed, /*deep*/ false);
break;
case 2:
this.setCollapsed(this.collapsed, /*deep*/ true);
break;
}
}
});
this.addEventListener("contextmenu", (e) => {
if (!this.disabled) {
e.preventDefault();
e.stopPropagation();
const menu = XUI.Menu.create([
{ text: "Collapse All", name: "collapseAll" },
{ text: "Expand All", name: "expandAll" }
]);
menu.addEventListener("menuitemselected", (menuEvent) => {
switch (menuEvent.xuiMenuItem.name) {
case "collapseAll":
this.setCollapsed(true, /*deep*/ true);
break;
case "expandAll":
this.setCollapsed(false, /*deep*/ true);
break;
}
});
menu.open([e.clientX, e.clientY]);
}
});
}
getTemplate() {
return TreeCollapser.TEMPLATE;
}
setCollapsed(collapsed, deep) {
this.applyCollapsed(collapsed, deep);
let docView = DOMUtil.lookupAncestorByTag(this, "xxe-document-view");
if (docView !== null) {
// Wait until the collapser menu is hidden before focusing the
// document view.
setTimeout(() => { docView.requestFocus(); }, 0 /*ms*/);
}
}
// -----------------------------------------------------------------------
// Custom element
// -----------------------------------------------------------------------
connectedCallback() {
this.applyCollapsed(this.collapsed, /*deep*/ false);
}
static get observedAttributes() {
return [ "collapsed" ];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (!this._settingCollapsed) {
this.applyCollapsed(newVal, /*deep*/ false);
}
}
// -----------------------------------------------------------------------
// API
// -----------------------------------------------------------------------
get disabled() {
return this.hasAttribute("disabled");
}
set disabled(val) {
if (val) {
this.setAttribute("disabled", "disabled");
} else {
this.removeAttribute("disabled");
}
}
get collapsed() {
return this.hasAttribute("collapsed");
}
set collapsed(val) {
this.applyCollapsed(val, /*deep*/ false);
}
applyCollapsed(collapsed, deep) {
this.setCollapsedAttribute(collapsed, XUI.StockIcon,
"plus-squared", "minus-squared");
// ---
let be = this.parentElement;
if (be !== null && be.hasAttribute("data-be")) {
let e = be.parentElement;
if (e !== null && e.hasAttribute("data-e")) {
let ag = this.findAttributeGroup(be);
if (ag !== null) {
this.setHidden(ag, collapsed);
}
// ---
let sibling = be.nextSibling;
while (sibling !== null) {
if (sibling.nodeType === Node.ELEMENT_NODE &&
!sibling.hasAttribute("data-ae")) {
this.setHidden(sibling, collapsed);
if (deep) {
let collapser = this.findCollapser(sibling);
if (collapser !== null) {
collapser.applyCollapsed(collapsed, true);
}
}
}
sibling = sibling.nextSibling;
}
}
}
}
setCollapsedAttribute(collapsed,
iconSet, expandIconName, collapseIconName) {
if (collapsed) {
if (!this.hasAttribute("collapsed")) {
this._settingCollapsed = true;
// JS bug? hasAttribute returns false for collapsed="".
this.setAttribute("collapsed", "collapsed");
this._settingCollapsed = false;
}
this._iconSpan.textContent = iconSet[expandIconName];
} else {
if (this.hasAttribute("collapsed")) {
this._settingCollapsed = true;
this.removeAttribute("collapsed");
this._settingCollapsed = false;
}
this._iconSpan.textContent = iconSet[collapseIconName];
}
}
setHidden(element, hide) {
if (hide) {
if (!element.classList.contains("xxe-collapsed")) {
element.classList.add("xxe-collapsed");
}
} else {
if (element.classList.contains("xxe-collapsed")) {
element.classList.remove("xxe-collapsed");
}
}
}
findAttributeGroup(element) {
let node = element.firstChild;
while (node !== null) {
if (node.nodeType === Node.ELEMENT_NODE &&
node.classList.contains("xxe-ag")) {
return node;
}
node = node.nextSibling;
}
return null;
}
findCollapser(element) {
if (element !== null &&
element.nodeType === Node.ELEMENT_NODE &&
element.hasAttribute("data-e")) {
let firstChild = element.firstChild;
if (firstChild !== null &&
firstChild.nodeType === Node.ELEMENT_NODE &&
firstChild.hasAttribute("data-be")) {
firstChild = firstChild.firstChild;
if (firstChild !== null &&
firstChild.nodeType === Node.ELEMENT_NODE &&
firstChild.localName === "xxe-collapser") {
return firstChild;
}
}
}
return null;
}
}
TreeCollapser.TEMPLATE = document.createElement("template");
TreeCollapser.TEMPLATE.innerHTML = `
<style>
.collapser {
display: inline-block;
width: 14px;
/* Note that inheritable styles (e.g. font-style) are also inherited by
the Shadow DOM. */
font-family: "xui-stock-icons";
font-size: 12px;
font-style: normal;
font-weight: normal;
text-decoration: none;
color: gray;
cursor: default;
}
</style>
<span class="collapser"></span>
`;
// The :host{display:inline} default is OK.
window.customElements.define("xxe-collapser", TreeCollapser);