Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime): add slotchange event and assignedNodes / assignedElements methods for scoped: true slots #6151

Merged
merged 12 commits into from
Feb 19, 2025
2 changes: 1 addition & 1 deletion src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,7 +1440,7 @@ export interface RenderNode extends HostElement {

/**
* Node reference:
* This is a reference for a original location node
* This is a reference from an original location node
* back to the node that's been moved around.
*/
['s-nr']?: PatchedSlotNode | RenderNode;
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/client-hydrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
TEXT_NODE_ID,
VNODE_FLAGS,
} from './runtime-constants';
import { addSlotRelocateNode } from './slot-polyfill-utils';
import { addSlotRelocateNode, patchSlotNode } from './slot-polyfill-utils';
import { newVNode } from './vdom/h';

/**
Expand Down Expand Up @@ -615,6 +615,7 @@ function addSlot(

// attempt to find any mock slotted nodes which we'll move later
addSlottedNodes(slottedNodes, slotId, slotName, node, shouldMove ? parentNodeId : childVNode.$hostId$);
patchSlotNode(node);

if (shouldMove) {
// Move slot comment node (to after any other comment nodes)
Expand Down
61 changes: 28 additions & 33 deletions src/runtime/dom-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { supportsShadow } from '@platform';
import type * as d from '../declarations';
import {
addSlotRelocateNode,
getHostSlotChildNodes,
dispatchSlotChangeEvent,
findSlotFromSlottedNode,
getHostSlotNodes,
getSlotChildSiblings,
getSlotName,
getSlottedChildNodes,
updateFallbackSlotVisibility,
Expand Down Expand Up @@ -90,22 +92,18 @@ export const patchCloneNode = (HostElementPrototype: HTMLElement) => {
*/
export const patchSlotAppendChild = (HostElementPrototype: any) => {
HostElementPrototype.__appendChild = HostElementPrototype.appendChild;

HostElementPrototype.appendChild = function (this: d.RenderNode, newChild: d.RenderNode) {
const slotName = (newChild['s-sn'] = getSlotName(newChild));
const slotNode = getHostSlotNodes((this as any).__childNodes || this.childNodes, this.tagName, slotName)[0];
const { slotName, slotNode } = findSlotFromSlottedNode(newChild, this);
if (slotNode) {
addSlotRelocateNode(newChild, slotNode);

const slotChildNodes = getHostSlotChildNodes(slotNode, slotName);
const slotChildNodes = getSlotChildSiblings(slotNode, slotName);
const appendAfter = slotChildNodes[slotChildNodes.length - 1];

const parent = intrnlCall(appendAfter, 'parentNode') as d.RenderNode;
let insertedNode: d.RenderNode;
if (parent.__insertBefore) {
insertedNode = parent.__insertBefore(newChild, appendAfter.nextSibling);
} else {
insertedNode = parent.insertBefore(newChild, appendAfter.nextSibling);
}
const parent = internalCall(appendAfter, 'parentNode') as d.RenderNode;
const insertedNode: d.RenderNode = internalCall(parent, 'insertBefore')(newChild, appendAfter.nextSibling);
dispatchSlotChangeEvent(slotNode);

// Check if there is fallback content that should be hidden
updateFallbackSlotVisibility(this);
Expand Down Expand Up @@ -155,20 +153,18 @@ export const patchSlotPrepend = (HostElementPrototype: HTMLElement) => {
if (typeof newChild === 'string') {
newChild = this.ownerDocument.createTextNode(newChild) as unknown as d.RenderNode;
}
const slotName = (newChild['s-sn'] = getSlotName(newChild));
const childNodes = (this as any).__childNodes || this.childNodes;
const slotName = (newChild['s-sn'] = getSlotName(newChild)) || '';
const childNodes = internalCall(this, 'childNodes');
const slotNode = getHostSlotNodes(childNodes, this.tagName, slotName)[0];
if (slotNode) {
addSlotRelocateNode(newChild, slotNode, true);
const slotChildNodes = getHostSlotChildNodes(slotNode, slotName);
const slotChildNodes = getSlotChildSiblings(slotNode, slotName);
const appendAfter = slotChildNodes[0];
const parent = intrnlCall(appendAfter, 'parentNode') as d.RenderNode;

if (parent.__insertBefore) {
return parent.__insertBefore(newChild, intrnlCall(appendAfter, 'nextSibling'));
} else {
return parent.insertBefore(newChild, intrnlCall(appendAfter, 'nextSibling'));
}
const parent = internalCall(appendAfter, 'parentNode') as d.RenderNode;
const toReturn = internalCall(parent, 'insertBefore')(newChild, internalCall(appendAfter, 'nextSibling'));
dispatchSlotChangeEvent(slotNode);
return toReturn;
}

if (newChild.nodeType === 1 && !!newChild.getAttribute('slot')) {
Expand Down Expand Up @@ -263,8 +259,7 @@ const patchInsertBefore = (HostElementPrototype: HTMLElement) => {
newChild: T,
currentChild: d.RenderNode | null,
) {
const slotName = (newChild['s-sn'] = getSlotName(newChild));
const slotNode = getHostSlotNodes(this.__childNodes, this.tagName, slotName)[0];
const { slotName, slotNode } = findSlotFromSlottedNode(newChild, this);
const slottedNodes = this.__childNodes ? this.childNodes : getSlottedChildNodes(this.childNodes);

if (slotNode) {
Expand All @@ -286,13 +281,10 @@ const patchInsertBefore = (HostElementPrototype: HTMLElement) => {
// current child ('slot before' node) is 'in' the same slot
addSlotRelocateNode(newChild, slotNode);

const parent = intrnlCall(currentChild, 'parentNode') as d.RenderNode;
if (parent.__insertBefore) {
// the parent is a patched component, so we need to use the internal method
parent.__insertBefore(newChild, currentChild);
} else {
parent.insertBefore(newChild, currentChild);
}
const parent = internalCall(currentChild, 'parentNode') as d.RenderNode;
internalCall(parent, 'insertBefore')(newChild, currentChild);

dispatchSlotChangeEvent(slotNode);
}
return;
}
Expand Down Expand Up @@ -432,7 +424,7 @@ export const patchChildSlotNodes = (elm: HTMLElement) => {
* @param node the slotted node to be patched
*/
export const patchSlottedNode = (node: Node) => {
if (!node || (node as any).__nextSibling || !globalThis.Node) return;
if (!node || (node as any).__nextSibling !== undefined || !globalThis.Node) return;

patchNextSibling(node);
patchPreviousSibling(node);
Expand Down Expand Up @@ -595,10 +587,13 @@ function patchHostOriginalAccessor(
*
* @returns the original accessor or method of the node
*/
function intrnlCall<T extends d.RenderNode, P extends keyof d.RenderNode>(node: T, method: P): T[P] {
export function internalCall<T extends d.RenderNode, P extends keyof d.RenderNode>(node: T, method: P): T[P] {
if ('__' + method in node) {
return node[('__' + method) as keyof d.RenderNode] as T[P];
const toReturn = node[('__' + method) as keyof d.RenderNode] as T[P];
if (typeof toReturn !== 'function') return toReturn;
return toReturn.bind(node) as T[P];
} else {
return node[method];
if (typeof node[method] !== 'function') return node[method];
return node[method].bind(node) as T[P];
}
}
Loading
Loading