Skip to content

Commit

Permalink
Add insertAdjacentElement
Browse files Browse the repository at this point in the history
  • Loading branch information
edoardocavazza committed Sep 22, 2023
1 parent e4d7b1a commit de9c1e8
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/guide/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Thanks to the programmatic access to the realm children, it is possibile to wrap
render(
realm.root,
html`<ul>
${real.childNodes.map((child) => html` <li>${child}</li> `)}
${realm.childNodes.map((child) => html` <li>${child}</li> `)}
</ul>`
);
```
Expand Down
4 changes: 4 additions & 0 deletions docs/guide/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ Replace a node with other nodes in the realm.

Insert nodes before a reference node in the realm.

#### `realm.insertAdjacentElement(position, node)`

Insert node at given position in the realm.

#### `realm.childNodesBySlot(name?: string)`

Get the child nodes of the realm filtered by slot name. If no name is provided, it will provide children with undeclared slot.
51 changes: 51 additions & 0 deletions src/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const {
after,
before,
replaceWith,
insertAdjacentElement,
} = Object.getOwnPropertyDescriptors(ElementPrototype);

defineProperty(ElementPrototype, 'append', {
Expand Down Expand Up @@ -202,3 +203,53 @@ defineProperty(ElementPrototype, 'replaceWith', {
return parentRealm.replaceWith(this, ...nodes);
},
});

defineProperty(ElementPrototype, 'insertAdjacentElement', {
/**
* @this {Element}
* @param {'beforebegin'|'afterbegin'|'beforeend'|'afterend'} position
* @param {ChildNode} node
*/
value(position, node) {
const realm = getRealm(this);
const parentRealm = getParentRealm(this);
switch (position) {
case 'beforebegin':
if (!parentRealm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (insertAdjacentElement).value.apply(
this,
position,
node
);
}
return parentRealm.insertBefore(this, node);
case 'afterend':
if (!parentRealm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (insertAdjacentElement).value.apply(
this,
position,
node
);
}
return parentRealm.insertBefore(this.nextSibling, node);
case 'afterbegin':
if (!realm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (insertAdjacentElement).value.apply(
this,
position,
node
);
}
return realm.prepend(node);
case 'beforeend':
if (!realm) {
return /** @type {import('./utils.js').ValueDescriptor} */ (insertAdjacentElement).value.apply(
this,
position,
node
);
}
return realm.append(node);
}
},
});
81 changes: 74 additions & 7 deletions src/Realm.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function attachRealm(node) {
/**
* Get the realm instance for a node.
* @param {Node & { [REALM_SYMBOL]?: Realm }} node The root node.
* @returns The realm instance or null.
* @returns {Realm|null} The realm instance or null.
*/
export function getRealm(node) {
const realm = node[REALM_SYMBOL] ?? null;
Expand All @@ -48,12 +48,21 @@ export function getParentRealm(node) {
return null;
}

/**
* Get the owner realm instance for a node.
* @param {Node & { [REALM_PARENT_SYMBOL]?: Realm }} node The child node.
* @returns The owner realm instance or null.
*/
function getOwnerRealm(node) {
return node[REALM_PARENT_SYMBOL] ?? null;
}

/**
* Set the parent realm instance for a node.
* @param {Node & { [REALM_PARENT_SYMBOL]?: Realm | null }} node The child node.
* @param {Realm | null} realm The parent realm instance.
*/
export function setParentRealm(node, realm) {
function setParentRealm(node, realm) {
node[REALM_PARENT_SYMBOL] = realm;
}

Expand Down Expand Up @@ -168,6 +177,23 @@ export class Realm {
return this._open;
}

/**
* Get the closest realm ancestor of a node.
* @returns {Realm | null} A realm or null.
*/
get ownerRealm() {
let parentNode = this._node.parentNode;
while (parentNode) {
const realm = getRealm(parentNode);
if (realm) {
return realm;
}
parentNode = parentNode.parentNode;
}

return null;
}

/**
* Initialize the realm.
*/
Expand All @@ -176,7 +202,9 @@ export class Realm {

this._childNodes.forEach((node) => {
node.remove();
setParentRealm(node, this);
if (!getOwnerRealm(node)) {
setParentRealm(node, this);
}
});

this._notifyUpdate();
Expand All @@ -198,19 +226,35 @@ export class Realm {
this._callbacks.delete(callback);
}

/**
* Open the realm.
* When using this method, you must call `dangerouslyClose` when you are done,
* or things will get unstable.
*/
dangerouslyOpen() {
this._open = true;
}

/**
* Close the realm.
*/
dangerouslyClose() {
this._open = false;
}

/**
* Request an update of the realm.
* @param {Function} callback The callback to invoke.
*/
requestUpdate(callback) {
this._open = true;
this.dangerouslyOpen();
try {
callback();
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
this._open = false;
this.dangerouslyClose();
}

/**
Expand Down Expand Up @@ -268,7 +312,15 @@ export class Realm {
} else if (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
this._importNodes(Array.from(node.childNodes), acc);
} else {
setParentRealm(node, this);
const ownerRealm = getOwnerRealm(node);
if (ownerRealm) {
if (!ownerRealm.contains(this)) {
ownerRealm.remove(/** @type {ChildNode} */ (node));
setParentRealm(node, this);
}
} else {
setParentRealm(node, this);
}
acc.push(/** @type {ChildNode} */ (node));
}
return acc;
Expand Down Expand Up @@ -325,7 +377,9 @@ export class Realm {
nodes.forEach((child) => {
const io = this._childNodes.indexOf(child);
if (io !== -1) {
setParentRealm(child, null);
if (getOwnerRealm(child) === this) {
setParentRealm(child, null);
}
this._childNodes.splice(io, 1);
} else {
throw new Error(
Expand Down Expand Up @@ -468,6 +522,10 @@ export class Realm {
*/
childNodesBySlot(name = null) {
return this.childNodes.filter((child) => {
if (getOwnerRealm(child) !== this) {
// collect nodes from other realms
return !name;
}
if (child.nodeType !== 1 /** Node.ELEMENT_NODE */) {
// collect non-element nodes only if the slot is unnamed
return !name;
Expand All @@ -477,4 +535,13 @@ export class Realm {
return slotName === name;
});
}

/**
* Check if a realm is contained in this realm.
* @param {Realm} realm The child realm.
* @returns {boolean} Whether the realm is contained in this realm.
*/
contains(realm) {
return this.root.contains(realm.node);
}
}

0 comments on commit de9c1e8

Please sign in to comment.