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

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});