Skip to content

Commit

Permalink
fix(runtime): add root scope id to the user provided nested children …
Browse files Browse the repository at this point in the history
…as classname

#5749
  • Loading branch information
yigityuce committed May 10, 2024
1 parent 31259a5 commit f434429
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 33 deletions.
9 changes: 5 additions & 4 deletions src/runtime/dom-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CMP_FLAGS, HOST_FLAGS, NODE_TYPES } from '@utils/constants';

import type * as d from '../declarations';
import { PLATFORM_FLAGS } from './runtime-constants';
import { updateFallbackSlotVisibility } from './vdom/vdom-render';
import { insertBefore, updateFallbackSlotVisibility } from './vdom/vdom-render';

export const patchPseudoShadowDom = (
hostElementPrototype: HTMLElement,
Expand Down Expand Up @@ -47,6 +47,7 @@ export const patchCloneNode = (HostElementPrototype: HTMLElement) => {
's-nr',
's-si',
's-rf',
's-rsc',
];

for (; i < srcNode.childNodes.length; i++) {
Expand Down Expand Up @@ -84,7 +85,7 @@ export const patchSlotAppendChild = (HostElementPrototype: any) => {
if (slotNode) {
const slotChildNodes = getHostSlotChildNodes(slotNode, slotName);
const appendAfter = slotChildNodes[slotChildNodes.length - 1];
const insertedNode = appendAfter.parentNode.insertBefore(newChild, appendAfter.nextSibling);
const insertedNode = insertBefore(appendAfter.parentNode, newChild, appendAfter.nextSibling);

// Check if there is fallback content that should be hidden
updateFallbackSlotVisibility(this);
Expand Down Expand Up @@ -149,7 +150,7 @@ export const patchSlotPrepend = (HostElementPrototype: HTMLElement) => {

const slotChildNodes = getHostSlotChildNodes(slotNode, slotName);
const appendAfter = slotChildNodes[0];
return appendAfter.parentNode.insertBefore(newChild, appendAfter.nextSibling);
return insertBefore(appendAfter.parentNode, newChild, appendAfter.nextSibling);
}

if (newChild.nodeType === 1 && !!newChild.getAttribute('slot')) {
Expand Down Expand Up @@ -309,7 +310,7 @@ export const patchTextContent = (hostElementPrototype: HTMLElement): void => {
if (node['s-sn'] === '') {
const textNode = this.ownerDocument.createTextNode(value);
textNode['s-sn'] = '';
node.parentElement.insertBefore(textNode, node.nextSibling);
insertBefore(node.parentElement, textNode, node.nextSibling);
} else {
node.remove();
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/test/shadow.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ describe('shadow', () => {
<cmp-a class="hydrated sc-cmp-a-h">
<!---->
<div class="sc-cmp-a sc-cmp-a-s">
<span slot=\"start\">
<span class="sc-cmp-a" slot=\"start\">
Start
</span>
<span class='sc-cmp-a sc-cmp-a-s'>
Text
</span>
<div class='end sc-cmp-a sc-cmp-a-s'>
<span slot=\"end\">
<span class="sc-cmp-a" slot=\"end\">
End
</span>
</div>
Expand Down
77 changes: 55 additions & 22 deletions src/runtime/vdom/vdom-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,6 @@ const createElm = (oldParentVNode: d.VNode, newParentVNode: d.VNode, childIndex:
elm.classList.add((elm['s-si'] = scopeId));
}

if (BUILD.scoped) {
// to be able to style the deep nested scoped component from the root component,
// root component's scope id needs to be added to the child nodes since sass compiler
// adds scope id to the nested selectors during compilation phase
const rootScopeId =
newParentVNode.$elm$?.['s-rsc'] || newParentVNode.$elm$?.['s-si'] || newParentVNode.$elm$?.['s-sc'];

if (rootScopeId) {
elm['s-rsc'] = rootScopeId;
!elm.classList.contains(rootScopeId) && elm.classList.add(rootScopeId);
}
}

if (newVNode.$children$) {
for (i = 0; i < newVNode.$children$.length; ++i) {
// create the node
Expand Down Expand Up @@ -206,7 +193,7 @@ const relocateToHostRoot = (parentElm: Element) => {
for (const childNode of contentRefNode ? childNodeArray.reverse() : childNodeArray) {
// Only relocate nodes that were slotted in
if (childNode['s-sh'] != null) {
host.insertBefore(childNode, contentRefNode ?? null);
insertBefore(host, childNode, contentRefNode ?? null);

// Reset so we can correctly move the node around again.
childNode['s-sh'] = undefined;
Expand Down Expand Up @@ -237,7 +224,7 @@ const putBackInOriginalLocation = (parentElm: d.RenderNode, recursive: boolean)
const childNode = oldSlotChildNodes[i] as any;
if (childNode['s-hn'] !== hostTagName && childNode['s-ol']) {
// and relocate it back to it's original location
parentReferenceNode(childNode).insertBefore(childNode, referenceNode(childNode));
insertBefore(parentReferenceNode(childNode), childNode, referenceNode(childNode));

// remove the old original location comment entirely
// later on the patch function will know what to do
Expand Down Expand Up @@ -293,7 +280,7 @@ const addVnodes = (
childNode = createElm(null, parentVNode, startIdx, parentElm);
if (childNode) {
vnodes[startIdx].$elm$ = childNode as any;
containerElm.insertBefore(childNode, BUILD.slotRelocation ? referenceNode(before) : before);
insertBefore(containerElm, childNode, BUILD.slotRelocation ? referenceNode(before) : before);
}
}
}
Expand Down Expand Up @@ -490,7 +477,7 @@ const updateChildren = (
// `parentElm`. Luckily, `Node.nextSibling` will return `null` if there
// aren't any siblings, and passing `null` to `Node.insertBefore` will
// append it to the children of the parent element.
parentElm.insertBefore(oldStartVnode.$elm$, oldEndVnode.$elm$.nextSibling as any);
insertBefore(parentElm, oldStartVnode.$elm$, oldEndVnode.$elm$.nextSibling as any);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (isSameVnode(oldEndVnode, newStartVnode, isInitialRender)) {
Expand Down Expand Up @@ -518,7 +505,7 @@ const updateChildren = (
// can move the element for `oldEndVnode` _before_ the element for
// `oldStartVnode`, leaving `oldStartVnode` to be reconciled in the
// future.
parentElm.insertBefore(oldEndVnode.$elm$, oldStartVnode.$elm$);
insertBefore(parentElm, oldEndVnode.$elm$, oldStartVnode.$elm$);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
Expand Down Expand Up @@ -569,9 +556,9 @@ const updateChildren = (
if (node) {
// if we created a new node then handle inserting it to the DOM
if (BUILD.slotRelocation) {
parentReferenceNode(oldStartVnode.$elm$).insertBefore(node, referenceNode(oldStartVnode.$elm$));
insertBefore(parentReferenceNode(oldStartVnode.$elm$), node, referenceNode(oldStartVnode.$elm$));
} else {
oldStartVnode.$elm$.parentNode.insertBefore(node, oldStartVnode.$elm$);
insertBefore(oldStartVnode.$elm$.parentNode, node, oldStartVnode.$elm$);
}
}
}
Expand Down Expand Up @@ -924,6 +911,52 @@ export const nullifyVNodeRefs = (vNode: d.VNode) => {
}
};

/**
* Inserts a node before a reference node as a child of a specified parent node.
* Additionally, adds parent element's scope id as class to the new node.
*
* @param parent parent node
* @param newNode element to be inserted
* @param reference anchor element
* @returns inserted node
*/
export const insertBefore = (parent: Node, newNode: Node, reference?: Node): Node => {
const inserted = parent?.insertBefore(newNode, reference);

if (BUILD.scoped) {
setParentScopeIdAsClassName(newNode as d.RenderNode, parent as d.RenderNode);
}

return inserted;
};

const findParentScopeId = (element: any): any => {
return element
? element['s-rsc'] || element['s-si'] || element['s-sc'] || findParentScopeId(element.parentElement)
: undefined;
};

/**
* to be able to style the deep nested scoped component from the root component,
* root component's scope id needs to be added to the child nodes since sass compiler
* adds scope id to the nested selectors during compilation phase
*
* @param element an element to be updated
* @param parent a parent element that scope id is retrieved
*/
export const setParentScopeIdAsClassName = (element: d.RenderNode, parent: d.RenderNode) => {
if (element && parent) {
const oldRootScopeId = element['s-rsc'];
const newRootScopeId = findParentScopeId(parent);

oldRootScopeId && element.classList?.contains(oldRootScopeId) && element.classList.remove(oldRootScopeId);
if (newRootScopeId) {
element['s-rsc'] = newRootScopeId;
!element.classList?.contains(newRootScopeId) && element.classList?.add(newRootScopeId);
}
}
};

/**
* Information about nodes to be relocated in order to support
* `<slot>` elements in scoped (i.e. non-shadow DOM) components
Expand Down Expand Up @@ -1045,7 +1078,7 @@ render() {
: (doc.createTextNode('') as any);
orgLocationNode['s-nr'] = nodeToRelocate;

nodeToRelocate.parentNode.insertBefore((nodeToRelocate['s-ol'] = orgLocationNode), nodeToRelocate);
insertBefore(nodeToRelocate.parentNode, (nodeToRelocate['s-ol'] = orgLocationNode), nodeToRelocate);
}
}

Expand Down Expand Up @@ -1115,7 +1148,7 @@ render() {
// If we get to this point and `insertBeforeNode` is `null`, that means
// we're just going to append the node as the last child of the parent. Passing
// `null` as the second arg here will trigger that behavior.
parentNodeRef.insertBefore(nodeToRelocate, insertBeforeNode);
insertBefore(parentNodeRef, nodeToRelocate, insertBeforeNode);

// Reset the `hidden` value back to what it was defined as originally
// This solves a problem where a `slot` is dynamically rendered and `hidden` may have
Expand Down
4 changes: 4 additions & 0 deletions test/wdio/scoped-id-in-nested-classname/cmp-level-1.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
cmp-level-2 {
cmp-level-3 {
padding: 32px;

#test-element {
padding: 24px;
}
}
}
}
6 changes: 5 additions & 1 deletion test/wdio/scoped-id-in-nested-classname/cmp-level-1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import { Component, h } from '@stencil/core';
})
export class CmpLevel1 {
render() {
return <cmp-level-2></cmp-level-2>;
return (
<cmp-level-2>
<slot />
</cmp-level-2>
);
}
}
19 changes: 15 additions & 4 deletions test/wdio/scoped-id-in-nested-classname/cmp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ import { h } from '@stencil/core';
import { render } from '@wdio/browser-runner/stencil';

describe('scope-id-in-nested-classname', function () {
beforeEach(() => {
it('should have root scope id in the nested element as classname', async () => {
render({
template: () => <cmp-level-1></cmp-level-1>,
});
});

it('should have root scope id in the nested element as classname', async () => {
await expect($('cmp-level-3')).toHaveElementClass('sc-cmp-level-1');

const appliedCss = await (await $('cmp-level-3')).getCSSProperty('padding');
await expect(appliedCss.parsed.value).toBe(32);
});

it('should have root scope id in the user provided nested element as classname', async () => {
render({
template: () => (
<cmp-level-1>
<span id="test-element">Test</span>
</cmp-level-1>
),
});
await expect($('#test-element')).toHaveElementClass('sc-cmp-level-1');

const appliedCss = await (await $('#test-element')).getCSSProperty('padding');
await expect(appliedCss.parsed.value).toBe(24);
});
});

0 comments on commit f434429

Please sign in to comment.