From 416554723f3d273a2ce5053b56a8adb7f3c67619 Mon Sep 17 00:00:00 2001 From: nicholasrice Date: Mon, 18 Jul 2022 11:22:54 -0700 Subject: [PATCH] re-organize assets, exports, and files. delete legacy design-token --- .../fast-foundation/docs/api-report.md | 99 +- .../fast-foundation/package.json | 6 +- .../src/design-token-2/core/exports.ts | 0 .../src/design-token-2/exports.ts | 2 - .../README.md | 16 +- .../core/design-token-node.spec.ts | 0 .../core/design-token-node.ts | 18 +- .../core/design-token.ts | 3 + .../src/design-token/core/exports.ts | 10 + .../design-token/custom-property-manager.ts | 46 +- .../src/design-token/design-token.spec.ts | 1085 ----------------- .../src/design-token/design-token.ts | 930 -------------- .../src/design-token/exports.ts | 14 + .../fast-design-token.spec.ts | 8 +- .../fast-design-token.ts | 42 +- .../src/design-token/interfaces.ts | 37 - .../fast-foundation/src/index.ts | 13 +- 17 files changed, 160 insertions(+), 2169 deletions(-) delete mode 100644 packages/web-components/fast-foundation/src/design-token-2/core/exports.ts delete mode 100644 packages/web-components/fast-foundation/src/design-token-2/exports.ts rename packages/web-components/fast-foundation/src/{design-token-2 => design-token}/README.md (89%) rename packages/web-components/fast-foundation/src/{design-token-2 => design-token}/core/design-token-node.spec.ts (100%) rename packages/web-components/fast-foundation/src/{design-token-2 => design-token}/core/design-token-node.ts (98%) rename packages/web-components/fast-foundation/src/{design-token-2 => design-token}/core/design-token.ts (79%) create mode 100644 packages/web-components/fast-foundation/src/design-token/core/exports.ts delete mode 100644 packages/web-components/fast-foundation/src/design-token/design-token.spec.ts delete mode 100644 packages/web-components/fast-foundation/src/design-token/design-token.ts create mode 100644 packages/web-components/fast-foundation/src/design-token/exports.ts rename packages/web-components/fast-foundation/src/{design-token-2 => design-token}/fast-design-token.spec.ts (99%) rename packages/web-components/fast-foundation/src/{design-token-2 => design-token}/fast-design-token.ts (90%) delete mode 100644 packages/web-components/fast-foundation/src/design-token/interfaces.ts diff --git a/packages/web-components/fast-foundation/docs/api-report.md b/packages/web-components/fast-foundation/docs/api-report.md index 8db7c9ae202..0296156642b 100644 --- a/packages/web-components/fast-foundation/docs/api-report.md +++ b/packages/web-components/fast-foundation/docs/api-report.md @@ -4,8 +4,10 @@ ```ts +import { AddBehavior } from '@microsoft/fast-element'; import { Behavior } from '@microsoft/fast-element'; import type { CaptureType } from '@microsoft/fast-element'; +import { ComposableStyles } from '@microsoft/fast-element'; import { composedContains } from '@microsoft/fast-element/utilities'; import { composedParent } from '@microsoft/fast-element/utilities'; import { Constructable } from '@microsoft/fast-element'; @@ -260,11 +262,18 @@ export { composedParent } // @beta export type ConstructableFormAssociated = Constructable; -// @public -export interface CSSDesignToken | symbol | ({ - createCSS?(): string; -} & Record)> extends DesignToken, CSSDirective { - readonly cssCustomProperty: string; +// @public (undocumented) +export class CSSDesignToken extends DesignToken implements CSSDirective { + constructor(configuration: CSSDesignTokenConfiguration); + // (undocumented) + createCSS(add: AddBehavior): ComposableStyles; + // (undocumented) + cssCustomProperty: string; +} + +// @public (undocumented) +export interface CSSDesignTokenConfiguration extends DesignTokenConfiguration { + cssCustomPropertyName: string; } // @public @deprecated @@ -464,49 +473,72 @@ export interface DelegatesARIAToolbar extends ARIAGlobalStatesAndProperties { } // @public -export type DerivedDesignTokenValue = T extends Function ? never : (target: HTMLElement) => T; +export type DerivedDesignTokenValue = (resolve: DesignTokenResolver) => T; -// @public -export interface DesignToken | symbol | {}> { - readonly appliedTo: HTMLElement[]; - deleteValueFor(element: HTMLElement): this; - getValueFor(element: HTMLElement): StaticDesignTokenValue; - readonly name: string; - setValueFor(element: HTMLElement, value: DesignTokenValue | DesignToken): void; - subscribe(subscriber: DesignTokenSubscriber, target?: HTMLElement): void; - unsubscribe(subscriber: DesignTokenSubscriber, target?: HTMLElement): void; - withDefault(value: DesignTokenValue | DesignToken): this; +// @public (undocumented) +export class DesignToken { + // (undocumented) + get $value(): T | undefined; + constructor(configuration: DesignTokenConfiguration); + // (undocumented) + static create(name: string): CSSDesignToken; + // (undocumented) + static create(config: DesignTokenConfiguration): DesignToken; + // (undocumented) + static create(config: CSSDesignTokenConfiguration): CSSDesignToken; + // (undocumented) + get default(): T | undefined; + // (undocumented) + deleteValueFor(target: FASTElement): this; + // (undocumented) + getValueFor(target: FASTElement): T; + // (undocumented) + name: string; + static registerRoot(target?: FASTElement | Document): void; + // Warning: (ae-forgotten-export) The symbol "DesignTokenValue" needs to be exported by the entry point index.d.ts + // + // (undocumented) + setValueFor(target: FASTElement, value: DesignToken | DesignTokenValue): void; + // (undocumented) + subscribe(subscriber: DesignTokenSubscriber): void; + static unregisterRoot(target?: FASTElement | Document): void; + // (undocumented) + unsubscribe(subscriber: DesignTokenSubscriber): void; + // (undocumented) + withDefault(value: DesignToken | DesignTokenValue): this; } -// @public -export const DesignToken: Readonly<{ - create: typeof create; - notifyConnection(element: HTMLElement): boolean; - notifyDisconnection(element: HTMLElement): boolean; - registerRoot(target?: HTMLElement | Document): void; - unregisterRoot(target?: HTMLElement | Document): void; -}>; - -// @public +// @public (undocumented) export interface DesignTokenChangeRecord> { - target: HTMLElement; + target: FASTElement | "default"; token: T; } // @public export interface DesignTokenConfiguration { - cssCustomPropertyName?: string | null; name: string; } -// @public -export interface DesignTokenSubscriber> { +// @public (undocumented) +export const enum DesignTokenMutationType { + // (undocumented) + add = 0, + // (undocumented) + change = 1, // (undocumented) - handleChange(record: DesignTokenChangeRecord): void; + delete = 2 } +// Warning: (ae-forgotten-export) The symbol "DesignToken" needs to be exported by the entry point index.d.ts +// // @public -export type DesignTokenValue = StaticDesignTokenValue | DerivedDesignTokenValue; +export type DesignTokenResolver = (token: DesignToken_2) => T; + +// @public +export interface DesignTokenSubscriber> { + // (undocumented) + handleChange(token: T, record: DesignTokenChangeRecord): void; +} // @public export function dialogTemplate(): ElementViewTemplate; @@ -2509,7 +2541,7 @@ export type StartOptions = { export function startSlotTemplate(options: StartOptions): ViewTemplate; // @public -export type StaticDesignTokenValue = T extends Function ? never : T; +export type StaticDesignTokenValue = T extends (...args: any[]) => any ? DerivedDesignTokenValue : T; // @alpha (undocumented) export const supportsElementInternals: boolean; @@ -2665,7 +2697,6 @@ export type YearFormat = typeof YearFormat[keyof typeof YearFormat]; // dist/dts/calendar/calendar.d.ts:51:5 - (ae-incompatible-release-tags) The symbol "dataGrid" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta // dist/dts/data-grid/data-grid-row.template.d.ts:9:5 - (ae-incompatible-release-tags) The symbol "dataGridCell" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta // dist/dts/data-grid/data-grid.template.d.ts:9:5 - (ae-incompatible-release-tags) The symbol "dataGridRow" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta -// dist/dts/design-token/design-token.d.ts:91:5 - (ae-forgotten-export) The symbol "create" needs to be exported by the entry point index.d.ts // dist/dts/menu-item/menu-item.d.ts:20:5 - (ae-incompatible-release-tags) The symbol "anchoredRegion" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta // dist/dts/picker/picker.template.d.ts:9:5 - (ae-incompatible-release-tags) The symbol "anchoredRegion" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta // dist/dts/picker/picker.template.d.ts:10:5 - (ae-incompatible-release-tags) The symbol "pickerMenu" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta diff --git a/packages/web-components/fast-foundation/package.json b/packages/web-components/fast-foundation/package.json index 2ed9242c6e6..0c4d0b4aa5c 100644 --- a/packages/web-components/fast-foundation/package.json +++ b/packages/web-components/fast-foundation/package.json @@ -26,9 +26,9 @@ "types": "./dist/dts/index.d.ts", "default": "./dist/esm/index.js" }, - "./design-token-2": { - "types": "./dist/dts/design-token-2/exports.d.ts", - "default": "./dist/esm/design-token-2/exports.js" + "./design-token-core": { + "types": "./dist/dts/design-token/core/exports.d.ts", + "default": "./dist/esm/design-token/core/exports.js" } }, "scripts": { diff --git a/packages/web-components/fast-foundation/src/design-token-2/core/exports.ts b/packages/web-components/fast-foundation/src/design-token-2/core/exports.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/web-components/fast-foundation/src/design-token-2/exports.ts b/packages/web-components/fast-foundation/src/design-token-2/exports.ts deleted file mode 100644 index 04961b4b8bf..00000000000 --- a/packages/web-components/fast-foundation/src/design-token-2/exports.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./core/design-token-node.js"; -export * from "./core/design-token.js"; diff --git a/packages/web-components/fast-foundation/src/design-token-2/README.md b/packages/web-components/fast-foundation/src/design-token/README.md similarity index 89% rename from packages/web-components/fast-foundation/src/design-token-2/README.md rename to packages/web-components/fast-foundation/src/design-token/README.md index 6e22dbf8467..1f140e2950f 100644 --- a/packages/web-components/fast-foundation/src/design-token-2/README.md +++ b/packages/web-components/fast-foundation/src/design-token/README.md @@ -41,8 +41,8 @@ Observable.getNotifier(token).subscribe({ | `setValueFor` | public | | `target: FASTElement, value: DesignToken or DesignTokenValue` | `void` | | | `deleteValueFor` | public | | `target: FASTElement` | `this` | | | `withDefault` | public | | `value: DesignToken or DesignTokenValue` | `this` | | -| `subscribe` | public | | `subscriber: FASTDesignTokenSubscriber` | `void` | | -| `unsubscribe` | public | | `subscriber: FASTDesignTokenSubscriber` | `void` | | +| `subscribe` | public | | `subscriber: DesignTokenSubscriber` | `void` | | +| `unsubscribe` | public | | `subscriber: DesignTokenSubscriber` | `void` | |
@@ -50,9 +50,9 @@ Observable.getNotifier(token).subscribe({ #### Superclass -| Name | Module | Package | -| ------------- | --------------------------------------- | ------- | -| `DesignToken` | src/design-token-2/fast-design-token.ts | | +| Name | Module | Package | +| ------------- | ------------------------------------- | ------- | +| `DesignToken` | src/design-token/fast-design-token.ts | | #### Fields @@ -72,8 +72,8 @@ Observable.getNotifier(token).subscribe({ | `setValueFor` | public | | `target: FASTElement, value: DesignToken or DesignTokenValue` | `void` | DesignToken | | `deleteValueFor` | public | | `target: FASTElement` | `this` | DesignToken | | `withDefault` | public | | `value: DesignToken or DesignTokenValue` | `this` | DesignToken | -| `subscribe` | public | | `subscriber: FASTDesignTokenSubscriber` | `void` | DesignToken | -| `unsubscribe` | public | | `subscriber: FASTDesignTokenSubscriber` | `void` | DesignToken | +| `subscribe` | public | | `subscriber: DesignTokenSubscriber` | `void` | DesignToken | +| `unsubscribe` | public | | `subscriber: DesignTokenSubscriber` | `void` | DesignToken |
@@ -101,3 +101,5 @@ Observable.getNotifier(token).subscribe({
+ + diff --git a/packages/web-components/fast-foundation/src/design-token-2/core/design-token-node.spec.ts b/packages/web-components/fast-foundation/src/design-token/core/design-token-node.spec.ts similarity index 100% rename from packages/web-components/fast-foundation/src/design-token-2/core/design-token-node.spec.ts rename to packages/web-components/fast-foundation/src/design-token/core/design-token-node.spec.ts diff --git a/packages/web-components/fast-foundation/src/design-token-2/core/design-token-node.ts b/packages/web-components/fast-foundation/src/design-token/core/design-token-node.ts similarity index 98% rename from packages/web-components/fast-foundation/src/design-token-2/core/design-token-node.ts rename to packages/web-components/fast-foundation/src/design-token/core/design-token-node.ts index 7b024f21bbc..4d1fc5ff70e 100644 --- a/packages/web-components/fast-foundation/src/design-token-2/core/design-token-node.ts +++ b/packages/web-components/fast-foundation/src/design-token/core/design-token-node.ts @@ -3,6 +3,7 @@ import type { DesignToken } from "./design-token.js"; /** * A function that resolves the value of a DesignToken. + * @public */ export type DesignTokenResolver = (token: DesignToken) => T; @@ -80,6 +81,10 @@ class DerivedValueEvaluator { Observable.getNotifier(this).notify(undefined); } } + +/** + * @public + */ export interface DesignTokenChangeRecord { readonly target: DesignTokenNode; readonly type: DesignTokenMutationType; @@ -98,21 +103,22 @@ export class DesignTokenChangeRecordImpl implements DesignTokenChangeRecord = new Set(); diff --git a/packages/web-components/fast-foundation/src/design-token-2/core/design-token.ts b/packages/web-components/fast-foundation/src/design-token/core/design-token.ts similarity index 79% rename from packages/web-components/fast-foundation/src/design-token-2/core/design-token.ts rename to packages/web-components/fast-foundation/src/design-token/core/design-token.ts index 18485f15c71..2aafcd6b14f 100644 --- a/packages/web-components/fast-foundation/src/design-token-2/core/design-token.ts +++ b/packages/web-components/fast-foundation/src/design-token/core/design-token.ts @@ -1,3 +1,6 @@ +/** + * @public + */ export interface DesignToken { readonly $value: T | undefined; } diff --git a/packages/web-components/fast-foundation/src/design-token/core/exports.ts b/packages/web-components/fast-foundation/src/design-token/core/exports.ts new file mode 100644 index 00000000000..d4b0f9e8bb1 --- /dev/null +++ b/packages/web-components/fast-foundation/src/design-token/core/exports.ts @@ -0,0 +1,10 @@ +export { DesignToken } from "./design-token.js"; +export { + DesignTokenNode, + DesignTokenResolver, + DerivedDesignTokenValue, + StaticDesignTokenValue, + DesignTokenValue, + DesignTokenMutationType, + DesignTokenChangeRecord, +} from "./design-token-node.js"; diff --git a/packages/web-components/fast-foundation/src/design-token/custom-property-manager.ts b/packages/web-components/fast-foundation/src/design-token/custom-property-manager.ts index b612a1af593..e97a70f71da 100644 --- a/packages/web-components/fast-foundation/src/design-token/custom-property-manager.ts +++ b/packages/web-components/fast-foundation/src/design-token/custom-property-manager.ts @@ -8,12 +8,6 @@ import { Updates, } from "@microsoft/fast-element"; -export const defaultElement = document.createElement("div"); - -function isFastElement(element: HTMLElement | FASTElement): element is FASTElement { - return element instanceof FASTElement; -} - interface PropertyTarget { setProperty(name: string, value: string | null): void; removeProperty(name: string): void; @@ -34,7 +28,7 @@ abstract class QueuedStyleSheetTarget implements PropertyTarget { */ class ConstructableStyleSheetTarget extends QueuedStyleSheetTarget { protected target: PropertyTarget; - constructor(source: HTMLElement & FASTElement) { + constructor(source: FASTElement) { super(); const sheet = new CSSStyleSheet(); @@ -97,7 +91,7 @@ class StyleElementStyleSheetTarget implements PropertyTarget { } } - constructor(target: HTMLElement & FASTElement) { + constructor(target: FASTElement) { const controller = target.$fastController; this.style = document.createElement("style") as HTMLStyleElement; @@ -148,24 +142,6 @@ class StyleElementStyleSheetTarget implements PropertyTarget { } } -/** - * Handles setting properties for a normal HTMLElement - */ -class ElementStyleSheetTarget implements PropertyTarget { - private target: PropertyTarget; - constructor(source: HTMLElement) { - this.target = source.style; - } - - setProperty(name: string, value: any) { - Updates.enqueue(() => this.target.setProperty(name, value)); - } - - removeProperty(name: string) { - Updates.enqueue(() => this.target.removeProperty(name)); - } -} - /** * Controls emission for default values. This control is capable * of emitting to multiple {@link PropertyTarget | PropertyTargets}, @@ -174,7 +150,7 @@ class ElementStyleSheetTarget implements PropertyTarget { * @internal */ export class RootStyleSheetTarget implements PropertyTarget { - private static roots = new Set(); + private static roots = new Set(); private static properties: Record = {}; public setProperty(name: string, value: any): void { RootStyleSheetTarget.properties[name] = value; @@ -191,7 +167,7 @@ export class RootStyleSheetTarget implements PropertyTarget { } } - public static registerRoot(root: HTMLElement | Document) { + public static registerRoot(root: FASTElement | Document) { const { roots } = RootStyleSheetTarget; if (!roots.has(root)) { roots.add(root); @@ -202,7 +178,7 @@ export class RootStyleSheetTarget implements PropertyTarget { } } - public static unregisterRoot(root: HTMLElement | Document) { + public static unregisterRoot(root: FASTElement | Document) { const { roots } = RootStyleSheetTarget; if (roots.has(root)) { roots.delete(root); @@ -217,7 +193,7 @@ export class RootStyleSheetTarget implements PropertyTarget { // Caches PropertyTarget instances const propertyTargetCache: WeakMap< - HTMLElement | Document, + FASTElement | Document, PropertyTarget > = new WeakMap(); // Use Constructable StyleSheets for FAST elements when supported, otherwise use @@ -232,7 +208,7 @@ const propertyTargetCtor: Constructable = ElementStyles.supports * @internal */ export const PropertyTargetManager = Object.freeze({ - getOrCreate(source: HTMLElement | Document): PropertyTarget { + getOrCreate(source: FASTElement | Document): PropertyTarget { if (propertyTargetCache.has(source)) { /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ return propertyTargetCache.get(source)!; @@ -240,16 +216,12 @@ export const PropertyTargetManager = Object.freeze({ let target: PropertyTarget; - if (source === defaultElement) { - target = new RootStyleSheetTarget(); - } else if (source instanceof Document) { + if (source instanceof Document) { target = ElementStyles.supportsAdoptedStyleSheets ? new DocumentStyleSheetTarget() : new HeadStyleElementStyleSheetTarget(); - } else if (isFastElement(source as HTMLElement)) { - target = new propertyTargetCtor(source); } else { - target = new ElementStyleSheetTarget(source as HTMLElement); + target = new propertyTargetCtor(source); } propertyTargetCache.set(source, target); diff --git a/packages/web-components/fast-foundation/src/design-token/design-token.spec.ts b/packages/web-components/fast-foundation/src/design-token/design-token.spec.ts deleted file mode 100644 index 931e25d7a33..00000000000 --- a/packages/web-components/fast-foundation/src/design-token/design-token.spec.ts +++ /dev/null @@ -1,1085 +0,0 @@ -import { css, customElement, FASTElement, html, Observable, Updates } from "@microsoft/fast-element"; -import chia, { expect } from "chai"; -import { uniqueElementName } from "@microsoft/fast-element/testing"; -import { CSSDesignToken, DesignToken, DesignTokenChangeRecord, DesignTokenSubscriber } from "./design-token.js"; -import spies from "chai-spies"; - -chia.use(spies); -const elementName = uniqueElementName("token-test"); - -@customElement({ - name: `fast-${elementName}`, - template: html`` -}) -class MyElement extends FASTElement {} - -function addElement(parent = document.body): FASTElement & HTMLElement { - const el = document.createElement(elementName) as any; - parent.appendChild(el); - return el; -} - -function removeElement(...els: HTMLElement[]) { - els.forEach(el => { - el.parentElement?.removeChild(el); - }) -} - -describe.skip("A DesignToken", () => { - beforeEach(async () => { - DesignToken.registerRoot(); - await Updates.next(); - }); - - after(async () => { - DesignToken.unregisterRoot(); - await Updates.next(); - }); - it("should support declared types", () => { - const number: DesignToken = DesignToken.create('number'); - const nil: DesignToken = DesignToken.create('number'); - const bool: DesignToken = DesignToken.create("bool"); - const arr: DesignToken = DesignToken.create("arr"); - const obj: DesignToken<{}> = DesignToken.create<{}>("obj"); - class Foo { } - const _class: DesignToken = DesignToken.create("class"); - const sym: DesignToken = DesignToken.create("symbol") - }) - - describe("that is a CSSDesignToken", () => { - it("should have a createCSS() method that returns a string with the name property formatted as a CSS variable", () => { - const add = () => void 0; - expect(DesignToken.create("implicit").createCSS(add)).to.equal("var(--implicit)"); - }); - it("should have a readonly cssCustomProperty property that is the name formatted as a CSS custom property", () => { - expect(DesignToken.create("implicit").cssCustomProperty).to.equal("--implicit"); - }); - it("should have a cssCustomProperty property that aligns with a provided cssCustomPropertyName configuration", () => { - expect(DesignToken.create({name: "name", cssCustomPropertyName: "css-custom-property"}).cssCustomProperty).to.equal("--css-custom-property") - }); - }); - - describe("that is not a CSSDesignToken", () => { - it("should not have a cssCustomProperty property", () => { - expect("cssCustomProperty" in DesignToken.create({name: "test", cssCustomPropertyName: null})).to.equal(false); - }); - it("should not have a cssVar property", () => { - expect("cssVar" in DesignToken.create({name: "test", cssCustomPropertyName: null})).to.equal(false); - }); - }); - - describe("getting and setting a simple value", () => { - it("should throw if the token value has never been set on the element or it's any ancestors", () => { - const target = addElement(); - const token = DesignToken.create("test"); - - expect(() => token.getValueFor(target)).to.throw(); - removeElement(target); - }); - - it("should return the value set for the element if one has been set", () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.setValueFor(target, 12); - - expect(token.getValueFor(target)).to.equal(12); - removeElement(target); - }); - - it("should return the value set for an ancestor if a value has not been set for the target", () => { - const ancestor = addElement(); - const target = addElement(ancestor); - const token = DesignToken.create("test"); - token.setValueFor(ancestor, 12); - - expect(token.getValueFor(target)).to.equal(12); - removeElement(ancestor); - }); - - it("sound return the nearest ancestor's value after an intermediary value is set where no value was set prior", async () => { - const grandparent = addElement(); - const parent = addElement(grandparent); - const target = addElement(parent); - - const token = DesignToken.create("test"); - - token.setValueFor(grandparent, 12); - - expect(token.getValueFor(target)).to.equal(12); - - token.setValueFor(parent, 14); - - await Updates.next(); - - expect(token.getValueFor(target)).to.equal(14); - removeElement(grandparent); - }); - - it("should return the new ancestor's value after being re-parented", async () => { - const parentA = addElement(); - const parentB = addElement(); - const target = addElement(parentA); - const token = DesignToken.create("test"); - - token.setValueFor(parentA, 12); - token.setValueFor(parentB, 14); - - expect(token.getValueFor(target)).to.equal(12); - parentA.removeChild(target); - parentB.appendChild(target); - - expect(token.getValueFor(target)).to.equal(14); - - removeElement(parentA, parentB); - }); - - it("should support getting and setting falsey values", () => { - const target = addElement(); - [false, null, 0, "", NaN].forEach(value => { - - const token = DesignToken.create("test"); - token.setValueFor(target, value); - - if (typeof value === "number" && isNaN(value)) { - expect(isNaN(token.getValueFor(target) as number)).to.equal(true) - } else { - expect(token.getValueFor(target)).to.equal(value); - } - }); - - removeElement(target); - }); - - describe("that is a CSSDesignToken", () => { - it("should set the CSS custom property for the element", async () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.setValueFor(target, 12); - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal('12'); - removeElement(target) - }); - }); - describe("that is not a CSSDesignToken", () => { - it("should not set a CSS custom property for the element", () => { - const target = addElement(); - const token = DesignToken.create({ name: "test", cssCustomPropertyName: null }); - token.setValueFor(target, 12); - expect(window.getComputedStyle(target).getPropertyValue(("--test"))).to.equal(''); - removeElement(target) - }); - }); - }); - describe("getting and setting derived values", () => { - it("should get the return value of a derived value", () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.setValueFor(target, () => 12); - - expect(token.getValueFor(target)).to.equal(12); - removeElement(target) - }); - it("should get an updated value when observable properties used in a derived property are changed", async () => { - const target = addElement(); - const token = DesignToken.create("test"); - const dependencies: { value: number } = {} as { value: number } - Observable.defineProperty(dependencies, "value"); - dependencies.value = 6 - - token.setValueFor(target, () => dependencies.value * 2); - - expect(token.getValueFor(target)).to.equal(12); - - dependencies.value = 7; - await Updates.next(); - - expect(token.getValueFor(target)).to.equal(14); - removeElement(target) - }); - it("should get an updated value when other design tokens used in a derived property are changed", async () => { - const target = addElement(); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(target, 6); - tokenB.setValueFor(target, (target) => tokenA.getValueFor(target) * 2); - - expect(tokenB.getValueFor(target)).to.equal(12); - - tokenA.setValueFor(target, 7); - await Updates.next(); - - expect(tokenB.getValueFor(target)).to.equal(14); - removeElement(target); - }); - it("should use the closest value of a dependent token when getting a token for a target", () => { - const ancestor = addElement() - const parent = addElement(ancestor); - const target = addElement(parent); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(ancestor, 7); - tokenA.setValueFor(parent, 6); - tokenB.setValueFor(ancestor, (target) => tokenA.getValueFor(target) * 2); - - const value = tokenB.getValueFor(target); - expect(value).to.equal(12); - removeElement(ancestor, parent, target); - }); - - it("should update value of a dependent token when getting a token for a target", async () => { - const ancestor = addElement() - const parent = addElement(ancestor); - const target = addElement(parent); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(ancestor, 7); - tokenA.setValueFor(parent, 6); - tokenB.setValueFor(ancestor, (target ) => tokenA.getValueFor(target) * 2); - - expect(tokenB.getValueFor(target)).to.equal(12); - - tokenA.setValueFor(parent, 7); - await Updates.next(); - - expect(tokenB.getValueFor(target)).to.equal(14); - removeElement(ancestor); - }); - - it("should get an updated value when a used design token is set for a node closer to the target", () => { - const ancestor = addElement() - const parent = addElement(ancestor); - const target = addElement(parent); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(ancestor, 6); - tokenB.setValueFor(ancestor, (target) => tokenA.getValueFor(target) * 2); - - expect(tokenB.getValueFor(target)).to.equal(12); - - tokenA.setValueFor(target, 7); - - expect(tokenB.getValueFor(target)).to.equal(14); - removeElement(ancestor); - }); - - describe("that is a CSSDesignToken", () => { - it("should set a CSS custom property equal to the resolved value of a derived token value", async () => { - const target = addElement(); - const token = DesignToken.create("test"); - - token.setValueFor(target, (target) => 12); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal('12'); - - removeElement(target); - }); - it("should set a CSS custom property equal to the resolved value of a derived token value with a dependent token", async () => { - const target = addElement(); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(target, 6); - tokenB.setValueFor(target, (target) => tokenA.getValueFor(target) * 2); - - await Updates.next(); - - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal('12'); - removeElement(target); - }); - - it("should update a CSS custom property to the resolved value of a derived token value with a dependent token when the dependent token changes", async () => { - const target = addElement(); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(target, 6); - tokenB.setValueFor(target, (target) => tokenA.getValueFor(target) * 2); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal('12'); - - tokenA.setValueFor(target, 7); - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal('14'); - - removeElement(target); - }); - - it("should set a CSS custom property equal to the resolved value for an element of a derived token value with a dependent token", async () => { - const parent = addElement(); - const target = addElement(parent); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(parent, 6); - tokenB.setValueFor(parent, (target) => tokenA.getValueFor(target) * 2); - tokenA.setValueFor(target, 7); - - await Updates.next(); - - expect(window.getComputedStyle(parent).getPropertyValue(tokenB.cssCustomProperty)).to.equal('12'); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal('14'); - removeElement(parent); - }); - - it("should set a CSS custom property equal to the resolved value for an element in a shadow DOM of a derived token value with a dependent token", async () => { - const parent = addElement(); - const child = addElement(parent); - const target = document.createElement("div"); - child.shadowRoot!.appendChild(target); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(parent, 6); - tokenB.setValueFor(parent, (target) => tokenA.getValueFor(target) * 2); - tokenA.setValueFor(target, 7); - - await Updates.next(); - - expect(window.getComputedStyle(parent).getPropertyValue(tokenB.cssCustomProperty)).to.equal('12'); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal('14'); - removeElement(parent); - }); - - it("should set a CSS custom property equal to the resolved value for both elements for which a dependent token is set when setting a derived token value", async () => { - const parent = addElement(); - const target = addElement(parent); - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - - tokenA.setValueFor(parent, 6); - tokenA.setValueFor(target, 7); - tokenB.setValueFor(parent, (target) => tokenA.getValueFor(target) * 2); - - await Updates.next(); - - expect(window.getComputedStyle(parent).getPropertyValue(tokenB.cssCustomProperty)).to.equal('12'); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal('14'); - removeElement(parent); - }); - - it("should revert a CSS custom property back to a previous value when the Design Token value is reverted", async () => { - const token = DesignToken.create("test"); - const target = addElement(); - - token.setValueFor(target, 12); - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal('12'); - - token.setValueFor(target, 14); - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal('14'); - - token.setValueFor(target, 12); - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal('12'); - }) - }); - - describe("that is not a CSSDesignToken", () => { - it("should not emit a CSS custom property", () => { - const target = addElement(); - const token = DesignToken.create({name: "test", cssCustomPropertyName: null}); - - token.setValueFor(target, (target) => 12); - - expect(window.getComputedStyle(target).getPropertyValue('--test')).to.equal(''); - - removeElement(target); - }) - }) - - it("should support getting and setting falsey values", () => { - const target = addElement(); - [false, null, 0, "", NaN].forEach(value => { - - const token = DesignToken.create("test"); - token.setValueFor(target, () => value as any); - - if (typeof value === "number" && isNaN(value)) { - expect(isNaN(token.getValueFor(target) as number)).to.equal(true) - } else { - expect(token.getValueFor(target)).to.equal(value); - } - }) - removeElement(target) - }); - }); - - describe("getting and setting a token value", () => { - it("should retrieve the value of the token it was set to", () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const target = addElement(); - - tokenA.setValueFor(target, 12); - tokenB.setValueFor(target, tokenA); - - expect(tokenB.getValueFor(target)).to.equal(12); - - removeElement(target); - }); - it("should update the value of the token it was set to when the token's value changes", () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const target = addElement(); - - tokenA.setValueFor(target, 12); - tokenB.setValueFor(target, tokenA); - - expect(tokenB.getValueFor(target)).to.equal(12); - - tokenA.setValueFor(target, 14); - - expect(tokenB.getValueFor(target)).to.equal(14); - - removeElement(target); - }); - it("should update the derived value of the token when a dependency of the derived value changes", () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const tokenC = DesignToken.create("token-b"); - const target = addElement(); - - tokenA.setValueFor(target, 6); - tokenB.setValueFor(target, (target) => tokenA.getValueFor(target) * 2); - tokenC.setValueFor(target, tokenB); - - expect(tokenC.getValueFor(target)).to.equal(12); - - tokenA.setValueFor(target, 7); - - expect(tokenC.getValueFor(target)).to.equal(14); - - removeElement(target); - }); - - describe("that is a CSSDesignToken", () => { - it("should emit a CSS custom property", () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const target = addElement(); - - tokenA.setValueFor(target, 12); - tokenB.setValueFor(target, tokenA); - - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal("12"); - - removeElement(target); - }); - it("should update the emitted CSS custom property when the token's value changes", async () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const target = addElement(); - - tokenA.setValueFor(target, 12); - tokenB.setValueFor(target, tokenA); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal("12"); - - tokenA.setValueFor(target, 14); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(tokenB.cssCustomProperty)).to.equal("14"); - - removeElement(target); - }); - it("should update the emitted CSS custom property of a token assigned a derived value when the token dependency changes", async () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const tokenC = DesignToken.create("token-c"); - const target = addElement(); - - tokenA.setValueFor(target, 6); - tokenB.setValueFor(target, (target) => tokenA.getValueFor(target) * 2); - tokenC.setValueFor(target, tokenB); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(tokenC.cssCustomProperty)).to.equal("12"); - - tokenA.setValueFor(target, 7); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(tokenC.cssCustomProperty)).to.equal("14"); - - removeElement(target); - }); - - it("should support accessing the token for being assigned from the derived value", () => { - const tokenA = DesignToken.create("token-a"); - const parent = addElement(); - const child = addElement(parent); - tokenA.withDefault(6); - const recipe = (el: HTMLElement) => tokenA.getValueFor(el.parentElement!) * 2; - tokenA.setValueFor(parent, recipe); - tokenA.setValueFor(child, recipe); - - expect(tokenA.getValueFor(parent)).to.equal(12); - expect(tokenA.getValueFor(child)).to.equal(24); - }) - }); - it("should update the CSS custom property of a derived token with a dependency that is a derived token that depends on a third token", async () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - const tokenC = DesignToken.create("token-c"); - const grandparent = addElement() - const parent = addElement(grandparent); - const child = addElement(parent); - - tokenA.setValueFor(grandparent, 3); - tokenB.setValueFor(grandparent, (el: HTMLElement) => tokenA.getValueFor(el) * 2); - tokenC.setValueFor(grandparent, (el) => tokenB.getValueFor(el) * 2) - - await Updates.next(); - - expect(tokenC.getValueFor(child)).to.equal(12); - expect(window.getComputedStyle(child).getPropertyValue(tokenC.cssCustomProperty)).to.equal("12"); - - tokenA.setValueFor(child, 4); - - await Updates.next(); - expect(tokenC.getValueFor(child)).to.equal(16); - expect(window.getComputedStyle(child).getPropertyValue(tokenC.cssCustomProperty)).to.equal("16"); - }); - it("should update tokens when an element for which a token with static dependencies is set is appended to the DOM", async () => { - - - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - - tokenA.withDefault(6); - tokenB.withDefault(el => tokenA.getValueFor(el) * 2); - - const element = document.createElement(elementName); - - tokenA.setValueFor(element, 7); - - document.body.appendChild(element); - - await Updates.next(); - - expect(window.getComputedStyle(element).getPropertyValue(tokenB.cssCustomProperty)).to.equal('14'); - }); - it("should update tokens and notify when an element for which a token with dynamic dependencies is set is appended to the DOM", async () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - - tokenA.withDefault(() => 6); - tokenB.withDefault(el => tokenA.getValueFor(el) * 2); - - const parent = document.createElement(elementName); - const child = document.createElement(elementName); - parent.appendChild(child); - - const handleChange = chia.spy(() => {}); - const subscriber = { handleChange }; - expect(tokenB.getValueFor(child)).to.equal(12); - - tokenB.subscribe(subscriber, child); - - document.body.appendChild(parent); - - await Updates.next(); - - expect(handleChange).not.to.have.been.called(); - - tokenA.setValueFor(parent, () => 7); - expect(tokenB.getValueFor(child)).to.equal(14); - await Updates.next(); - expect(handleChange).to.have.been.called.once; - }); - it("should notify a subscriber for a token after being appended to a parent with a different token value than the previous context", async () => { - const tokenA = DesignToken.create("token-a"); - const tokenB = DesignToken.create("token-b"); - - tokenA.withDefault(() => 6); - tokenB.withDefault(el => tokenA.getValueFor(el) * 2); - - const parent = document.createElement(elementName); - const child = document.createElement(elementName); - document.body.appendChild(parent); - tokenA.setValueFor(parent, () => 7); - - const handleChange = chia.spy(() => {}); - const subscriber = { handleChange }; - - expect(tokenB.getValueFor(child)).to.equal(12); - tokenB.subscribe(subscriber, child); - - expect(handleChange).not.to.have.been.called() - parent.appendChild(child); - - expect(tokenB.getValueFor(child)).to.equal(14); - expect(handleChange).to.have.been.called.once - }); - it("should notify a subscriber for a token after being appended to a parent with a different token value than the previous context", async () => { - const tokenA = DesignToken.create("token-a"); - tokenA.withDefault(6); - - const parent = document.createElement(elementName); - const child = document.createElement(elementName); - document.body.appendChild(parent); - tokenA.setValueFor(parent, 7); - - const handleChange = chia.spy(() => {}); - const subscriber = { handleChange }; - - expect(tokenA.getValueFor(child)).to.equal(6); - tokenA.subscribe(subscriber, child); - expect(handleChange).not.to.have.been.called() - parent.appendChild(child); - - expect(handleChange).to.have.been.called.once; - expect(tokenA.getValueFor(child)).to.equal(7); - }); - }) - describe("deleting simple values", () => { - it("should throw when deleted and no parent token value is set", () => { - const target = addElement(); - const token = DesignToken.create("test"); - - token.setValueFor(target, 12); - - expect(token.getValueFor(target)).to.equal(12) - - token.deleteValueFor(target); - - expect(() => token.getValueFor(target)).to.throw(); - removeElement(target) - }); - it("should allow getting a value that was set upstream", () => { - const parent = addElement() - const target = addElement(parent); - const token = DesignToken.create("test"); - - token.setValueFor(parent, 12); - token.setValueFor(target, 14); - - expect(token.getValueFor(target)).to.equal(14) - - token.deleteValueFor(target); - - expect(token.getValueFor(target)).to.equal(12) - removeElement(parent) - }); - }); - describe("deleting derived values", () => { - it("should throw when deleted and no parent token value is set", () => { - const target = addElement(); - const token = DesignToken.create("test"); - - token.setValueFor(target, () => 12); - - expect(token.getValueFor(target)).to.equal(12) - - token.deleteValueFor(target); - - expect(() => token.getValueFor(target)).to.throw(); - removeElement(target) - }); - it("should allow getting a value that was set upstream", () => { - const parent = addElement() - const target = addElement(parent); - const token = DesignToken.create("test"); - - token.setValueFor(parent, () => 12); - token.setValueFor(target, () => 14); - - expect(token.getValueFor(target)).to.equal(14) - - token.deleteValueFor(target); - - expect(token.getValueFor(target)).to.equal(12) - removeElement(parent) - }); - - it("should cause dependent tokens to re-evaluate", () => { - const tokenA = DesignToken.create("A"); - const tokenB = DesignToken.create("B"); - const parent = addElement(); - const target = addElement(parent); - - tokenA.setValueFor(parent, 7); - tokenA.setValueFor(target, 6); - tokenB.setValueFor(target, (element) => tokenA.getValueFor(element) * 2); - - expect(tokenB.getValueFor(target)).to.equal(12); - - tokenA.deleteValueFor(target); - - expect(tokenB.getValueFor(target)).to.equal(14); - removeElement(parent) - }) - }); - - describe("when used as a CSSDirective", () => { - it("should set a CSS custom property for the element when the token is set for the element", async () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.setValueFor(target, 12); - const styles = css`:host{width: calc(${token} * 1px);}` - target.$fastController.addStyles(styles); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - removeElement(target) - }); - it("should set a CSS custom property for the element when the token is set for an ancestor element", async () => { - const parent = addElement() - const target = addElement(parent); - const token = DesignToken.create("test"); - token.setValueFor(parent, 12); - const styles = css`:host{width: calc(${token} * 1px);}` - target.$fastController.addStyles(styles); - - await Updates.next(); - expect(window.getComputedStyle(target).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - removeElement(parent) - }) - }); - - describe("with a default value set", () => { - it("should return the default value if no value is set for a target", () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.withDefault(2) - - expect(token.getValueFor(target)).to.equal(2); - removeElement(target) - }); - it("should return the default value for a descendent if no value is set for a target", () => { - const parent = addElement() - const target = addElement(parent); - const token = DesignToken.create("test"); - token.withDefault(2) - - expect(token.getValueFor(target)).to.equal(2); - removeElement(parent) - }); - it("should return the value set and not the default if value is set", () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.withDefault(4) - token.setValueFor(target, 2) - - expect(token.getValueFor(target)).to.equal(2); - removeElement(target) - }); - it("should get a new default value if a new default is provided", () => { - const target = addElement(); - const token = DesignToken.create("test"); - token.withDefault(2); - token.withDefault(4); - - expect(token.getValueFor(target)).to.equal(4); - removeElement(target) - }); - }); - - describe("with subscribers", () => { - it("should notify an un-targeted subscriber when the value changes for any element", () => { - const ancestor = addElement(); - const parent = addElement(ancestor); - const target = addElement(parent); - const token = DesignToken.create("test"); - const spy = new Map([[ancestor, false], [parent, false], [ target, false ]]); - - const subscriber: DesignTokenSubscriber = { - handleChange(record: DesignTokenChangeRecord) { - spy.set(record.target, true) - } - } - - token.subscribe(subscriber); - - expect(spy.get(ancestor)).to.be.false; - expect(spy.get(parent)).to.be.false; - expect(spy.get(target)).to.be.false; - - token.setValueFor(ancestor, 12); - expect(spy.get(ancestor)).to.be.true; - expect(spy.get(parent)).to.be.false; - expect(spy.get(target)).to.be.false; - - token.setValueFor(parent, 14); - expect(spy.get(ancestor)).to.be.true; - expect(spy.get(parent)).to.be.true; - expect(spy.get(target)).to.be.false; - - token.setValueFor(target, 16); - expect(spy.get(target)).to.be.true; - expect(spy.get(parent)).to.be.true; - expect(spy.get(target)).to.be.true; - - removeElement(ancestor); - }); - it("should notify a target-subscriber if the value is changed for the provided target", () => { - const parent = addElement(); - const target = addElement(parent); - const token = DesignToken.create("test"); - - const handleChange = chia.spy(() => {}); - const subscriber: DesignTokenSubscriber = { - handleChange - } - - token.subscribe(subscriber, target); - - token.setValueFor(parent, 12); - expect(handleChange).to.have.been.called.once; - - token.setValueFor(target, 14); - expect(handleChange).to.have.been.called.twice; - - removeElement(parent); - }); - - it("should not notify a subscriber after unsubscribing", () => { - let invoked = false; - const target = addElement(); - const token = DesignToken.create("test"); - - const subscriber: DesignTokenSubscriber = { - handleChange(record: DesignTokenChangeRecord) { - invoked = true; - } - } - - token.subscribe(subscriber); - token.unsubscribe(subscriber); - - token.setValueFor(target, 12); - expect(invoked).to.be.false; - - removeElement(target); - }); - - it("should infer DesignToken and CSSDesignToken token types on subscription record", () => { - type AssertCSSDesignToken = T extends CSSDesignToken ? T : never; - DesignToken.create("css").subscribe({handleChange(record) { - const test: AssertCSSDesignToken = record.token; - }}); - - type AssertDesignToken = T extends CSSDesignToken ? never : T; - - DesignToken.create({name: "no-css", cssCustomPropertyName: null}).subscribe({handleChange(record) { - const test: AssertDesignToken = record.token; - }}) - }); - - it("should notify a subscriber when a dependency of a subscribed token changes", async () => { - const tokenA = DesignToken.create("a"); - const tokenB = DesignToken.create("b"); - - tokenA.withDefault(6); - tokenB.withDefault((el) => tokenA.getValueFor(el) * 2); - - const handleChange = chia.spy(() => {}) - const subscriber = { - handleChange - } - - - tokenB.subscribe(subscriber); - - tokenA.withDefault(7); - await Updates.next(); - expect(handleChange).to.have.been.called(); - }); - - it("should notify a subscriber when a dependency of a dependency of a subscribed token changes", async () => { - const tokenA = DesignToken.create("a"); - const tokenB = DesignToken.create("b"); - const tokenC = DesignToken.create("c"); - - tokenA.withDefault(6); - tokenB.withDefault((el) => tokenA.getValueFor(el) * 2); - tokenC.withDefault((el) => tokenB.getValueFor(el) * 2); - - const handleChange = chia.spy(() => {}) - const subscriber = { - handleChange - } - - - tokenC.subscribe(subscriber); - - tokenA.withDefault(7); - await Updates.next(); - expect(handleChange).to.have.been.called() - }); - - it("should notify a subscriber when a dependency changes for an element down the DOM tree", async () => { - const tokenA = DesignToken.create("a"); - const tokenB = DesignToken.create("b"); - - const target = addElement(); - - tokenA.withDefault(6); - tokenB.withDefault((el) => tokenA.getValueFor(el) * 2); - - const handleChange = chia.spy(() => {}) - const subscriber = { - handleChange - } - - - tokenB.subscribe(subscriber); - - tokenA.setValueFor(target, 7); - await Updates.next(); - expect(handleChange).to.have.been.called(); - }) - it("should notify a subscriber when a static-value dependency of subscribed token changes for a parent of the subscription target", async () => { - const tokenA = DesignToken.create("a"); - const tokenB = DesignToken.create("b"); - - const parent = addElement(); - const target = addElement(parent) - - tokenA.withDefault(6); - tokenB.withDefault((el) => tokenA.getValueFor(el) * 2); - - const handleChange = chia.spy(() => {}) - const subscriber = { - handleChange - } - - - tokenB.subscribe(subscriber, target); - - tokenA.setValueFor(parent, 7); - await Updates.next(); - expect(handleChange).to.have.been.called(); - expect(tokenB.getValueFor(target)).to.equal(14) - }); - it("should notify a subscriber when a derived-value dependency of subscribed token changes for a parent of the subscription target", async () => { - const tokenA = DesignToken.create("a"); - const tokenB = DesignToken.create("b"); - - const parent = addElement(); - const target = addElement(parent) - - tokenA.withDefault(() => 6); - tokenB.withDefault((el) => tokenA.getValueFor(el) * 2); - - const handleChange = chia.spy(() => {}) - const subscriber = { - handleChange - } - - - tokenB.subscribe(subscriber, target); - - tokenA.setValueFor(parent, () => 7); - await Updates.next(); - expect(handleChange).to.have.been.called(); - expect(tokenB.getValueFor(target)).to.equal(14) - }); - it("should notify a subscriber when a dependency of subscribed token changes for a parent of the subscription target", async () => { - const tokenA = DesignToken.create("a"); - const tokenB = DesignToken.create("b"); - - const grandparent = addElement(); - const parent = addElement(grandparent) - const child = addElement(parent) - - tokenA.withDefault(() => 6); - tokenB.withDefault((el) => tokenA.getValueFor(el) * 2); - - const handleChange = chia.spy(() => {}) - const subscriber = { - handleChange - } - - - tokenB.subscribe(subscriber, child); - - tokenA.setValueFor(grandparent, () => 7); - await Updates.next(); - expect(handleChange).to.have.been.called(); - }); - }); - - describe("with root registration", () => { - it("should not emit CSS custom properties for the default value", () => { - DesignToken.unregisterRoot(); - const token = DesignToken.create('default-no-root').withDefault(12); - const styles = window.getComputedStyle(document.body); - - expect(styles.getPropertyValue(token.cssCustomProperty)).to.equal(""); - }); - - it("should emit CSS custom properties for the default value when a design token root is registered", async () => { - const token = DesignToken.create('default-with-root').withDefault(12); - const styles = window.getComputedStyle(document.body); - - DesignToken.registerRoot(); - - await Updates.next(); - - expect(styles.getPropertyValue(token.cssCustomProperty)).to.equal("12"); - DesignToken.unregisterRoot(); - }); - - it("should remove emitted CSS custom properties for a root when the root is deregistered", async () => { - const token = DesignToken.create('deregistered-root').withDefault(12); - const styles = window.getComputedStyle(document.body); - - DesignToken.registerRoot(); - - await Updates.next(); - - expect(styles.getPropertyValue(token.cssCustomProperty)).to.equal("12"); - DesignToken.unregisterRoot(); - - await Updates.next(); - - expect(styles.getPropertyValue(token.cssCustomProperty)).to.equal(""); - }); - - it("should emit CSS custom properties to an element when the element is provided as a root", async () => { - const token = DesignToken.create('default-with-element-root').withDefault(12); - const element = addElement(); - - DesignToken.registerRoot(element); - - await Updates.next(); - const styles = window.getComputedStyle(element); - - expect(styles.getPropertyValue(token.cssCustomProperty)).to.equal("12"); - DesignToken.unregisterRoot(element); - }); - it("should emit CSS custom properties to multiple roots", async () => { - DesignToken.unregisterRoot(); - const token = DesignToken.create('default-with-multiple-roots').withDefault(12); - const a = addElement(); - const b = addElement(); - - DesignToken.registerRoot(a); - await Updates.next(); - - expect(window.getComputedStyle(a).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - expect(window.getComputedStyle(b).getPropertyValue(token.cssCustomProperty)).to.equal(""); - expect(window.getComputedStyle(document.body).getPropertyValue(token.cssCustomProperty)).to.equal(""); - - DesignToken.registerRoot(b); - await Updates.next(); - expect(window.getComputedStyle(a).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - expect(window.getComputedStyle(b).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - expect(window.getComputedStyle(document.body).getPropertyValue(token.cssCustomProperty)).to.equal(""); - - DesignToken.registerRoot(); - await Updates.next(); - expect(window.getComputedStyle(a).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - expect(window.getComputedStyle(b).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - expect(window.getComputedStyle(document.body).getPropertyValue(token.cssCustomProperty)).to.equal("12"); - }); - }); -}); diff --git a/packages/web-components/fast-foundation/src/design-token/design-token.ts b/packages/web-components/fast-foundation/src/design-token/design-token.ts deleted file mode 100644 index 598ee10dab4..00000000000 --- a/packages/web-components/fast-foundation/src/design-token/design-token.ts +++ /dev/null @@ -1,930 +0,0 @@ -import { - Behavior, - Binding, - BindingObserver, - CSSDirective, - Disposable, - ExecutionContext, - FASTElement, - observable, - Observable, - Subscriber, -} from "@microsoft/fast-element"; -import { composedContains, composedParent } from "@microsoft/fast-element/utilities"; -import { - PropertyTargetManager, - RootStyleSheetTarget, -} from "./custom-property-manager.js"; -import type { - DerivedDesignTokenValue, - DesignTokenConfiguration, - DesignTokenValue, - StaticDesignTokenValue, -} from "./interfaces.js"; -import { defaultElement } from "./custom-property-manager.js"; -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/** - * Describes a DesignToken instance. - * @public - */ -export interface DesignToken< - T extends string | number | boolean | BigInteger | null | Array | symbol | {} -> { - /** - * The name of the token - */ - readonly name: string; - - /** - * A list of elements for which the DesignToken has a value set - */ - readonly appliedTo: HTMLElement[]; - - /** - * Get the token value for an element. - * @param element - The element to get the value for - * @returns - The value set for the element, or the value set for the nearest element ancestor. - */ - getValueFor(element: HTMLElement): StaticDesignTokenValue; - - /** - * Sets the token to a value for an element. - * @param element - The element to set the value for. - * @param value - The value. - */ - setValueFor(element: HTMLElement, value: DesignTokenValue | DesignToken): void; - - /** - * Removes a value set for an element. - * @param element - The element to remove the value from - */ - deleteValueFor(element: HTMLElement): this; - - /** - * Associates a default value to the token - */ - withDefault(value: DesignTokenValue | DesignToken): this; - - /** - * Subscribes a subscriber to change records for a token. If an element is provided, only - * change records for that element will be emitted. - */ - subscribe(subscriber: DesignTokenSubscriber, target?: HTMLElement): void; - - /** - * Unsubscribes a subscriber from change records for a token. - */ - unsubscribe(subscriber: DesignTokenSubscriber, target?: HTMLElement): void; -} - -/** - * A {@link (DesignToken:interface)} that emits a CSS custom property. - * @public - */ -export interface CSSDesignToken< - T extends - | string - | number - | boolean - | BigInteger - | null - | Array - | symbol - | ({ - createCSS?(): string; - } & Record) -> extends DesignToken, CSSDirective { - /** - * The {@link (DesignToken:interface)} formatted as a CSS custom property if the token is - * configured to write a CSS custom property. - */ - readonly cssCustomProperty: string; -} - -/** - * Change record provided to to a {@link DesignTokenSubscriber} when a token changes for a target. - * @public - */ -export interface DesignTokenChangeRecord> { - /** - * The element for which the value was changed - */ - target: HTMLElement; - - /** - * The token that was changed - */ - token: T; -} - -/** - * A subscriber that should receive {@link DesignTokenChangeRecord | change records} when a token changes for a target - * @public - */ -export interface DesignTokenSubscriber> { - handleChange(record: DesignTokenChangeRecord): void; -} - -/** - * Implementation of {@link (DesignToken:interface)} - */ -class DesignTokenImpl - implements CSSDirective, DesignToken { - public readonly name: string; - public readonly cssCustomProperty: string | undefined; - public readonly id: string; - private cssVar: string | undefined; - private subscribers = new WeakMap< - HTMLElement | this, - Set> - >(); - private _appliedTo = new Set(); - public get appliedTo() { - return [...this._appliedTo]; - } - - public static from( - nameOrConfig: string | DesignTokenConfiguration - ): DesignTokenImpl { - return new DesignTokenImpl({ - name: typeof nameOrConfig === "string" ? nameOrConfig : nameOrConfig.name, - cssCustomPropertyName: - typeof nameOrConfig === "string" - ? nameOrConfig - : nameOrConfig.cssCustomPropertyName === void 0 - ? nameOrConfig.name - : nameOrConfig.cssCustomPropertyName, - }); - } - - public static isCSSDesignToken( - token: DesignToken | CSSDesignToken - ): token is CSSDesignToken { - return typeof (token as CSSDesignToken).cssCustomProperty === "string"; - } - - public static isDerivedDesignTokenValue( - value: any - ): value is DerivedDesignTokenValue { - return typeof value === "function"; - } - - public static uniqueId: () => string = (() => { - let id = 0; - return () => { - id++; - return id.toString(16); - }; - })(); - - /** - * Gets a token by ID. Returns undefined if the token was not found. - * @param id - The ID of the token - * @returns - */ - public static getTokenById(id: string): DesignTokenImpl | undefined { - return DesignTokenImpl.tokensById.get(id); - } - - /** - * Token storage by token ID - */ - private static tokensById = new Map>(); - - private getOrCreateSubscriberSet( - target: HTMLElement | this = this - ): Set> { - return ( - this.subscribers.get(target) || - (this.subscribers.set(target, new Set()) && this.subscribers.get(target)!) - ); - } - - constructor(configuration: Required) { - this.name = configuration.name; - - if (configuration.cssCustomPropertyName !== null) { - this.cssCustomProperty = `--${configuration.cssCustomPropertyName}`; - this.cssVar = `var(${this.cssCustomProperty})`; - } - - this.id = DesignTokenImpl.uniqueId(); - DesignTokenImpl.tokensById.set(this.id, this); - } - - public createCSS(): string { - return this.cssVar || ""; - } - - public getValueFor(element: HTMLElement): StaticDesignTokenValue { - const value = DesignTokenNode.getOrCreate(element).get(this); - - if (value !== undefined) { - return value; - } - - throw new Error( - `Value could not be retrieved for token named "${this.name}". Ensure the value is set for ${element} or an ancestor of ${element}.` - ); - } - - public setValueFor( - element: HTMLElement, - value: DesignTokenValue | DesignToken - ): this { - this._appliedTo.add(element); - if (value instanceof DesignTokenImpl) { - value = this.alias(value); - } - - DesignTokenNode.getOrCreate(element).set(this, value as DesignTokenValue); - return this; - } - - public deleteValueFor(element: HTMLElement): this { - this._appliedTo.delete(element); - - if (DesignTokenNode.existsFor(element)) { - DesignTokenNode.getOrCreate(element).delete(this); - } - - return this; - } - - public withDefault(value: DesignTokenValue | DesignToken) { - this.setValueFor(defaultElement, value); - return this; - } - - public subscribe( - subscriber: DesignTokenSubscriber, - target?: HTMLElement - ): void { - const subscriberSet = this.getOrCreateSubscriberSet(target); - - if (target && !DesignTokenNode.existsFor(target)) { - DesignTokenNode.getOrCreate(target); - } - - if (!subscriberSet.has(subscriber)) { - subscriberSet.add(subscriber); - } - } - - public unsubscribe( - subscriber: DesignTokenSubscriber, - target?: HTMLElement - ): void { - const list = this.subscribers.get(target || this); - - if (list && list.has(subscriber)) { - list.delete(subscriber); - } - } - - /** - * Notifies subscribers that the value for an element has changed. - * @param element - The element to emit a notification for - */ - public notify(element: HTMLElement) { - const record = Object.freeze({ token: this, target: element }); - - if (this.subscribers.has(this)) { - this.subscribers.get(this)!.forEach(sub => sub.handleChange(record)); - } - - if (this.subscribers.has(element)) { - this.subscribers.get(element)!.forEach(sub => sub.handleChange(record)); - } - } - - /** - * Alias the token to the provided token. - * @param token - the token to alias to - */ - private alias(token: DesignToken): DerivedDesignTokenValue { - return (((target: HTMLElement) => - token.getValueFor(target)) as unknown) as DerivedDesignTokenValue; - } -} - -class CustomPropertyReflector { - public startReflection(token: CSSDesignToken, target: HTMLElement) { - token.subscribe(this, target); - this.handleChange({ token, target }); - } - - public stopReflection(token: CSSDesignToken, target: HTMLElement) { - token.unsubscribe(this, target); - this.remove(token, target); - } - - public handleChange(record: DesignTokenChangeRecord) { - const { token, target } = record; - this.add(token, target); - } - - private add(token: CSSDesignToken, target: HTMLElement) { - PropertyTargetManager.getOrCreate(target).setProperty( - token.cssCustomProperty, - this.resolveCSSValue( - DesignTokenNode.getOrCreate(target).get(token as DesignTokenImpl) - ) - ); - } - - private remove(token: CSSDesignToken, target: HTMLElement) { - PropertyTargetManager.getOrCreate(target).removeProperty(token.cssCustomProperty); - } - - private resolveCSSValue(value: any) { - return value && typeof value.createCSS === "function" ? value.createCSS() : value; - } -} - -/** - * A light wrapper around BindingObserver to handle value caching and - * token notification - */ -class DesignTokenBindingObserver implements Disposable { - public readonly dependencies = new Set>(); - private observer: BindingObserver>; - constructor( - public readonly source: Binding>, - public readonly token: DesignTokenImpl, - public readonly node: DesignTokenNode - ) { - this.observer = Observable.binding(source, this, false); - - // This is a little bit hacky because it's using internal APIs of BindingObserverImpl. - // BindingObserverImpl queues updates to batch it's notifications which doesn't work for this - // scenario because the DesignToken.getValueFor API is not async. Without this, using DesignToken.getValueFor() - // after DesignToken.setValueFor() when setting a dependency of the value being retrieved can return a stale - // value. Assigning .handleChange to .call forces immediate invocation of this classes handleChange() method, - // allowing resolution of values synchronously. - // TODO: https://github.com/microsoft/fast/issues/5110 - (this.observer as any).handleChange = (this.observer as any).call; - this.handleChange(); - } - - public dispose(): void { - this.observer.dispose(); - } - - /** - * @internal - */ - public handleChange() { - this.node.store.set( - this.token, - - (this.observer.observe( - this.node.target, - ExecutionContext.default - ) as unknown) as StaticDesignTokenValue - ); - } -} - -/** - * Stores resolved token/value pairs and notifies on changes - */ -class Store { - private values = new Map, any>(); - set(token: DesignTokenImpl, value: StaticDesignTokenValue) { - if (this.values.get(token) !== value) { - this.values.set(token, value); - Observable.getNotifier(this).notify(token.id); - } - } - - get(token: DesignTokenImpl): StaticDesignTokenValue | undefined { - Observable.track(this, token.id); - return this.values.get(token); - } - - delete(token: DesignTokenImpl) { - this.values.delete(token); - } - - all() { - return this.values.entries(); - } -} - -const nodeCache = new WeakMap(); -const childToParent = new WeakMap(); - -/** - * A node responsible for setting and getting token values, - * emitting values to CSS custom properties, and maintaining - * inheritance structures. - */ -class DesignTokenNode implements Behavior, Subscriber { - /** - * Returns a DesignTokenNode for an element. - * Creates a new instance if one does not already exist for a node, - * otherwise returns the cached instance - * - * @param target - The HTML element to retrieve a DesignTokenNode for - */ - public static getOrCreate(target: HTMLElement): DesignTokenNode { - return nodeCache.get(target) || new DesignTokenNode(target); - } - - /** - * Determines if a DesignTokenNode has been created for a target - * @param target - The element to test - */ - public static existsFor(target: HTMLElement): boolean { - return nodeCache.has(target); - } - - /** - * Searches for and return the nearest parent DesignTokenNode. - * Null is returned if no node is found or the node provided is for a default element. - */ - public static findParent(node: DesignTokenNode): DesignTokenNode | null { - if (!(defaultElement === node.target)) { - let parent = composedParent(node.target); - - while (parent !== null) { - if (nodeCache.has(parent)) { - return nodeCache.get(parent)!; - } - - parent = composedParent(parent); - } - - return DesignTokenNode.getOrCreate(defaultElement); - } - - return null; - } - - /** - * Finds the closest node with a value explicitly assigned for a token, otherwise null. - * @param token - The token to look for - * @param start - The node to start looking for value assignment - * @returns - */ - public static findClosestAssignedNode( - token: DesignTokenImpl, - start: DesignTokenNode - ): DesignTokenNode | null { - let current: DesignTokenNode | null = start; - do { - if (current.has(token)) { - return current; - } - - current = current.parent - ? current.parent - : current.target !== defaultElement - ? DesignTokenNode.getOrCreate(defaultElement) - : null; - } while (current !== null); - - return null; - } - - /** - * Responsible for reflecting tokens to CSS custom properties - */ - public static cssCustomPropertyReflector = new CustomPropertyReflector(); - - /** - * Stores all resolved token values for a node - */ - public readonly store: Store = new Store(); - - /** - * The parent DesignTokenNode, or null. - */ - public get parent(): DesignTokenNode | null { - return childToParent.get(this) || null; - } - - /** - * All children assigned to the node - */ - @observable - private children: Array = []; - - /** - * All values explicitly assigned to the node in their raw form - */ - private assignedValues: Map, DesignTokenValue> = new Map(); - - /** - * Tokens currently being reflected to CSS custom properties - */ - private reflecting = new Set>(); - - /** - * Binding observers for assigned and inherited derived values. - */ - private bindingObservers = new Map< - DesignTokenImpl, - DesignTokenBindingObserver - >(); - - /** - * Emits notifications to token when token values - * change the DesignTokenNode - */ - private tokenValueChangeHandler: Subscriber = { - handleChange: (source: Store, arg: string) => { - const token = DesignTokenImpl.getTokenById(arg); - - if (token) { - // Notify any token subscribers - token.notify(this.target); - - if (DesignTokenImpl.isCSSDesignToken(token)) { - const parent = this.parent; - const reflecting = this.isReflecting(token); - - if (parent) { - const parentValue = parent.get(token); - const sourceValue = source.get(token); - if (parentValue !== sourceValue && !reflecting) { - this.reflectToCSS(token); - } else if (parentValue === sourceValue && reflecting) { - this.stopReflectToCSS(token); - } - } else if (!reflecting) { - this.reflectToCSS(token); - } - } - } - }, - }; - - constructor(public readonly target: HTMLElement | (HTMLElement & FASTElement)) { - nodeCache.set(target, this); - - // Map store change notifications to token change notifications - Observable.getNotifier(this.store).subscribe(this.tokenValueChangeHandler); - - if (target instanceof FASTElement) { - (target as FASTElement).$fastController.addBehaviors([this]); - } else if (target.isConnected) { - this.bind(); - } - } - - /** - * Checks if a token has been assigned an explicit value the node. - * @param token - the token to check. - */ - public has(token: DesignTokenImpl): boolean { - return this.assignedValues.has(token); - } - - /** - * Gets the value of a token for a node - * @param token - The token to retrieve the value for - * @returns - */ - public get(token: DesignTokenImpl): StaticDesignTokenValue | undefined { - const value = this.store.get(token); - - if (value !== undefined) { - return value; - } - - const raw = this.getRaw(token); - - if (raw !== undefined) { - this.hydrate(token, raw); - return this.get(token); - } - } - - /** - * Retrieves the raw assigned value of a token from the nearest assigned node. - * @param token - The token to retrieve a raw value for - * @returns - */ - public getRaw(token: DesignTokenImpl): DesignTokenValue | undefined { - if (this.assignedValues.has(token)) { - return this.assignedValues.get(token); - } - - return DesignTokenNode.findClosestAssignedNode(token, this)?.getRaw(token); - } - - /** - * Sets a token to a value for a node - * @param token - The token to set - * @param value - The value to set the token to - */ - public set(token: DesignTokenImpl, value: DesignTokenValue): void { - if (DesignTokenImpl.isDerivedDesignTokenValue(this.assignedValues.get(token))) { - this.tearDownBindingObserver(token); - } - - this.assignedValues.set(token, value); - - if (DesignTokenImpl.isDerivedDesignTokenValue(value)) { - this.setupBindingObserver(token, value); - } else { - this.store.set(token, value); - } - } - - /** - * Deletes a token value for the node. - * @param token - The token to delete the value for - */ - public delete(token: DesignTokenImpl): void { - this.assignedValues.delete(token); - this.tearDownBindingObserver(token); - const upstream = this.getRaw(token); - - if (upstream) { - this.hydrate(token, upstream); - } else { - this.store.delete(token); - } - } - - /** - * Invoked when the DesignTokenNode.target is attached to the document - */ - public bind(): void { - const parent = DesignTokenNode.findParent(this); - - if (parent) { - parent.appendChild(this); - } - - for (const key of this.assignedValues.keys()) { - key.notify(this.target); - } - } - - /** - * Invoked when the DesignTokenNode.target is detached from the document - */ - public unbind(): void { - if (this.parent) { - const parent = childToParent.get(this)!; - parent.removeChild(this); - } - } - - /** - * Appends a child to a parent DesignTokenNode. - * @param child - The child to append to the node - */ - public appendChild(child: DesignTokenNode): void { - if (child.parent) { - childToParent.get(child)!.removeChild(child); - } - const reParent = this.children.filter(x => child.contains(x)); - - childToParent.set(child, this); - this.children.push(child); - - reParent.forEach(x => child.appendChild(x)); - - Observable.getNotifier(this.store).subscribe(child); - - // How can we not notify *every* subscriber? - for (const [token, value] of this.store.all()) { - child.hydrate( - token, - this.bindingObservers.has(token) ? this.getRaw(token) : value - ); - } - } - - /** - * Removes a child from a node. - * @param child - The child to remove. - */ - public removeChild(child: DesignTokenNode): boolean { - const childIndex = this.children.indexOf(child); - - if (childIndex !== -1) { - this.children.splice(childIndex, 1); - } - - Observable.getNotifier(this.store).unsubscribe(child); - return child.parent === this ? childToParent.delete(child) : false; - } - - /** - * Tests whether a provided node is contained by - * the calling node. - * @param test - The node to test - */ - public contains(test: DesignTokenNode): boolean { - return composedContains(this.target, test.target); - } - - /** - * Instructs the node to reflect a design token for the provided token. - * @param token - The design token to reflect - */ - public reflectToCSS(token: CSSDesignToken) { - if (!this.isReflecting(token)) { - this.reflecting.add(token); - DesignTokenNode.cssCustomPropertyReflector.startReflection( - token, - this.target - ); - } - } - - /** - * Stops reflecting a DesignToken to CSS - * @param token - The design token to stop reflecting - */ - public stopReflectToCSS(token: CSSDesignToken) { - if (this.isReflecting(token)) { - this.reflecting.delete(token); - - DesignTokenNode.cssCustomPropertyReflector.stopReflection(token, this.target); - } - } - - /** - * Determines if a token is being reflected to CSS for a node. - * @param token - The token to check for reflection - * @returns - */ - public isReflecting(token: CSSDesignToken): boolean { - return this.reflecting.has(token); - } - - /** - * Handle changes to upstream tokens - * @param source - The parent DesignTokenNode - * @param property - The token ID that changed - */ - public handleChange(source: Store, property: string) { - const token = DesignTokenImpl.getTokenById(property); - - if (!token) { - return; - } - - this.hydrate(token, this.getRaw(token)); - } - - /** - * Hydrates a token with a DesignTokenValue, making retrieval available. - * @param token - The token to hydrate - * @param value - The value to hydrate - */ - public hydrate(token: DesignTokenImpl, value: DesignTokenValue) { - if (!this.has(token)) { - const observer = this.bindingObservers.get(token); - - if (DesignTokenImpl.isDerivedDesignTokenValue(value)) { - if (observer) { - // If the binding source doesn't match, we need - // to update the binding - if ((observer.source as any) !== value) { - this.tearDownBindingObserver(token); - this.setupBindingObserver(token, value); - } - } else { - this.setupBindingObserver(token, value); - } - } else { - if (observer) { - this.tearDownBindingObserver(token); - } - - this.store.set(token, value as StaticDesignTokenValue); - } - } - } - - /** - * Sets up a binding observer for a derived token value that notifies token - * subscribers on change. - * - * @param token - The token to notify when the binding updates - * @param source - The binding source - */ - private setupBindingObserver( - token: DesignTokenImpl, - source: DerivedDesignTokenValue - ): DesignTokenBindingObserver { - const binding = new DesignTokenBindingObserver(source as any, token, this); - - this.bindingObservers.set(token, binding); - return binding; - } - - /** - * Tear down a binding observer for a token. - */ - private tearDownBindingObserver(token: DesignTokenImpl): boolean { - if (this.bindingObservers.has(token)) { - this.bindingObservers.get(token)!.dispose(); - this.bindingObservers.delete(token); - return true; - } - - return false; - } -} -/* eslint-disable @typescript-eslint/no-unused-vars */ -function create( - nameOrConfig: string | DesignTokenConfiguration -): never; -function create( - nameOrConfig: string | DesignTokenConfiguration -): never; -function create(nameOrConfig: string): CSSDesignToken; -function create( - nameOrConfig: - | Omit - | (DesignTokenConfiguration & Record<"cssCustomPropertyName", string>) -): CSSDesignToken; -function create( - nameOrConfig: DesignTokenConfiguration & Record<"cssCustomPropertyName", null> -): DesignToken; -function create(nameOrConfig: string | DesignTokenConfiguration): any { - return DesignTokenImpl.from(nameOrConfig); -} -/* eslint-enable @typescript-eslint/no-unused-vars */ -/** - * Factory object for creating {@link (DesignToken:interface)} instances. - * @public - */ -export const DesignToken = Object.freeze({ - create, - - /** - * Informs DesignToken that an HTMLElement for which tokens have - * been set has been connected to the document. - * - * The browser does not provide a reliable mechanism to observe an HTMLElement's connectedness - * in all scenarios, so invoking this method manually is necessary when: - * - * 1. Token values are set for an HTMLElement. - * 2. The HTMLElement does not inherit from FASTElement. - * 3. The HTMLElement is not connected to the document when token values are set. - * - * @param element - The element to notify - * @returns - true if notification was successful, otherwise false. - */ - notifyConnection(element: HTMLElement): boolean { - if (!element.isConnected || !DesignTokenNode.existsFor(element)) { - return false; - } - - DesignTokenNode.getOrCreate(element).bind(); - - return true; - }, - - /** - * Informs DesignToken that an HTMLElement for which tokens have - * been set has been disconnected to the document. - * - * The browser does not provide a reliable mechanism to observe an HTMLElement's connectedness - * in all scenarios, so invoking this method manually is necessary when: - * - * 1. Token values are set for an HTMLElement. - * 2. The HTMLElement does not inherit from FASTElement. - * - * @param element - The element to notify - * @returns - true if notification was successful, otherwise false. - */ - notifyDisconnection(element: HTMLElement): boolean { - if (element.isConnected || !DesignTokenNode.existsFor(element)) { - return false; - } - - DesignTokenNode.getOrCreate(element).unbind(); - return true; - }, - - /** - * Registers and element or document as a DesignToken root. - * {@link CSSDesignToken | CSSDesignTokens} with default values assigned via - * {@link (DesignToken:interface).withDefault} will emit CSS custom properties to all - * registered roots. - * @param target - The root to register - */ - registerRoot(target: HTMLElement | Document = defaultElement) { - RootStyleSheetTarget.registerRoot(target); - }, - - /** - * Unregister an element or document as a DesignToken root. - * @param target - The root to deregister - */ - unregisterRoot(target: HTMLElement | Document = defaultElement) { - RootStyleSheetTarget.unregisterRoot(target); - }, -}); -/* eslint-enable @typescript-eslint/no-non-null-assertion */ diff --git a/packages/web-components/fast-foundation/src/design-token/exports.ts b/packages/web-components/fast-foundation/src/design-token/exports.ts new file mode 100644 index 00000000000..dc075873a42 --- /dev/null +++ b/packages/web-components/fast-foundation/src/design-token/exports.ts @@ -0,0 +1,14 @@ +export { + DesignToken, + CSSDesignToken, + DesignTokenConfiguration, + CSSDesignTokenConfiguration, + DesignTokenChangeRecord, + DesignTokenSubscriber, +} from "./fast-design-token.js"; +export type { + StaticDesignTokenValue, + DerivedDesignTokenValue, + DesignTokenResolver, + DesignTokenMutationType, +} from "./core/exports.js"; diff --git a/packages/web-components/fast-foundation/src/design-token-2/fast-design-token.spec.ts b/packages/web-components/fast-foundation/src/design-token/fast-design-token.spec.ts similarity index 99% rename from packages/web-components/fast-foundation/src/design-token-2/fast-design-token.spec.ts rename to packages/web-components/fast-foundation/src/design-token/fast-design-token.spec.ts index 70dfcabdf9f..7082c470c3c 100644 --- a/packages/web-components/fast-foundation/src/design-token-2/fast-design-token.spec.ts +++ b/packages/web-components/fast-foundation/src/design-token/fast-design-token.spec.ts @@ -3,7 +3,7 @@ import chia, { expect } from "chai"; import spies from "chai-spies"; import { uniqueElementName } from "@microsoft/fast-element/testing"; import type { DesignTokenResolver } from "./core/design-token-node.js"; -import { CSSDesignToken, DesignToken, FASTDesignTokenSubscriber } from "./fast-design-token.js"; +import { CSSDesignToken, DesignToken, DesignTokenSubscriber } from "./fast-design-token.js"; chia.use(spies); const elementName = uniqueElementName(); @@ -761,7 +761,7 @@ describe("A DesignToken", () => { const token = DesignToken.create("test"); const spy = new Map([[ancestor, false], [parent, false], [ target, false ]]); - const subscriber: FASTDesignTokenSubscriber = { + const subscriber: DesignTokenSubscriber = { handleChange(token, record) { spy.set(record.target, true) } @@ -796,7 +796,7 @@ describe("A DesignToken", () => { const token = DesignToken.create("test"); const handleChange = chia.spy(() => {}); - const subscriber: FASTDesignTokenSubscriber = { + const subscriber: DesignTokenSubscriber = { handleChange } @@ -816,7 +816,7 @@ describe("A DesignToken", () => { const target = addElement(); const token = DesignToken.create("test"); - const subscriber: FASTDesignTokenSubscriber = { + const subscriber: DesignTokenSubscriber = { handleChange(token, record) { invoked = true; } diff --git a/packages/web-components/fast-foundation/src/design-token-2/fast-design-token.ts b/packages/web-components/fast-foundation/src/design-token/fast-design-token.ts similarity index 90% rename from packages/web-components/fast-foundation/src/design-token-2/fast-design-token.ts rename to packages/web-components/fast-foundation/src/design-token/fast-design-token.ts index 41ee344be61..19a2518544d 100644 --- a/packages/web-components/fast-foundation/src/design-token-2/fast-design-token.ts +++ b/packages/web-components/fast-foundation/src/design-token/fast-design-token.ts @@ -12,17 +12,20 @@ import { composedContains, composedParent } from "@microsoft/fast-element/utilit import { PropertyTargetManager, RootStyleSheetTarget, -} from "../design-token/custom-property-manager.js"; +} from "./custom-property-manager.js"; import { + DesignTokenChangeRecord as CoreDesignTokenChangeRecord, DerivedDesignTokenValue, - DesignTokenChangeRecord, DesignTokenMutationType, DesignTokenNode, DesignTokenResolver, DesignTokenValue, } from "./core/design-token-node.js"; -export interface FASTDesignTokenChangeRecord> { +/** + * @public + */ +export interface DesignTokenChangeRecord> { /** * The element for which the value was changed */ @@ -38,12 +41,12 @@ export interface FASTDesignTokenChangeRecord> { * A subscriber that should receive {@link DesignTokenChangeRecord | change records} when a token changes for a target * @public */ -export interface FASTDesignTokenSubscriber> { - handleChange(token: T, record: FASTDesignTokenChangeRecord): void; +export interface DesignTokenSubscriber> { + handleChange(token: T, record: DesignTokenChangeRecord): void; } /** - * Describes a {@link (DesignToken:interface)} configuration + * Describes a {@link DesignToken} configuration * @public */ export interface DesignTokenConfiguration { @@ -53,6 +56,9 @@ export interface DesignTokenConfiguration { name: string; } +/** + * @public + */ export interface CSSDesignTokenConfiguration extends DesignTokenConfiguration { /** * The name of the CSS custom property to associate to the {@link CSSDesignToken} @@ -60,6 +66,9 @@ export interface CSSDesignTokenConfiguration extends DesignTokenConfiguration { cssCustomPropertyName: string; } +/** + * @public + */ export class DesignToken { public name: string; public get $value() { @@ -102,7 +111,7 @@ export class DesignToken { /** * Registers and element or document as a DesignToken root. * {@link CSSDesignToken | CSSDesignTokens} with default values assigned via - * {@link (DesignToken:interface).withDefault} will emit CSS custom properties to all + * {@link DesignToken.withDefault} will emit CSS custom properties to all * registered roots. * @param target - The root to register */ @@ -141,11 +150,11 @@ export class DesignToken { return this; } - public subscribe(subscriber: FASTDesignTokenSubscriber): void { + public subscribe(subscriber: DesignTokenSubscriber): void { this.subscribers.subscribe(subscriber); } - public unsubscribe(subscriber: FASTDesignTokenSubscriber): void { + public unsubscribe(subscriber: DesignTokenSubscriber): void { this.subscribers.unsubscribe(subscriber); } @@ -167,8 +176,11 @@ export class DesignToken { } private subscriberNotifier: Subscriber = { - handleChange: (source: DesignToken, change: DesignTokenChangeRecord) => { - const record: FASTDesignTokenChangeRecord = { + handleChange: ( + source: DesignToken, + change: CoreDesignTokenChangeRecord + ) => { + const record: DesignTokenChangeRecord = { target: change.target === FASTDesignTokenNode.defaultNode ? "default" @@ -180,6 +192,9 @@ export class DesignToken { }; } +/** + * @public + */ export class CSSDesignToken extends DesignToken implements CSSDirective { public cssCustomProperty: string; private cssVar: string; @@ -188,7 +203,10 @@ export class CSSDesignToken extends DesignToken implements CSSDirective { return this.cssVar; } private cssReflector: Subscriber = { - handleChange: (source: DesignToken, record: DesignTokenChangeRecord) => { + handleChange: ( + source: DesignToken, + record: CoreDesignTokenChangeRecord + ) => { const target = record.target === FASTDesignTokenNode.defaultNode ? FASTDesignTokenNode.rootStyleSheetTarget diff --git a/packages/web-components/fast-foundation/src/design-token/interfaces.ts b/packages/web-components/fast-foundation/src/design-token/interfaces.ts deleted file mode 100644 index c4d207f5227..00000000000 --- a/packages/web-components/fast-foundation/src/design-token/interfaces.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * A {@link (DesignToken:interface)} value that is derived. These values can depend on other {@link (DesignToken:interface)}s - * or arbitrary observable properties. - * @public - */ -export type DerivedDesignTokenValue = T extends Function - ? never - : (target: HTMLElement) => T; - -/** - * A design token value with no observable dependencies - * @public - */ -export type StaticDesignTokenValue = T extends Function ? never : T; - -/** - * The type that a {@link (DesignToken:interface)} can be set to. - * @public - */ -export type DesignTokenValue = StaticDesignTokenValue | DerivedDesignTokenValue; - -/** - * Describes a {@link (DesignToken:interface)} configuration - * @public - */ -export interface DesignTokenConfiguration { - /** - * The name of the {@link (DesignToken:interface)}. - */ - name: string; - - /** - * The name of the CSS custom property to associate to the {@link (DesignToken:interface)}, or null - * if not CSS custom property should be associated. - */ - cssCustomPropertyName?: string | null; -} diff --git a/packages/web-components/fast-foundation/src/index.ts b/packages/web-components/fast-foundation/src/index.ts index 7e203afd592..f01b9b0ded3 100644 --- a/packages/web-components/fast-foundation/src/index.ts +++ b/packages/web-components/fast-foundation/src/index.ts @@ -12,18 +12,7 @@ export * from "./card/index.js"; export * from "./checkbox/index.js"; export * from "./combobox/index.js"; export * from "./data-grid/index.js"; -export { - DesignToken, - CSSDesignToken, - DesignTokenChangeRecord, - DesignTokenSubscriber, -} from "./design-token/design-token.js"; -export { - StaticDesignTokenValue, - DerivedDesignTokenValue, - DesignTokenValue, - DesignTokenConfiguration, -} from "./design-token/interfaces.js"; +export * from "./design-token/exports.js"; export * from "./dialog/index.js"; export { reflectAttributes } from "./directives/reflect-attributes.js"; export * from "./disclosure/index.js";