Skip to content

Commit

Permalink
feat(runtime): initial runtime support for styles
Browse files Browse the repository at this point in the history
  • Loading branch information
EisenbergEffect committed Aug 12, 2019
1 parent b4cdd38 commit 6aafcca
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 1 deletion.
10 changes: 9 additions & 1 deletion packages/kernel/src/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export interface IServiceLocator {
}

export interface IRegistry {
register(container: IContainer): void;
register(container: IContainer, ...params: any[]): void;
}

export interface IContainer extends IServiceLocator {
Expand Down Expand Up @@ -782,6 +782,14 @@ export const Registration = Object.freeze({
alias<T>(originalKey: T, aliasKey: Key): IRegistration<Resolved<T>> {
return new Resolver(aliasKey, ResolverStrategy.alias, originalKey);
},
defer(key: Key, ...params: any[]): IRegistry {
return {
register(container: IContainer) {
const r = container.get<IRegistry>(key);
r.register(container, ...params);
}
};
}
});

export class InstanceProvider<K extends Key> implements IResolver<K | null> {
Expand Down
6 changes: 6 additions & 0 deletions packages/runtime-html/src/projectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
IProjectorLocator,
TemplateDefinition
} from '@aurelia/runtime';
import { IShadowDOMStyleManager } from './styles/shadow-dom-styles';

const slice = Array.prototype.slice;

Expand Down Expand Up @@ -53,10 +54,12 @@ export class ShadowDOMProjector implements IElementProjector<Node> {
public host: CustomElementHost<Node>;
public shadowRoot: CustomElementHost<ShadowRoot>;
public dom: IDOM<Node>;
private $controller: IController<Node>;

constructor(dom: IDOM<Node>, $controller: IController<Node>, host: CustomElementHost<HTMLElement>, definition: TemplateDefinition) {
this.dom = dom;
this.host = host;
this.$controller = $controller;

let shadowOptions: ShadowRootInit;
if (
Expand Down Expand Up @@ -86,6 +89,9 @@ export class ShadowDOMProjector implements IElementProjector<Node> {
}

public project(nodes: INodeSequence<Node>): void {
const context = this.$controller.context!;
const manager = context.get(IShadowDOMStyleManager);
manager.applyTo(this.shadowRoot);
nodes.appendTo(this.shadowRoot);
}

Expand Down
32 changes: 32 additions & 0 deletions packages/runtime-html/src/styles/css-modules-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IContainer } from '@aurelia/kernel';
import { bindable, customAttribute } from '@aurelia/runtime';

export class CSSModulesRegistry {
public register(container: IContainer, ...params: (Record<string, string>)[]) {
const classLookup = Object.assign({}, ...params) as Record<string, string>;

@customAttribute('class')
class ClassCustomAttribute {
@bindable public value!: string;

constructor(private element: HTMLElement) {}

public binding() {
this.valueChanged();
}

public valueChanged() {
if (!this.value) {
this.element.className = '';
return;
}

this.element.className = this.value.split(' ')
.map(x => classLookup[x] || x)
.join(' ');
}
}

container.register(ClassCustomAttribute);
}
}
29 changes: 29 additions & 0 deletions packages/runtime-html/src/styles/shadow-dom-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { IContainer, Registration } from '@aurelia/kernel';
import {
AdoptedStyleSheetsStyleManager,
IShadowDOMStyleManager,
StyleElementStyleManager
} from './shadow-dom-styles';

export function adoptedStyleSheetsSupported(): boolean {
return 'adoptedStyleSheets' in ShadowRoot.prototype;
}

export class ShadowDOMRegistry {
private useAdoptedStyleSheets: boolean;

constructor(private parent: IShadowDOMStyleManager) {
this.useAdoptedStyleSheets = adoptedStyleSheetsSupported();
}

public register(container: IContainer, ...params: any[]) {
container.register(
Registration.instance(
IShadowDOMStyleManager,
this.useAdoptedStyleSheets
? new AdoptedStyleSheetsStyleManager(params, this.parent)
: new StyleElementStyleManager(params, this.parent)
)
);
}
}
56 changes: 56 additions & 0 deletions packages/runtime-html/src/styles/shadow-dom-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { DI, PLATFORM } from '@aurelia/kernel';

type HasAdoptedStyleSheets = ShadowRoot & {
adoptedStyleSheets: CSSStyleSheet[];
};

export const IShadowDOMStyleManager =
DI.createInterface<IShadowDOMStyleManager>('IShadowDOMStyleManager')
.withDefault(x => x.instance({ applyTo: PLATFORM.noop }));

export interface IShadowDOMStyleManager {
applyTo(shadowRoot: ShadowRoot): void;
}

export class AdoptedStyleSheetsStyleManager implements IShadowDOMStyleManager {
private readonly styleSheets: CSSStyleSheet[];

constructor(styles: string[], private parent: IShadowDOMStyleManager | null = null) {
this.styleSheets = styles.map(x => {
const sheet = new CSSStyleSheet();
(sheet as any).replaceSync(x);
return sheet;
});
}

public applyTo(shadowRoot: HasAdoptedStyleSheets) {
if (this.parent !== null) {
this.parent.applyTo(shadowRoot);
}

// https://wicg.github.io/construct-stylesheets/
// https://developers.google.com/web/updates/2019/02/constructable-stylesheets
shadowRoot.adoptedStyleSheets = [
...shadowRoot.adoptedStyleSheets,
...this.styleSheets
];
}
}

export class StyleElementStyleManager implements IShadowDOMStyleManager {
constructor(private styles: string[], private parent: IShadowDOMStyleManager | null = null) {}

public applyTo(shadowRoot: ShadowRoot) {
const styles = this.styles;

for (let i = styles.length - 1; i > -1; --i) {
const element = document.createElement('style');
element.innerHTML = styles[i];
shadowRoot.prepend(element);
}

if (this.parent !== null) {
this.parent.applyTo(shadowRoot);
}
}
}
51 changes: 51 additions & 0 deletions packages/runtime-html/src/styles/style-configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { IContainer, IRegistry, Registration } from '@aurelia/kernel';
import { CSSModulesRegistry } from './css-modules-registry';
import { adoptedStyleSheetsSupported, ShadowDOMRegistry } from './shadow-dom-registry';
import {
AdoptedStyleSheetsStyleManager,
IShadowDOMStyleManager,
StyleElementStyleManager
} from './shadow-dom-styles';

const ext = '.css';

export interface IShadowDOMConfiguration {
sharedStyles?: string[];
}

export function styles(...styles: any[]) {
return Registration.defer(ext, ...styles);
}

export const StyleConfiguration = {
cssModules(): IRegistry {
return {
register(container: IContainer) {
container.register(
Registration.singleton(ext, CSSModulesRegistry)
);
}
};
},

shadowDOM(config?: IShadowDOMConfiguration): IRegistry {
return {
register(container: IContainer) {
if (config && config.sharedStyles) {
container.register(
Registration.instance(
IShadowDOMStyleManager,
adoptedStyleSheetsSupported()
? new AdoptedStyleSheetsStyleManager(config.sharedStyles, null)
: new StyleElementStyleManager(config.sharedStyles, null)
)
);
}

container.register(
Registration.singleton(ext, ShadowDOMRegistry)
);
}
};
}
};

0 comments on commit 6aafcca

Please sign in to comment.