ember-source
Version:
A JavaScript framework for creating ambitious web applications
149 lines (127 loc) • 6.03 kB
JavaScript
import { meta } from '../@ember/-internals/meta/lib/meta.js';
import { isEmberArray } from '../@ember/array/-internals.js';
import '../@ember/debug/index.js';
import { k as consumeTag } from './cache-B7dqAS38.js';
import { d as dirtyTagFor, t as tagFor } from './meta-BmRXesrk.js';
import { t as trackedData } from './tracked-data-CGnA4ytZ.js';
import { S as SELF_TAG, C as CHAIN_PASS_THROUGH } from './chain-tags-D6tuFUj_.js';
import { s as setClassicDecorator, i as isElementDescriptor, C as COMPUTED_SETTERS } from './decorator-B5Uh5NFI.js';
import { assert } from '../@ember/debug/lib/assert.js';
/**
@decorator
@private
Marks a property as tracked.
By default, a component's properties are expected to be static,
meaning you are not able to update them and have the template update accordingly.
Marking a property as tracked means that when that property changes,
a rerender of the component is scheduled so the template is kept up to date.
There are two usages for the `@tracked` decorator, shown below.
@example No dependencies
If you don't pass an argument to `@tracked`, only changes to that property
will be tracked:
```typescript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class MyComponent extends Component {
@tracked
remainingApples = 10
}
```
When something changes the component's `remainingApples` property, the rerender
will be scheduled.
@example Dependents
In the case that you have a computed property that depends other
properties, you want to track both so that when one of the
dependents change, a rerender is scheduled.
In the following example we have two properties,
`eatenApples`, and `remainingApples`.
```typescript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
const totalApples = 100;
export default class MyComponent extends Component {
@tracked
eatenApples = 0
get remainingApples() {
return totalApples - this.eatenApples;
}
increment() {
this.eatenApples = this.eatenApples + 1;
}
}
```
@param dependencies Optional dependents to be tracked.
*/
function tracked(...args) {
(!(!(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)) && assert(`@tracked can only be used directly as a native decorator. If you're using tracked in classic classes, add parenthesis to call it like a function: tracked()`, !(isElementDescriptor(args.slice(0, 3)) && args.length === 5 && args[4] === true)));
if (!isElementDescriptor(args)) {
let propertyDesc = args[0];
(!(args.length === 0 || typeof propertyDesc === 'object' && propertyDesc !== null) && assert(`tracked() may only receive an options object containing 'value' or 'initializer', received ${propertyDesc}`, args.length === 0 || typeof propertyDesc === 'object' && propertyDesc !== null));
if (propertyDesc) {
let keys = Object.keys(propertyDesc);
(!(keys.length <= 1 && (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'initializer')) && assert(`The options object passed to tracked() may only contain a 'value' or 'initializer' property, not both. Received: [${keys}]`, keys.length <= 1 && (keys[0] === undefined || keys[0] === 'value' || keys[0] === 'initializer')));
(!(!('initializer' in propertyDesc) || typeof propertyDesc.initializer === 'function') && assert(`The initializer passed to tracked must be a function. Received ${propertyDesc.initializer}`, !('initializer' in propertyDesc) || typeof propertyDesc.initializer === 'function'));
}
let initializer = propertyDesc ? propertyDesc.initializer : undefined;
let value = propertyDesc ? propertyDesc.value : undefined;
let decorator = function (target, key, _desc, _meta, isClassicDecorator) {
(!(isClassicDecorator) && assert(`You attempted to set a default value for ${key} with the @tracked({ value: 'default' }) syntax. You can only use this syntax with classic classes. For native classes, you can use class initializers: @tracked field = 'default';`, isClassicDecorator));
let fieldDesc = {
initializer: initializer || (() => value)
};
return descriptorForField([target, key, fieldDesc]);
};
setClassicDecorator(decorator);
return decorator;
}
return descriptorForField(args);
}
{
// Normally this isn't a classic decorator, but we want to throw a helpful
// error in development so we need it to treat it like one
setClassicDecorator(tracked);
}
function descriptorForField([target, key, desc]) {
(!(!desc || !desc.value && !desc.get && !desc.set) && assert(`You attempted to use @tracked on ${key}, but that element is not a class field. @tracked is only usable on class fields. Native getters and setters will autotrack add any tracked fields they encounter, so there is no need mark getters and setters with @tracked.`, !desc || !desc.value && !desc.get && !desc.set));
let {
getter,
setter
} = trackedData(key, desc ? desc.initializer : undefined);
function get() {
let value = getter(this);
// Add the tag of the returned value if it is an array, since arrays
// should always cause updates if they are consumed and then changed
if (Array.isArray(value) || isEmberArray(value)) {
consumeTag(tagFor(value, '[]'));
}
return value;
}
function set(newValue) {
setter(this, newValue);
dirtyTagFor(this, SELF_TAG);
}
let newDesc = {
enumerable: true,
configurable: true,
isTracked: true,
get,
set
};
COMPUTED_SETTERS.add(set);
meta(target).writeDescriptors(key, new TrackedDescriptor(get, set));
return newDesc;
}
class TrackedDescriptor {
constructor(_get, _set) {
this._get = _get;
this._set = _set;
CHAIN_PASS_THROUGH.add(this);
}
get(obj) {
return this._get.call(obj);
}
set(obj, _key, value) {
this._set.call(obj, value);
}
}
export { TrackedDescriptor as T, tracked as t };