Skip to content
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

feat: new CSSDirective design #5835

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
4 changes: 2 additions & 2 deletions packages/web-components/fast-components/src/styles/size.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { cssPartial } from "@microsoft/fast-element";
import { css } from "@microsoft/fast-element";
import { baseHeightMultiplier, density, designUnit } from "../design-tokens.js";

/**
* A formula to retrieve the control height.
* Use this as the value of any CSS property that
* accepts a pixel size.
*/
export const heightNumber = cssPartial`(${baseHeightMultiplier} + ${density}) * ${designUnit}`;
export const heightNumber = css.partial`(${baseHeightMultiplier} + ${density}) * ${designUnit}`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { css, cssPartial, ElementStyles } from "@microsoft/fast-element";
import { css, ElementStyles } from "@microsoft/fast-element";
import {
DesignToken,
disabledCursor,
Expand Down Expand Up @@ -68,7 +68,7 @@ const rtl = css`
* Tree item expand collapse button size CSS Partial
* @public
*/
export const expandCollapseButtonSize = cssPartial`((${baseHeightMultiplier} / 2) * ${designUnit}) + ((${designUnit} * ${density}) / 2)`;
export const expandCollapseButtonSize = css.partial`((${baseHeightMultiplier} / 2) * ${designUnit}) + ((${designUnit} * ${density}) / 2)`;

const expandCollapseHoverBehavior = DesignToken.create<Swatch>(
"tree-item-expand-collapse-hover"
Expand Down
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