Skip to content

Commit

Permalink
feat: enable shared CSSStyleSheets in DesignSystemProvider (microsoft…
Browse files Browse the repository at this point in the history
…#4065)

* adding tests for ensuring CSS custom properties exist

* use a HTMLStyleSheet element instead of inline style if addoptedStyleSheets is not supported

* wire assignable custom property stylesheet

* fixing comment

* manager incorporated

* correct dsp property behavior

* ensure registry gets updated when stylesheet updates

* adding parent provider resolution tests

* more tests

* refactor to invert control of the custom property manager to make testing easier

* organize the custom property manager to its own file

* export manager

* write manager code

* add some provider tests to test subscription and unsubscription

* cleanup

* revert checkbox fixture

* address comments

* adding documentation

Co-authored-by: nicholasrice <nicholasrice@users.noreply.github.com>
  • Loading branch information
nicholasrice and nicholasrice authored Oct 29, 2020
1 parent bd09fbc commit 5579c2e
Show file tree
Hide file tree
Showing 7 changed files with 1,058 additions and 99 deletions.
57 changes: 54 additions & 3 deletions packages/web-components/fast-foundation/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,22 @@ export const CheckboxTemplate: import("@microsoft/fast-element").ViewTemplate<Ch
// @public
export function composedParent<T extends HTMLElement>(element: T): HTMLElement | null;

// Warning: (ae-forgotten-export) The symbol "CustomPropertyManagerBase" needs to be exported by the entry point index.d.ts
//
// @public
export class ConstructableStylesCustomPropertyManager extends CustomPropertyManagerBase {
constructor(sheet: CSSStyleSheet);
// (undocumented)
protected customPropertyTarget: CSSStyleDeclaration;
isSubscribed(client: CustomPropertyManagerClient): boolean;
// (undocumented)
protected readonly sheet: CSSStyleSheet;
// (undocumented)
protected styles: ElementStyles;
subscribe(client: CustomPropertyManagerClient): void;
unsubscribe(client: CustomPropertyManagerClient): void;
}

// @public
export class CSSCustomPropertyBehavior implements Behavior, CSSCustomPropertyDefinition {
constructor(name: string, value: CSSCustomPropertyDefinition["value"], host: (source: HTMLElement) => Partial<CSSCustomPropertyTarget> | null);
Expand Down Expand Up @@ -245,6 +261,25 @@ export interface CSSCustomPropertyTarget {
// @public
export type CSSDisplayPropertyValue = "block" | "contents" | "flex" | "grid" | "inherit" | "initial" | "inline" | "inline-block" | "inline-flex" | "inline-grid" | "inline-table" | "list-item" | "none" | "run-in" | "table" | "table-caption" | "table-cell" | "table-column" | "table-column-group" | "table-footer-group" | "table-header-group" | "table-row" | "table-row-group";

// @public
export interface CustomPropertyManager {
isSubscribed?(provider: CustomPropertyManagerClient): boolean;
readonly owner: CustomPropertyManagerClient | null;
register(definition: CSSCustomPropertyDefinition): void;
remove(name: string): void;
set(definition: CSSCustomPropertyDefinition): void;
setAll(): void;
subscribe?(provider: CustomPropertyManagerClient): void;
unregister(name: string): void;
unsubscribe?(provider: CustomPropertyManagerClient): void;
}

// @public
export interface CustomPropertyManagerClient extends FASTElement {
cssCustomPropertyDefinitions: Map<string, CSSCustomPropertyDefinition>;
evaluate(definition: CSSCustomPropertyDefinition): string;
}

// @public
export interface DecoratorDesignSystemPropertyConfiguration extends Omit<DecoratorAttributeConfiguration, "attribute"> {
attribute?: string | false;
Expand Down Expand Up @@ -283,15 +318,20 @@ export const designSystemConsumerBehavior: Behavior;
export function designSystemProperty<T extends DesignSystemProvider>(config: DecoratorDesignSystemPropertyConfiguration): (source: T, property: string) => void;

// @public
export class DesignSystemProvider extends FASTElement implements CSSCustomPropertyTarget, DesignSystemConsumer {
export class DesignSystemProvider extends FASTElement implements CSSCustomPropertyTarget, DesignSystemConsumer, CustomPropertyManagerClient {
constructor();
// @internal (undocumented)
connectedCallback(): void;
// @internal
cssCustomPropertyDefinitions: Map<string, CSSCustomPropertyDefinition>;
customPropertyManager: CustomPropertyManager;
designSystem: {};
// @internal
designSystemProperties: {
[propertyName: string]: Required<Pick<DecoratorDesignSystemPropertyConfiguration, "cssCustomProperty" | "default">>;
};
// (undocumented)
disconnectedCallback(): void;
// @deprecated
disconnectedCSSCustomPropertyRegistry: CSSCustomPropertyDefinition[];
disconnectedRegistry: Array<(provider: DesignSystemProvider) => void> | void;
Expand All @@ -300,10 +340,10 @@ export class DesignSystemProvider extends FASTElement implements CSSCustomProper
static isDesignSystemProvider(el: HTMLElement | DesignSystemProvider): el is DesignSystemProvider;
readonly isDesignSystemProvider = true;
provider: DesignSystemProvider | null;
registerCSSCustomProperty(behavior: CSSCustomPropertyDefinition): void;
registerCSSCustomProperty(def: CSSCustomPropertyDefinition): void;
static registerTagName(tagName: string): void;
static get tagNames(): string[];
unregisterCSSCustomProperty(behavior: CSSCustomPropertyDefinition): void;
unregisterCSSCustomProperty(def: CSSCustomPropertyDefinition): void;
useDefaults: boolean;
}

Expand Down Expand Up @@ -717,6 +757,17 @@ export class StartEnd {
// @public
export const startTemplate: import("@microsoft/fast-element").ViewTemplate<StartEnd, any>;

// @public
export class StyleElementCustomPropertyManager extends CustomPropertyManagerBase {
constructor(style: HTMLStyleElement, client: CustomPropertyManagerClient);
// (undocumented)
protected customPropertyTarget: CSSStyleDeclaration;
// (undocumented)
readonly sheet: CSSStyleSheet;
// (undocumented)
readonly styles: HTMLStyleElement;
}

// @alpha (undocumented)
export const supportsElementInternals: boolean;

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./behavior";
export * from "./manager";
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { customElement, elements, FASTElement } from "@microsoft/fast-element";
import { assert, expect } from "chai";
import { fixture } from "../fixture";
import { CSSCustomPropertyDefinition } from "./behavior";
import {
ConstructableStylesCustomPropertyManager,
CustomPropertyManagerClient,
StyleElementCustomPropertyManager,
} from "./manager";

@customElement("fast-client")
class Client extends FASTElement implements CustomPropertyManagerClient {
public evaluate(definition: CSSCustomPropertyDefinition) {
return typeof definition.value === "function"
? definition.value()
: definition.value;
}

public cssCustomPropertyDefinitions = new Map();
}

async function setup() {
const { element, connect, disconnect } = await fixture<Client>("fast-client");

return {
element,
connect,
disconnect,
};
}

describe("ConstructableStylesCustomPropertyManager", () => {
it("should construct with a constructable stylesheet", () => {
assert(new ConstructableStylesCustomPropertyManager(new CSSStyleSheet()));
});

describe("on subscription", () => {
it("should set the subscriber to the owner if it is the first subscriber", async () => {
const { element, connect, disconnect } = await setup();
const manager = new ConstructableStylesCustomPropertyManager(
new CSSStyleSheet()
);

await connect();
manager.subscribe(element);

expect(manager.owner).to.equal(element);
await disconnect();
});
it("should keep the first subscriber as the owner after multiple subscribers", async () => {
const { element, connect, disconnect } = await setup();
const clone = element.cloneNode() as Client;
document.body.appendChild(clone);
const manager = new ConstructableStylesCustomPropertyManager(
new CSSStyleSheet()
);

await connect();
manager.subscribe(element);
manager.subscribe(clone);

expect(manager.owner).to.equal(element);
await disconnect();
});
it("should evaluate and write the value of all CSSCustomPropertyDefinitions in the client", async () => {
const { element, connect, disconnect } = await setup();
const manager = new ConstructableStylesCustomPropertyManager(
new CSSStyleSheet()
);

await connect();
element.cssCustomPropertyDefinitions.set("my-property", {
name: "my-property",
value: "value",
});

manager.subscribe(element);

expect(
window.getComputedStyle(element).getPropertyValue("--my-property")
).to.equal("value");
await disconnect();
});
});
describe("on un-subscription", () => {
it("should set the owner to null if it is the only subscriber", async () => {
const { element, connect, disconnect } = await setup();
const manager = new ConstructableStylesCustomPropertyManager(
new CSSStyleSheet()
);

await connect();
manager.subscribe(element);
manager.unsubscribe(element);

expect(manager.owner).to.equal(null);
await disconnect();
});
it("of the owner should set the owner to the subsequent subscriber", async () => {
const { element, connect, disconnect } = await setup();
const b = element.cloneNode() as Client;
const c = element.cloneNode() as Client;
document.body.appendChild(b);
document.body.appendChild(c);
const manager = new ConstructableStylesCustomPropertyManager(
new CSSStyleSheet()
);

await connect();
manager.subscribe(element);
manager.subscribe(b);
manager.subscribe(c);

manager.unsubscribe(element);
expect(manager.owner).to.equal(b);

await disconnect();
});
it("should remove all CSSCustomPropertyDefinition custom properties in the client", async () => {
const { element, connect, disconnect } = await setup();
const manager = new ConstructableStylesCustomPropertyManager(
new CSSStyleSheet()
);

await connect();
element.cssCustomPropertyDefinitions.set("my-property", {
name: "my-property",
value: "value",
});

manager.subscribe(element);
manager.unsubscribe(element);

expect(
window.getComputedStyle(element).getPropertyValue("--my-property")
).to.equal("");
await disconnect();
});
});
});

describe("StyleElementCustomPropertyManager", () => {
it("should construct with a style element and client instance", async () => {
const { element, connect, disconnect } = await setup();

await connect();

assert(
new StyleElementCustomPropertyManager(
document.createElement("style"),
element
)
);

await disconnect();
});

it("should connect the style element to the DOM during construction", async () => {
const { element, connect, disconnect } = await setup();

await connect();
const style = document.createElement("style");

const manager = new StyleElementCustomPropertyManager(style, element);

assert(style.isConnected);
assert(element.shadowRoot?.contains(style));

await disconnect();
});

it("should evaluate and write the value of all CSSCustomPropertyDefinitions in the client on construction", async () => {
const { element, connect, disconnect } = await setup();

element.cssCustomPropertyDefinitions.set("my-property", {
name: "my-property",
value: "value",
});

await connect();
const style = document.createElement("style");
const manager = new StyleElementCustomPropertyManager(style, element);

assert.equal(
window.getComputedStyle(element).getPropertyValue("--my-property"),
"value"
);

await disconnect();
});
});
Loading

0 comments on commit 5579c2e

Please sign in to comment.