@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
161 lines (139 loc) • 3.64 kB
JavaScript
/**
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact Volker Schukai.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import { assembleMethodSymbol } from "../../dom/customelement.mjs";
import { Popper } from "../layout/popper.mjs";
export { ContextBase };
/**
* @private
* @type {symbol}
*/
const slotElementSymbol = Symbol("slotElement");
/**
* @private
* @type {symbol}
*/
const contentObserverSymbol = Symbol("contentObserver");
/**
* @private
* @type {symbol}
*/
const slotChangeHandlerSymbol = Symbol("slotChangeHandler");
/**
* @private
* @type {symbol}
*/
const iconElementSymbol = Symbol("iconElement");
/**
* Base class for context popper controls that depend on slot content.
*/
class ContextBase extends Popper {
/**
* @return {Object}
*/
get defaults() {
return Object.assign({}, super.defaults, {
features: Object.assign({}, super.defaults.features, {
showIconWithoutContent: false,
}),
});
}
/**
* @private
* @return {void}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initContentObserver.call(this);
updateContentState.call(this);
}
/**
* @return {void}
*/
disconnectedCallback() {
super.disconnectedCallback();
disconnectContentObserver.call(this);
}
}
/**
* @private
* @return {void}
*/
function initContentObserver() {
this[slotElementSymbol] = this.shadowRoot?.querySelector("slot") || null;
this[iconElementSymbol] =
this.shadowRoot?.querySelector("[data-monster-role=button] svg") || null;
this[slotChangeHandlerSymbol] = () => updateContentState.call(this);
if (this[slotElementSymbol]) {
this[slotElementSymbol].addEventListener(
"slotchange",
this[slotChangeHandlerSymbol],
);
}
this[contentObserverSymbol] = new MutationObserver(() => {
updateContentState.call(this);
});
this[contentObserverSymbol].observe(this, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
});
}
/**
* @private
* @return {void}
*/
function disconnectContentObserver() {
if (this[slotElementSymbol] && this[slotChangeHandlerSymbol]) {
this[slotElementSymbol].removeEventListener(
"slotchange",
this[slotChangeHandlerSymbol],
);
}
if (this[contentObserverSymbol] instanceof MutationObserver) {
this[contentObserverSymbol].disconnect();
}
}
/**
* @private
* @return {boolean}
*/
function hasContent() {
for (const node of Array.from(this.childNodes)) {
if (node.nodeType === Node.ELEMENT_NODE) {
return true;
}
if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {
return true;
}
}
return false;
}
/**
* @private
* @return {void}
*/
function updateContentState() {
const contentAvailable = hasContent.call(this);
const showIconWithoutContent =
this.getOption("features.showIconWithoutContent", false) === true;
this.setOption("disabled", !contentAvailable);
if (!contentAvailable) {
this.hideDialog();
}
if (this[iconElementSymbol]) {
const showIcon = contentAvailable || showIconWithoutContent;
this[iconElementSymbol].classList.toggle("hidden", !showIcon);
}
}