UNPKG

@glimmer/reference

Version:

Objects used to track values and their dirtiness in Glimmer

438 lines (425 loc) 12.1 kB
'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: @index, @identity, 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