Skip to content

Commit e600612

Browse files
authored
Fix inheriting styles from a superclass (#470)
* Add test for extending a class with styles * Fix `styles` in extended elements * UpdatingElement: makes `finalize` protected * LitElement: build `styles` in `finalize`, taking care to inherit when needed * Fix shadowing of `styles` in `_getUniqueStyles` * [ci skip] Update changelog
1 parent 4ffa707 commit e600612

File tree

4 files changed

+65
-42
lines changed

4 files changed

+65
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1717

1818
### Fixed
1919
* Fixed a bug where we broke compatibility with closure compiler's property renaming optimizations. JSCompiler_renameProperty can't be a module export ([#465](https://github.com/Polymer/lit-element/pull/465)).
20+
* Fixed an issue with inheriting from `styles` property when extending a superclass that is never instanced. ([#470](https://github.com/Polymer/lit-element/pull/470)).
2021

2122
## [2.0.0-rc.3] - 2019-01-18
2223
### Fixed

src/lib/updating-element.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ export abstract class UpdatingElement extends HTMLElement {
232232
* @nocollapse
233233
*/
234234
static get observedAttributes() {
235-
// note: piggy backing on this to ensure we're _finalized.
236-
this._finalize();
235+
// note: piggy backing on this to ensure we're finalized.
236+
this.finalize();
237237
const attributes: string[] = [];
238238
// Use forEach so this works even if for/of loops are compiled to for loops
239239
// expecting arrays
@@ -249,7 +249,7 @@ export abstract class UpdatingElement extends HTMLElement {
249249

250250
/**
251251
* Ensures the private `_classProperties` property metadata is created.
252-
* In addition to `_finalize` this is also called in `createProperty` to
252+
* In addition to `finalize` this is also called in `createProperty` to
253253
* ensure the `@property` decorator can add property metadata.
254254
*/
255255
/** @nocollapse */
@@ -278,7 +278,7 @@ export abstract class UpdatingElement extends HTMLElement {
278278
options:
279279
PropertyDeclaration = defaultPropertyDeclaration) {
280280
// Note, since this can be called by the `@property` decorator which
281-
// is called before `_finalize`, we ensure storage exists for property
281+
// is called before `finalize`, we ensure storage exists for property
282282
// metadata.
283283
this._ensureClassProperties();
284284
this._classProperties!.set(name, options);
@@ -309,15 +309,15 @@ export abstract class UpdatingElement extends HTMLElement {
309309
* any superclasses are also finalized.
310310
* @nocollapse
311311
*/
312-
private static _finalize() {
312+
protected static finalize() {
313313
if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this)) &&
314314
this.finalized) {
315315
return;
316316
}
317317
// finalize any superclasses
318318
const superCtor = Object.getPrototypeOf(this);
319-
if (typeof superCtor._finalize === 'function') {
320-
superCtor._finalize();
319+
if (typeof superCtor.finalize === 'function') {
320+
superCtor.finalize();
321321
}
322322
this.finalized = true;
323323
this._ensureClassProperties();

src/lit-element.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,40 +70,42 @@ export class LitElement extends UpdatingElement {
7070

7171
private static _styles: CSSResult[]|undefined;
7272

73-
private static get _uniqueStyles(): CSSResult[] {
74-
if (!this.hasOwnProperty(JSCompiler_renameProperty('_styles', this))) {
75-
// Inherit styles from superclass if none have been set.
76-
if (!this.hasOwnProperty(JSCompiler_renameProperty('styles', this))) {
77-
this._styles = this._styles !== undefined ? this._styles : [];
78-
} else {
79-
// Take care not to call `this.styles` multiple times since this generates
80-
// new CSSResults each time.
81-
// TODO(sorvell): Since we do not cache CSSResults by input, any
82-
// shared styles will generate new stylesheet objects, which is wasteful.
83-
// This should be addressed when a browser ships constructable
84-
// stylesheets.
85-
const userStyles = this.styles;
86-
if (Array.isArray(userStyles)) {
87-
const styles = flattenStyles(userStyles);
88-
// As a performance optimization to avoid duplicated styling that can
89-
// occur especially when composing via subclassing, de-duplicate styles
90-
// preserving the last item in the list. The last item is kept to
91-
// try to preserve cascade order with the assumption that it's most
92-
// important that last added styles override previous styles.
93-
const styleSet = styles.reduceRight((set, s) => {
94-
set.add(s);
95-
// on IE set.add does not return the set.
96-
return set;
97-
}, new Set<CSSResult>());
98-
// Array.from does not work on Set in IE
99-
this._styles = [];
100-
styleSet.forEach((v) => this._styles!.unshift(v));
101-
} else {
102-
this._styles = userStyles ? [userStyles] : [];
103-
}
104-
}
73+
protected static finalize() {
74+
super.finalize();
75+
// Prepare styling that is stamped at first render time. Styling
76+
// is built from user provided `styles` or is inherited from the superclass.
77+
this._styles = this.hasOwnProperty(JSCompiler_renameProperty('styles', this)) ?
78+
this._getUniqueStyles() :
79+
this._styles || [];
80+
}
81+
82+
private static _getUniqueStyles(): CSSResult[] {
83+
// Take care not to call `this.styles` multiple times since this generates
84+
// new CSSResults each time.
85+
// TODO(sorvell): Since we do not cache CSSResults by input, any
86+
// shared styles will generate new stylesheet objects, which is wasteful.
87+
// This should be addressed when a browser ships constructable
88+
// stylesheets.
89+
const userStyles = this.styles;
90+
let styles: CSSResult[] = [];
91+
if (Array.isArray(userStyles)) {
92+
styles = flattenStyles(userStyles);
93+
// As a performance optimization to avoid duplicated styling that can
94+
// occur especially when composing via subclassing, de-duplicate styles
95+
// preserving the last item in the list. The last item is kept to
96+
// try to preserve cascade order with the assumption that it's most
97+
// important that last added styles override previous styles.
98+
const styleSet = styles.reduceRight((set, s) => {
99+
set.add(s);
100+
// on IE set.add does not return the set.
101+
return set;
102+
}, new Set<CSSResult>());
103+
// Array.from does not work on Set in IE
104+
styleSet.forEach((v) => styles!.unshift(v));
105+
} else if (userStyles) {
106+
styles = [userStyles];
105107
}
106-
return this._styles as CSSResult[];
108+
return styles;
107109
}
108110

109111
private _needsShimAdoptedStyleSheets?: boolean;
@@ -151,7 +153,7 @@ export class LitElement extends UpdatingElement {
151153
* behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).
152154
*/
153155
protected adoptStyles() {
154-
const styles = (this.constructor as typeof LitElement)._uniqueStyles;
156+
const styles = (this.constructor as typeof LitElement)._styles!;
155157
if (styles.length === 0) {
156158
return;
157159
}
@@ -201,7 +203,7 @@ export class LitElement extends UpdatingElement {
201203
// priority.
202204
if (this._needsShimAdoptedStyleSheets) {
203205
this._needsShimAdoptedStyleSheets = false;
204-
(this.constructor as typeof LitElement)._uniqueStyles.forEach((s) => {
206+
(this.constructor as typeof LitElement)._styles!.forEach((s) => {
205207
const style = document.createElement('style');
206208
style.textContent = s.cssText;
207209
this.renderRoot!.appendChild(style);

src/test/lit-element_styling_test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,26 @@ suite('Static get styles', () => {
690690
assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(),
691691
'4px');
692692
});
693+
694+
test('elements should inherit `styles` by default', async () => {
695+
const base = generateElementName();
696+
customElements.define(base, class extends LitElement {
697+
static styles = css`div {border: 4px solid black;}`;
698+
});
699+
700+
const sub = generateElementName();
701+
customElements.define(sub, class extends customElements.get(base) {
702+
render() {
703+
return htmlWithStyles`<div></div>`;
704+
}
705+
});
706+
707+
const el = document.createElement(sub);
708+
container.appendChild(el);
709+
await (el as LitElement).updateComplete;
710+
const div = el.shadowRoot!.querySelector('div');
711+
assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '4px');
712+
});
693713
});
694714

695715
suite('ShadyDOM', () => {

0 commit comments

Comments
 (0)