cloneUp.js

import cloneNode from './cloneNode.js';

/**
 * Clones an element and its ancestor chain up to and including the first ancestor that matches the given selector. Optionally includes siblings of each cloned ancestor.
 * 
 * @param {Element} element - An Element to start cloning from
 * @param {string} selector - A String CSS selector that determines where to stop cloning ancestors
 * @param {Object} [options={}] - An Object with optional configuration:
 * - `siblings.exclude`: A CSS selector string to exclude matching sibling elements from being cloned
 * @returns {Element} The cloned element with its ancestor chain
 */

export default function cloneUp (el, selector, options={}) {
    const clone = cloneNode(el)
    
    if (el.matches(selector)) {
        return clone
    }
    
    if (!el.parentElement) {
        return clone
    }
    
    const parentClone = cloneUp(el.parentElement, selector)
    const shouldExclude = (node) => 
        options.siblings?.exclude && 
        node instanceof Element && 
        node.matches(options.siblings.exclude)
    
    // Clone previous siblings
    let cursor = el.previousSibling
    while (cursor) {
        if (!shouldExclude(cursor)) {
            parentClone.prepend(cloneNode(cursor, true))
        }
        cursor = cursor.previousSibling
    }
    
    // Add current element clone
    parentClone.append(clone)
    
    // Clone next siblings
    cursor = el.nextSibling
    while (cursor) {
        if (!shouldExclude(cursor)) {
            parentClone.append(cloneNode(cursor, true))
        }
        cursor = cursor.nextSibling
    }
    
    return clone
}