Skip to content

Commit

Permalink
feat(css): add shadow part detection
Browse files Browse the repository at this point in the history
add shadow part detection to the output target. similar to stencil's
auto generation of docs data, we pull the shadow part information from
the compiler metadata.

a new example was added to the project to showcase how shadow parts
work and where autocompletion can help the development experience here.

the readme was updated to clarify scenarios where overriding the local
dependency might not give the latest/greatest features.

unused styles from my-component (where i originally intended to showcase
this functionality) has been removed
  • Loading branch information
rwaskiewicz committed Apr 19, 2024
1 parent b5d4821 commit 12c5813
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 12 deletions.
2 changes: 1 addition & 1 deletion example/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This project demonstrates the usage of the `@stencil-community/web-types-output-
## Set Up

To set up this project, you may either first build the output target from source, or override this project's dependency on `@stencil-community/web-types-output-target` with a version published to the NPM registry.
Both allow you to take the output target for a 'test drive' - the only difference is the former allows you to tweak the output target's source code and see how it affects the example project.
Both allow you to take the output target for a 'test drive' - however, the former will allow you to try out potentially unreleased functionality.

After setting up the dependencies, continue to the next section.

Expand Down
29 changes: 29 additions & 0 deletions example/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export namespace Components {
*/
"suffix": string;
}
/**
* An example using Shadow Parts.
* The 'label' part is declared in the component-level JSDoc using "@part NAME - DESCRIPTION".
*/
interface ShadowParts {
}
interface SlotExample {
}
}
Expand All @@ -41,6 +47,16 @@ declare global {
prototype: HTMLMyComponentElement;
new (): HTMLMyComponentElement;
};
/**
* An example using Shadow Parts.
* The 'label' part is declared in the component-level JSDoc using "@part NAME - DESCRIPTION".
*/
interface HTMLShadowPartsElement extends Components.ShadowParts, HTMLStencilElement {
}
var HTMLShadowPartsElement: {
prototype: HTMLShadowPartsElement;
new (): HTMLShadowPartsElement;
};
interface HTMLSlotExampleElement extends Components.SlotExample, HTMLStencilElement {
}
var HTMLSlotExampleElement: {
Expand All @@ -49,6 +65,7 @@ declare global {
};
interface HTMLElementTagNameMap {
"my-component": HTMLMyComponentElement;
"shadow-parts": HTMLShadowPartsElement;
"slot-example": HTMLSlotExampleElement;
}
}
Expand All @@ -75,10 +92,17 @@ declare namespace LocalJSX {
*/
"suffix"?: string;
}
/**
* An example using Shadow Parts.
* The 'label' part is declared in the component-level JSDoc using "@part NAME - DESCRIPTION".
*/
interface ShadowParts {
}
interface SlotExample {
}
interface IntrinsicElements {
"my-component": MyComponent;
"shadow-parts": ShadowParts;
"slot-example": SlotExample;
}
}
Expand All @@ -90,6 +114,11 @@ declare module "@stencil/core" {
* A component for displaying a person's name
*/
"my-component": LocalJSX.MyComponent & JSXBase.HTMLAttributes<HTMLMyComponentElement>;
/**
* An example using Shadow Parts.
* The 'label' part is declared in the component-level JSDoc using "@part NAME - DESCRIPTION".
*/
"shadow-parts": LocalJSX.ShadowParts & JSXBase.HTMLAttributes<HTMLShadowPartsElement>;
"slot-example": LocalJSX.SlotExample & JSXBase.HTMLAttributes<HTMLSlotExampleElement>;
}
}
Expand Down
3 changes: 0 additions & 3 deletions example/src/components/my-component/my-component.css

This file was deleted.

1 change: 0 additions & 1 deletion example/src/components/my-component/my-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Component, Prop, h } from '@stencil/core';
*/
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
Expand Down
28 changes: 28 additions & 0 deletions example/src/components/shadow-parts/shadow-parts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component, h } from '@stencil/core';

/**
* An example using Shadow Parts.
*
* The 'label' part is declared in the component-level JSDoc using "@part NAME - DESCRIPTION".
*
* @part first-msg - The text describing the first message of the component.
* @part second-msg - The text describing the second message of the component.
*/
@Component({
tag: 'shadow-parts',
styles: 'div { background: LightGray; }',
shadow: true,
})
export class ShadowParts {

render() {
return (
<div>
<div part="first-msg">I am styled with Shadow Parts!</div>
<div part="second-msg">I am also styled with Shadow Parts!</div>
<div>I am not styled with Shadow Parts</div>
</div>
);
}

}
19 changes: 19 additions & 0 deletions example/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@

<script type="module" src="/build/example.esm.js"></script>
<script nomodule src="/build/example.js"></script>

<!-- See the repo's README for setup instructions -->
<!-- Corresponds to the shadow-parts element in the body of the document below -->
<!-- After setup, hover over the shadow part name to see its description. -->
<!-- After setup, autocomplete for the shadow part names will also work. Try deleting 'first-msg' below and retyping it -->
<style>
shadow-parts::part(first-msg) {
background: aqua;
}
shadow-parts::part(second-msg) {
background: lightgreen;
}
</style>
</head>
<body>
<!-- See the repo's README for setup instructions -->
Expand All @@ -31,5 +44,11 @@ <h2>Slot Example (&lt;slot-example&gt;)</h2>
<div slot="primary">Primary Content</div>
<div slot="secondary">Secondary Content</div>
</slot-example>

<br/>

<!-- Demonstrates how shadow parts work. See the style tag in the document's head -->
<h2>CSS Shadow Part Example (&lt;shadow-parts&gt;)</h2>
<shadow-parts></shadow-parts>
</body>
</html>
35 changes: 33 additions & 2 deletions example/web-types.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,29 @@
"priority": "high"
}
],
"slots": []
"slots": [],
"css": {
"parts": []
}
},
{
"name": "shadow-parts",
"deprecated": false,
"description": "An example using Shadow Parts.\n\nThe 'label' part is declared in the component-level JSDoc using \"@part NAME - DESCRIPTION\".",
"attributes": [],
"slots": [],
"css": {
"parts": [
{
"name": "first-msg",
"description": "The text describing the first message of the component."
},
{
"name": "second-msg",
"description": "The text describing the second message of the component."
}
]
}
},
{
"name": "slot-example",
Expand All @@ -64,7 +86,10 @@
"name": "secondary",
"description": ""
}
]
],
"css": {
"parts": []
}
}
]
},
Expand All @@ -73,6 +98,9 @@
{
"events": []
},
{
"events": []
},
{
"events": []
}
Expand All @@ -94,6 +122,9 @@
}
]
},
{
"properties": []
},
{
"properties": []
}
Expand Down
78 changes: 75 additions & 3 deletions src/contributions/html-contributions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('generateElementInfo', () => {
description: 'a simple component that shows us your name',
attributes: [],
slots: [],
css: {},
};

const actual: ElementInfo[] = generateElementInfo([cmpMeta]);
Expand Down Expand Up @@ -54,6 +55,7 @@ describe('generateElementInfo', () => {
description: 'a simple component that shows us your name',
attributes: [],
slots: [],
css: {},
};

cmpMeta.properties = [];
Expand Down Expand Up @@ -117,6 +119,7 @@ describe('generateElementInfo', () => {
},
],
slots: [],
css: {},
};

cmpMeta.properties = [
Expand Down Expand Up @@ -158,6 +161,7 @@ describe('generateElementInfo', () => {
},
],
slots: [],
css: {},
};

cmpMeta.properties = [
Expand Down Expand Up @@ -201,16 +205,17 @@ describe('generateElementInfo', () => {
it('parses a component with no slots', () => {
const expected: ElementInfo = {
name: 'my-component',
deprecated: false,
deprecated: true,
description: 'a simple component that shows us your name',
attributes: [],
slots: [],
css: {},
};

cmpMeta.docs.tags = [
{
name: 'part',
text: 'label - The label text describing the component',
name: 'deprecated',
text: "please don't use this",
},
];
const actual: ElementInfo[] = generateElementInfo([cmpMeta]);
Expand All @@ -231,6 +236,7 @@ describe('generateElementInfo', () => {
description: 'Content is placed between the named slots if provided without a slot.',
},
],
css: {},
};

cmpMeta.docs.tags = [
Expand All @@ -257,6 +263,7 @@ describe('generateElementInfo', () => {
description: '',
},
],
css: {},
};

cmpMeta.docs.tags = [
Expand All @@ -283,6 +290,7 @@ describe('generateElementInfo', () => {
description: 'Content is placed to the right of the main slotted in text',
},
],
css: {},
};

cmpMeta.docs.tags = [
Expand All @@ -297,6 +305,70 @@ describe('generateElementInfo', () => {
expect(actual[0]).toEqual(expected);
});
});

describe('shadow parts', () => {
let cmpMeta: ComponentCompilerMeta;

beforeEach(() => {
cmpMeta = stubComponentCompilerMeta({
tagName: 'my-component',
docs: {
text: 'a simple component that shows us your name',
tags: [],
},
properties: [],
});
});

it('parses a component with shadow parts', () => {
const expected: ElementInfo = {
name: 'my-component',
deprecated: false,
description: 'a simple component that shows us your name',
attributes: [],
slots: [],
css: {
parts: [
// note how these will be sorted by name
{ name: 'another-label', description: 'Another label describing the component' },
{ name: 'label', description: 'The label describing the component' },
],
},
};

cmpMeta.docs.tags = [
{
name: 'part',
text: 'label - The label describing the component',
},
{
name: 'part',
text: 'another-label - Another label describing the component',
},
];
const actual: ElementInfo[] = generateElementInfo([cmpMeta]);

expect(actual).toHaveLength(1);
expect(actual[0]).toEqual(expected);
});

it('omits the parts section when there are no shadow parts', () => {
const expected: ElementInfo = {
name: 'my-component',
deprecated: false,
description: 'a simple component that shows us your name',
attributes: [],
slots: [],
css: {},
};

cmpMeta.docs.tags = [];
const actual: ElementInfo[] = generateElementInfo([cmpMeta]);

expect(actual).toHaveLength(1);
expect(actual[0]).toEqual(expected);
});
});
});

/**
Expand Down
Loading

0 comments on commit 12c5813

Please sign in to comment.