ember-source
Version:
A JavaScript framework for creating ambitious web applications
242 lines (237 loc) • 8.82 kB
JavaScript
import { setProp, getProp, toIterator, getPath } from '../global-context/index.js';
import { expect, isDict, EMPTY_ARRAY, isObject } from '../util/index.js';
import { CONSTANT_TAG, validateTag, track, valueForTag, consumeTag, dirtyTag as DIRTY_TAG, createTag, INITIAL } from '../validator/index.js';
import { isDevelopingApp } from '@embroider/macros';
const REFERENCE = Symbol("REFERENCE"),
COMPUTE = 1,
UNBOUND = 2;
//////////
class ReferenceImpl {
[REFERENCE];
tag = null;
lastRevision = INITIAL;
lastValue;
children = null;
compute = null;
update = null;
debugLabel;
constructor(type) {
this[REFERENCE] = type;
}
}
function createPrimitiveRef(value) {
const ref = new ReferenceImpl(UNBOUND);
return ref.tag = CONSTANT_TAG, ref.lastValue = value, isDevelopingApp() && (ref.debugLabel = String(value)), ref;
}
const UNDEFINED_REFERENCE = createPrimitiveRef(void 0),
NULL_REFERENCE = createPrimitiveRef(null),
TRUE_REFERENCE = createPrimitiveRef(!0),
FALSE_REFERENCE = createPrimitiveRef(!1);
function createConstRef(value, debugLabel) {
const ref = new ReferenceImpl(0);
return ref.lastValue = value, ref.tag = CONSTANT_TAG, isDevelopingApp() && (ref.debugLabel = debugLabel), ref;
}
function createUnboundRef(value, debugLabel) {
const ref = new ReferenceImpl(UNBOUND);
return ref.lastValue = value, ref.tag = CONSTANT_TAG, isDevelopingApp() && (ref.debugLabel = debugLabel), ref;
}
function createComputeRef(compute, update = null, debugLabel = "unknown") {
const ref = new ReferenceImpl(COMPUTE);
return ref.compute = compute, ref.update = update, isDevelopingApp() && (ref.debugLabel = `(result of a \`${debugLabel}\` helper)`), ref;
}
function createReadOnlyRef(ref) {
return isUpdatableRef(ref) ? createComputeRef(() => valueForRef(ref), null, ref.debugLabel) : ref;
}
function isInvokableRef(ref) {
return 3 === ref[REFERENCE];
}
function createInvokableRef(inner) {
const ref = createComputeRef(() => valueForRef(inner), value => updateRef(inner, value));
return ref.debugLabel = inner.debugLabel, ref[REFERENCE] = 3, ref;
}
function isConstRef(_ref) {
return _ref.tag === CONSTANT_TAG;
}
function isUpdatableRef(_ref) {
return null !== _ref.update;
}
function valueForRef(_ref) {
const ref = _ref;
let {
tag: tag
} = ref;
if (tag === CONSTANT_TAG) return ref.lastValue;
const {
lastRevision: lastRevision
} = ref;
let lastValue;
if (null !== tag && validateTag(tag, lastRevision)) lastValue = ref.lastValue;else {
const {
compute: compute
} = ref,
newTag = track(() => {
lastValue = ref.lastValue = compute();
}, isDevelopingApp() && ref.debugLabel);
tag = ref.tag = newTag, ref.lastRevision = valueForTag(newTag);
}
return consumeTag(tag), lastValue;
}
function updateRef(_ref, value) {
expect(_ref.update, "called update on a non-updatable reference")(value);
}
function childRefFor(_parentRef, path) {
const parentRef = _parentRef,
type = parentRef[REFERENCE];
let child,
children = parentRef.children;
if (null === children) children = parentRef.children = new Map();else if (child = children.get(path), void 0 !== child) return child;
if (type === UNBOUND) {
const parent = valueForRef(parentRef);
child = isDict(parent) ? createUnboundRef(parent[path], isDevelopingApp() && `${parentRef.debugLabel}.${path}`) : UNDEFINED_REFERENCE;
} else child = createComputeRef(() => {
const parent = valueForRef(parentRef);
if (isDict(parent)) return getProp(parent, path);
}, val => {
const parent = valueForRef(parentRef);
if (isDict(parent)) return setProp(parent, path, val);
}), isDevelopingApp() && (child.debugLabel = `${parentRef.debugLabel}.${path}`);
return children.set(path, child), child;
}
function childRefFromParts(root, parts) {
let reference = root;
for (const part of parts) reference = childRefFor(reference, part);
return reference;
}
let createDebugAliasRef;
isDevelopingApp() && (createDebugAliasRef = (debugLabel, inner) => {
const ref = createComputeRef(() => valueForRef(inner), isUpdatableRef(inner) ? value => updateRef(inner, value) : null);
return ref[REFERENCE] = inner[REFERENCE], ref.debugLabel = debugLabel, ref;
});
const NULL_IDENTITY = {},
KEY = (_, index) => index,
INDEX = (_, index) => String(index),
IDENTITY = item => null === item ? NULL_IDENTITY : item;
class WeakMapWithPrimitives {
_weakMap;
_primitiveMap;
get weakMap() {
return void 0 === this._weakMap && (this._weakMap = new WeakMap()), this._weakMap;
}
get primitiveMap() {
return void 0 === this._primitiveMap && (this._primitiveMap = new Map()), this._primitiveMap;
}
set(key, value) {
isObject(key) ? this.weakMap.set(key, value) : this.primitiveMap.set(key, value);
}
get(key) {
return isObject(key) ? this.weakMap.get(key) : this.primitiveMap.get(key);
}
}
const IDENTITIES = new WeakMapWithPrimitives();
/**
* When iterating over a list, it's possible that an item with the same unique
* key could be encountered twice:
*
* ```js
* let arr = ['same', 'different', 'same', 'same'];
* ```
*
* In general, we want to treat these items as _unique within the list_. To do
* this, we track the occurences of every item as we iterate the list, and when
* an item occurs more than once, we generate a new unique key just for that
* item, and that occurence within the list. The next time we iterate the list,
* and encounter an item for the nth time, we can get the _same_ key, and let
* Glimmer know that it should reuse the DOM for the previous nth occurence.
*/
function uniqueKeyFor(keyFor) {
let seen = new WeakMapWithPrimitives();
return (value, memo) => {
let key = keyFor(value, memo),
count = seen.get(key) || 0;
return seen.set(key, count + 1), 0 === count ? key : function (value, count) {
let identities = IDENTITIES.get(value);
void 0 === identities && (identities = [], IDENTITIES.set(value, identities));
let identity = identities[count];
return void 0 === identity && (identity = {
value: value,
count: count
}, identities[count] = identity), identity;
}(key, count);
};
}
function createIteratorRef(listRef, key) {
return createComputeRef(() => {
let iterable = valueForRef(listRef),
keyFor = function (key) {
switch (key) {
case "@key":
return uniqueKeyFor(KEY);
case "@index":
return uniqueKeyFor(INDEX);
case "@identity":
return uniqueKeyFor(IDENTITY);
default:
return function (path) {
if (isDevelopingApp() && "@" === path[0]) throw new Error(`invalid keypath: '${path}', valid keys: , , or a path`);
return uniqueKeyFor(item => getPath(item, path));
}(key);
}
}(key);
if (Array.isArray(iterable)) return new ArrayIterator(iterable, keyFor);
let maybeIterator = toIterator(iterable);
return null === maybeIterator ? new ArrayIterator(EMPTY_ARRAY, () => null) : new IteratorWrapper(maybeIterator, keyFor);
});
}
function createIteratorItemRef(_value) {
let value = _value,
tag = createTag();
return createComputeRef(() => (consumeTag(tag), value), newValue => {
value !== newValue && (value = newValue, DIRTY_TAG(tag));
});
}
class IteratorWrapper {
constructor(inner, keyFor) {
this.inner = inner, this.keyFor = keyFor;
}
isEmpty() {
return this.inner.isEmpty();
}
next() {
let nextValue = this.inner.next();
return null !== nextValue && (nextValue.key = this.keyFor(nextValue.value, nextValue.memo)), nextValue;
}
}
class ArrayIterator {
current;
pos = 0;
constructor(iterator, keyFor) {
this.iterator = iterator, this.keyFor = keyFor, 0 === iterator.length ? this.current = {
kind: "empty"
} : this.current = {
kind: "first",
value: iterator[this.pos]
};
}
isEmpty() {
return "empty" === this.current.kind;
}
next() {
let value,
current = this.current;
if ("first" === current.kind) this.current = {
kind: "progress"
}, value = current.value;else {
if (this.pos >= this.iterator.length - 1) return null;
value = this.iterator[++this.pos];
}
let {
keyFor: keyFor
} = this;
return {
key: keyFor(value, this.pos),
value: value,
memo: this.pos
};
}
}
export { FALSE_REFERENCE, NULL_REFERENCE, REFERENCE, TRUE_REFERENCE, UNDEFINED_REFERENCE, childRefFor, childRefFromParts, createComputeRef, createConstRef, createDebugAliasRef, createInvokableRef, createIteratorItemRef, createIteratorRef, createPrimitiveRef, createReadOnlyRef, createUnboundRef, isConstRef, isInvokableRef, isUpdatableRef, updateRef, valueForRef };