ember-source
Version:
A JavaScript framework for creating ambitious web applications
1,690 lines (1,543 loc) • 170 kB
JavaScript
import './fragment-EpVz5Xuc.js';
import { untrack, consumeTag, tagFor, createCache, getValue, valueForTag, beginUntrackFrame, endUntrackFrame, beginTrackFrame, endTrackFrame, validateTag, createTag, dirtyTag as DIRTY_TAG$1, CONSTANT_TAG, isTracking, CURRENT_TAG } from '../@glimmer/validator/index.js';
import { v as valueForRef, a as createConstRef, i as isConstRef, l as isUpdatableRef, u as updateRef, c as createComputeRef, d as createPrimitiveRef, b as childRefFor, f as childRefFromParts, U as UNDEFINED_REFERENCE, h as createInvokableRef, j as createReadOnlyRef, k as createUnboundRef } from './reference-BNqcwZWH.js';
import { t as templateFactory } from './index-CSVCFS_p.js';
import { c as capabilityFlagsFrom } from './capabilities-DGmQ_mz4.js';
import { getFactoryFor, privatize } from '../@ember/-internals/container/index.js';
import { g as guidFor, i as isObject } from './super-Cm_a_cLQ.js';
import { ENV } from '../@ember/-internals/environment/index.js';
import { t as reifyPositional, E as EMPTY_ARGS, k as createCapturedArgs, d as EMPTY_POSITIONAL, l as curry, o as hash, n as get$1, m as fn, j as concat, h as array, q as on$1, T as TEMPLATE_ONLY_COMPONENT_MANAGER, w as templateOnlyComponent } from './on-CrTl7JQU.js';
import './constants-b-2IVErl.js';
import { join, schedule, _backburner, _getCurrentRunLoop } from '../@ember/runloop/index.js';
import { h as hasDOM } from './has-dom-DdQORPzI.js';
import { action } from '../@ember/object/index.js';
import { on } from '../@ember/modifier/on.js';
import { g as get, t as tagForObject, n as objectAt, a as tagForProperty, q as _getProp } from './observers-R1ZklwWy.js';
import { t as tracked } from './tracked-ChVNBE2f.js';
import { setOwner, getOwner, isFactory } from '../@ember/-internals/owner/index.js';
import { assert } from '../@ember/debug/lib/assert.js';
import { s as setInternalComponentManager, g as getInternalHelperManager, c as helperCapabilities, i as setInternalHelperManager, b as hasInternalComponentManager, e as getInternalComponentManager } from './api-Co-k4HVs.js';
import { s as setComponentTemplate, g as getComponentTemplate } from './template-Dc_cBOoX.js';
import { d as decorateFieldV2, i as initializeDeferredDecorator, a as decorateMethodV2 } from './chunk-3SQBS3Y5-Cj4eryg1.js';
import { isSimpleClick, getViewElement, clearElementView, clearViewElement, addChildView, setViewElement, setElementView, getChildViews, getViewId } from '../@ember/-internals/views/lib/system/utils.js';
import CoreView from '../@ember/-internals/views/lib/views/core_view.js';
import ActionSupport from '../@ember/-internals/views/lib/mixins/action_support.js';
import '../@ember/-internals/views/lib/views/states.js';
import { getEngineParent } from '../@ember/engine/lib/engine-parent.js';
import { flaggedInstrument, _instrumentStart } from '../@ember/instrumentation/index.js';
import { service } from '../@ember/service/index.js';
import { n as nativeDescDecorator } from './decorator-BdDDBUd2.js';
import { P as PROPERTY_DID_CHANGE } from './namespace_search-Aog9nySA.js';
import '../@ember/-internals/runtime/lib/mixins/registry_proxy.js';
import '../@ember/-internals/runtime/lib/mixins/container_proxy.js';
import '../@ember/-internals/runtime/lib/mixins/comparable.js';
import '../@ember/-internals/runtime/lib/mixins/action_handler.js';
import { contentFor } from '../@ember/-internals/runtime/lib/mixins/-proxy.js';
import '../@ember/enumerable/mutable.js';
import TargetActionSupport from '../@ember/-internals/runtime/lib/mixins/target_action_support.js';
import '../@ember/-internals/runtime/lib/ext/rsvp.js';
import { n as normalizeProperty, c as clientBuilder } from './element-builder-CiLTrXD6.js';
import { E as EMPTY_ARRAY$2 } from './array-utils-CZQxrdD3.js';
import { dasherize } from '../@ember/-internals/string/index.js';
import { registerDestructor, associateDestroyableChild, isDestroyed, destroy, isDestroying } from '../@glimmer/destroyable/index.js';
import { MUTABLE_CELL } from '../@ember/-internals/views/lib/compat/attrs.js';
import { FrameworkObject } from '../@ember/object/-internals.js';
import { g as getDebugName } from './get-debug-name-BDxIL2Y1.js';
import { setHelperManager } from '../@glimmer/manager/index.js';
import { d as dict } from './collections-GpG8lT2g.js';
import { artifacts } from '../@glimmer/program/index.js';
import { R as RuntimeOpImpl } from './program-B7SJZ5NF.js';
import { c as runtimeOptions, i as inTransaction, r as renderComponent$1, a as renderMain } from './render-C1ZnScKH.js';
import { a as RSVP } from './rsvp-CnCSY930.js';
import '../@ember/engine/instance.js';
import setGlobalContext from '../@glimmer/global-context/index.js';
import { _ as _setProp, s as set } from './property_set-O080KTKZ.js';
import { isEmberArray } from '../@ember/array/-internals.js';
import { i as isProxy } from './is_proxy-Cr1qlMv_.js';
import { isArray } from '../@ember/array/index.js';
import '../route-recognizer/index.js';
import './unrecognized-url-error-DDBwfzdm.js';
import '../@ember/routing/lib/routing-service.js';
import { generateControllerFactory } from '../@ember/routing/lib/generate_controller.js';
import { E as EvaluationContextImpl } from './program-context-C-JdYXRA.js';
const RootTemplate = templateFactory(
/*
{{component this}}
*/
{
"id": null,
"block": "[[[46,[30,0],null,null,null]],[],[\"component\"]]",
"moduleName": "packages/@ember/-internals/glimmer/lib/templates/root.hbs",
"isStrictMode": true
});
const InputTemplate = templateFactory(
/*
<input
{{!-- for compatibility --}}
id={{this.id}}
class={{this.class}}
...attributes
type={{this.type}}
checked={{this.checked}}
value={{this.value}}
{{on "change" this.change}}
{{on "input" this.input}}
{{on "keyup" this.keyUp}}
{{on "paste" this.valueDidChange}}
{{on "cut" this.valueDidChange}}
/>
*/
{
"id": null,
"block": "[[[11,\"input\"],[16,1,[30,0,[\"id\"]]],[16,0,[30,0,[\"class\"]]],[17,1],[16,4,[30,0,[\"type\"]]],[16,\"checked\",[30,0,[\"checked\"]]],[16,2,[30,0,[\"value\"]]],[4,[32,0],[\"change\",[30,0,[\"change\"]]],null],[4,[32,0],[\"input\",[30,0,[\"input\"]]],null],[4,[32,0],[\"keyup\",[30,0,[\"keyUp\"]]],null],[4,[32,0],[\"paste\",[30,0,[\"valueDidChange\"]]],null],[4,[32,0],[\"cut\",[30,0,[\"valueDidChange\"]]],null],[12],[13]],[\"&attrs\"],[]]",
"moduleName": "packages/@ember/-internals/glimmer/lib/templates/input.hbs",
"scope": () => ({
on
}),
"isStrictMode": true
});
function NOOP$1() {}
class InternalComponent {
// Override this
static toString() {
return 'internal component';
}
constructor(owner, args, caller) {
this.owner = owner;
this.args = args;
this.caller = caller;
setOwner(this, owner);
}
/**
* The default HTML id attribute. We don't really _need_ one, this is just
* added for compatibility as it's hard to tell if people rely on it being
* present, and it doens't really hurt.
*
* However, don't rely on this internally, like passing it to `getElementId`.
* This can be (and often is) overriden by passing an `id` attribute on the
* invocation, which shadows this default id via `...attributes`.
*/
get id() {
return guidFor(this);
}
/**
* The default HTML class attribute. Similar to the above, we don't _need_
* them, they are just added for compatibility as it's similarly hard to tell
* if people rely on it in their CSS etc, and it doens't really hurt.
*/
get class() {
return 'ember-view';
}
validateArguments() {
for (let name of Object.keys(this.args.named)) {
if (!this.isSupportedArgument(name)) {
this.onUnsupportedArgument(name);
}
}
}
named(name) {
let ref = this.args.named[name];
return ref ? valueForRef(ref) : undefined;
}
positional(index) {
let ref = this.args.positional[index];
return ref ? valueForRef(ref) : undefined;
}
listenerFor(name) {
let listener = this.named(name);
if (listener) {
return listener;
} else {
return NOOP$1;
}
}
isSupportedArgument(_name) {
return false;
}
onUnsupportedArgument(_name) {}
toString() {
return `<${this.constructor}:${guidFor(this)}>`;
}
}
const OPAQUE_CONSTRUCTOR_MAP = new WeakMap();
function opaquify(constructor, template) {
let _opaque = {
// Factory interface
create() {
throw assert('Use constructor instead of create');
},
toString() {
return constructor.toString();
}
};
let opaque = _opaque;
OPAQUE_CONSTRUCTOR_MAP.set(opaque, constructor);
setInternalComponentManager(INTERNAL_COMPONENT_MANAGER, opaque);
setComponentTemplate(template, opaque);
return opaque;
}
function deopaquify(opaque) {
let constructor = OPAQUE_CONSTRUCTOR_MAP.get(opaque);
return constructor;
}
const CAPABILITIES$3 = {
dynamicLayout: false,
dynamicTag: false,
prepareArgs: false,
createArgs: true,
attributeHook: false,
elementHook: false,
createCaller: true,
dynamicScope: false,
updateHook: false,
createInstance: true,
wrapped: false,
willDestroy: false,
hasSubOwner: false
};
class InternalManager {
getCapabilities() {
return CAPABILITIES$3;
}
create(owner, definition, args, _env, _dynamicScope, caller) {
let ComponentClass = deopaquify(definition);
let instance = new ComponentClass(owner, args.capture(), valueForRef(caller));
untrack(instance['validateArguments'].bind(instance));
return instance;
}
didCreate() {}
didUpdate() {}
didRenderLayout() {}
didUpdateLayout() {}
getDebugName(definition) {
return definition.toString();
}
getSelf(instance) {
return createConstRef(instance);
}
getDestroyable(instance) {
return instance;
}
}
const INTERNAL_COMPONENT_MANAGER = new InternalManager();
const UNINITIALIZED = Object.freeze({});
function elementForEvent(event) {
return event.target;
}
function valueForEvent(event) {
return elementForEvent(event).value;
}
function devirtualize(callback) {
return event => callback(valueForEvent(event), event);
}
function valueFrom(reference) {
if (reference === undefined) {
return new LocalValue(undefined);
} else if (isConstRef(reference)) {
return new LocalValue(valueForRef(reference));
} else if (isUpdatableRef(reference)) {
return new UpstreamValue(reference);
} else {
return new ForkedValue(reference);
}
}
class LocalValue {
static {
decorateFieldV2(this.prototype, "value", [tracked]);
}
#value = (initializeDeferredDecorator(this, "value"), void 0);
constructor(value) {
this.value = value;
}
get() {
return this.value;
}
set(value) {
this.value = value;
}
}
class UpstreamValue {
constructor(reference) {
this.reference = reference;
}
get() {
return valueForRef(this.reference);
}
set(value) {
updateRef(this.reference, value);
}
}
class ForkedValue {
local;
upstream;
lastUpstreamValue = UNINITIALIZED;
constructor(reference) {
this.upstream = new UpstreamValue(reference);
}
get() {
let upstreamValue = this.upstream.get();
if (upstreamValue !== this.lastUpstreamValue) {
this.lastUpstreamValue = upstreamValue;
this.local = new LocalValue(upstreamValue);
}
return this.local.get();
}
set(value) {
this.local.set(value);
}
}
class AbstractInput extends InternalComponent {
validateArguments() {
super.validateArguments();
}
_value = valueFrom(this.args.named['value']);
get value() {
return this._value.get();
}
set value(value) {
this._value.set(value);
}
valueDidChange(event) {
this.value = valueForEvent(event);
}
/**
* The `change` and `input` actions need to be overridden in the `Input`
* subclass. Unfortunately, some ember-source builds currently uses babel
* loose mode to transpile its classes. Having the `@action` decorator on the
* super class creates a getter on the prototype, and when the subclass
* overrides the method, the loose mode transpilation would emit something
* like `Subclass.prototype['change'] = function change() { ... }`, which
* fails because `prototype['change']` is getter-only/readonly. The correct
* solution is to use `Object.defineProperty(prototype, 'change', ...)` but
* that requires disabling loose mode. For now, the workaround is to add the
* decorator only on the subclass. This is more of a configuration issue on
* our own builds and doesn't really affect apps.
*/
/* @action */
static {
decorateMethodV2(this.prototype, "valueDidChange", [action]);
}
change(event) {
this.valueDidChange(event);
}
/* @action */
input(event) {
this.valueDidChange(event);
}
keyUp(event) {
switch (event.key) {
case 'Enter':
this.listenerFor('enter')(event);
this.listenerFor('insert-newline')(event);
break;
case 'Escape':
this.listenerFor('escape-press')(event);
break;
}
}
static {
decorateMethodV2(this.prototype, "keyUp", [action]);
}
listenerFor(name) {
let listener = super.listenerFor(name);
if (this.isVirtualEventListener(name, listener)) {
return devirtualize(listener);
} else {
return listener;
}
}
isVirtualEventListener(name, _listener) {
let virtualEvents = ['enter', 'insert-newline', 'escape-press'];
return virtualEvents.indexOf(name) !== -1;
}
}
/**
@module @ember/component
*/
let isValidInputType;
if (hasDOM) {
const INPUT_TYPES = Object.create(null);
const INPUT_ELEMENT = document.createElement('input');
INPUT_TYPES[''] = false;
INPUT_TYPES['text'] = true;
INPUT_TYPES['checkbox'] = true;
isValidInputType = type => {
let isValid = INPUT_TYPES[type];
if (isValid === undefined) {
try {
INPUT_ELEMENT.type = type;
isValid = INPUT_ELEMENT.type === type;
} catch (_e) {
isValid = false;
} finally {
INPUT_ELEMENT.type = 'text';
}
INPUT_TYPES[type] = isValid;
}
return isValid;
};
} else {
isValidInputType = type => type !== '';
}
/**
See [Ember.Templates.components.Input](/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input).
@method input
@for Ember.Templates.helpers
@param {Hash} options
@public
*/
/**
An opaque interface which can be imported and used in strict-mode
templates to call <Input>.
See [Ember.Templates.components.Input](/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input).
@for @ember/component
@method Input
@see {Ember.Templates.components.Input}
@public
**/
/**
The `Input` component lets you create an HTML `<input>` element.
```handlebars
<Input @value="987" />
```
creates an `<input>` element with `type="text"` and value set to 987.
### Text field
If no `type` argument is specified, a default of type 'text' is used.
```handlebars
Search:
<Input @value={{this.searchWord}} />
```
In this example, the initial value in the `<input>` will be set to the value of
`this.searchWord`. If the user changes the text, the value of `this.searchWord` will also be
updated.
### Actions
The `Input` component takes a number of arguments with callbacks that are invoked in response to
user events.
* `enter`
* `insert-newline`
* `escape-press`
* `focus-in`
* `focus-out`
* `key-down`
* `key-press`
* `key-up`
These callbacks are passed to `Input` like this:
```handlebars
<Input @value={{this.searchWord}} @enter={{this.query}} />
```
Starting with Ember Octane, we recommend using the `{{on}}` modifier to call actions
on specific events, such as the input event.
```handlebars
<label for="input-name">Name:</label>
<Input
@id="input-name"
@value={{this.name}}
{{on "input" this.validateName}}
/>
```
The event name (e.g. `focusout`, `input`, `keydown`) always follows the casing
that the HTML standard uses.
### `<input>` HTML Attributes to Avoid
In most cases, if you want to pass an attribute to the underlying HTML `<input>` element, you
can pass the attribute directly, just like any other Ember component.
```handlebars
<Input @type="text" size="10" />
```
In this example, the `size` attribute will be applied to the underlying `<input>` element in the
outputted HTML.
However, there are a few attributes where you **must** use the `@` version.
* `@type`: This argument is used to control which Ember component is used under the hood
* `@value`: The `@value` argument installs a two-way binding onto the element. If you wanted a
one-way binding, use `<input>` with the `value` property and the `input` event instead.
* `@checked` (for checkboxes): like `@value`, the `@checked` argument installs a two-way binding
onto the element. If you wanted a one-way binding, use `<input type="checkbox">` with
`checked` and the `input` event instead.
### Checkbox
To create an `<input type="checkbox">`:
```handlebars
Emberize Everything:
<Input @type="checkbox" @checked={{this.isEmberized}} name="isEmberized" />
```
This will bind the checked state of this checkbox to the value of `isEmberized` -- if either one
changes, it will be reflected in the other.
@method Input
@for Ember.Templates.components
@param {Hash} options
@public
*/
class _Input extends AbstractInput {
static toString() {
return 'Input';
}
/**
* The HTML class attribute.
*/
get class() {
if (this.isCheckbox) {
return 'ember-checkbox ember-view';
} else {
return 'ember-text-field ember-view';
}
}
/**
* The HTML type attribute.
*/
get type() {
let type = this.named('type');
if (type === null || type === undefined) {
return 'text';
}
return isValidInputType(type) ? type : 'text';
}
get isCheckbox() {
return this.named('type') === 'checkbox';
}
_checked = valueFrom(this.args.named['checked']);
get checked() {
if (this.isCheckbox) {
return this._checked.get();
} else {
return undefined;
}
}
set checked(checked) {
this._checked.set(checked);
}
change(event) {
if (this.isCheckbox) {
this.checkedDidChange(event);
} else {
super.change(event);
}
}
static {
decorateMethodV2(this.prototype, "change", [action]);
}
input(event) {
if (!this.isCheckbox) {
super.input(event);
}
}
static {
decorateMethodV2(this.prototype, "input", [action]);
}
checkedDidChange(event) {
let element = event.target;
this.checked = element.checked;
}
static {
decorateMethodV2(this.prototype, "checkedDidChange", [action]);
}
isSupportedArgument(name) {
let supportedArguments = ['type', 'value', 'checked', 'enter', 'insert-newline', 'escape-press'];
return supportedArguments.indexOf(name) !== -1 || super.isSupportedArgument(name);
}
}
const Input = opaquify(_Input, InputTemplate);
const LinkToTemplate = templateFactory(
/*
<a
{{!-- for compatibility --}}
id={{this.id}}
class={{this.class}}
{{!-- deprecated attribute bindings --}}
role={{this.role}}
title={{this.title}}
rel={{this.rel}}
tabindex={{this.tabindex}}
target={{this.target}}
...attributes
href={{this.href}}
{{on 'click' this.click}}
>{{yield}}</a>
*/
{
"id": null,
"block": "[[[11,3],[16,1,[30,0,[\"id\"]]],[16,0,[30,0,[\"class\"]]],[16,\"role\",[30,0,[\"role\"]]],[16,\"title\",[30,0,[\"title\"]]],[16,\"rel\",[30,0,[\"rel\"]]],[16,\"tabindex\",[30,0,[\"tabindex\"]]],[16,\"target\",[30,0,[\"target\"]]],[17,1],[16,6,[30,0,[\"href\"]]],[4,[32,0],[\"click\",[30,0,[\"click\"]]],null],[12],[18,2,null],[13]],[\"&attrs\",\"&default\"],[\"yield\"]]",
"moduleName": "packages/@ember/-internals/glimmer/lib/templates/link-to.hbs",
"scope": () => ({
on
}),
"isStrictMode": true
});
const EMPTY_ARRAY$1 = [];
const EMPTY_QUERY_PARAMS = {};
function isMissing(value) {
return value === null || value === undefined;
}
function isPresent(value) {
return !isMissing(value);
}
function isQueryParams(value) {
return typeof value === 'object' && value !== null && value['isQueryParams'] === true;
}
/**
The `LinkTo` component renders a link to the supplied `routeName` passing an optionally
supplied model to the route as its `model` context of the route. The block for `LinkTo`
becomes the contents of the rendered element:
```handlebars
<LinkTo @route='photoGallery'>
Great Hamster Photos
</LinkTo>
```
This will result in:
```html
<a href="/hamster-photos">
Great Hamster Photos
</a>
```
### Disabling the `LinkTo` component
The `LinkTo` component can be disabled by using the `disabled` argument. A disabled link
doesn't result in a transition when activated, and adds the `disabled` class to the `<a>`
element.
(The class name to apply to the element can be overridden by using the `disabledClass`
argument)
```handlebars
<LinkTo @route='photoGallery' @disabled={{true}}>
Great Hamster Photos
</LinkTo>
```
### Handling `href`
`<LinkTo>` will use your application's Router to fill the element's `href` property with a URL
that matches the path to the supplied `routeName`.
### Handling current route
The `LinkTo` component will apply a CSS class name of 'active' when the application's current
route matches the supplied routeName. For example, if the application's current route is
'photoGallery.recent', then the following invocation of `LinkTo`:
```handlebars
<LinkTo @route='photoGallery.recent'>
Great Hamster Photos
</LinkTo>
```
will result in
```html
<a href="/hamster-photos/this-week" class="active">
Great Hamster Photos
</a>
```
The CSS class used for active classes can be customized by passing an `activeClass` argument:
```handlebars
<LinkTo @route='photoGallery.recent' @activeClass="current-url">
Great Hamster Photos
</LinkTo>
```
```html
<a href="/hamster-photos/this-week" class="current-url">
Great Hamster Photos
</a>
```
### Keeping a link active for other routes
If you need a link to be 'active' even when it doesn't match the current route, you can use the
`current-when` argument.
```handlebars
<LinkTo @route='photoGallery' @current-when='photos'>
Photo Gallery
</LinkTo>
```
This may be helpful for keeping links active for:
* non-nested routes that are logically related
* some secondary menu approaches
* 'top navigation' with 'sub navigation' scenarios
A link will be active if `current-when` is `true` or the current
route is the route this link would transition to.
To match multiple routes 'space-separate' the routes:
```handlebars
<LinkTo @route='gallery' @current-when='photos drawings paintings'>
Art Gallery
</LinkTo>
```
### Supplying a model
An optional `model` argument can be used for routes whose
paths contain dynamic segments. This argument will become
the model context of the linked route:
```javascript
Router.map(function() {
this.route("photoGallery", {path: "hamster-photos/:photo_id"});
});
```
```handlebars
<LinkTo @route='photoGallery' @model={{this.aPhoto}}>
{{aPhoto.title}}
</LinkTo>
```
```html
<a href="/hamster-photos/42">
Tomster
</a>
```
### Supplying multiple models
For deep-linking to route paths that contain multiple
dynamic segments, the `models` argument can be used.
As the router transitions through the route path, each
supplied model argument will become the context for the
route with the dynamic segments:
```javascript
Router.map(function() {
this.route("photoGallery", { path: "hamster-photos/:photo_id" }, function() {
this.route("comment", {path: "comments/:comment_id"});
});
});
```
This argument will become the model context of the linked route:
```handlebars
<LinkTo @route='photoGallery.comment' @models={{array this.aPhoto this.comment}}>
{{comment.body}}
</LinkTo>
```
```html
<a href="/hamster-photos/42/comments/718">
A+++ would snuggle again.
</a>
```
### Supplying an explicit dynamic segment value
If you don't have a model object available to pass to `LinkTo`,
an optional string or integer argument can be passed for routes whose
paths contain dynamic segments. This argument will become the value
of the dynamic segment:
```javascript
Router.map(function() {
this.route("photoGallery", { path: "hamster-photos/:photo_id" });
});
```
```handlebars
<LinkTo @route='photoGallery' @model={{aPhotoId}}>
{{this.aPhoto.title}}
</LinkTo>
```
```html
<a href="/hamster-photos/42">
Tomster
</a>
```
When transitioning into the linked route, the `model` hook will
be triggered with parameters including this passed identifier.
### Supplying query parameters
If you need to add optional key-value pairs that appear to the right of the ? in a URL,
you can use the `query` argument.
```handlebars
<LinkTo @route='photoGallery' @query={{hash page=1 per_page=20}}>
Great Hamster Photos
</LinkTo>
```
This will result in:
```html
<a href="/hamster-photos?page=1&per_page=20">
Great Hamster Photos
</a>
```
@for Ember.Templates.components
@method LinkTo
@public
*/
/**
@module @ember/routing
*/
/**
See [Ember.Templates.components.LinkTo](/ember/release/classes/Ember.Templates.components/methods/input?anchor=LinkTo).
@for Ember.Templates.helpers
@method link-to
@see {Ember.Templates.components.LinkTo}
@public
**/
/**
An opaque interface which can be imported and used in strict-mode
templates to call <LinkTo>.
See [Ember.Templates.components.LinkTo](/ember/release/classes/Ember.Templates.components/methods/input?anchor=LinkTo).
@for @ember/routing
@method LinkTo
@see {Ember.Templates.components.LinkTo}
@public
**/
class _LinkTo extends InternalComponent {
static toString() {
return 'LinkTo';
}
static {
decorateFieldV2(this.prototype, "routing", [service('-routing')]);
}
#routing = (initializeDeferredDecorator(this, "routing"), void 0);
validateArguments() {
super.validateArguments();
}
get class() {
let classes = 'ember-view';
if (this.isActive) {
classes += this.classFor('active');
if (this.willBeActive === false) {
classes += ' ember-transitioning-out';
}
} else if (this.willBeActive) {
classes += ' ember-transitioning-in';
}
if (this.isLoading) {
classes += this.classFor('loading');
}
if (this.isDisabled) {
classes += this.classFor('disabled');
}
return classes;
}
get href() {
if (this.isLoading) {
return '#';
}
let {
routing,
route,
models,
query
} = this;
// TODO: can we narrow this down to QP changes only?
consumeTag(tagFor(routing, 'currentState'));
{
return routing.generateURL(route, models, query);
}
}
click(event) {
if (!isSimpleClick(event)) {
return;
}
let element = event.currentTarget;
let target = element instanceof SVGAElement ? element.target.baseVal : element.target;
let isSelf = target === '' || target === '_self';
if (isSelf) {
this.preventDefault(event);
} else {
return;
}
if (this.isDisabled) {
return;
}
if (this.isLoading) {
return;
}
let {
routing,
route,
models,
query,
replace
} = this;
let payload = {
transition: undefined
};
flaggedInstrument('interaction.link-to', payload, () => {
payload.transition = routing.transitionTo(route, models, query, replace);
});
}
static {
decorateMethodV2(this.prototype, "click", [action]);
}
get route() {
if ('route' in this.args.named) {
let route = this.named('route');
return route && this.namespaceRoute(route);
} else {
return this.currentRoute;
}
}
// GH #17963
currentRouteCache = createCache(() => {
consumeTag(tagFor(this.routing, 'currentState'));
return untrack(() => this.routing.currentRouteName);
});
get currentRoute() {
return getValue(this.currentRouteCache);
}
// TODO: not sure why generateURL takes {}[] instead of unknown[]
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
get models() {
if ('models' in this.args.named) {
let models = this.named('models');
return models;
} else if ('model' in this.args.named) {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
return [this.named('model')];
} else {
return EMPTY_ARRAY$1;
}
}
get query() {
if ('query' in this.args.named) {
let query = this.named('query');
return {
...query
};
} else {
return EMPTY_QUERY_PARAMS;
}
}
get replace() {
return this.named('replace') === true;
}
get isActive() {
return this.isActiveForState(this.routing.currentState);
}
get willBeActive() {
let current = this.routing.currentState;
let target = this.routing.targetState;
if (current === target) {
return null;
} else {
return this.isActiveForState(target);
}
}
get isLoading() {
return isMissing(this.route) || this.models.some(model => isMissing(model));
}
get isDisabled() {
return Boolean(this.named('disabled'));
}
get isEngine() {
let owner = this.owner;
return getEngineParent(owner) !== undefined;
}
get engineMountPoint() {
let owner = this.owner;
return owner.mountPoint;
}
classFor(state) {
let className = this.named(`${state}Class`);
if (className === true || isMissing(className)) {
return ` ${state}`;
} else if (className) {
return ` ${className}`;
} else {
return '';
}
}
namespaceRoute(route) {
let {
engineMountPoint
} = this;
if (engineMountPoint === undefined) {
return route;
} else if (route === 'application') {
return engineMountPoint;
} else {
return `${engineMountPoint}.${route}`;
}
}
isActiveForState(state) {
if (!isPresent(state)) {
return false;
}
if (this.isLoading) {
return false;
}
let currentWhen = this.named('current-when');
if (typeof currentWhen === 'boolean') {
return currentWhen;
} else if (typeof currentWhen === 'string') {
let {
routing
} = this;
return currentWhen.split(' ').some(route => routing.isActiveForRoute([], undefined, this.namespaceRoute(route), state));
} else {
let {
route,
models,
query,
routing
} = this;
return routing.isActiveForRoute(models, query, route, state);
}
}
preventDefault(event) {
event.preventDefault();
}
isSupportedArgument(name) {
let supportedArguments = ['route', 'model', 'models', 'query', 'replace', 'disabled', 'current-when', 'activeClass', 'loadingClass', 'disabledClass'];
return supportedArguments.indexOf(name) !== -1 || super.isSupportedArgument(name);
}
}
let {
prototype
} = _LinkTo;
let descriptorFor = (target, property) => {
if (target) {
return Object.getOwnPropertyDescriptor(target, property) || descriptorFor(Object.getPrototypeOf(target), property);
} else {
return null;
}
};
// @href
{
let superOnUnsupportedArgument = prototype['onUnsupportedArgument'];
Object.defineProperty(prototype, 'onUnsupportedArgument', {
configurable: true,
enumerable: false,
value: function onUnsupportedArgument(name) {
if (name === 'href') ; else {
superOnUnsupportedArgument.call(this, name);
}
}
});
}
// QP
{
let superModelsDescriptor = descriptorFor(prototype, 'models');
let superModelsGetter = superModelsDescriptor.get;
Object.defineProperty(prototype, 'models', {
configurable: true,
enumerable: false,
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
get: function models() {
let models = superModelsGetter.call(this);
if (models.length > 0 && !('query' in this.args.named)) {
if (isQueryParams(models[models.length - 1])) {
models = models.slice(0, -1);
}
}
return models;
}
});
let superQueryDescriptor = descriptorFor(prototype, 'query');
let superQueryGetter = superQueryDescriptor.get;
Object.defineProperty(prototype, 'query', {
configurable: true,
enumerable: false,
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
get: function query() {
if ('query' in this.args.named) {
let qp = superQueryGetter.call(this);
if (isQueryParams(qp)) {
return qp.values ?? EMPTY_QUERY_PARAMS;
} else {
return qp;
}
} else {
let models = superModelsGetter.call(this);
if (models.length > 0) {
let qp = models[models.length - 1];
if (isQueryParams(qp) && qp.values !== null) {
return qp.values;
}
}
return EMPTY_QUERY_PARAMS;
}
}
});
}
// Positional Arguments
{
let superOnUnsupportedArgument = prototype['onUnsupportedArgument'];
Object.defineProperty(prototype, 'onUnsupportedArgument', {
configurable: true,
enumerable: false,
value: function onUnsupportedArgument(name) {
if (name !== 'params') {
superOnUnsupportedArgument.call(this, name);
}
}
});
}
const LinkTo = opaquify(_LinkTo, LinkToTemplate);
const TextareaTemplate = templateFactory(
/*
<textarea
{{!-- for compatibility --}}
id={{this.id}}
class={{this.class}}
...attributes
value={{this.value}}
{{on "change" this.change}}
{{on "input" this.input}}
{{on "keyup" this.keyUp}}
{{on "paste" this.valueDidChange}}
{{on "cut" this.valueDidChange}}
/>
*/
{
"id": null,
"block": "[[[11,\"textarea\"],[16,1,[30,0,[\"id\"]]],[16,0,[30,0,[\"class\"]]],[17,1],[16,2,[30,0,[\"value\"]]],[4,[32,0],[\"change\",[30,0,[\"change\"]]],null],[4,[32,0],[\"input\",[30,0,[\"input\"]]],null],[4,[32,0],[\"keyup\",[30,0,[\"keyUp\"]]],null],[4,[32,0],[\"paste\",[30,0,[\"valueDidChange\"]]],null],[4,[32,0],[\"cut\",[30,0,[\"valueDidChange\"]]],null],[12],[13]],[\"&attrs\"],[]]",
"moduleName": "packages/@ember/-internals/glimmer/lib/templates/textarea.hbs",
"scope": () => ({
on
}),
"isStrictMode": true
});
/**
@module @ember/component
*/
class _Textarea extends AbstractInput {
static toString() {
return 'Textarea';
}
get class() {
return 'ember-text-area ember-view';
}
// See abstract-input.ts for why these are needed
change(event) {
super.change(event);
}
static {
decorateMethodV2(this.prototype, "change", [action]);
}
input(event) {
super.input(event);
}
static {
decorateMethodV2(this.prototype, "input", [action]);
}
isSupportedArgument(name) {
let supportedArguments = ['type', 'value', 'enter', 'insert-newline', 'escape-press'];
return supportedArguments.indexOf(name) !== -1 || super.isSupportedArgument(name);
}
}
const Textarea = opaquify(_Textarea, TextareaTemplate);
/**
* @deprecated
*/
function unwrapTemplate(template) {
if (template.result === 'error') {
throw new Error(`Compile Error: ${template.problem} @ ${template.span.start}..${template.span.end}`);
}
return template;
}
function isTemplateFactory(template) {
return typeof template === 'function';
}
function referenceForParts(rootRef, parts) {
let isAttrs = parts[0] === 'attrs';
// TODO deprecate this
if (isAttrs) {
parts.shift();
if (parts.length === 1) {
return childRefFor(rootRef, parts[0]);
}
}
return childRefFromParts(rootRef, parts);
}
function parseAttributeBinding(microsyntax) {
let colonIndex = microsyntax.indexOf(':');
if (colonIndex === -1) {
return [microsyntax, microsyntax, true];
} else {
let prop = microsyntax.substring(0, colonIndex);
let attribute = microsyntax.substring(colonIndex + 1);
return [prop, attribute, false];
}
}
function installAttributeBinding(component, rootRef, parsed, operations) {
let [prop, attribute, isSimple] = parsed;
if (attribute === 'id') {
// SAFETY: `get` could not infer the type of `prop` and just gave us `unknown`.
// we may want to throw an error in the future if the value isn't string or null/undefined.
let elementId = get(component, prop);
if (elementId === undefined || elementId === null) {
elementId = component.elementId;
}
let elementIdRef = createPrimitiveRef(elementId);
operations.setAttribute('id', elementIdRef, true, null);
return;
}
let isPath = prop.indexOf('.') > -1;
let reference = isPath ? referenceForParts(rootRef, prop.split('.')) : childRefFor(rootRef, prop);
operations.setAttribute(attribute, reference, false, null);
}
function createClassNameBindingRef(rootRef, microsyntax, operations) {
let parts = microsyntax.split(':');
let [prop, truthy, falsy] = parts;
let isStatic = prop === '';
if (isStatic) {
operations.setAttribute('class', createPrimitiveRef(truthy), true, null);
} else {
let isPath = prop.indexOf('.') > -1;
let parts = isPath ? prop.split('.') : [];
let value = isPath ? referenceForParts(rootRef, parts) : childRefFor(rootRef, prop);
let ref;
if (truthy === undefined) {
ref = createSimpleClassNameBindingRef(value, isPath ? parts[parts.length - 1] : prop);
} else {
ref = createColonClassNameBindingRef(value, truthy, falsy);
}
operations.setAttribute('class', ref, false, null);
}
}
function createSimpleClassNameBindingRef(inner, path) {
let dasherizedPath;
return createComputeRef(() => {
let value = valueForRef(inner);
if (value === true) {
return dasherizedPath || (dasherizedPath = dasherize(path));
} else if (value || value === 0) {
return String(value);
} else {
return null;
}
});
}
function createColonClassNameBindingRef(inner, truthy, falsy) {
return createComputeRef(() => {
return valueForRef(inner) ? truthy : falsy;
});
}
function NOOP() {}
/**
@module ember
*/
/**
Represents the internal state of the component.
@class ComponentStateBucket
@private
*/
class ComponentStateBucket {
classRef = null;
rootRef;
argsRevision;
constructor(component, args, argsTag, finalizer, hasWrappedElement, isInteractive) {
this.component = component;
this.args = args;
this.argsTag = argsTag;
this.finalizer = finalizer;
this.hasWrappedElement = hasWrappedElement;
this.isInteractive = isInteractive;
this.classRef = null;
this.argsRevision = args === null ? 0 : valueForTag(argsTag);
this.rootRef = createConstRef(component);
registerDestructor(this, () => this.willDestroy(), true);
registerDestructor(this, () => this.component.destroy());
}
willDestroy() {
let {
component,
isInteractive
} = this;
if (isInteractive) {
beginUntrackFrame();
component.trigger('willDestroyElement');
component.trigger('willClearRender');
endUntrackFrame();
let element = getViewElement(component);
if (element) {
clearElementView(element);
clearViewElement(component);
}
}
component.renderer.unregister(component);
}
finalize() {
let {
finalizer
} = this;
finalizer();
this.finalizer = NOOP;
}
}
// ComponentArgs takes EvaluatedNamedArgs and converts them into the
// inputs needed by CurlyComponents (attrs and props, with mutable
// cells, etc).
function processComponentArgs(namedArgs) {
let attrs = Object.create(null);
let props = Object.create(null);
for (let name in namedArgs) {
let ref = namedArgs[name];
let value = valueForRef(ref);
if (isUpdatableRef(ref)) {
attrs[name] = new MutableCell(ref, value);
} else {
attrs[name] = value;
}
props[name] = value;
}
props.attrs = attrs;
return props;
}
const REF = Symbol('REF');
class MutableCell {
value;
[MUTABLE_CELL];
[REF];
constructor(ref, value) {
this[MUTABLE_CELL] = true;
this[REF] = ref;
this.value = value;
}
update(val) {
updateRef(this[REF], val);
}
}
const COMPONENT_ARGS_MAP = new WeakMap();
function getComponentCapturedArgs(component) {
return COMPONENT_ARGS_MAP.get(component);
}
const DIRTY_TAG = Symbol('DIRTY_TAG');
const IS_DISPATCHING_ATTRS = Symbol('IS_DISPATCHING_ATTRS');
const BOUNDS = Symbol('BOUNDS');
const EMBER_VIEW_REF = createPrimitiveRef('ember-view');
function aliasIdToElementId(args, props) {
if (args.named.has('id')) {
props.elementId = props.id;
}
}
// We must traverse the attributeBindings in reverse keeping track of
// what has already been applied. This is essentially refining the concatenated
// properties applying right to left.
function applyAttributeBindings(attributeBindings, component, rootRef, operations) {
let seen = [];
let i = attributeBindings.length - 1;
while (i !== -1) {
let binding = attributeBindings[i];
let parsed = parseAttributeBinding(binding);
let attribute = parsed[1];
if (seen.indexOf(attribute) === -1) {
seen.push(attribute);
installAttributeBinding(component, rootRef, parsed, operations);
}
i--;
}
if (seen.indexOf('id') === -1) {
let id = component.elementId ? component.elementId : guidFor(component);
operations.setAttribute('id', createPrimitiveRef(id), false, null);
}
}
class CurlyComponentManager {
templateFor(component) {
let {
layout,
layoutName
} = component;
let owner = getOwner(component);
let factory;
if (layout === undefined) {
if (layoutName !== undefined) {
let _factory = owner.lookup(`template:${layoutName}`);
factory = _factory;
} else {
return null;
}
} else if (isTemplateFactory(layout)) {
factory = layout;
} else {
// no layout was found, use the default layout
return null;
}
return unwrapTemplate(factory(owner)).asWrappedLayout();
}
getDynamicLayout(bucket) {
return this.templateFor(bucket.component);
}
getTagName(state) {
let {
component,
hasWrappedElement
} = state;
if (!hasWrappedElement) {
return null;
}
return component && component.tagName || 'div';
}
getCapabilities() {
return CURLY_CAPABILITIES;
}
prepareArgs(ComponentClass, args) {
if (args.named.has('__ARGS__')) {
let {
__ARGS__,
...rest
} = args.named.capture();
let __args__ = valueForRef(__ARGS__);
let prepared = {
positional: __args__.positional,
named: {
...rest,
...__args__.named
}
};
return prepared;
}
const {
positionalParams
} = ComponentClass.class ?? ComponentClass;
// early exits
if (positionalParams === undefined || positionalParams === null || args.positional.length === 0) {
return null;
}
let named;
if (typeof positionalParams === 'string') {
let captured = args.positional.capture();
named = {
[positionalParams]: createComputeRef(() => reifyPositional(captured))
};
Object.assign(named, args.named.capture());
} else if (Array.isArray(positionalParams) && positionalParams.length > 0) {
const count = Math.min(positionalParams.length, args.positional.length);
named = {};
Object.assign(named, args.named.capture());
for (let i = 0; i < count; i++) {
let name = positionalParams[i];
named[name] = args.positional.at(i);
}
} else {
return null;
}
return {
positional: EMPTY_ARRAY$2,
named
};
}
/*
* This hook is responsible for actually instantiating the component instance.
* It also is where we perform additional bookkeeping to support legacy
* features like exposed by view mixins like ChildViewSupport, ActionSupport,
* etc.
*/
create(owner, ComponentClass, args, {
isInteractive
}, dynamicScope, callerSelfRef) {
// Get the nearest concrete component instance from the scope. "Virtual"
// components will be skipped.
let parentView = dynamicScope.view;
// Capture the arguments, which tells Glimmer to give us our own, stable
// copy of the Arguments object that is safe to hold on to between renders.
let capturedArgs = args.named.capture();
beginTrackFrame();
let props = processComponentArgs(capturedArgs);
let argsTag = endTrackFrame();
// Alias `id` argument to `elementId` property on the component instance.
aliasIdToElementId(args, props);
// Set component instance's parentView property to point to nearest concrete
// component.
props.parentView = parentView;
// Save the current `this` context of the template as the component's
// `_target`, so bubbled actions are routed to the right place.
props._target = valueForRef(callerSelfRef);
setOwner(props, owner);
// caller:
// <FaIcon @name="bug" />
//
// callee:
// <i class="fa-{{@name}}"></i>
// Now that we've built up all of the properties to set on the component instance,
// actually create it.
beginUntrackFrame();
let component = ComponentClass.create(props);
// Store capturedArgs in a WeakMap keyed by the component instance so that
// PROPERTY_DID_CHANGE can look them up
COMPONENT_ARGS_MAP.set(component, capturedArgs);
let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component);
// We become the new parentView for downstream components, so save our
// component off on the dynamic scope.
dynamicScope.view = component;
// Unless we're the root component, we need to add ourselves to our parent
// component's childViews array.
if (parentView !== null && parentView !== undefined) {
addChildView(parentView, component);
}
component.trigger('didReceiveAttrs');
let hasWrappedElement = component.tagName !== '';
// We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components
if (!hasWrappedElement) {
if (isInteractive) {
component.trigger('willRender');
}
component._transitionTo('hasElement');
if (isInteractive) {
component.trigger('willInsertElement');
}
}
// Track additional lifecycle metadata about this component in a state bucket.
// Essentially we're saving off all the state we'll need in the future.
let bucket = new ComponentStateBucket(component, capturedArgs, argsTag, finalizer, hasWrappedElement, isInteractive);
if (args.named.has('class')) {
bucket.classRef = args.named.get('class');
}
if (isInteractive && hasWrappedElement) {
component.trigger('willRender');
}
endUntrackFrame();
// consume every argument so we always run again
consumeTag(bucket.argsTag);
consumeTag(component[DIRTY_TAG]);
return bucket;
}
getDebugName(definition) {
return definition.fullName || definition.normalizedName || definition.class?.name || definition.name;
}
getSelf({
rootRef
}) {
return rootRef;
}
didCreateElement({
component,
classRef,
isInteractive,
rootRef
}, element, operations) {
setViewElement(component, element);
setElementView(element, component);
let {
attributeBindings,
classNames,
classNameBindings
} = component;
if (attributeBindings && attributeBindings.length) {
applyAttributeBindings(attributeBindings, component, rootRef, operations);
} else {
let id = component.elementId ? component.elementId : guidFor(component);
operations.setAttribute('id', createPrimitiveRef(id), false, null);
}
if (classRef) {
const ref = createSimpleClassNameBindingRef(classRef);
operations.setAttribute('class', ref, false, null);
}
if (classNames && classNames.length) {
classNames.forEach(name => {
operations.setAttribute('class', createPrimitiveRef(name), false, null);
});
}
if (classNameBindings && classNameBindings.length) {
classNameBindings.forEach(binding => {
createClassNameBindingRef(rootRef, binding, operations);
});
}
operations.setAttribute('class', EMBER_VIEW_REF, false, null);
if ('ariaRole' in component) {
operations.setAttribute('role', childRefFor(rootRef, 'ariaRole'), false, null);
}
component._transitionTo('hasElement');
if (isInteractive) {
beginUntrackFrame();
component.trigger('willInsertElement');
endUntrackFrame();
}
}
didRenderLayout(bucket, bounds) {
bucket.component[BOUNDS] = bounds;
bucket.finalize();
}
didCreate({
component,
isInteractive
}) {
if (isInteractive) {
component._transitionTo('inDOM');
component.trigger('didInsertElement');
component.trigger('didRender');
}
}
update(bucket) {
let {
component,
args,
argsTag,
argsRevision,
isInteractive
} = bucket;
bucket.finalizer = _instrumentStart('render.component', rerenderInstrumentDetails, component);
beginUntrackFrame();
if (args !== null && !validateTag(argsTag, argsRevision)) {
beginTrackFrame();
let props = processComponentArgs(args);
argsTag = bucket.argsTag = endTrackFrame();
bucket.argsRevision = valueForTag(argsTag);
component[IS_DISPATCHING_ATTRS] = true;
component.setProperties(props);
component[IS_DISPATCHING_ATTRS] = false;
component.trigger('didUpdateAttrs');
component.trigger('didReceiveAttrs');
}
if (isInteractive) {
component.trigger('