Skip to content

Commit

Permalink
fix(design-system): make FoundationElement.compose safe to use direct…
Browse files Browse the repository at this point in the history
…ly (microsoft#5157)

* fix(design=system): don't allow FoundationElement to be defined directly

* fix(design-system): don't allow FoundationElement to be defined directly

* chore: correct the change file

* fix(design-system): move files to remove circular module references

* chore: fixing after merge from resolving changes incompletely

Co-authored-by: EisenbergEffect <roeisenb@microsoft.com>
  • Loading branch information
EisenbergEffect and EisenbergEffect authored Sep 8, 2021
1 parent 3e85a30 commit a508e1e
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix(design-system): make FoundationElement.compose safe to use directly",
"packageName": "@microsoft/fast-foundation",
"email": "roeisenb@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { Constructable } from "@microsoft/fast-element";
import { expect } from "chai";
import { FoundationElement } from "..";
import { Container, DI } from "../di";
import { uniqueElementName } from "../test-utilities/fixture";
import { DesignSystem, DesignSystemRegistrationContext, ElementDisambiguation } from "./design-system";
import { DesignSystem, ElementDisambiguation } from "./design-system";
import { DesignSystemRegistrationContext } from "./registration-context";

describe("DesignSystem", () => {
it("Should return the same instance for the same element", () => {
Expand Down Expand Up @@ -281,6 +283,27 @@ describe("DesignSystem", () => {
expect(callbackCalled).to.be.false;
});

it("Should auto-subclass if attempting to define FoundationElement", () => {
const elementName = uniqueElementName();
const host = document.createElement("div");

DesignSystem.getOrCreate(host)
.register({
register(container: Container) {
container.get(DesignSystemRegistrationContext)
.tryDefineElement(elementName, FoundationElement, x => {
x.defineElement();
});
},
});

const type = customElements.get(elementName)!;
const proto = Reflect.getPrototypeOf(type);

expect(type).to.not.equal(FoundationElement);
expect(proto).to.equal(FoundationElement);
});

it("Should have an undefined shadow mode by default", () => {
const elementName = uniqueElementName();
const customElement = class extends HTMLElement {};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,141 +1,14 @@
import {
Constructable,
FASTElementDefinition,
PartialFASTElementDefinition,
} from "@microsoft/fast-element";
import { Container, DI, InterfaceSymbol, Registration } from "../di/di";
import { Constructable, FASTElementDefinition } from "@microsoft/fast-element";
import { FoundationElement } from "../foundation-element/foundation-element";
import { Container, DI, Registration } from "../di/di";
import { ComponentPresentation } from "./component-presentation";

/**
* Enables defining an element within the context of a design system.
* @public
*/
export type ContextualElementDefinition = Omit<PartialFASTElementDefinition, "name">;

/**
* The design system context in which an element can be defined.
* @public
*/
export interface ElementDefinitionContext {
/**
* The name that the element will be defined as.
* @public
*/
readonly name: string;

/**
* The type that will be defined.
* @public
*/
readonly type: Constructable;

/**
* The dependency injection container associated with the design system.
* @public
*/
readonly container: Container;

/**
* Indicates whether or not a platform define call will be made in order
* to define the element.
* @public
*/
readonly willDefine: boolean;

/**
* The shadow root mode specified by the design system's configuration.
* @public
*/
readonly shadowRootMode: ShadowRootMode | undefined;

/**
* Defines the element.
* @param definition - The definition for the element.
* @public
*/
defineElement(definition?: ContextualElementDefinition): void;

/**
* Defines a presentation for the element.
* @param presentation - The presentation configuration.
* @public
*/
definePresentation(presentation: ComponentPresentation): void;

/**
* Returns the HTML element tag name that the type will be defined as.
* @param type - The type to lookup.
* @public
*/
tagFor(type: Constructable): string;
}

/**
* The callback type that is invoked when an element can be defined by a design system.
* @public
*/
export type ElementDefinitionCallback = (ctx: ElementDefinitionContext) => void;

/**
* The element definition context interface. Designed to be used in `tryDefineElement`
* @public
*/
export interface ElementDefinitionParams
extends Pick<ElementDefinitionContext, "name" | "type"> {
/**
* FAST actual base class instance.
* @public
*/
readonly baseClass?: Constructable;
/**
* A callback to invoke if definition will happen.
* @public
*/
callback: ElementDefinitionCallback;
}

/**
* Design system contextual APIs and configuration usable within component
* registries.
* @public
*/
export interface DesignSystemRegistrationContext {
/**
* The element prefix specified by the design system's configuration.
* @public
*/
readonly elementPrefix: string;

/**
* Used to attempt to define a custom element.
* @param name - The name of the element to define.
* @param type - The type of the constructor to use to define the element.
* @param callback - A callback to invoke if definition will happen.
* @public
* @deprecated - Use the signature with the ElementDefinitionParams param type instead
*/
tryDefineElement(
name: string,
type: Constructable,
callback: ElementDefinitionCallback
);

/**
* Used to attempt to define a custom element.
* @param params - The custom element definition.
* @public
*/
tryDefineElement(params: ElementDefinitionParams);
}

/**
* Design system contextual APIs and configuration usable within component
* registries.
* @public
*/
export const DesignSystemRegistrationContext: InterfaceSymbol<DesignSystemRegistrationContext> = DI.createInterface<
DesignSystemRegistrationContext
>();
import {
ContextualElementDefinition,
DesignSystemRegistrationContext,
ElementDefinitionCallback,
ElementDefinitionContext,
ElementDefinitionParams,
} from "./registration-context";

/**
* Indicates what to do with an ambiguous (duplicate) element.
Expand Down Expand Up @@ -388,7 +261,7 @@ class DefaultDesignSystem implements DesignSystem {
}

if (needsDefine) {
if (elementTagsByType.has(type)) {
if (elementTagsByType.has(type) || type === FoundationElement) {
type = class extends type {};
}
elementTypesByTag.set(elementName, type);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./design-system";
export * from "./component-presentation";
export * from "./registration-context";
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Container, DI, InterfaceSymbol } from "../di/di";
import type {
Constructable,
PartialFASTElementDefinition,
} from "@microsoft/fast-element";
import type { ComponentPresentation } from "./component-presentation";

/**
* Enables defining an element within the context of a design system.
* @public
*/
export type ContextualElementDefinition = Omit<PartialFASTElementDefinition, "name">;

/**
* The design system context in which an element can be defined.
* @public
*/
export interface ElementDefinitionContext {
/**
* The name that the element will be defined as.
* @public
*/
readonly name: string;

/**
* The type that will be defined.
* @public
*/
readonly type: Constructable;

/**
* The dependency injection container associated with the design system.
* @public
*/
readonly container: Container;

/**
* Indicates whether or not a platform define call will be made in order
* to define the element.
* @public
*/
readonly willDefine: boolean;

/**
* The shadow root mode specified by the design system's configuration.
* @public
*/
readonly shadowRootMode: ShadowRootMode | undefined;

/**
* Defines the element.
* @param definition - The definition for the element.
* @public
*/
defineElement(definition?: ContextualElementDefinition): void;

/**
* Defines a presentation for the element.
* @param presentation - The presentation configuration.
* @public
*/
definePresentation(presentation: ComponentPresentation): void;

/**
* Returns the HTML element tag name that the type will be defined as.
* @param type - The type to lookup.
* @public
*/
tagFor(type: Constructable): string;
}

/**
* The callback type that is invoked when an element can be defined by a design system.
* @public
*/
export type ElementDefinitionCallback = (ctx: ElementDefinitionContext) => void;

/**
* The element definition context interface. Designed to be used in `tryDefineElement`
* @public
*/
export interface ElementDefinitionParams
extends Pick<ElementDefinitionContext, "name" | "type"> {
/**
* FAST actual base class instance.
* @public
*/
readonly baseClass?: Constructable;
/**
* A callback to invoke if definition will happen.
* @public
*/
callback: ElementDefinitionCallback;
}

/**
* Design system contextual APIs and configuration usable within component
* registries.
* @public
*/
export interface DesignSystemRegistrationContext {
/**
* The element prefix specified by the design system's configuration.
* @public
*/
readonly elementPrefix: string;

/**
* Used to attempt to define a custom element.
* @param name - The name of the element to define.
* @param type - The type of the constructor to use to define the element.
* @param callback - A callback to invoke if definition will happen.
* @public
* @deprecated - Use the signature with the ElementDefinitionParams param type instead
*/
tryDefineElement(
name: string,
type: Constructable,
callback: ElementDefinitionCallback
);

/**
* Used to attempt to define a custom element.
* @param params - The custom element definition.
* @public
*/
tryDefineElement(params: ElementDefinitionParams);
}

/**
* Design system contextual APIs and configuration usable within component
* registries.
* @public
*/
export const DesignSystemRegistrationContext: InterfaceSymbol<DesignSystemRegistrationContext> = DI.createInterface<
DesignSystemRegistrationContext
>();
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { ElementStyles, FASTElement, observable } from "@microsoft/fast-element"
import {
ComponentPresentation,
DefaultComponentPresentation,
} from "../design-system/component-presentation";
import {
DesignSystemRegistrationContext,
ElementDefinitionContext,
} from "../design-system";
import type { Container, Registry } from "../di";
} from "../design-system/registration-context";
import type { Container, Registry } from "../di/di";

type LazyFoundationOption<T, K extends FoundationElementDefinition> = (
context: ElementDefinitionContext,
Expand Down

0 comments on commit a508e1e

Please sign in to comment.