Re: [whatwg/dom] Proposal: a DocumentFragment whose nodes do not get removed once inserted (#736)

DeepDoge left a comment (whatwg/dom#736)

@rniwa 
> But the devil is in the details. Suppose we had an element e_1 and NodeGroup (the new node type) was inserted under e_1. Then e_1.childNodes[0] will return the NodeGroup but what happens to e_1.children[0]. Would it return the first element child under NodeGroup? Or would it return undefined because there is no direct child of e_1 which is an element. What happens if NodeGroup had another NodeGroup as a direct child. Would we be "unwrapping" nested NodeGroups until we hit a real non-NodeGroup Node? In that case, this proposal is very much similar to DOM Part proposal we made. The only difference is whether NodeGroup appears as an actual Node or not.

No it's not that complex, `NodeGroup`, is a `ChildNode` like any other `ChildNode`, they can be nested, within each-other. `NodeGroups` appear in the `childNodes` like any other `ChildNode`. It's literally a `ChildNode & ParentNode`, acts exactly as you expected, you can discover them while tree-walking like any other node. Only difference is, it's transparent to query selectors, that's all.

> FWIW, there is little to no appetite for changing the HTML parser (due to XSS concerns, etc...) so introducing a new element like <group> which can appear anywhere in HTML is a non-starter.

Also this `<group>` idea made me think.

When it comes to something like `<group>`, the believe, it has nothing to do with HTML or HTML parsing—it's about querying the DOM. Instead of introducing a new tag, we could have an attribute similar to `inert` that makes query selectors skip the element, effectively making it transparent. Attributes are more modular than new element types.

This new attribute would simply:  
- Act exactly like an `<div>` with `display: contents`, no difference, inheriting all its existing gotchas as well.  
- And It would be transparent to query selectors in both JS and CSS.

That's it. Nothing overengineered or introducing entirely new behavior.

If not an attribute, an alternative could be defining a list of tag names to be transparent in the `<head>` via a `<meta>` tag. But I think an attribute would offer more flexibility, and would be more modular.

The advantage of using `Element`(s) would be, it would benefit from all existing (or future) `Element`/`HTMLElement` features, including Custom Elements and possibly [WICG#1029 - Custom Attributes](https://github.com/WICG/webcomponents/issues/1029).  

This can't be done in user-land without being stuck in a framework, because existing methods break when you mutate the DOM without the framework.

An example use case:
<details>

<summary> Code </summary>

```ts
class SignalElement<T> extends HTMLElement {
    static {
        customElements.define("x-signal", this)
    }
    
    #signal: Signal<T>
    constructor(signal: Signal<T>) {
        this.#signal = signal
        this.setAttribute(/** transparent attribute **/, "")
        // or there can be a property for it as well
        this./** transparent property **/ = true
    }
    
    #signalUnsub: { (): void } | undefined | null
    connectedCallback() {
        #signalUnsub = #signal.subscribe((value) => this.replaceChildren(toChild(value)), { immediate: true })
    }

    disconnectedCallback() {
        #signalUnsub?.()
        #signalUnsub = null
    }
}

function toChild(value: unknown): Node | string {
    if (value instanceof Node) {
        return value
    }

    if (Array.isArray(value)) {
        const fragment = document.createDocumentFragment()
        fragment.append(...value.map(toChild))
        return fragment
    }

    if (/** is Signal **/) {
        return new SignalElement(value)
    }

    return String(value)
}

const currentOrg = new Signal.State<Org>(/** ... **/)

type Tab = /** ... **/
class Org extends HTMLElement {
   static {
      customElements.define('x-org', this)
   }

   constructor(org: Org) {
      // ...
      const currentTab = new Signal.Computed<Tab>(/** ... **/)
      // ...
      this.replaceChildren(toChild(new Signal.Computed(() => this.#Tab(currentTab), [currentTab])))
   }

   #Tab(tab: Tab) {
      // Render tab
   }
}

const orgRender = new Signal.Computed(() => new Org(currentOrg.get()), [currentOrg])

document.body.replaceChildren(toChild(orgRender))
```

</details>

Not only signals but also many things would benefit from having a transparent wrapper like this, such as `<x-if>` or partial boosting of the document. Since it's just an attribute or meta tag, it’s also easier to use it in HTML as well.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/736#issuecomment-2757190297
You are receiving this because you are subscribed to this thread.

Message ID: <whatwg/dom/issues/736/2757190297@github.com>

Received on Thursday, 27 March 2025 08:37:37 UTC