Skip to content

Commit

Permalink
feat: new CSSDirective design (#5835)
Browse files Browse the repository at this point in the history
* feat: new CSSDirective design

* feat: attach the partial helper to the css helper

* fix: update foundation to new CSSDirective API

* fix: update components to new CSSDirective API

* docs: add missing docs to CSSTEmplateTag type

* Change files

* fix: add back cssPartial with deprecation message

Co-authored-by: EisenbergEffect <[email protected]>
  • Loading branch information
EisenbergEffect and EisenbergEffect committed May 31, 2022
1 parent 6a0c833 commit f1328e4
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "fix: update components to new CSSDirective API",
"packageName": "@microsoft/fast-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "feat: new CSSDirective design",
"packageName": "@microsoft/fast-element",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "fix: update foundation to new CSSDirective API",
"packageName": "@microsoft/fast-foundation",
"email": "[email protected]",
"dependentChangeType": "patch"
}
32 changes: 27 additions & 5 deletions packages/web-components/fast-element/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export interface Accessor {
setValue(source: any, value: any): void;
}

// @public
export type AddBehavior = (behavior: Behavior<HTMLElement>) => void;

// @public
export type AddViewBehaviorFactory = (factory: ViewBehaviorFactory) => string;

Expand Down Expand Up @@ -227,16 +230,35 @@ export class Controller<TElement extends HTMLElement = HTMLElement> extends Prop
export function createTypeRegistry<TDefinition extends TypeDefinition>(): TypeRegistry<TDefinition>;

// @public
export function css(strings: TemplateStringsArray, ...values: (ComposableStyles | CSSDirective)[]): ElementStyles;
export const css: CSSTemplateTag;

// @public
export class CSSDirective {
createBehavior(): Behavior<HTMLElement> | undefined;
createCSS(): ComposableStyles;
export interface CSSDirective {
createCSS(add: AddBehavior): ComposableStyles;
}

// @public
export function cssPartial(strings: TemplateStringsArray, ...values: (ComposableStyles | CSSDirective)[]): CSSDirective;
export const CSSDirective: Readonly<{
getForInstance: (object: any) => CSSDirectiveDefinition<Constructable<CSSDirective>> | undefined;
getByType: (key: Function) => CSSDirectiveDefinition<Constructable<CSSDirective>> | undefined;
define<TType extends Constructable<CSSDirective>>(type: any): TType;
}>;

// @public
export function cssDirective(): (type: Constructable<CSSDirective>) => void;

// @public
export interface CSSDirectiveDefinition<TType extends Constructable<CSSDirective> = Constructable<CSSDirective>> {
readonly type: TType;
}

// @public @deprecated (undocumented)
export const cssPartial: (strings: TemplateStringsArray, ...values: (ComposableStyles | CSSDirective)[]) => CSSDirective;

// @public
export type CSSTemplateTag = ((strings: TemplateStringsArray, ...values: (ComposableStyles | CSSDirective)[]) => ElementStyles) & {
partial(strings: TemplateStringsArray, ...values: (ComposableStyles | CSSDirective)[]): CSSDirective;
};

// @public
export function customElement(nameOrDef: string | PartialFASTElementDefinition): (type: Constructable<HTMLElement>) => void;
Expand Down
56 changes: 47 additions & 9 deletions packages/web-components/fast-element/src/styles/css-directive.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,63 @@
import type { Constructable } from "../interfaces.js";
import type { Behavior } from "../observation/behavior.js";
import { createTypeRegistry } from "../platform.js";
import type { ComposableStyles } from "./element-styles.js";

/**
* Used to add behaviors when constructing styles.
* @public
*/
export type AddBehavior = (behavior: Behavior<HTMLElement>) => void;

/**
* Directive for use in {@link css}.
*
* @public
*/
export class CSSDirective {
export interface CSSDirective {
/**
* Creates a CSS fragment to interpolate into the CSS document.
* @returns - the string to interpolate into CSS
*/
public createCSS(): ComposableStyles {
return "";
}
createCSS(add: AddBehavior): ComposableStyles;
}

/**
* Defines metadata for a CSSDirective.
* @public
*/
export interface CSSDirectiveDefinition<
TType extends Constructable<CSSDirective> = Constructable<CSSDirective>
> {
/**
* Creates a behavior to bind to the host element.
* @returns - the behavior to bind to the host element, or undefined.
* The type that the definition provides metadata for.
*/
public createBehavior(): Behavior<HTMLElement> | undefined {
return undefined;
}
readonly type: TType;
}

const registry = createTypeRegistry<CSSDirectiveDefinition>();

/**
* Instructs the css engine to provide dynamic styles or
* associate behaviors with styles.
* @public
*/
export const CSSDirective = Object.freeze({
getForInstance: registry.getForInstance,
getByType: registry.getByType,
define<TType extends Constructable<CSSDirective>>(type): TType {
registry.register({ type });
return type;
},
});

/**
* Decorator: Defines a CSSDirective.
* @public
*/
export function cssDirective() {
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
return function (type: Constructable<CSSDirective>) {
CSSDirective.define(type);
};
}
97 changes: 56 additions & 41 deletions packages/web-components/fast-element/src/styles/css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FASTElement } from "../components/fast-element.js";
import { isString } from "../interfaces.js";
import type { Behavior } from "../observation/behavior.js";
import { CSSDirective } from "./css-directive.js";
import { AddBehavior, CSSDirective } from "./css-directive.js";
import { ComposableStyles, ElementStyles } from "./element-styles.js";

function collectStyles(
Expand All @@ -11,18 +11,16 @@ function collectStyles(
const styles: ComposableStyles[] = [];
let cssString = "";
const behaviors: Behavior<HTMLElement>[] = [];
const add = (behavior: Behavior<HTMLElement>): void => {
behaviors.push(behavior);
};

for (let i = 0, ii = strings.length - 1; i < ii; ++i) {
cssString += strings[i];
let value = values[i];

if (value instanceof CSSDirective) {
const behavior = value.createBehavior();
value = value.createCSS();

if (behavior) {
behaviors.push(behavior);
}
if (CSSDirective.getForInstance(value) !== void 0) {
value = (value as CSSDirective).createCSS(add);
}

if (value instanceof ElementStyles || value instanceof CSSStyleSheet) {
Expand Down Expand Up @@ -55,23 +53,47 @@ function collectStyles(
* @param values - The values that are interpolated with the string fragments.
* @remarks
* The css helper supports interpolation of strings and ElementStyle instances.
* Use the .partial method to create partial CSS fragments.
* @public
*/
export function css(
export type CSSTemplateTag = ((
strings: TemplateStringsArray,
...values: (ComposableStyles | CSSDirective)[]
): ElementStyles {
) => ElementStyles) & {
/**
* Transforms a template literal string into partial CSS.
* @param strings - The string fragments that are interpolated with the values.
* @param values - The values that are interpolated with the string fragments.
* @public
*/
partial(
strings: TemplateStringsArray,
...values: (ComposableStyles | CSSDirective)[]
): CSSDirective;
};

/**
* Transforms a template literal string into styles.
* @param strings - The string fragments that are interpolated with the values.
* @param values - The values that are interpolated with the string fragments.
* @remarks
* The css helper supports interpolation of strings and ElementStyle instances.
* @public
*/
export const css: CSSTemplateTag = ((
strings: TemplateStringsArray,
...values: (ComposableStyles | CSSDirective)[]
): ElementStyles => {
const { styles, behaviors } = collectStyles(strings, values);
const elementStyles = new ElementStyles(styles);
return behaviors.length ? elementStyles.withBehaviors(...behaviors) : elementStyles;
}
}) as any;

class CSSPartial extends CSSDirective implements Behavior<HTMLElement> {
class CSSPartial implements CSSDirective, Behavior<HTMLElement> {
private css: string = "";
private styles?: ElementStyles;
constructor(styles: ComposableStyles[], private behaviors: Behavior<HTMLElement>[]) {
super();

constructor(styles: ComposableStyles[], private behaviors: Behavior<HTMLElement>[]) {
const stylesheets: ReadonlyArray<Exclude<
ComposableStyles,
string
Expand All @@ -85,6 +107,7 @@ class CSSPartial extends CSSDirective implements Behavior<HTMLElement> {
} else {
accumulated.push(current);
}

return accumulated;
},
[]
Expand All @@ -95,45 +118,37 @@ class CSSPartial extends CSSDirective implements Behavior<HTMLElement> {
}
}

createBehavior(): Behavior<HTMLElement> {
return this;
}
createCSS(add: AddBehavior): string {
this.behaviors.forEach(add);

if (this.styles) {
add(this);
}

createCSS(): string {
return this.css;
}

bind(el: FASTElement): void {
if (this.styles) {
el.$fastController.addStyles(this.styles);
}

if (this.behaviors.length) {
el.$fastController.addBehaviors(this.behaviors);
}
el.$fastController.addStyles(this.styles);
}

unbind(el: FASTElement): void {
if (this.styles) {
el.$fastController.removeStyles(this.styles);
}

if (this.behaviors.length) {
el.$fastController.removeBehaviors(this.behaviors);
}
el.$fastController.removeStyles(this.styles);
}
}

/**
* Transforms a template literal string into partial CSS.
* @param strings - The string fragments that are interpolated with the values.
* @param values - The values that are interpolated with the string fragments.
* @public
*/
export function cssPartial(
CSSDirective.define(CSSPartial);

css.partial = (
strings: TemplateStringsArray,
...values: (ComposableStyles | CSSDirective)[]
): CSSDirective {
): CSSDirective => {
const { styles, behaviors } = collectStyles(strings, values);
return new CSSPartial(styles, behaviors);
}
};

/**
* @deprecated Use css.partial instead.
* @public
*/
export const cssPartial = css.partial;
Loading

0 comments on commit f1328e4

Please sign in to comment.