UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

149 lines (127 loc) 6.03 kB
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 };