@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
219 lines (190 loc) • 5.08 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 {
resolveClippingBoundaryElement,
resolveParentPopperContentBoundary,
} from "./util/floating-ui.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, {
popper: Object.assign({}, super.defaults.popper, {
contentOverflow: "visible",
}),
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);
}
/**
* Context poppers should escape ancestor clipping containers when possible.
*
* @return {object}
*/
resolvePopperOptions() {
const options = super.resolvePopperOptions();
const { controlElement, popperElement } = getPopperElements.call(this);
const parentPopperBoundary = resolveParentPopperContentBoundary(
controlElement,
popperElement,
);
if (
resolveClippingBoundaryElement(controlElement, popperElement) ||
parentPopperBoundary
) {
options.strategy = "fixed";
}
return options;
}
/**
* Nested context poppers inside another popper need bounded height.
*
* @return {string}
*/
resolveContentOverflowMode() {
const configuredMode = super.resolveContentOverflowMode();
if (configuredMode !== "visible") {
return configuredMode;
}
const { controlElement, popperElement } = getPopperElements.call(this);
if (resolveParentPopperContentBoundary(controlElement, popperElement)) {
return "both";
}
return configuredMode;
}
}
/**
* @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,
});
}
function getPopperElements() {
return {
controlElement:
this.shadowRoot?.querySelector('[data-monster-role="control"]') || null,
popperElement:
this.shadowRoot?.querySelector('[data-monster-role="popper"]') || null,
};
}
/**
* @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);
}
}