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

Future @ember/component types #19948

Merged
merged 6 commits into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
valueForTag,
} from '@glimmer/validator';
import { SimpleElement } from '@simple-dom/interface';
import Component from '../component';
import { DynamicScope } from '../renderer';
import RuntimeResolver from '../resolver';
import { isTemplateFactory } from '../template';
Expand All @@ -49,7 +50,7 @@ import {
parseAttributeBinding,
} from '../utils/bindings';

import ComponentStateBucket, { Component } from '../utils/curly-component-state-bucket';
import ComponentStateBucket from '../utils/curly-component-state-bucket';
import { processComponentArgs } from '../utils/process-args';

export const ARGS = enumerableSymbol('ARGS');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
} from '@glimmer/interfaces';
import { capabilityFlagsFrom } from '@glimmer/manager';
import { CONSTANT_TAG, consumeTag } from '@glimmer/validator';
import Component from '../component';
import { DynamicScope } from '../renderer';
import ComponentStateBucket, { Component } from '../utils/curly-component-state-bucket';
import ComponentStateBucket from '../utils/curly-component-state-bucket';
import CurlyComponentManager, {
DIRTY_TAG,
initialRenderInstrumentDetails,
Expand Down
56 changes: 36 additions & 20 deletions packages/@ember/-internals/glimmer/lib/component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { get, PROPERTY_DID_CHANGE } from '@ember/-internals/metal';
import { getOwner } from '@ember/-internals/owner';
import { TargetActionSupport } from '@ember/-internals/runtime';
import { CoreObjectClass } from '@ember/-internals/runtime/lib/system/core_object';
import {
ActionSupport,
ChildViewsSupport,
Expand All @@ -13,7 +14,7 @@ import {
} from '@ember/-internals/views';
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { Environment } from '@glimmer/interfaces';
import { Environment, Template, TemplateFactory } from '@glimmer/interfaces';
import { setInternalComponentManager } from '@glimmer/manager';
import { isUpdatableRef, updateRef } from '@glimmer/reference';
import { normalizeProperty } from '@glimmer/runtime';
Expand Down Expand Up @@ -648,7 +649,39 @@ let lazyEventsProcessed = new WeakMap<EventDispatcher, WeakSet<object>>();
@uses Ember.ViewStateSupport
@public
*/
const Component = CoreView.extend(
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ComponentClass extends CoreObjectClass<Component> {}
interface Component
extends CoreView,
ChildViewsSupport,
ViewStateSupport,
ClassNamesSupport,
TargetActionSupport,
ActionSupport,
ViewMixin {
attributeBindings?: string[];

/**
Layout can be used to wrap content in a component.
@property layout
@type Function
@public
*/
layout?: TemplateFactory | Template;

/**
The name of the layout to lookup if no layout is provided.
By default `Component` will lookup a template with this name in
`Ember.TEMPLATES` (a shared global object).
@property layoutName
@type String
@default undefined
@private
*/
layoutName?: string;
}

const Component = (CoreView.extend(
ChildViewsSupport,
ViewStateSupport,
ClassNamesSupport,
Expand Down Expand Up @@ -986,23 +1019,6 @@ const Component = CoreView.extend(
@since 1.13.0
*/

/**
Layout can be used to wrap content in a component.
@property layout
@type Function
@public
*/

/**
The name of the layout to lookup if no layout is provided.
By default `Component` will lookup a template with this name in
`Ember.TEMPLATES` (a shared global object).
@property layoutName
@type String
@default null
@private
*/

/**
The HTML `id` of the component's element in the DOM. You can provide this
value yourself but it must be unique (just as in HTML):
Expand Down Expand Up @@ -1036,7 +1052,7 @@ const Component = CoreView.extend(
@public
*/
}
);
) as unknown) as ComponentClass;

Component.toString = () => '@ember/component';

Expand Down
2 changes: 1 addition & 1 deletion packages/@ember/-internals/glimmer/lib/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ setGlobalContext({

warnIfStyleNotTrusted(value: unknown) {
warn(
constructStyleDeprecationMessage(value),
constructStyleDeprecationMessage(String(value)),
(() => {
if (value === null || value === undefined || isHTMLSafe(value)) {
return true;
Expand Down
27 changes: 18 additions & 9 deletions packages/@ember/-internals/glimmer/lib/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ import { consumeTag, createTag, dirtyTag } from '@glimmer/validator';

export const RECOMPUTE_TAG = symbol('RECOMPUTE_TAG');

export type HelperFunction<T = unknown> = (positional: unknown[], named: Dict<unknown>) => T;

export type SimpleHelperFactory = Factory<SimpleHelper, HelperFactory<SimpleHelper>>;
export type HelperFunction<T, P extends unknown[], N extends Dict<unknown>> = (
positional: P,
named: N
) => T;

export type SimpleHelperFactory<T, P extends unknown[], N extends Dict<unknown>> = Factory<
SimpleHelper<T, P, N>,
HelperFactory<SimpleHelper<T, P, N>>
>;
export type ClassHelperFactory = Factory<HelperInstance, HelperFactory<HelperInstance>>;

export interface HelperFactory<T> {
Expand All @@ -30,8 +36,8 @@ export interface HelperInstance<T = unknown> {

const IS_CLASSIC_HELPER: unique symbol = Symbol('IS_CLASSIC_HELPER');

export interface SimpleHelper<T = unknown> {
compute: HelperFunction<T>;
export interface SimpleHelper<T, P extends unknown[], N extends Dict<unknown>> {
compute: HelperFunction<T, P, N>;
}

/**
Expand Down Expand Up @@ -89,7 +95,7 @@ interface Helper {
@public
@since 1.13.0
*/
compute(params: unknown[], hash: object): unknown;
compute(params: unknown[], hash: Dict<unknown>): unknown;
}
class Helper extends FrameworkObject {
static isHelperFactory = true;
Expand Down Expand Up @@ -199,10 +205,11 @@ export const CLASSIC_HELPER_MANAGER = getInternalHelperManager(Helper);

///////////

class Wrapper implements HelperFactory<SimpleHelper> {
class Wrapper<T = unknown, P extends unknown[] = unknown[], N extends Dict<unknown> = Dict<unknown>>
implements HelperFactory<SimpleHelper<T, P, N>> {
isHelperFactory: true = true;

constructor(public compute: HelperFunction) {}
constructor(public compute: HelperFunction<T, P, N>) {}

create() {
// needs new instance or will leak containers
Expand Down Expand Up @@ -256,7 +263,9 @@ setHelperManager(() => SIMPLE_CLASSIC_HELPER_MANAGER, Wrapper.prototype);
@public
@since 1.13.0
*/
export function helper(helperFn: HelperFunction): HelperFactory<SimpleHelper> {
export function helper<T, P extends unknown[], N extends Dict<unknown>>(
helperFn: HelperFunction<T, P, N>
): HelperFactory<SimpleHelper<T, P, N>> {
return new Wrapper(helperFn);
}

Expand Down
21 changes: 14 additions & 7 deletions packages/@ember/-internals/glimmer/lib/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { privatize as P } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { getOwner, Owner } from '@ember/-internals/owner';
import { guidFor } from '@ember/-internals/utils';
import { getViewElement, getViewId } from '@ember/-internals/views';
import { assert } from '@ember/debug';
import { _backburner, _getCurrentRunLoop } from '@ember/runloop';
Expand Down Expand Up @@ -38,23 +39,29 @@ import { unwrapTemplate } from '@glimmer/util';
import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator';
import { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface';
import RSVP from 'rsvp';
import Component from './component';
import { BOUNDS } from './component-managers/curly';
import { createRootOutlet } from './component-managers/outlet';
import { RootComponentDefinition } from './component-managers/root';
import { NodeDOMTreeConstruction } from './dom';
import { EmberEnvironmentDelegate } from './environment';
import ResolverImpl from './resolver';
import { Component } from './utils/curly-component-state-bucket';
import { OutletState } from './utils/outlet';
import OutletView from './views/outlet';

export type IBuilder = (env: Environment, cursor: Cursor) => ElementBuilder;

export interface View {
parentView: Option<View>;
renderer: Renderer;
tagName: string | null;
elementId: string | null;
isDestroying: boolean;
isDestroyed: boolean;
}

export class DynamicScope implements GlimmerDynamicScope {
constructor(
public view: Component | {} | null,
public outletState: Reference<OutletState | undefined>
) {}
constructor(public view: View | null, public outletState: Reference<OutletState | undefined>) {}

child() {
return new DynamicScope(this.view, this.outletState);
Expand Down Expand Up @@ -129,7 +136,7 @@ class RootState {
template !== undefined
);

this.id = getViewId(root);
this.id = root instanceof OutletView ? guidFor(root) : getViewId(root);
this.result = undefined;
this.destroyed = false;

Expand Down Expand Up @@ -449,7 +456,7 @@ export class Renderer {
this._clearAllRoots();
}

getElement(view: unknown): Option<SimpleElement> {
getElement(view: View): Option<SimpleElement> {
if (this._isInteractive) {
return getViewElement(view);
} else {
Expand Down
5 changes: 4 additions & 1 deletion packages/@ember/-internals/glimmer/lib/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ export default class ResolverImpl implements RuntimeResolver<Owner>, CompileTime
}

const factory = owner.factoryFor(`helper:${name}`) as
| Factory<SimpleHelper, HelperFactory<SimpleHelper>>
| Factory<
SimpleHelper<unknown, unknown[], Record<string, unknown>>,
HelperFactory<SimpleHelper<unknown, unknown[], Record<string, unknown>>>
>
| Factory<HelperInstance, HelperFactory<HelperInstance>>;

if (factory === undefined) {
Expand Down
2 changes: 1 addition & 1 deletion packages/@ember/-internals/glimmer/lib/utils/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Reference,
valueForRef,
} from '@glimmer/reference';
import { Component } from './curly-component-state-bucket';
import Component from '../component';

function referenceForParts(rootRef: Reference<Component>, parts: string[]): Reference {
let isAttrs = parts[0] === 'attrs';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
import { clearElementView, clearViewElement, getViewElement } from '@ember/-internals/views';
import { registerDestructor } from '@glimmer/destroyable';
import { CapturedNamedArguments, Template, TemplateFactory } from '@glimmer/interfaces';
import { CapturedNamedArguments } from '@glimmer/interfaces';
import { createConstRef, Reference } from '@glimmer/reference';
import { beginUntrackFrame, endUntrackFrame, Revision, Tag, valueForTag } from '@glimmer/validator';
import { Renderer } from '../renderer';

export interface Component {
_debugContainerKey: string;
_transitionTo(name: string): void;
layout?: TemplateFactory | Template;
layoutName?: string;
attributeBindings: Array<string>;
classNames: Array<string>;
classNameBindings: Array<string>;
elementId: string;
tagName: string;
isDestroying: boolean;
appendChild(view: {}): void;
trigger(event: string): void;
destroy(): void;
setProperties(props: { [key: string]: any }): void;
renderer: Renderer;
}
import Component from '../component';

type Finalizer = () => void;
function NOOP() {}
Expand Down
6 changes: 3 additions & 3 deletions packages/@ember/-internals/glimmer/lib/utils/managers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import {
@return {Object} the same object passed in
@public
*/
export function setComponentManager(
export function setComponentManager<T extends object>(
manager: (owner: Owner) => ComponentManager<unknown>,
obj: object
): object {
obj: T
): T {
return glimmerSetComponentManager(manager, obj);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ if (ENV._DEBUG_RENDER_TREE) {
);

this.addComponent('hello-world', {
ComponentClass: Component.extend(),
ComponentClass: class extends Component {},
template: 'Hello World',
});

Expand Down Expand Up @@ -1498,11 +1498,12 @@ if (ENV._DEBUG_RENDER_TREE) {
);

this.addComponent('hello-world', {
ComponentClass: Component.extend({
init() {
ComponentClass: class extends Component {
constructor(owner: Owner) {
super(owner);
throw new Error('oops!');
},
}),
}
},
template: '{{@name}}',
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Mixin } from '@ember/-internals/metal';

export default class ActionHandler extends Mixin {}
export default class ActionHandler extends Mixin {
actions?: Record<string, (...args: unknown[]) => void>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Does it have to return void (i.e. will anything else be thrown away anyway)? I think the answer is no, it can return anything. The type-checker will more or less shrug at it I think, but it should probably be unknown.

  2. I don't think the arguments will type check in practice either. 🤔 playground For types like this, unknown[] is usually wrong for arguments, because it means subclasses can never have narrower types in the function parameters than than, because it breaks assignability/Liskov substitution/variance rules (whichever way is easiest to think about it for you; they all boil down to the same issue here).

Net, the type should probably be:

Suggested change
actions?: Record<string, (...args: unknown[]) => void>;
actions?: Record<string, (...args: any[]) => unknown>;

(Technically, ...args: never[] is the "right" answer here, because all functions are valid super types of that, since never is the "bottom" type… but that confuses the heck out of people. And reasonably so.)

send(actionName: string, ...args: unknown[]): void;
}
7 changes: 7 additions & 0 deletions packages/@ember/-internals/runtime/lib/mixins/evented.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ interface Evented {
method: string | ((this: Target, ...args: any[]) => void)
): this;
on(name: string, method: ((...args: any[]) => void) | string): this;
// Allow for easier super calls
on<Target>(
name: string,
...args:
| [Target, string | ((this: Target, ...args: any[]) => void)]
| [((...args: any[]) => void) | string]
): this;
/**
* Subscribes a function to a named event and then cancels the subscription
* after the first time the event is triggered. It is good to use ``one`` when
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Mixin from '@ember/object/mixin';

interface TargetActionSupport {
target: unknown;
action: unknown;
actionContext: unknown;
actionContextObject: unknown;
triggerAction(opts?: object): unknown;
}
declare const TargetActionSupport: Mixin;

export default TargetActionSupport;
Loading