Skip to content
Snippets Groups Projects
Select Git revision
  • 32d43486403593de2d98457a874d36f6b2e88c3e
  • master default protected
2 results

FileInput.js

Blame
  • util.mjs 6.76 KiB
    /**
     * Copyright schukai GmbH and contributors 2023. All Rights Reserved.
     * Node module: @schukai/monster
     * This file is licensed under the AGPLv3 License.
     * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
     */
    
    import { getGlobal } from "../types/global.mjs";
    import { validateString } from "../types/validate.mjs";
    
    export { getDocument, getWindow, getDocumentFragmentFromString, findElementWithIdUpwards, getContainingDocument };
    
    /**
     * This method fetches the document object
     *
     * In nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom).
     *
     * ```
     * import {JSDOM} from "jsdom"
     * if (typeof window !== "object") {
     *    const {window} = new JSDOM('', {
     *        url: 'http://example.com/',
     *        pretendToBeVisual: true
     *    });
     *
     *    [
     *        'self',
     *        'document',
     *        'Document',
     *        'Node',
     *        'Element',
     *        'HTMLElement',
     *        'DocumentFragment',
     *        'DOMParser',
     *        'XMLSerializer',
     *        'NodeFilter',
     *        'InputEvent',
     *        'CustomEvent'
     *    ].forEach(key => (getGlobal()[key] = window[key]));
     * }
     * ```
     *
     * @returns {object}
     * @license AGPLv3
     * @since 1.6.0
     * @copyright schukai GmbH
     * @memberOf Monster.DOM
     * @throws {Error} not supported environment
     */
    function getDocument() {
        let document = getGlobal()?.["document"];
        if (typeof document !== "object") {
            throw new Error("not supported environment");
        }
    
        return document;
    }
    
    /**
     * This method fetches the window object
     *
     * In nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom).
     *
     * ```
     * import {JSDOM} from "jsdom"
     * if (typeof window !== "object") {
     *    const {window} = new JSDOM('', {
     *        url: 'http://example.com/',
     *        pretendToBeVisual: true
     *    });
     *
     *    getGlobal()['window']=window;
     *
     *    [
     *        'self',
     *        'document',
     *        'Document',
     *        'Node',
     *        'Element',
     *        'HTMLElement',
     *        'DocumentFragment',
     *        'DOMParser',
     *        'XMLSerializer',
     *        'NodeFilter',
     *        'InputEvent',
     *        'CustomEvent'
     *    ].forEach(key => (getGlobal()[key] = window[key]));
     * }
     * ```
     *
     * @returns {object}
     * @license AGPLv3
     * @since 1.6.0
     * @copyright schukai GmbH
     * @memberOf Monster.DOM
     * @throws {Error} not supported environment
     */
    function getWindow() {
        let window = getGlobal()?.["window"];
        if (typeof window !== "object") {
            throw new Error("not supported environment");
        }
    
        return window;
    }
    
    /**
     * This method fetches the document object
     *
     * In nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom).
     *
     * ```
     * import {JSDOM} from "jsdom"
     * if (typeof window !== "object") {
     *    const {window} = new JSDOM('', {
     *        url: 'http://example.com/',
     *        pretendToBeVisual: true
     *    });
     *
     *    [
     *        'self',
     *        'document',
     *        'Document',
     *        'Node',
     *        'Element',
     *        'HTMLElement',
     *        'DocumentFragment',
     *        'DOMParser',
     *        'XMLSerializer',
     *        'NodeFilter',
     *        'InputEvent',
     *        'CustomEvent'
     *    ].forEach(key => (getGlobal()[key] = window[key]));
     * }
     * ```
     *
     * @returns {DocumentFragment}
     * @license AGPLv3
     * @since 1.6.0
     * @copyright schukai GmbH
     * @memberOf Monster.DOM
     * @throws {Error} not supported environment
     * @throws {TypeError} value is not a string
     */
    function getDocumentFragmentFromString(html) {
        validateString(html);
    
        const document = getDocument();
        const template = document.createElement("template");
        template.innerHTML = html;
    
        return template.content;
    }
    
    /**
     * Recursively searches upwards from a given element to find an ancestor element
     * with a specified ID, considering both normal DOM and shadow DOM.
     *
     * @param {HTMLElement|ShadowRoot} element - The starting element or shadow root to search from.
     * @param {string} targetId - The ID of the target element to find.
     * @returns {HTMLElement|null} - The ancestor element with the specified ID, or null if not found.
     * @memberOf Monster.DOM
     * @since 3.29.0
     * @license AGPLv3
     * @copyright schukai GmbH
     */
    function findElementWithIdUpwards(element, targetId) {
        if (!element) {
            return null;
        }
    
        // Check if the current element has the target ID
        if (element.id === targetId) {
            return element;
        }
    
        // Search within the current element's shadow root, if it exists
        if (element.shadowRoot) {
            const target = element.shadowRoot.getElementById(targetId);
            if (target) {
                return target;
            }
        }
    
        // If the current element is the document.documentElement, search within the main document
        if (element === document.documentElement) {
            const target = document.getElementById(targetId);
            if (target) {
                return target;
            }
        }
    
        // If the current element is inside a shadow root, search its host's ancestors
        const rootNode = element.getRootNode();
        if (rootNode && rootNode instanceof ShadowRoot) {
            return findElementWithIdUpwards(rootNode.host, targetId);
        }
    
        // Otherwise, search the current element's parent
        return findElementWithIdUpwards(element.parentElement, targetId);
    }
    
    /**
     * @private
     * @param {HTMLElement} element
     * @returns {HTMLElement|null}
     */
    function traverseShadowRoots(element) {
        let currentRoot = element.shadowRoot;
        let currentParent = element.parentNode;
    
        while (
            currentParent &&
            currentParent.nodeType !== Node.DOCUMENT_NODE &&
            currentParent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
        ) {
            if (currentRoot && currentRoot.parentNode) {
                currentParent = currentRoot.parentNode;
                currentRoot = currentParent.shadowRoot;
            } else if (currentParent.parentNode) {
                currentParent = currentParent.parentNode;
                currentRoot = null;
            } else if (currentRoot && currentRoot.host && currentRoot.host.nodeType === Node.DOCUMENT_NODE) {
                currentParent = currentRoot.host;
                currentRoot = null;
            } else {
                currentParent = null;
                currentRoot = null;
            }
        }
    
        return currentParent;
    }
    
    /**
     * Recursively searches upwards from a given element to find an ancestor element
     *
     * @param {HTMLElement} element
     * @returns {*}
     * @throws {Error} Invalid argument. Expected an HTMLElement.
     * @memberOf Monster.DOM
     * @since 3.36.0
     */
    function getContainingDocument(element) {
        if (
            !element ||
            !(element instanceof HTMLElement || element instanceof element.ownerDocument.defaultView.HTMLElement)
        ) {
            throw new Error("Invalid argument. Expected an HTMLElement.");
        }
    
        return traverseShadowRoots(element) || null;
    }