Skip to content

Commit

Permalink
fix(runtime): add missing intermediate parents scope ids to the eleme…
Browse files Browse the repository at this point in the history
…nts (#5775)

* fix(runtime): add missing intermediate parents scope ids to the elements

#5774

* test: add missing e2e test cases

* test: remove duplicated await statement
  • Loading branch information
yigityuce authored May 24, 2024
1 parent 3830dad commit 56c60d4
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 26 deletions.
6 changes: 3 additions & 3 deletions src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1121,11 +1121,11 @@ export interface HostElement extends HTMLElement {
['s-sc']?: string;

/**
* Root Scope Id
* The scope id of the root component when using scoped css encapsulation
* Scope Ids
* All the possible scope ids of this component when using scoped css encapsulation
* or using shadow dom but the browser doesn't support it
*/
['s-rsc']?: string;
['s-scs']?: string[];

/**
* Hot Module Replacement, dev mode only
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/dom-extras.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const patchCloneNode = (HostElementPrototype: HTMLElement) => {
's-nr',
's-si',
's-rf',
's-rsc',
's-scs',
];

for (; i < srcNode.childNodes.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/test/scoped.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('scoped', () => {
<cmp-b class="hydrated sc-cmp-a sc-cmp-b-h sc-cmp-b-s">
<!---->
<div class="sc-cmp-a sc-cmp-b sc-cmp-b-s">
<span class="sc-cmp-a">
<span class="sc-cmp-a sc-cmp-b">
Hola
</span>
</div>
Expand Down
53 changes: 36 additions & 17 deletions src/runtime/vdom/vdom-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ const createElm = (oldParentVNode: d.VNode, newParentVNode: d.VNode, childIndex:
elm.classList.add((elm['s-si'] = scopeId));
}

if (BUILD.scoped) {
updateElementScopeIds(elm as d.RenderNode, parentElm as d.RenderNode);
}

if (newVNode.$children$) {
for (i = 0; i < newVNode.$children$.length; ++i) {
// create the node
Expand Down Expand Up @@ -913,7 +917,7 @@ 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.
* Additionally, adds parent elements' scope ids as class names to the new node.
*
* @param parent parent node
* @param newNode element to be inserted
Expand All @@ -924,35 +928,50 @@ export const insertBefore = (parent: Node, newNode: Node, reference?: Node): Nod
const inserted = parent?.insertBefore(newNode, reference);

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

return inserted;
};

const findParentScopeId = (element: d.RenderNode): string | undefined => {
return element
? element['s-rsc'] || element['s-si'] || element['s-sc'] || findParentScopeId(element.parentElement)
: undefined;
const findScopeIds = (element: d.RenderNode): string[] => {
const scopeIds: string[] = [];
if (element) {
scopeIds.push(
...(element['s-scs'] || []),
element['s-si'],
element['s-sc'],
...findScopeIds(element.parentElement),
);
}
return scopeIds;
};

/**
* 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
* To be able to style the deep nested scoped component from the parent components,
* all the scope ids of its parents need to be added to the child node 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
* @param iterateChildNodes iterate child nodes
*/
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);
const updateElementScopeIds = (element: d.RenderNode, parent: d.RenderNode, iterateChildNodes = false) => {
if (element && parent && element.nodeType === NODE_TYPE.ElementNode) {
const scopeIds = new Set(findScopeIds(parent).filter(Boolean));
if (scopeIds.size) {
element.classList?.add(...(element['s-scs'] = [...scopeIds]));

if (element['s-ol'] || iterateChildNodes) {
/**
* If the element has an original location, this means element is relocated.
* So, we need to notify the child nodes to update their new scope ids since
* the DOM structure is changed.
*/
for (const childNode of Array.from(element.childNodes)) {
updateElementScopeIds(childNode as d.RenderNode, element, true);
}
}
}
}
};
Expand Down
9 changes: 9 additions & 0 deletions test/wdio/scoped-id-in-nested-classname/cmp-level-2.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:host {
cmp-level-3 {
font-weight: 800;

#test-element {
font-weight: 600;
}
}
}
1 change: 1 addition & 0 deletions test/wdio/scoped-id-in-nested-classname/cmp-level-2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, h } from '@stencil/core';

@Component({
tag: 'cmp-level-2',
styleUrl: 'cmp-level-2.scss',
shadow: false,
scoped: true,
})
Expand Down
16 changes: 12 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 @@ -7,9 +7,13 @@ describe('scope-id-in-nested-classname', function () {
template: () => <cmp-level-1></cmp-level-1>,
});
await expect($('cmp-level-3')).toHaveElementClass('sc-cmp-level-1');
await expect($('cmp-level-3')).toHaveElementClass('sc-cmp-level-2');

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

const fontWeight = await $('cmp-level-3').getCSSProperty('font-weight');
await expect(fontWeight.parsed.value).toBe(800);
});

it('should have root scope id in the user provided nested element as classname', async () => {
Expand All @@ -21,8 +25,12 @@ describe('scope-id-in-nested-classname', function () {
),
});
await expect($('#test-element')).toHaveElementClass('sc-cmp-level-1');
await expect($('#test-element')).toHaveElementClass('sc-cmp-level-2');

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

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

0 comments on commit 56c60d4

Please sign in to comment.