UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

715 lines (662 loc) 22.5 kB
import { i as isObject, d as setListeners } from './super-Cm_a_cLQ.js'; import { ENV } from '../@ember/-internals/environment/index.js'; import { tagFor, dirtyTagFor, CONSTANT_TAG, combine, validateTag, tagMetaFor, createUpdatableTag, updateTag as UPDATE_TAG, valueForTag, CURRENT_TAG, track, isTracking, consumeTag } from '../@glimmer/validator/index.js'; import './reference-BNqcwZWH.js'; import './capabilities-DGmQ_mz4.js'; import { peekMeta, meta } from '../@ember/-internals/meta/lib/meta.js'; import { registerDestructor } from '../@glimmer/destroyable/index.js'; import { g as getCustomTagFor } from './args-proxy-Dl0A0YWI.js'; import { s as setProxy } from './is_proxy-Cr1qlMv_.js'; import { isEmberArray } from '../@ember/array/-internals.js'; import { C as Cache } from './cache-qDyqAcpg.js'; import VERSION from '../ember/version.js'; function objectAt(array, index) { if (Array.isArray(array)) { return array[index]; } else { return array.objectAt(index); } } ///////// // This is exported for `@tracked`, but should otherwise be avoided. Use `tagForObject`. const SELF_TAG = Symbol('SELF_TAG'); function tagForProperty(obj, propertyKey, addMandatorySetter = false, meta) { let customTagFor = getCustomTagFor(obj); if (customTagFor !== undefined) { return customTagFor(obj, propertyKey, addMandatorySetter); } let tag = tagFor(obj, propertyKey, meta); return tag; } function tagForObject(obj) { if (isObject(obj)) { return tagFor(obj, SELF_TAG); } return CONSTANT_TAG; } function markObjectAsDirty(obj, propertyKey) { dirtyTagFor(obj, propertyKey); dirtyTagFor(obj, SELF_TAG); } const CHAIN_PASS_THROUGH = new WeakSet(); function finishLazyChains(meta, key, value) { let lazyTags = meta.readableLazyChainsFor(key); if (lazyTags === undefined) { return; } if (isObject(value)) { for (let [tag, deps] of lazyTags) { UPDATE_TAG(tag, getChainTagsForKey(value, deps, tagMetaFor(value), peekMeta(value))); } } lazyTags.length = 0; } function getChainTagsForKeys(obj, keys, tagMeta, meta) { let tags = []; for (let key of keys) { getChainTags(tags, obj, key, tagMeta, meta); } return combine(tags); } function getChainTagsForKey(obj, key, tagMeta, meta) { return combine(getChainTags([], obj, key, tagMeta, meta)); } function getChainTags(chainTags, obj, path, tagMeta, meta$1) { let current = obj; let currentTagMeta = tagMeta; let currentMeta = meta$1; let pathLength = path.length; let segmentEnd = -1; // prevent closures let segment, descriptor; while (true) { let lastSegmentEnd = segmentEnd + 1; segmentEnd = path.indexOf('.', lastSegmentEnd); if (segmentEnd === -1) { segmentEnd = pathLength; } segment = path.slice(lastSegmentEnd, segmentEnd); // If the segment is an @each, we can process it and then break if (segment === '@each' && segmentEnd !== pathLength) { lastSegmentEnd = segmentEnd + 1; segmentEnd = path.indexOf('.', lastSegmentEnd); let arrLength = current.length; if (typeof arrLength !== 'number' || // TODO: should the second test be `isEmberArray` instead? !(Array.isArray(current) || 'objectAt' in current)) { // If the current object isn't an array, there's nothing else to do, // we don't watch individual properties. Break out of the loop. break; } else if (arrLength === 0) { // Fast path for empty arrays chainTags.push(tagForProperty(current, '[]')); break; } if (segmentEnd === -1) { segment = path.slice(lastSegmentEnd); } else { // Deprecated, remove once we turn the deprecation into an assertion segment = path.slice(lastSegmentEnd, segmentEnd); } // Push the tags for each item's property for (let i = 0; i < arrLength; i++) { let item = objectAt(current, i); if (item) { chainTags.push(tagForProperty(item, segment, true)); currentMeta = peekMeta(item); descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; // If the key is an alias, we need to bootstrap it if (descriptor !== undefined && typeof descriptor.altKey === 'string') { // eslint-disable-next-line @typescript-eslint/no-unused-expressions item[segment]; } } } // Push the tag for the array length itself chainTags.push(tagForProperty(current, '[]', true, currentTagMeta)); break; } let propertyTag = tagForProperty(current, segment, true, currentTagMeta); descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; chainTags.push(propertyTag); // If we're at the end of the path, processing the last segment, and it's // not an alias, we should _not_ get the last value, since we already have // its tag. There's no reason to access it and do more work. if (segmentEnd === pathLength) { // If the key was an alias, we should always get the next value in order to // bootstrap the alias. This is because aliases, unlike other CPs, should // always be in sync with the aliased value. if (CHAIN_PASS_THROUGH.has(descriptor)) { // eslint-disable-next-line @typescript-eslint/no-unused-expressions current[segment]; } break; } if (descriptor === undefined) { // If the descriptor is undefined, then its a normal property, so we should // lookup the value to chain off of like normal. if (!(segment in current) && typeof current.unknownProperty === 'function') { current = current.unknownProperty(segment); } else { current = current[segment]; } } else if (CHAIN_PASS_THROUGH.has(descriptor)) { current = current[segment]; } else { // If the descriptor is defined, then its a normal CP (not an alias, which // would have been handled earlier). We get the last revision to check if // the CP is still valid, and if so we use the cached value. If not, then // we create a lazy chain lookup, and the next time the CP is calculated, // it will update that lazy chain. let instanceMeta = currentMeta.source === current ? currentMeta : meta(current); let lastRevision = instanceMeta.revisionFor(segment); if (lastRevision !== undefined && validateTag(propertyTag, lastRevision)) { current = instanceMeta.valueFor(segment); } else { // use metaFor here to ensure we have the meta for the instance let lazyChains = instanceMeta.writableLazyChainsFor(segment); let rest = path.substring(segmentEnd + 1); let placeholderTag = createUpdatableTag(); lazyChains.push([placeholderTag, rest]); chainTags.push(placeholderTag); break; } } if (!isObject(current)) { // we've hit the end of the chain for now, break out break; } currentTagMeta = tagMetaFor(current); currentMeta = peekMeta(current); } return chainTags; } const AFTER_OBSERVERS = ':change'; function changeEvent(keyName) { return keyName + AFTER_OBSERVERS; } /** @module @ember/object */ /* The event system uses a series of nested hashes to store listeners on an object. When a listener is registered, or when an event arrives, these hashes are consulted to determine which target and action pair to invoke. The hashes are stored in the object's meta hash, and look like this: // Object's meta hash { listeners: { // variable name: `listenerSet` "foo:change": [ // variable name: `actions` target, method, once ] } } */ /** Add an event listener @method addListener @static @for @ember/object/events @param obj @param {String} eventName @param {Object|Function} target A target object or a function @param {Function|String} method A function or the name of a function to be called on `target` @param {Boolean} once A flag whether a function should only be called once @public */ function addListener(obj, eventName, target, method, once, sync = true) { if (!method && 'function' === typeof target) { method = target; target = null; } meta(obj).addToListeners(eventName, target, method, once === true, sync); } /** Remove an event listener Arguments should match those passed to `addListener`. @method removeListener @static @for @ember/object/events @param obj @param {String} eventName @param {Object|Function} target A target object or a function @param {Function|String} method A function or the name of a function to be called on `target` @public */ function removeListener(obj, eventName, targetOrFunction, functionOrName) { let target, method; if (typeof targetOrFunction === 'object') { target = targetOrFunction; method = functionOrName; } else { target = null; method = targetOrFunction; } let m = meta(obj); m.removeFromListeners(eventName, target, method); } /** Send an event. The execution of suspended listeners is skipped, and once listeners are removed. A listener without a target is executed on the passed object. If an array of actions is not passed, the actions stored on the passed object are invoked. @method sendEvent @static @for @ember/object/events @param obj @param {String} eventName @param {Array} params Optional parameters for each listener. @return {Boolean} if the event was delivered to one or more actions @public */ function sendEvent(obj, eventName, params, actions, _meta) { if (actions === undefined) { let meta = _meta === undefined ? peekMeta(obj) : _meta; actions = meta !== null ? meta.matchingListeners(eventName) : undefined; } if (actions === undefined || actions.length === 0) { return false; } for (let i = actions.length - 3; i >= 0; i -= 3) { // looping in reverse for once listeners let target = actions[i]; let method = actions[i + 1]; let once = actions[i + 2]; if (!method) { continue; } if (once) { removeListener(obj, eventName, target, method); } if (!target) { target = obj; } let type = typeof method; if (type === 'string' || type === 'symbol') { method = target[method]; } method.apply(target, params); } return true; } /** @public @method hasListeners @static @for @ember/object/events @param obj @param {String} eventName @return {Boolean} if `obj` has listeners for event `eventName` */ function hasListeners(obj, eventName) { let meta = peekMeta(obj); if (meta === null) { return false; } let matched = meta.matchingListeners(eventName); return matched !== undefined && matched.length > 0; } /** Define a property as a function that should be executed when a specified event or events are triggered. ``` javascript import EmberObject from '@ember/object'; import { on } from '@ember/object/evented'; import { sendEvent } from '@ember/object/events'; let Job = EmberObject.extend({ logCompleted: on('completed', function() { console.log('Job completed!'); }) }); let job = Job.create(); sendEvent(job, 'completed'); // Logs 'Job completed!' ``` @method on @static @for @ember/object/evented @param {String} eventNames* @param {Function} func @return {Function} the listener function, passed as last argument to on(...) @public */ function on(...args) { let func = args.pop(); let events = args; setListeners(func, events); return func; } const SYNC_DEFAULT = !ENV._DEFAULT_ASYNC_OBSERVERS; const SYNC_OBSERVERS = new Map(); const ASYNC_OBSERVERS = new Map(); /** @module @ember/object */ /** @method addObserver @static @for @ember/object/observers @param obj @param {String} path @param {Object|Function} target @param {Function|String} [method] @public */ function addObserver(obj, path, target, method, sync = SYNC_DEFAULT) { let eventName = changeEvent(path); addListener(obj, eventName, target, method, false, sync); let meta = peekMeta(obj); if (meta === null || !(meta.isPrototypeMeta(obj) || meta.isInitializing())) { activateObserver(obj, eventName, sync); } } /** @method removeObserver @static @for @ember/object/observers @param obj @param {String} path @param {Object|Function} target @param {Function|String} [method] @public */ function removeObserver(obj, path, target, method, sync = SYNC_DEFAULT) { let eventName = changeEvent(path); let meta = peekMeta(obj); if (meta === null || !(meta.isPrototypeMeta(obj) || meta.isInitializing())) { deactivateObserver(obj, eventName, sync); } removeListener(obj, eventName, target, method); } function getOrCreateActiveObserversFor(target, sync) { let observerMap = sync === true ? SYNC_OBSERVERS : ASYNC_OBSERVERS; if (!observerMap.has(target)) { observerMap.set(target, new Map()); registerDestructor(target, () => destroyObservers(target), true); } return observerMap.get(target); } function activateObserver(target, eventName, sync = false) { let activeObservers = getOrCreateActiveObserversFor(target, sync); if (activeObservers.has(eventName)) { activeObservers.get(eventName).count++; } else { let path = eventName.substring(0, eventName.lastIndexOf(':')); let tag = getChainTagsForKey(target, path, tagMetaFor(target), peekMeta(target)); activeObservers.set(eventName, { count: 1, path, tag, lastRevision: valueForTag(tag), suspended: false }); } } let DEACTIVATE_SUSPENDED = false; let SCHEDULED_DEACTIVATE = []; function deactivateObserver(target, eventName, sync = false) { if (DEACTIVATE_SUSPENDED === true) { SCHEDULED_DEACTIVATE.push([target, eventName, sync]); return; } let observerMap = sync === true ? SYNC_OBSERVERS : ASYNC_OBSERVERS; let activeObservers = observerMap.get(target); if (activeObservers !== undefined) { let observer = activeObservers.get(eventName); observer.count--; if (observer.count === 0) { activeObservers.delete(eventName); if (activeObservers.size === 0) { observerMap.delete(target); } } } } function suspendedObserverDeactivation() { DEACTIVATE_SUSPENDED = true; } function resumeObserverDeactivation() { DEACTIVATE_SUSPENDED = false; for (let [target, eventName, sync] of SCHEDULED_DEACTIVATE) { deactivateObserver(target, eventName, sync); } SCHEDULED_DEACTIVATE = []; } /** * Primarily used for cases where we are redefining a class, e.g. mixins/reopen * being applied later. Revalidates all the observers, resetting their tags. * * @private * @param target */ function revalidateObservers(target) { if (ASYNC_OBSERVERS.has(target)) { ASYNC_OBSERVERS.get(target).forEach(observer => { observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); observer.lastRevision = valueForTag(observer.tag); }); } if (SYNC_OBSERVERS.has(target)) { SYNC_OBSERVERS.get(target).forEach(observer => { observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); observer.lastRevision = valueForTag(observer.tag); }); } } let lastKnownRevision = 0; function flushAsyncObservers(_schedule) { let currentRevision = valueForTag(CURRENT_TAG); if (lastKnownRevision === currentRevision) { return; } lastKnownRevision = currentRevision; ASYNC_OBSERVERS.forEach((activeObservers, target) => { let meta = peekMeta(target); activeObservers.forEach((observer, eventName) => { if (!validateTag(observer.tag, observer.lastRevision)) { let sendObserver = () => { try { sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); observer.lastRevision = valueForTag(observer.tag); } }; if (_schedule) { _schedule('actions', sendObserver); } else { sendObserver(); } } }); }); } function flushSyncObservers() { // When flushing synchronous observers, we know that something has changed (we // only do this during a notifyPropertyChange), so there's no reason to check // a global revision. SYNC_OBSERVERS.forEach((activeObservers, target) => { let meta = peekMeta(target); activeObservers.forEach((observer, eventName) => { if (!observer.suspended && !validateTag(observer.tag, observer.lastRevision)) { try { observer.suspended = true; sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { observer.tag = getChainTagsForKey(target, observer.path, tagMetaFor(target), peekMeta(target)); observer.lastRevision = valueForTag(observer.tag); observer.suspended = false; } } }); }); } function setObserverSuspended(target, property, suspended) { let activeObservers = SYNC_OBSERVERS.get(target); if (!activeObservers) { return; } let observer = activeObservers.get(changeEvent(property)); if (observer) { observer.suspended = suspended; } } function destroyObservers(target) { if (SYNC_OBSERVERS.size > 0) SYNC_OBSERVERS.delete(target); if (ASYNC_OBSERVERS.size > 0) ASYNC_OBSERVERS.delete(target); } const firstDotIndexCache = new Cache(1000, key => key.indexOf('.')); function isPath(path) { return typeof path === 'string' && firstDotIndexCache.get(path) !== -1; } /** @module @ember/object */ const PROXY_CONTENT = Symbol('PROXY_CONTENT'); function hasUnknownProperty(val) { return typeof val === 'object' && val !== null && typeof val.unknownProperty === 'function'; } // .......................................................... // GET AND SET // // If we are on a platform that supports accessors we can use those. // Otherwise simulate accessors by looking up the property directly on the // object. /** Gets the value of a property on an object. If the property is computed, the function will be invoked. If the property is not defined but the object implements the `unknownProperty` method then that will be invoked. ```javascript import { get } from '@ember/object'; get(obj, "name"); ``` You only need to use this method to retrieve properties if the property might not be defined on the object and you want to respect the `unknownProperty` handler. Otherwise you can access the property directly. Note that if the object itself is `undefined`, this method will throw an error. @method get @for @ember/object @static @param {Object} obj The object to retrieve from. @param {String} keyName The property key to retrieve @return {Object} the property value or `null`. @public */ function get(obj, keyName) { return isPath(keyName) ? _getPath(obj, keyName) : _getProp(obj, keyName); } function _getProp(obj, keyName) { if (obj == null) { return; } let value; if (typeof obj === 'object' || typeof obj === 'function') { { value = obj[keyName]; } if (value === undefined && typeof obj === 'object' && !(keyName in obj) && hasUnknownProperty(obj)) { value = obj.unknownProperty(keyName); } if (isTracking()) { consumeTag(tagFor(obj, keyName)); if (Array.isArray(value) || isEmberArray(value)) { // 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 consumeTag(tagFor(value, '[]')); } } } else { // SAFETY: It should be ok to access properties on any non-nullish value value = obj[keyName]; } return value; } function _getPath(obj, path, forSet) { let parts = typeof path === 'string' ? path.split('.') : path; for (let part of parts) { if (obj === undefined || obj === null || obj.isDestroyed) { return undefined; } if (forSet && (part === '__proto__' || part === 'constructor')) { return; } obj = _getProp(obj, part); } return obj; } // Warm it up _getProp('foo', 'a'); _getProp('foo', 1); _getProp({}, 'a'); _getProp({}, 1); _getProp({ unknownProperty() {} }, 'a'); _getProp({ unknownProperty() {} }, 1); get({}, 'foo'); get({}, 'foo.bar'); let fakeProxy = {}; setProxy(fakeProxy); track(() => _getProp({}, 'a')); track(() => _getProp({}, 1)); track(() => _getProp({ a: [] }, 'a')); track(() => _getProp({ a: fakeProxy }, 'a')); /** @module ember */ /** Helper class that allows you to register your library with Ember. Singleton created at `Ember.libraries`. @class Libraries @constructor @private */ class Libraries { _registry; _coreLibIndex; constructor() { this._registry = []; this._coreLibIndex = 0; } _getLibraryByName(name) { let libs = this._registry; for (let lib of libs) { if (lib.name === name) { return lib; } } return undefined; } register(name, version, isCoreLibrary) { let index = this._registry.length; if (!this._getLibraryByName(name)) { if (isCoreLibrary) { index = this._coreLibIndex++; } this._registry.splice(index, 0, { name, version }); } } registerCoreLibrary(name, version) { this.register(name, version, true); } deRegister(name) { let lib = this._getLibraryByName(name); let index; if (lib) { index = this._registry.indexOf(lib); this._registry.splice(index, 1); } } } const LIBRARIES = new Libraries(); LIBRARIES.registerCoreLibrary('Ember', VERSION); export { ASYNC_OBSERVERS as A, setObserverSuspended as B, CHAIN_PASS_THROUGH as C, Libraries as L, PROXY_CONTENT as P, SELF_TAG as S, _getPath as _, tagForProperty as a, activateObserver as b, addObserver as c, revalidateObservers as d, addListener as e, removeListener as f, get as g, hasListeners as h, isPath as i, flushAsyncObservers as j, getChainTagsForKey as k, finishLazyChains as l, hasUnknownProperty as m, objectAt as n, on as o, SYNC_OBSERVERS as p, _getProp as q, removeObserver as r, sendEvent as s, tagForObject as t, LIBRARIES as u, markObjectAsDirty as v, flushSyncObservers as w, resumeObserverDeactivation as x, suspendedObserverDeactivation as y, getChainTagsForKeys as z };