/**
* A collapser/expander button for a styled element rendered as a block.
*/
class StyledCollapser extends TreeCollapser {
constructor() {
super();
this._onClickCollapsedContent = this.onClickCollapsedContent.bind(this);
this.unconfigure();
}
onClickCollapsedContent(event) {
event.preventDefault();
event.stopPropagation();
if (!this.disabled && event.detail === 1) {
this.setCollapsed(false, /*deep*/ false);
}
}
getTemplate() {
return StyledCollapser.TEMPLATE;
}
unconfigure() {
this._collapsibleView = null;
this._notCollapsibleHead = 0;
this._notCollapsibleFoot = 0;
this._collapsedContentAlign = null;
this._collapsedContent = null;
this._collapsedIconName = "collapsed-right";
this._expandedIconName = "expanded-down";
}
configure(options) {
this.unconfigure();
let collapsibleInfo = null;
let ancestor = this.parentElement;
while (ancestor !== null) {
collapsibleInfo = ancestor.getAttribute("data-collapsible");
if (collapsibleInfo !== null) {
this._collapsibleView = ancestor;
break;
}
ancestor = ancestor.parentElement;
}
let collapsibleUID = null;
if (this._collapsibleView !== null &&
(collapsibleUID =
NodeView.uidIfElementView(this._collapsibleView)) === null) {
this._collapsibleView = null;
collapsibleInfo = null;
}
let initiallyCollapsed = false;
if (collapsibleInfo !== null) {
let infoList = collapsibleInfo.split(';', 4);
if (infoList.length >= 3) {
initiallyCollapsed = (infoList[0] === "true");
let notCollapsibleHead = parseInt(infoList[1]);
if (!isNaN(notCollapsibleHead) && notCollapsibleHead >= 0) {
this._notCollapsibleHead = notCollapsibleHead;
}
let notCollapsibleFoot = parseInt(infoList[2]);
if (!isNaN(notCollapsibleFoot) && notCollapsibleFoot >= 0) {
this._notCollapsibleFoot = notCollapsibleFoot;
}
if (infoList.length >= 4) {
this._collapsedContentAlign = infoList[3];
if (this._collapsedContentAlign.length === 0) {
this._collapsedContentAlign = null;
}
// After collapsedContentAlign, it's collapsedContent, some
// HTML which may contain ';'.
let pos = -1;
for (let k = 0; k <= 3; ++k) {
pos = collapsibleInfo.indexOf(';', pos+1);
if (pos < 0) {
// Should not happen.
break;
}
}
if (pos > 0 && pos+1 < collapsibleInfo.length) {
this._collapsedContent =
collapsibleInfo.substring(pos+1);
}
}
}
}
// ---
if (options !== null) {
let optionList = options.split(';');
if (optionList.length >= 2) {
let collapsedIconName = optionList[0];
let expandedIconName = optionList[1];
if (collapsedIconName.length > 0 &&
(collapsedIconName in CSSIcon)) {
this._collapsedIconName = collapsedIconName;
}
if (expandedIconName.length > 0 &&
(expandedIconName in CSSIcon)) {
this._expandedIconName = expandedIconName;
}
}
}
// ---
if (this._collapsibleView === null) {
// Show a grayed icon.
this._iconSpan.textContent = CSSIcon[this._expandedIconName];
this.disabled = true;
} else {
this.setAttribute("for", collapsibleUID);
if (initiallyCollapsed) {
this._settingCollapsed = true;
this.setAttribute("collapsed", "collapsed");
this._settingCollapsed = false;
}
}
}
connectedCallback() {
this.configure(this.getAttribute("options"));
super.connectedCallback();
}
applyCollapsed(collapsed, deep) {
if (this._collapsibleView !== null) {
StyledCollapser.doApplyCollapsed(this._collapsibleView, collapsed,
deep);
}
}
static doApplyCollapsed(tree, collapsed, deep) {
let uid = null;
if (tree.hasAttribute("data-collapsible") &&
(uid = NodeView.uidIfElementView(tree)) !== null) {
let collapsers =
tree.querySelectorAll(`xxe-collapser2[for="${uid}"]`);
const count = collapsers.length;
if (count > 0) {
const noCollapsibleChildren =
(collapsers[0].getCollapsibleChildren(tree) === null);
for (let i = 0; i < count; ++i) {
let collapser = collapsers[i];
let expandIcon = collapser._collapsedIconName;
let collapseIcon = collapser._expandedIconName;
if (noCollapsibleChildren) {
expandIcon = collapseIcon = "pop-se";
}
collapser.setCollapsedAttribute(collapsed, CSSIcon,
expandIcon, collapseIcon);
}
collapsers[0].collapseView(tree, collapsed);
}
// Otherwise, no collapsers yet, which may happen
// (e.g. table without a caption yet).
}
// Otherwise, tree is not a collapsible element view.
// ---
if (deep) {
let child = tree.firstElementChild;
while (child !== null) {
StyledCollapser.doApplyCollapsed(child, collapsed, true);
child = child.nextElementSibling;
}
}
}
collapseView(collapsibleView, collapse) {
if (collapse) {
if (!collapsibleView.hasAttribute("data-collapsed")) {
collapsibleView.setAttribute("data-collapsed", "true");
this.doCollapseView(collapsibleView, true);
}
} else {
if (collapsibleView.hasAttribute("data-collapsed")) {
collapsibleView.removeAttribute("data-collapsed");
this.doCollapseView(collapsibleView, false);
}
}
}
doCollapseView(collapsibleView, collapse) {
let collapsibleChildren = this.getCollapsibleChildren(collapsibleView);
if (collapsibleChildren === null) {
return;
}
for (let child of collapsibleChildren) {
let style = child.getAttribute("style");
if (style === null) {
style = "";
}
if (collapse) {
style += ";display:none";
} else {
if (style.endsWith(";display:none")) {
style = style.substring(0, style.length - 13);
}
}
if (style.length === 0) {
child.removeAttribute("style");
} else {
child.setAttribute("style", style);
}
}
if (this._collapsedContent !== null) {
let lastCollapsibleChild =
collapsibleChildren[collapsibleChildren.length-1];
if (collapse) {
let placeholder = document.createElement("div");
placeholder.innerHTML = this._collapsedContent;
placeholder.onclick = this._onClickCollapsedContent;
const clsList = placeholder.classList;
clsList.add("xxe-collapsed-content");
for (let cls of lastCollapsibleChild.classList.values()) {
if (cls.startsWith("xxe-s")) {
clsList.add(cls);
}
}
if (this._collapsedContentAlign !== null) {
// May be non-effective depending on the collapsedContent
// HTML.
placeholder.style.textAlign = this._collapsedContentAlign;
}
lastCollapsibleChild.parentNode.insertBefore(
placeholder, lastCollapsibleChild.nextElementSibling);
} else {
let placeholder = lastCollapsibleChild.nextElementSibling;
if (placeholder !== null &&
placeholder.classList.contains("xxe-collapsed-content")) {
placeholder.parentNode.removeChild(placeholder);
}
}
}
}
getCollapsibleChildren(collapsibleView) {
// Generated content being display:block or display:marker before or
// after actual content is NOT considered to be collapsible children.
//
// Generated content contained inside actual content is considered
// to be collapsible children.
//
// This seems to be the behavior of desktop app and how
// not-collapsible-head, not-collapsible-foot are interpreted.
const collapsibleContent = NodeView.getContent(collapsibleView);
let collapsibleChildren = [];
let child = collapsibleContent.firstElementChild;
while (child !== null) {
if (this._collapsedContent !== null &&
child.classList.contains("xxe-collapsed-content")) {
// Always added after last collapsible child.
child = child.nextElementSibling;
continue;
}
collapsibleChildren.push(child);
child = child.nextElementSibling;
}
const start = this._notCollapsibleHead;
const end = collapsibleChildren.length - this._notCollapsibleFoot;
if (end <= start) {
return null;
} else {
if (end < collapsibleChildren.length) {
collapsibleChildren.splice(end);
}
if (start > 0) {
collapsibleChildren.splice(0, start);
}
return ((collapsibleChildren.length === 0)?
null : collapsibleChildren);
}
}
}
StyledCollapser.TEMPLATE = document.createElement("template");
StyledCollapser.TEMPLATE.innerHTML = `
<style>
.collapser {
font-family: "xxe-css-icons";
font-size: 14px;
font-style: normal;
font-weight: normal;
text-decoration: none;
/*color is inherited*/
vertical-align: 2px;
cursor: default;
}
:host([disabled]) {
color: #A6A6A6;
}
</style>
<span class="collapser"></span>
`;
window.customElements.define("xxe-collapser2", StyledCollapser);