@glimmer/reference
Version:
Objects used to track values and their dirtiness in Glimmer
438 lines (425 loc) • 12.1 kB
JavaScript
'use strict';
var env = require('@glimmer/env');
var globalContext = require('@glimmer/global-context');
var util = require('@glimmer/util');
var validator = require('@glimmer/validator');
function _typeof(obj) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _toPrimitive(input, hint) {
if (_typeof(input) !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (_typeof(res) !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return _typeof(key) === "symbol" ? key : String(key);
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const REFERENCE = Symbol('REFERENCE');
const CONSTANT = 0;
const COMPUTE = 1;
const UNBOUND = 2;
const INVOKABLE = 3;
//////////
class ReferenceImpl {
constructor(type) {
_defineProperty(this, REFERENCE, void 0);
_defineProperty(this, "tag", null);
_defineProperty(this, "lastRevision", validator.INITIAL);
_defineProperty(this, "lastValue", void 0);
_defineProperty(this, "children", null);
_defineProperty(this, "compute", null);
_defineProperty(this, "update", null);
_defineProperty(this, "debugLabel", void 0);
this[REFERENCE] = type;
}
}
function createPrimitiveRef(value) {
const ref = new ReferenceImpl(UNBOUND);
ref.tag = validator.CONSTANT_TAG;
ref.lastValue = value;
if (env.DEBUG) {
ref.debugLabel = String(value);
}
return ref;
}
const UNDEFINED_REFERENCE = createPrimitiveRef(undefined);
const NULL_REFERENCE = createPrimitiveRef(null);
const TRUE_REFERENCE = createPrimitiveRef(true);
const FALSE_REFERENCE = createPrimitiveRef(false);
function createConstRef(value, debugLabel) {
const ref = new ReferenceImpl(CONSTANT);
ref.lastValue = value;
ref.tag = validator.CONSTANT_TAG;
if (env.DEBUG) {
ref.debugLabel = debugLabel;
}
return ref;
}
function createUnboundRef(value, debugLabel) {
const ref = new ReferenceImpl(UNBOUND);
ref.lastValue = value;
ref.tag = validator.CONSTANT_TAG;
if (env.DEBUG) {
ref.debugLabel = debugLabel;
}
return ref;
}
function createComputeRef(compute) {
let update = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
let debugLabel = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'unknown';
const ref = new ReferenceImpl(COMPUTE);
ref.compute = compute;
ref.update = update;
if (env.DEBUG) {
ref.debugLabel = `(result of a \`${debugLabel}\` helper)`;
}
return ref;
}
function createReadOnlyRef(ref) {
if (!isUpdatableRef(ref)) return ref;
return createComputeRef(() => valueForRef(ref), null, ref.debugLabel);
}
function isInvokableRef(ref) {
return ref[REFERENCE] === INVOKABLE;
}
function createInvokableRef(inner) {
const ref = createComputeRef(() => valueForRef(inner), value => updateRef(inner, value));
ref.debugLabel = inner.debugLabel;
ref[REFERENCE] = INVOKABLE;
return ref;
}
function isConstRef(_ref) {
const ref = _ref;
return ref.tag === validator.CONSTANT_TAG;
}
function isUpdatableRef(_ref) {
const ref = _ref;
return ref.update !== null;
}
function valueForRef(_ref) {
const ref = _ref;
let {
tag
} = ref;
if (tag === validator.CONSTANT_TAG) {
return ref.lastValue;
}
const {
lastRevision
} = ref;
let lastValue;
if (tag === null || !validator.validateTag(tag, lastRevision)) {
const {
compute
} = ref;
const newTag = validator.track(() => {
lastValue = ref.lastValue = compute();
}, env.DEBUG && ref.debugLabel);
tag = ref.tag = newTag;
ref.lastRevision = validator.valueForTag(newTag);
} else {
lastValue = ref.lastValue;
}
validator.consumeTag(tag);
return lastValue;
}
function updateRef(_ref, value) {
const ref = _ref;
const update = util.expect(ref.update, 'called update on a non-updatable reference');
update(value);
}
function childRefFor(_parentRef, path) {
const parentRef = _parentRef;
const type = parentRef[REFERENCE];
let children = parentRef.children;
let child;
if (children === null) {
children = parentRef.children = new Map();
} else {
child = children.get(path);
if (child !== undefined) {
return child;
}
}
if (type === UNBOUND) {
const parent = valueForRef(parentRef);
if (util.isDict(parent)) {
child = createUnboundRef(parent[path], env.DEBUG && `${parentRef.debugLabel}.${path}`);
} else {
child = UNDEFINED_REFERENCE;
}
} else {
child = createComputeRef(() => {
const parent = valueForRef(parentRef);
if (util.isDict(parent)) {
return globalContext.getProp(parent, path);
}
}, val => {
const parent = valueForRef(parentRef);
if (util.isDict(parent)) {
return globalContext.setProp(parent, path, val);
}
});
if (env.DEBUG) {
child.debugLabel = `${parentRef.debugLabel}.${path}`;
}
}
children.set(path, child);
return child;
}
function childRefFromParts(root, parts) {
let reference = root;
for (const part of parts) {
reference = childRefFor(reference, part);
}
return reference;
}
exports.createDebugAliasRef = void 0;
if (env.DEBUG) {
exports.createDebugAliasRef = (debugLabel, inner) => {
const update = isUpdatableRef(inner) ? value => updateRef(inner, value) : null;
const ref = createComputeRef(() => valueForRef(inner), update);
ref[REFERENCE] = inner[REFERENCE];
ref.debugLabel = debugLabel;
return ref;
};
}
const NULL_IDENTITY = {};
const KEY = (_, index) => index;
const INDEX = (_, index) => String(index);
const IDENTITY = item => {
if (item === null) {
// Returning null as an identity will cause failures since the iterator
// can't tell that it's actually supposed to be null
return NULL_IDENTITY;
}
return item;
};
function keyForPath(path) {
if (env.DEBUG && path[0] === '@') {
throw new Error(`invalid keypath: '${path}', valid keys: , , or a path`);
}
return uniqueKeyFor(item => globalContext.getPath(item, path));
}
function makeKeyFor(key) {
switch (key) {
case '@key':
return uniqueKeyFor(KEY);
case '@index':
return uniqueKeyFor(INDEX);
case '@identity':
return uniqueKeyFor(IDENTITY);
default:
return keyForPath(key);
}
}
class WeakMapWithPrimitives {
constructor() {
_defineProperty(this, "_weakMap", void 0);
_defineProperty(this, "_primitiveMap", void 0);
}
get weakMap() {
if (this._weakMap === undefined) {
this._weakMap = new WeakMap();
}
return this._weakMap;
}
get primitiveMap() {
if (this._primitiveMap === undefined) {
this._primitiveMap = new Map();
}
return this._primitiveMap;
}
set(key, value) {
if (util.isObject(key)) {
this.weakMap.set(key, value);
} else {
this.primitiveMap.set(key, value);
}
}
get(key) {
if (util.isObject(key)) {
return this.weakMap.get(key);
} else {
return this.primitiveMap.get(key);
}
}
}
const IDENTITIES = new WeakMapWithPrimitives();
function identityForNthOccurence(value, count) {
let identities = IDENTITIES.get(value);
if (identities === undefined) {
identities = [];
IDENTITIES.set(value, identities);
}
let identity = identities[count];
if (identity === undefined) {
identity = {
value,
count
};
identities[count] = identity;
}
return identity;
}
/**
* 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);
let count = seen.get(key) || 0;
seen.set(key, count + 1);
if (count === 0) {
return key;
}
return identityForNthOccurence(key, count);
};
}
function createIteratorRef(listRef, key) {
return createComputeRef(() => {
let iterable = valueForRef(listRef);
let keyFor = makeKeyFor(key);
if (Array.isArray(iterable)) {
return new ArrayIterator(iterable, keyFor);
}
let maybeIterator = globalContext.toIterator(iterable);
if (maybeIterator === null) {
return new ArrayIterator(util.EMPTY_ARRAY, () => null);
}
return new IteratorWrapper(maybeIterator, keyFor);
});
}
function createIteratorItemRef(_value) {
let value = _value;
let tag = validator.createTag();
return createComputeRef(() => {
validator.consumeTag(tag);
return value;
}, newValue => {
if (value !== newValue) {
value = newValue;
validator.dirtyTag(tag);
}
});
}
class IteratorWrapper {
constructor(inner, keyFor) {
this.inner = inner;
this.keyFor = keyFor;
}
isEmpty() {
return this.inner.isEmpty();
}
next() {
let nextValue = this.inner.next();
if (nextValue !== null) {
nextValue.key = this.keyFor(nextValue.value, nextValue.memo);
}
return nextValue;
}
}
class ArrayIterator {
constructor(iterator, keyFor) {
_defineProperty(this, "current", void 0);
_defineProperty(this, "pos", 0);
this.iterator = iterator;
this.keyFor = keyFor;
if (iterator.length === 0) {
this.current = {
kind: 'empty'
};
} else {
this.current = {
kind: 'first',
value: iterator[this.pos]
};
}
}
isEmpty() {
return this.current.kind === 'empty';
}
next() {
let value;
let current = this.current;
if (current.kind === 'first') {
this.current = {
kind: 'progress'
};
value = current.value;
} else if (this.pos >= this.iterator.length - 1) {
return null;
} else {
value = this.iterator[++this.pos];
}
let {
keyFor
} = this;
let key = keyFor(value, this.pos);
let memo = this.pos;
return {
key,
value,
memo
};
}
}
exports.FALSE_REFERENCE = FALSE_REFERENCE;
exports.NULL_REFERENCE = NULL_REFERENCE;
exports.REFERENCE = REFERENCE;
exports.TRUE_REFERENCE = TRUE_REFERENCE;
exports.UNDEFINED_REFERENCE = UNDEFINED_REFERENCE;
exports.childRefFor = childRefFor;
exports.childRefFromParts = childRefFromParts;
exports.createComputeRef = createComputeRef;
exports.createConstRef = createConstRef;
exports.createInvokableRef = createInvokableRef;
exports.createIteratorItemRef = createIteratorItemRef;
exports.createIteratorRef = createIteratorRef;
exports.createPrimitiveRef = createPrimitiveRef;
exports.createReadOnlyRef = createReadOnlyRef;
exports.createUnboundRef = createUnboundRef;
exports.isConstRef = isConstRef;
exports.isInvokableRef = isInvokableRef;
exports.isUpdatableRef = isUpdatableRef;
exports.updateRef = updateRef;
exports.valueForRef = valueForRef;
//# sourceMappingURL=index.cjs.map