diff --git a/broccoli/packages.js b/broccoli/packages.js index 7c19582105f..e6e77d329f0 100644 --- a/broccoli/packages.js +++ b/broccoli/packages.js @@ -140,6 +140,12 @@ module.exports.backburnerES = function _backburnerES() { return funnelLib('backburner.js', 'dist/es6', { files: ['backburner.js'], annotation: 'backburner es', + // This writes the "output" to `backburner.js.js` in the funnel, which means + // that when it gets fed into the Babel AMD transform, which (implicitly) + // pulls off the trailing `.js`, the result is just `backburner.js`, which + // is the actual Node-resolve-able (and therefore TS-resolve-able) ES module + // on disk. + getDestinationPath: (relativePath) => relativePath + '.js', }); }; diff --git a/package.json b/package.json index 9d1ed2a78ac..47148148079 100644 --- a/package.json +++ b/package.json @@ -58,9 +58,24 @@ "@babel/helper-module-imports": "^7.16.7", "@babel/plugin-transform-block-scoping": "^7.20.5", "@ember/edition-utils": "^1.2.0", + "@glimmer/compiler": "0.84.2", + "@glimmer/component": "^1.1.2", + "@glimmer/destroyable": "0.84.2", + "@glimmer/env": "^0.1.7", + "@glimmer/global-context": "0.84.2", + "@glimmer/interfaces": "0.84.2", + "@glimmer/manager": "0.84.2", + "@glimmer/node": "0.84.2", + "@glimmer/opcode-compiler": "0.84.2", + "@glimmer/owner": "0.84.2", + "@glimmer/program": "0.84.2", + "@glimmer/reference": "0.84.2", + "@glimmer/runtime": "0.84.2", + "@glimmer/validator": "0.84.2", "@glimmer/vm-babel-plugins": "0.84.2", "babel-plugin-debug-macros": "^0.3.4", "babel-plugin-filter-imports": "^4.0.0", + "backburner.js": "^2.7.0", "broccoli-concat": "^4.2.5", "broccoli-debug": "^0.6.4", "broccoli-file-creator": "^2.1.1", @@ -79,26 +94,14 @@ "ember-router-generator": "^2.0.0", "inflection": "^1.13.2", "resolve": "^1.22.0", + "route-recognizer": "^0.3.4", + "router_js": "^8.0.3", "semver": "^7.3.8", "silent-error": "^1.1.1" }, "devDependencies": { "@aws-sdk/client-s3": "^3.321.1", "@babel/preset-env": "^7.16.11", - "@glimmer/compiler": "0.84.2", - "@glimmer/component": "^1.1.2", - "@glimmer/destroyable": "0.84.2", - "@glimmer/env": "^0.1.7", - "@glimmer/global-context": "0.84.2", - "@glimmer/interfaces": "0.84.2", - "@glimmer/manager": "0.84.2", - "@glimmer/node": "0.84.2", - "@glimmer/opcode-compiler": "0.84.2", - "@glimmer/owner": "0.84.2", - "@glimmer/program": "0.84.2", - "@glimmer/reference": "0.84.2", - "@glimmer/runtime": "0.84.2", - "@glimmer/validator": "0.84.2", "@simple-dom/document": "^1.4.0", "@tsconfig/ember": "^2.0.0", "@types/node": "^18.11.11", @@ -110,7 +113,6 @@ "auto-dist-tag": "^2.1.1", "aws-sdk": "^2.1325.0", "babel-template": "^6.26.0", - "backburner.js": "^2.7.0", "broccoli-babel-transpiler": "^7.8.1", "broccoli-persistent-filter": "^2.3.1", "broccoli-plugin": "^4.0.3", @@ -152,8 +154,6 @@ "puppeteer": "^19.4.1", "qunit": "^2.19.4", "recast": "^0.22.0", - "route-recognizer": "^0.3.4", - "router_js": "^8.0.3", "rsvp": "^4.8.5", "serve-static": "^1.14.2", "simple-dom": "^1.4.0", diff --git a/packages/@ember/-internals/glimmer/index.ts b/packages/@ember/-internals/glimmer/index.ts index 2149bb8d49b..7a6ab6937cd 100644 --- a/packages/@ember/-internals/glimmer/index.ts +++ b/packages/@ember/-internals/glimmer/index.ts @@ -450,7 +450,12 @@ export { default as Input } from './lib/components/input'; export { default as LinkTo } from './lib/components/link-to'; export { default as Textarea } from './lib/components/textarea'; export { default as Component } from './lib/component'; -export { default as Helper, helper } from './lib/helper'; +export { + default as Helper, + helper, + type FunctionBasedHelper, + type FunctionBasedHelperInstance, +} from './lib/helper'; export { SafeString, escapeExpression, htmlSafe, isHTMLSafe } from './lib/utils/string'; export { Renderer, _resetRenderers, renderSettled } from './lib/renderer'; export { diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts index 3845e5f526a..dc6d04ef544 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts @@ -219,7 +219,7 @@ export default class CurlyComponentManager Object.assign(named, args.named.capture()); for (let i = 0; i < count; i++) { - let name = positionalParams[i]; + let name: string | undefined = positionalParams[i]; assert('Expected at least one positional param', name); assert( diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index 12c2ca543e6..4b2f7ce2165 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -36,6 +36,154 @@ let lazyEventsProcessed = new WeakMap>(); @module @ember/component */ +interface ComponentMethods { + // Overrideable methods are defined here since you can't `declare` a method in a class + + /** + Called when the attributes passed into the component have been updated. + Called both during the initial render of a container and during a rerender. + Can be used in place of an observer; code placed here will be executed + every time any attribute updates. + @method didReceiveAttrs + @public + @since 1.13.0 + */ + didReceiveAttrs(): void; + + /** + Called when the attributes passed into the component have been updated. + Called both during the initial render of a container and during a rerender. + Can be used in place of an observer; code placed here will be executed + every time any attribute updates. + @event didReceiveAttrs + @public + @since 1.13.0 + */ + + /** + Called after a component has been rendered, both on initial render and + in subsequent rerenders. + @method didRender + @public + @since 1.13.0 + */ + didRender(): void; + + /** + Called after a component has been rendered, both on initial render and + in subsequent rerenders. + @event didRender + @public + @since 1.13.0 + */ + + /** + Called before a component has been rendered, both on initial render and + in subsequent rerenders. + @method willRender + @public + @since 1.13.0 + */ + willRender(): void; + + /** + Called before a component has been rendered, both on initial render and + in subsequent rerenders. + @event willRender + @public + @since 1.13.0 + */ + + /** + Called when the attributes passed into the component have been changed. + Called only during a rerender, not during an initial render. + @method didUpdateAttrs + @public + @since 1.13.0 + */ + didUpdateAttrs(): void; + + /** + Called when the attributes passed into the component have been changed. + Called only during a rerender, not during an initial render. + @event didUpdateAttrs + @public + @since 1.13.0 + */ + + /** + Called when the component is about to update and rerender itself. + Called only during a rerender, not during an initial render. + @method willUpdate + @public + @since 1.13.0 + */ + willUpdate(): void; + + /** + Called when the component is about to update and rerender itself. + Called only during a rerender, not during an initial render. + @event willUpdate + @public + @since 1.13.0 + */ + + /** + Called when the component has updated and rerendered itself. + Called only during a rerender, not during an initial render. + @method didUpdate + @public + @since 1.13.0 + */ + didUpdate(): void; + + /** + Called when the component has updated and rerendered itself. + Called only during a rerender, not during an initial render. + @event didUpdate + @public + @since 1.13.0 + */ + + /** + 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): + + ```handlebars + {{my-component elementId="a-really-cool-id"}} + ``` + + ```handlebars + + ``` + If not manually set a default value will be provided by the framework. + Once rendered an element's `elementId` is considered immutable and you + should never change it. If you need to compute a dynamic value for the + `elementId`, you should do this when the component or element is being + instantiated: + + ```javascript + export default Component.extend({ + init() { + this._super(...arguments); + + var index = this.get('index'); + this.set('elementId', `component-id${index}`); + } + }); + ``` + + @property elementId + @type String + @public + */ + layoutName?: string; +} + +// A zero-runtime-overhead private symbol to use in branding the component to +// preserve its type parameter. +declare const SIGNATURE: unique symbol; + /** A component is a reusable UI element that consists of a `.hbs` template and an optional JavaScript class that defines its behavior. For example, someone @@ -650,150 +798,9 @@ let lazyEventsProcessed = new WeakMap>(); @uses Ember.ViewStateSupport @public */ -interface ComponentMethods { - // Overrideable methods are defined here since you can't `declare` a method in a class - - /** - Called when the attributes passed into the component have been updated. - Called both during the initial render of a container and during a rerender. - Can be used in place of an observer; code placed here will be executed - every time any attribute updates. - @method didReceiveAttrs - @public - @since 1.13.0 - */ - didReceiveAttrs(): void; - - /** - Called when the attributes passed into the component have been updated. - Called both during the initial render of a container and during a rerender. - Can be used in place of an observer; code placed here will be executed - every time any attribute updates. - @event didReceiveAttrs - @public - @since 1.13.0 - */ - - /** - Called after a component has been rendered, both on initial render and - in subsequent rerenders. - @method didRender - @public - @since 1.13.0 - */ - didRender(): void; - - /** - Called after a component has been rendered, both on initial render and - in subsequent rerenders. - @event didRender - @public - @since 1.13.0 - */ - - /** - Called before a component has been rendered, both on initial render and - in subsequent rerenders. - @method willRender - @public - @since 1.13.0 - */ - willRender(): void; - - /** - Called before a component has been rendered, both on initial render and - in subsequent rerenders. - @event willRender - @public - @since 1.13.0 - */ - - /** - Called when the attributes passed into the component have been changed. - Called only during a rerender, not during an initial render. - @method didUpdateAttrs - @public - @since 1.13.0 - */ - didUpdateAttrs(): void; - - /** - Called when the attributes passed into the component have been changed. - Called only during a rerender, not during an initial render. - @event didUpdateAttrs - @public - @since 1.13.0 - */ - - /** - Called when the component is about to update and rerender itself. - Called only during a rerender, not during an initial render. - @method willUpdate - @public - @since 1.13.0 - */ - willUpdate(): void; - - /** - Called when the component is about to update and rerender itself. - Called only during a rerender, not during an initial render. - @event willUpdate - @public - @since 1.13.0 - */ - - /** - Called when the component has updated and rerendered itself. - Called only during a rerender, not during an initial render. - @method didUpdate - @public - @since 1.13.0 - */ - didUpdate(): void; - - /** - Called when the component has updated and rerendered itself. - Called only during a rerender, not during an initial render. - @event didUpdate - @public - @since 1.13.0 - */ - - /** - 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): - - ```handlebars - {{my-component elementId="a-really-cool-id"}} - ``` - - ```handlebars - - ``` - If not manually set a default value will be provided by the framework. - Once rendered an element's `elementId` is considered immutable and you - should never change it. If you need to compute a dynamic value for the - `elementId`, you should do this when the component or element is being - instantiated: - - ```javascript - export default Component.extend({ - init() { - this._super(...arguments); - - var index = this.get('index'); - this.set('elementId', `component-id${index}`); - } - }); - ``` - - @property elementId - @type String - @public - */ - layoutName?: string; -} -interface Component +// This type param is used in the class, so must appear here. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +interface Component extends CoreView, ChildViewsSupport, ViewStateSupport, @@ -803,7 +810,7 @@ interface Component ViewMixin, ComponentMethods {} -class Component +class Component extends CoreView.extend( ChildViewsSupport, ViewStateSupport, @@ -826,6 +833,10 @@ class Component { isComponent = true; + // SAFETY: this has no runtime existence whatsoever; it is a "phantom type" + // here to preserve the type param. + private declare [SIGNATURE]: S; + // SAFTEY: This is set in `init`. declare _superRerender: ViewMixin['rerender']; @@ -1009,50 +1020,65 @@ class Component declare attributeBindings?: string[]; /** - Layout can be used to wrap content in a component. - @property layout - @type Function - @public - */ - declare layout?: TemplateFactory | Template; + Enables components to take a list of parameters as arguments. + For example, a component that takes two parameters with the names + `name` and `age`: - /** - 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 - */ - declare layoutName?: string; + ```app/components/my-component.js + import Component from '@ember/component'; - /** - The WAI-ARIA role of the control represented by this view. For example, a - button may have a role of type 'button', or a pane may have a role of - type 'alertdialog'. This property is used by assistive software to help - visually challenged users navigate rich web applications. + let MyComponent = Component.extend(); - The full list of valid WAI-ARIA roles is available at: - [https://www.w3.org/TR/wai-aria/#roles_categorization](https://www.w3.org/TR/wai-aria/#roles_categorization) + MyComponent.reopenClass({ + positionalParams: ['name', 'age'] + }); - @property ariaRole - @type String - @default undefined - @public - */ - declare ariaRole?: string; + export default MyComponent; + ``` - static isComponentFactory = true; + It can then be invoked like this: - static toString() { - return '@ember/component'; - } -} + ```hbs + {{my-component "John" 38}} + ``` -// We continue to use reopenClass here so that positionalParams can be overridden with reopenClass in subclasses. -Component.reopenClass({ - /** + The parameters can be referred to just like named parameters: + + ```hbs + Name: {{name}}, Age: {{age}}. + ``` + + Using a string instead of an array allows for an arbitrary number of + parameters: + + ```app/components/my-component.js + import Component from '@ember/component'; + + let MyComponent = Component.extend(); + + MyComponent.reopenClass({ + positionalParams: 'names' + }); + + export default MyComponent; + ``` + + It can then be invoked like this: + + ```hbs + {{my-component "John" "Michael" "Scott"}} + ``` + The parameters can then be referred to by enumerating over the list: + + ```hbs + {{#each names as |name|}}{{name}}{{/each}} + ``` + + @static + @public + @property positionalParams + @since 1.13.0 + */ /** Enables components to take a list of parameters as arguments. For example, a component that takes two parameters with the names `name` and `age`: @@ -1112,6 +1138,52 @@ Component.reopenClass({ @property positionalParams @since 1.13.0 */ + declare static positionalParams: string | string[]; + + /** + Layout can be used to wrap content in a component. + @property layout + @type Function + @public + */ + declare 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 + */ + declare layoutName?: string; + + /** + The WAI-ARIA role of the control represented by this view. For example, a + button may have a role of type 'button', or a pane may have a role of + type 'alertdialog'. This property is used by assistive software to help + visually challenged users navigate rich web applications. + + The full list of valid WAI-ARIA roles is available at: + [https://www.w3.org/TR/wai-aria/#roles_categorization](https://www.w3.org/TR/wai-aria/#roles_categorization) + + @property ariaRole + @type String + @default undefined + @public + */ + declare ariaRole?: string; + + static isComponentFactory = true; + + static toString() { + return '@ember/component'; + } +} + +// We continue to use reopenClass here so that positionalParams can be overridden with reopenClass in subclasses. +Component.reopenClass({ positionalParams: [], }); diff --git a/packages/@ember/-internals/glimmer/lib/components/input.ts b/packages/@ember/-internals/glimmer/lib/components/input.ts index e22e6a35879..94d2fd706b9 100644 --- a/packages/@ember/-internals/glimmer/lib/components/input.ts +++ b/packages/@ember/-internals/glimmer/lib/components/input.ts @@ -2,13 +2,14 @@ @module @ember/component */ import { hasDOM } from '@ember/-internals/browser-environment'; +import { type Opaque } from '@ember/-internals/utility-types'; import { assert, warn } from '@ember/debug'; import { action } from '@ember/object'; import { valueForRef } from '@glimmer/reference'; import { untrack } from '@glimmer/validator'; import InputTemplate from '../templates/input'; import AbstractInput, { valueFrom } from './abstract-input'; -import { opaquify } from './internal'; +import { type OpaqueInternalComponentConstructor, opaquify } from './internal'; let isValidInputType: (type: string) => boolean; @@ -158,7 +159,7 @@ if (hasDOM) { @param {Hash} options @public */ -class Input extends AbstractInput { +class _Input extends AbstractInput { static toString(): string { return 'Input'; } @@ -270,4 +271,6 @@ class Input extends AbstractInput { } } -export default opaquify(Input, InputTemplate); +const Input = opaquify(_Input, InputTemplate) as Input; +interface Input extends Opaque<'component:input'>, OpaqueInternalComponentConstructor {} +export default Input; diff --git a/packages/@ember/-internals/glimmer/lib/components/link-to.ts b/packages/@ember/-internals/glimmer/lib/components/link-to.ts index f90bcfc951c..97c119a10f2 100644 --- a/packages/@ember/-internals/glimmer/lib/components/link-to.ts +++ b/packages/@ember/-internals/glimmer/lib/components/link-to.ts @@ -12,7 +12,8 @@ import type { Maybe, Option } from '@glimmer/interfaces'; import { consumeTag, createCache, getValue, tagFor, untrack } from '@glimmer/validator'; import type { Transition } from 'router_js'; import LinkToTemplate from '../templates/link-to'; -import InternalComponent, { opaquify } from './internal'; +import InternalComponent, { type OpaqueInternalComponentConstructor, opaquify } from './internal'; +import { type Opaque } from '@ember/-internals/utility-types'; const EMPTY_ARRAY: {}[] = []; const EMPTY_QUERY_PARAMS = {}; @@ -272,7 +273,7 @@ function isQueryParams(value: unknown): value is QueryParams { @public **/ -class LinkTo extends InternalComponent { +class _LinkTo extends InternalComponent { static toString(): string { return 'LinkTo'; } @@ -469,12 +470,12 @@ class LinkTo extends InternalComponent { } private get isActive(): boolean { - return this.isActiveForState(this.routing.currentState as Maybe>); + return this.isActiveForState(this.routing.currentState as Maybe); } private get willBeActive(): Option { - let current = this.routing.currentState as Maybe>; - let target = this.routing.targetState as Maybe>; + let current = this.routing.currentState; + let target = this.routing.targetState; if (current === target) { return null; @@ -530,7 +531,7 @@ class LinkTo extends InternalComponent { } } - private isActiveForState(state: Maybe>): boolean { + private isActiveForState(state: Maybe): boolean { if (!isPresent(state)) { return false; } @@ -582,7 +583,7 @@ class LinkTo extends InternalComponent { } } -let { prototype } = LinkTo; +let { prototype } = _LinkTo; let descriptorFor = (target: object, property: string): Option => { if (target) { @@ -602,7 +603,7 @@ let descriptorFor = (target: object, property: string): Option is not supported.`); } else { @@ -621,12 +622,12 @@ let descriptorFor = (target: object, property: string): Option {}[]; + let superModelsGetter = superModelsDescriptor.get as (this: _LinkTo) => {}[]; Object.defineProperty(prototype, 'models', { configurable: true, enumerable: false, - get: function models(this: LinkTo): {}[] { + get: function models(this: _LinkTo): {}[] { let models = superModelsGetter.call(this); if (models.length > 0 && !('query' in this.args.named)) { @@ -646,12 +647,12 @@ let descriptorFor = (target: object, property: string): Option {}; + let superQueryGetter = superQueryDescriptor.get as (this: _LinkTo) => {}; Object.defineProperty(prototype, 'query', { configurable: true, enumerable: false, - get: function query(this: LinkTo): {} { + get: function query(this: _LinkTo): {} { if ('query' in this.args.named) { let qp = superQueryGetter.call(this); @@ -684,7 +685,7 @@ let descriptorFor = (target: object, property: string): Option, OpaqueInternalComponentConstructor {} +export default LinkTo; diff --git a/packages/@ember/-internals/glimmer/lib/components/textarea.ts b/packages/@ember/-internals/glimmer/lib/components/textarea.ts index 8f3e3c32d71..9d0d4178753 100644 --- a/packages/@ember/-internals/glimmer/lib/components/textarea.ts +++ b/packages/@ember/-internals/glimmer/lib/components/textarea.ts @@ -1,10 +1,11 @@ /** @module @ember/component */ +import { type Opaque } from '@ember/-internals/utility-types'; import { action } from '@ember/object'; import TextareaTemplate from '../templates/textarea'; import AbstractInput from './abstract-input'; -import { opaquify } from './internal'; +import { type OpaqueInternalComponentConstructor, opaquify } from './internal'; /** The `Textarea` component inserts a new instance of `