- From: Andrea Giammarchi <notifications@github.com>
- Date: Thu, 14 Nov 2024 08:17:09 -0800
- To: whatwg/dom <dom@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/dom/issues/736/2476840911@github.com>
OK, I went ahead and created this implementation which leaves just a few details to discuss but it works already great:
**persistent-fragment.js**
```js
import native from 'https://esm.run/custom-function/factory';
const OWNERSHIP = 'ownerFragment';
const { defineProperty, getPrototypeOf, hasOwn } = Object;
const children = ({ nodeType }) => nodeType === 1;
const isOwned = (self, node) => !hasOwn(node, OWNERSHIP) || node[OWNERSHIP] == self;
const asValueOf = node => node instanceof PersistentFragment ? node.valueOf() : node;
export default class PersistentFragment extends native(DocumentFragment) {
#childNodes;
constructor(...childNodes) {
super(document.createDocumentFragment());
this.#childNodes = childNodes.map(asOwnedNode, this);
super.append(...childNodes);
}
append(...childNodes) {
childNodes = childNodes.map(asOwnedNode, this);
if (this.#childNodes.length)
this.#childNodes.at(-1).after(...childNodes);
else
super.append(...childNodes);
this.#childNodes.push(...childNodes);
}
appendChild(childNode) {
const node = asOwnedNode.call(this, childNode);
if (this.#childNodes.length)
this.#childNodes.at(-1).after(node);
else
super.appendChild(node);
this.#childNodes.push(node);
}
insertBefore(childNode, ownedNode) {
if (ownedNode) {
if (isOwned(this, ownedNode)) {
ownedNode.before(asOwnedNode.call(this, childNode));
const i = this.#childNodes.indexOf(ownedNode);
this.#childNodes.splice(i, 0, childNode);
}
else throw new Error('Illegal operation');
}
else this.appendChild(childNode);
}
prepend(...childNodes) {
childNodes = childNodes.map(asOwnedNode, this);
if (this.#childNodes.length)
this.#childNodes.at(0).before(...childNodes);
else
super.prepend(...childNodes);
this.#childNodes.unshift(...childNodes);
}
removeChild(childNode) {
const i = this.#childNodes.indexOf(ownedNode);
if (i < 0) throw new Error('Illegal operation');
childNode.remove();
this.#childNodes.splice(i, 1);
}
replaceChildren(...childNodes) {
this.#childNodes.forEach(notOwnedAnymore, this);
this.#childNodes = childNodes.map(asOwnedNode, this);
super.replaceChildren(...this.#childNodes);
}
valueOf() {
if (this.#childNodes.at(0)?.parentNode !== this)
super.append(...this.#childNodes);
return this;
}
}
function asOwnedNode(childNode) {
if (!isOwned(this, childNode))
throw new Error('Illegal operation');
return defineProperty(childNode, OWNERSHIP, {
value: this,
configurable: true
});
}
function notOwnedAnymore(childNode) {
delete childNode[OWNERSHIP];
}
// patch globals
const [
{ prototype: E },
{ prototype: N },
{ prototype: PF },
{ prototype: CD },
] = [
Element,
Node,
PersistentFragment,
getPrototypeOf(Text),
];
for (const key of Reflect.ownKeys(PF)) {
if (key === 'constructor') continue;
const { [key]: value } = PF;
if (typeof value !== 'function') continue;
for (const proto of [N, E]) {
if (hasOwn(proto, key)) {
const native = proto[key];
defineProperty(proto, key, {
value(...args) {
return native.apply(this, args.map(asValueOf));
}
});
}
}
}
for (const key of ['after', 'before']) {
for (const proto of [CD, E]) {
const native = proto[key];
defineProperty(proto, key, {
value(...args) {
return native.apply(this, args.map(asValueOf));
}
});
}
}
```
**index.html**
```html
<!doctype html>
<script type="module">
import PersistentFragment from "./persistent-fragment.js";
const text = value => document.createTextNode(value);
const a = text('a');
const c = text('c');
const pf = new PersistentFragment(a, c);
const hr = document.createElement('hr');
document.body.append(pf, hr);
setTimeout(
() => {
pf.insertBefore(text('b'), c);
setTimeout(
() => {
hr.after(pf);
},
1000
);
},
1000
);
</script>
```
You would see `ac`, then `abc`, both before the `hr`, then you'll see the fragment moved *after* that `hr` with `abc` as resulting DOM content.
I think this simplification is superior to my original proposal as it answers tons of questions and simplifies everything that needs simplification around this topic and I believe it would be an awesome feature to have for any library author out there.
--
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/736#issuecomment-2476840911
You are receiving this because you are subscribed to this thread.
Message ID: <whatwg/dom/issues/736/2476840911@github.com>
Received on Thursday, 14 November 2024 16:17:13 UTC