-
Notifications
You must be signed in to change notification settings - Fork 12
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
Improve style sheet adoption ergonomics. #166
Conversation
df8c01d
to
2604001
Compare
x-element.js
Outdated
* stylesheet file via import attributes. This has no effect if you are using | ||
* the “host” as your render root (versus attaching a shadow root). | ||
*/ | ||
static get styleSheets() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the crux of the change. We can fuss over the name here. I personally like cssStyleSheets
, styleSheets
, or styles
as the options. But, I’m willing to be convinced otherwise 😸
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import MyStylesAreTheBest from './path-to.css' with { type: 'css' };
import YourStyles from 'https://your.style/sheet.css' with { type: 'css' };
static get styles() {
const rules = `:host { display: block }`;
const inlineStyles = new CSSStyleSheet();
inlineStyles.replaceSync(rules);
return [
MyStylesAreTheBest,
YourStyles,
inlineStyles,
];
}
test/test-render-root.js
Outdated
|
||
class TestElement2 extends XElement { | ||
static get styleSheets() { | ||
// TODO: Replace with direct import of css file when better-supported in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’ll have to use your imagination a bit until import attributes are better-supported. This is the pattern we’d be using though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We no longer need to use our imagination :^)
We might be able to update these to import the CSS in directly. Assuming all our testing clients are using Chrome, that should probably work!.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
… except for ESLint doesn’t dig this yet. So close!
@klebba — This is more-or-less what I figured we’d do here. It’s pretty straightforward, just a way to declare things in isolation as an ergonomic benefit. |
x-element.js
Outdated
} | ||
|
||
/** | ||
* Declare an array of CSSSTyleSheet objects to adopt on the shadow root. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typo STyle
x-element.js
Outdated
@@ -68,7 +87,9 @@ export default class XElement extends HTMLElement { | |||
* E.g., setup focus delegation or return host instead of host.shadowRoot. | |||
*/ | |||
static createRenderRoot(host) { | |||
return host.attachShadow({ mode: 'open' }); | |||
const shadowRoot = host.attachShadow(this.shadowRootInit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend against the shadowRootInit
concept — developers should simply override createRenderRoot
to customize the render root init
x-element.js
Outdated
@@ -68,7 +87,9 @@ export default class XElement extends HTMLElement { | |||
* E.g., setup focus delegation or return host instead of host.shadowRoot. | |||
*/ | |||
static createRenderRoot(host) { | |||
return host.attachShadow({ mode: 'open' }); | |||
const shadowRoot = host.attachShadow(this.shadowRootInit); | |||
shadowRoot.adoptedStyleSheets = this.styleSheets; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend moving this out of createRenderRoot
and instead moving it to the constructor — it would throw an error if there is no shadowRoot
on the host
fc224a2
to
784cd5f
Compare
} else { | ||
throw new Error('Unexpected "styles" declared without a shadow root.'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind of a lot of lines dedicated to error-handling… but so it goes 🤷
784cd5f
to
3661bb0
Compare
|
||
### Fixed | ||
|
||
- The `map` function now works with properties / attributes bound across template contexts (#179). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just adding this in the [Unreleased]
section — forgot to request this earlier.
One common pattern for element authors (now that import attributes enable folks to import css files directly) is to adopt imported style sheets into a shadow root at custom element initialization time. This adds one static getter — `styles`. It’s still possible to do something more custom in `createRenderRoot`, but this adds a simple, declarative interface for the task of adding styles to a shadow root. Closes #52.
3661bb0
to
526c68f
Compare
el.remove(); | ||
}); | ||
|
||
it('should only get styles _once_ per constructor', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Importantly, this function is called once since it’s a static member, we follow our typical pattern — assume it can be set up once during static analysis and re-used for all instances.
@@ -21,6 +22,15 @@ export default class XElement extends HTMLElement { | |||
return XElement.defaultTemplateEngine; | |||
} | |||
|
|||
/** | |||
* Declare an array of CSSStyleSheet objects to adopt on the shadow root. | |||
* Note that a CSSStyleSheet object is the type returned when importing a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: an example syntax might be more useful here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ll add some additional commentary into the changes to use JSDoc 👌
@@ -534,7 +544,18 @@ export default class XElement extends HTMLElement { | |||
const computeMap = new Map(); | |||
const observeMap = new Map(); | |||
const defaultMap = new Map(); | |||
const { propertyMap } = XElement.#constructors.get(host.constructor); | |||
const { styles, propertyMap } = XElement.#constructors.get(host.constructor); | |||
if (styles.length > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: if (styles.length)
Looks awesome, nice work! |
One common pattern for element authors (now that import attributes enable folks to import css files directly) is to adopt imported style sheets into a shadow root at custom element initialization time.
This adds one static getters —
styles
. The goal is to make shadow root initialization as declarative as possible. Note that we still exposecreateRenderRoot
so we don’t get in the way if folks need to do something more advanced (it’s also still the only way to forgo the creation of a shadow root at all).Closes #52.