Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typed children for function-based widgets #544

Merged
merged 16 commits into from
Oct 1, 2019
Merged
20 changes: 11 additions & 9 deletions src/core/Registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export interface RegistryInterface {
* @param widgetLabel The label of the widget to return
* @returns The RegistryItem for the widgetLabel, `null` if no entry exists
*/
get(label: RegistryLabel): WNodeFactory<any> | Callback<any, any, RenderResult> | Constructor<any> | null;
get(label: RegistryLabel): WNodeFactory<any> | Callback<any, any, any, RenderResult> | Constructor<any> | null;
get<T extends WNodeFactory<any>>(label: RegistryLabel): T | null;
get<T extends Callback<any, any, RenderResult>>(label: RegistryLabel): T | null;
get<T extends Callback<any, any, any, RenderResult>>(label: RegistryLabel): T | null;
get<T extends WidgetBaseInterface = WidgetBaseInterface>(label: RegistryLabel): Constructor<T> | null;

/**
Expand Down Expand Up @@ -99,7 +99,7 @@ export function isWidgetBaseConstructor<T extends WidgetBaseInterface = any>(ite
return Boolean(item && item._type === WIDGET_BASE_TYPE);
}

export function isWidgetFunction(item: any): item is Callback<any, any, RenderResult> {
export function isWidgetFunction(item: any): item is Callback<any, any, any, RenderResult> {
return Boolean(item && item.isWidget);
}

Expand All @@ -112,7 +112,7 @@ export function isWNodeFactory<W extends WidgetBaseTypes>(node: any): node is WN

export function isWidget<T extends WidgetBaseInterface = any>(
item: any
): item is Constructor<T> | Callback<any, any, RenderResult> {
): item is Constructor<T> | Callback<any, any, any, RenderResult> {
return isWidgetBaseConstructor(item) || isWidgetFunction(item);
}

Expand Down Expand Up @@ -199,21 +199,23 @@ export class Registry extends Evented<{}, RegistryLabel, RegistryEventObject> im
this.emitLoadedEvent(label, injectorItem);
}

public get(label: RegistryLabel): WNodeFactory<any> | Callback<any, any, RenderResult> | Constructor<any> | null;
public get(
label: RegistryLabel
): WNodeFactory<any> | Callback<any, any, any, RenderResult> | Constructor<any> | null;
public get<T extends WNodeFactory<any>>(label: RegistryLabel): T | null;
public get<T extends Callback<any, any, RenderResult>>(label: RegistryLabel): T | null;
public get<T extends Callback<any, any, any, RenderResult>>(label: RegistryLabel): T | null;
public get<T extends WidgetBaseInterface = WidgetBaseInterface>(label: RegistryLabel): Constructor<T> | null;
public get<T extends WidgetBaseInterface = WidgetBaseInterface>(
label: RegistryLabel
): WNodeFactory<T> | Callback<any, any, RenderResult> | Constructor<T> | null {
): WNodeFactory<T> | Callback<any, any, any, RenderResult> | Constructor<T> | null {
if (!this._widgetRegistry || !this.has(label)) {
return null;
}

const item = this._widgetRegistry.get(label);

if (isWidget<T>(item) || isWNodeFactory(item)) {
return item;
return item as any;
}

if (item instanceof Promise) {
Expand All @@ -224,7 +226,7 @@ export class Registry extends Evented<{}, RegistryLabel, RegistryEventObject> im
this._widgetRegistry.set(label, promise);

promise.then(
(widgetCtor) => {
(widgetCtor: any) => {
if (isWidgetConstructorDefaultExport<T>(widgetCtor)) {
widgetCtor = widgetCtor.default;
}
Expand Down
44 changes: 34 additions & 10 deletions src/core/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,30 +384,54 @@ export interface MiddlewareMap<

export type MiddlewareApiMap<U extends MiddlewareMap<any>> = { [P in keyof U]: ReturnType<U[P]>['api'] };

export interface Callback<Props, Middleware, ReturnValue> {
export interface Callback<Props, Children, Middleware, ReturnValue> {
(
options: {
id: string;
middleware: MiddlewareApiMap<Middleware>;
properties: () => Props;
children: () => DNode[];
children: () => Children extends any[] ? Children : [Children];
}
): ReturnValue;
}

export interface MiddlewareResult<Props, Middleware, ReturnValue> {
export interface MiddlewareResult<Props, Children, Middleware, ReturnValue> {
api: ReturnValue;
properties: Props;
callback: Callback<Props, Middleware, ReturnValue>;
callback: Callback<Props, Children, Middleware, ReturnValue>;
middlewares: Middleware;
}

export interface MiddlewareResultFactory<Props, Middleware, ReturnValue> {
(): MiddlewareResult<Props, Middleware, ReturnValue>;
export interface MiddlewareResultFactory<Props, Children, Middleware, ReturnValue> {
(): MiddlewareResult<Props, Children, Middleware, ReturnValue>;
}

export interface WNodeFactory<W extends WidgetBaseTypes> {
(properties: W['properties'], children?: W['children']): WNode<W>;
export interface DefaultChildrenWNodeFactory<W extends WNodeFactoryTypes> {
(properties: W['properties'], children?: W['children'] extends any[] ? W['children'] : [W['children']]): WNode<W>;
new (): {
properties: W['properties'] & { __children__?: DNode | DNode[] };
};
properties: W['properties'];
children: W['children'];
}

export interface WNodeFactory<W extends WNodeFactoryTypes> {
(
properties: W['properties'],
children: W['children'] extends [any]
? W['children'][0][]
: W['children'] extends any[] ? W['children'] : [W['children']]
): WNode<W>;
new (): {
properties: W['properties'] & { __children__: W['children'] };
};
properties: W['properties'];
children: W['children'];
}

export interface WNodeFactoryTypes<P = any, C = any> {
readonly properties: P;
readonly children: C;
}

export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
Expand All @@ -421,7 +445,7 @@ export interface WNode<W extends WidgetBaseTypes = any> {
/**
* Constructor to create a widget or string constructor label
*/
widgetConstructor: Constructor<W> | RegistryLabel | LazyDefine<W> | Callback<any, any, RenderResult>;
widgetConstructor: Constructor<W> | RegistryLabel | LazyDefine<W> | Callback<any, any, any, RenderResult>;

/**
* Properties to set against a widget instance
Expand All @@ -431,7 +455,7 @@ export interface WNode<W extends WidgetBaseTypes = any> {
/**
* DNode children
*/
children: DNode[];
children: any[];

/**
* The type of node
Expand Down
121 changes: 107 additions & 14 deletions src/core/vdom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
WidgetBaseTypes,
RegistryLabel,
DeferredVirtualProperties,
DomOptions
DomOptions,
DefaultChildrenWNodeFactory
} from './interfaces';
import { Registry, isWidget, isWidgetBaseConstructor, isWidgetFunction, isWNodeFactory } from './Registry';
import { auto } from './diff';
Expand All @@ -40,6 +41,9 @@ declare global {
interface IntrinsicElements {
[key: string]: VNodeProperties;
}
interface ElementChildrenAttribute {
__children__: {};
}
}
}

Expand All @@ -64,7 +68,7 @@ export interface WNodeWrapper extends BaseNodeWrapper {
instance?: any;
mergeNodes?: Node[];
nodeHandlerCalled?: boolean;
registryItem?: Callback<any, any, RenderResult> | Constructor<any> | null;
registryItem?: Callback<any, any, any, RenderResult> | Constructor<any> | null;
properties: any;
}

Expand Down Expand Up @@ -307,10 +311,20 @@ function updateAttributes(
export function w<W extends WidgetBaseTypes>(
node: WNode<W>,
properties: Partial<W['properties']>,
children?: W['children']
children?: W['properties'] extends { __children__: any } ? W['properties']['__children__'] : W['children']
): WNode<W>;
export function w<W extends WidgetBaseTypes>(
widgetConstructor: Constructor<W> | RegistryLabel | WNodeFactory<W> | LazyDefine<W>,
widgetConstructor: Constructor<W> | RegistryLabel | LazyDefine<W>,
properties: W['properties'],
children?: W['children']
): WNode<W>;
export function w<W extends WNodeFactory<any>>(
widgetConstructor: W,
properties: W['properties'],
children: W['children']
): WNode<W>;
export function w<W extends DefaultChildrenWNodeFactory<any>>(
widgetConstructor: W,
properties: W['properties'],
children?: W['children']
): WNode<W>;
Expand All @@ -321,10 +335,14 @@ export function w<W extends WidgetBaseTypes>(
| WNodeFactory<W>
| WNode<W>
| LazyDefine<W>
| Callback<any, any, RenderResult>,
| Callback<any, any, any, RenderResult>,
properties: W['properties'],
children?: W['children']
children?: any
): WNode<W> {
if ((properties as any).__children__) {
delete (properties as any).__children__;
}

if (isWNodeFactory<W>(widgetConstructorOrNode)) {
return widgetConstructorOrNode(properties, children);
}
Expand Down Expand Up @@ -641,30 +659,105 @@ function createFactory(callback: any, middlewares: any): any {
export function create<T extends MiddlewareMap, MiddlewareProps = ReturnType<T[keyof T]>['properties']>(
middlewares: T = {} as T
) {
function properties<Props extends {}>() {
function properties<Props>() {
function returns<ReturnValue>(
callback: Callback<WidgetProperties & Props & UnionToIntersection<MiddlewareProps>, T, ReturnValue>
callback: Callback<WidgetProperties & Props & UnionToIntersection<MiddlewareProps>, DNode[], T, ReturnValue>
): ReturnValue extends RenderResult
? WNodeFactory<{
? DefaultChildrenWNodeFactory<{
properties: Props & WidgetProperties & UnionToIntersection<MiddlewareProps>;
children: DNode[];
}>
: MiddlewareResultFactory<WidgetProperties & Props & UnionToIntersection<MiddlewareProps>, T, ReturnValue> {
: MiddlewareResultFactory<
WidgetProperties & Props & UnionToIntersection<MiddlewareProps>,
DNode[],
T,
ReturnValue
> {
return createFactory(callback, middlewares);
}

function children<Children>() {
function returns<ReturnValue>(
callback: Callback<
WidgetProperties & Props & UnionToIntersection<MiddlewareProps>,
Children,
T,
ReturnValue
>
): ReturnValue extends RenderResult
? WNodeFactory<{
properties: Props & WidgetProperties & UnionToIntersection<MiddlewareProps>;
children: Children;
}>
: MiddlewareResultFactory<
WidgetProperties & Props & UnionToIntersection<MiddlewareProps>,
Children,
T,
ReturnValue
> {
return createFactory(callback, middlewares);
}
return returns;
}
returns.children = children;
return returns;
}

function children<Children extends {}>() {
function properties<Props>() {
function returns<ReturnValue>(
callback: Callback<
WidgetProperties & Props & UnionToIntersection<MiddlewareProps>,
Children,
T,
ReturnValue
>
): ReturnValue extends RenderResult
? WNodeFactory<{
properties: Props & WidgetProperties & UnionToIntersection<MiddlewareProps>;
children: Children;
}>
: MiddlewareResultFactory<
WidgetProperties & Props & UnionToIntersection<MiddlewareProps>,
Children,
T,
ReturnValue
> {
return createFactory(callback, middlewares);
}
return returns;
}

function returns<ReturnValue>(
callback: Callback<WidgetProperties & UnionToIntersection<MiddlewareProps>, Children, T, ReturnValue>
): ReturnValue extends RenderResult
? WNodeFactory<{
properties: WidgetProperties & UnionToIntersection<MiddlewareProps>;
children: Children;
}>
: MiddlewareResultFactory<
WidgetProperties & UnionToIntersection<MiddlewareProps>,
Children,
T,
ReturnValue
> {
return createFactory(callback, middlewares);
}
returns.properties = properties;
return returns;
}

function returns<ReturnValue>(
callback: Callback<WidgetProperties & UnionToIntersection<MiddlewareProps>, T, ReturnValue>
callback: Callback<WidgetProperties & UnionToIntersection<MiddlewareProps>, DNode[], T, ReturnValue>
): ReturnValue extends RenderResult
? WNodeFactory<{
? DefaultChildrenWNodeFactory<{
properties: WidgetProperties & UnionToIntersection<MiddlewareProps>;
children: DNode[];
}>
: MiddlewareResultFactory<WidgetProperties & UnionToIntersection<MiddlewareProps>, T, ReturnValue> {
: MiddlewareResultFactory<WidgetProperties & UnionToIntersection<MiddlewareProps>, DNode[], T, ReturnValue> {
return createFactory(callback, middlewares);
}
returns.children = children;
returns.properties = properties;
return returns;
}
Expand Down Expand Up @@ -1269,7 +1362,7 @@ export function renderer(renderer: () => RenderResult): Renderer {
function mount(mountOptions: Partial<MountOptions> = {}) {
_mountOptions = { ..._mountOptions, ...mountOptions };
const { domNode } = _mountOptions;
const renderResult = wrapNodes(renderer)({});
const renderResult = wrapNodes(renderer)({}, []);
const nextWrapper = {
id: `${wrapperId++}`,
node: renderResult,
Expand Down
9 changes: 6 additions & 3 deletions src/testing/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ let middlewareId = 0;

interface HarnessOptions {
customComparator?: CustomComparator[];
middleware?: [MiddlewareResultFactory<any, any, any>, MiddlewareResultFactory<any, any, any>][];
middleware?: [MiddlewareResultFactory<any, any, any, any>, MiddlewareResultFactory<any, any, any, any>][];
}

const factory = create();
Expand All @@ -60,14 +60,17 @@ export function harness(renderFunc: () => WNode, options: HarnessOptions | Custo
let invalidated = true;
let wNode = renderFunc();
const renderStack: (DNode | DNode[])[] = [];
let widget: WidgetBase | Callback<any, any, RenderResult>;
let widget: WidgetBase | Callback<any, any, any, RenderResult>;
let middleware: any = {};
let properties: any = {};
let children: any = [];
let customDiffs: any[] = [];
let customDiffNames: string[] = [];
let customComparator: CustomComparator[] = [];
let mockMiddleware: [MiddlewareResultFactory<any, any, any>, MiddlewareResultFactory<any, any, any>][] = [];
let mockMiddleware: [
MiddlewareResultFactory<any, any, any, any>,
MiddlewareResultFactory<any, any, any, any>
][] = [];
if (Array.isArray(options)) {
customComparator = options;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/testing/mocks/middleware/breakpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function createBreakpointMock(breakpoints: Breakpoints = { SM: 0, MD: 576
};
});

function mockBreakpoint(): MiddlewareResult<any, any, any>;
function mockBreakpoint(): MiddlewareResult<any, any, any, any>;
function mockBreakpoint(
key: string,
breakpointResult: {
Expand All @@ -49,7 +49,7 @@ export function createBreakpointMock(breakpoints: Breakpoints = { SM: 0, MD: 576
breakpoint: string;
contentRect: Partial<DOMRectReadOnly>;
}
): void | MiddlewareResult<any, any, any> {
): void | MiddlewareResult<any, any, any, any> {
if (key && breakpointResult) {
if (!mockBreakpoints[key]) {
mockBreakpoints[key] = breakpointResult.breakpoint;
Expand Down
4 changes: 2 additions & 2 deletions src/testing/mocks/middleware/icache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export function createICacheMock() {
return icacheMiddleware;
});

function mockCache(): MiddlewareResult<any, any, any>;
function mockCache(): MiddlewareResult<any, any, any, any>;
function mockCache(key: string): Promise<any>;
function mockCache(key?: string): Promise<any> | MiddlewareResult<any, any, any> {
function mockCache(key?: string): Promise<any> | MiddlewareResult<any, any, any, any> {
if (key) {
if (map.has(key)) {
return map.get(key);
Expand Down
Loading