@skele/core
Version:
Core package of the Skele framework. Element definitions, registrations and traversal.
323 lines (271 loc) • 8.27 kB
JavaScript
'use strict'
/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Cursor is expected to be required in a node or other CommonJS context:
*
* var Cursor = require('immutable/contrib/cursor');
*
* If you wish to use it in the browser, please check out Browserify or WebPack!
*/
import { Iterable, Seq, Map, Record } from 'immutable'
const Iterator = Iterable.Iterator
function cursorFrom(rootData, keyPath) {
if (arguments.length === 1) {
keyPath = []
} else {
keyPath = valToKeyPath(keyPath)
}
return makeCursor(rootData, keyPath)
}
var KeyedCursorPrototype = Object.create(Seq.Keyed.prototype)
var IndexedCursorPrototype = Object.create(Seq.Indexed.prototype)
function KeyedCursor(rootData, keyPath, value) {
this.size = value.size
this._value = value
this._rootData = rootData
this._keyPath = keyPath
}
KeyedCursorPrototype.constructor = KeyedCursor
function IndexedCursor(rootData, keyPath, value) {
this.size = value.size
this._rootData = rootData
this._value = value
this._keyPath = keyPath
}
IndexedCursorPrototype.constructor = IndexedCursor
KeyedCursorPrototype.toString = function() {
return this.__toString('Cursor {', '}')
}
IndexedCursorPrototype.toString = function() {
return this.__toString('Cursor [', ']')
}
KeyedCursorPrototype.deref = KeyedCursorPrototype.valueOf = IndexedCursorPrototype.deref = IndexedCursorPrototype.valueOf = function(
notSetValue
) {
return this._value
}
KeyedCursorPrototype.get = IndexedCursorPrototype.get = function(
key,
notSetValue
) {
var value = this._value.get(key, NOT_SET)
return value === NOT_SET ? notSetValue : wrappedValue(this, [key], value)
}
KeyedCursorPrototype.getIn = IndexedCursorPrototype.getIn = function(
keyPath,
notSetValue
) {
keyPath = listToKeyPath(keyPath)
if (keyPath.length === 0) {
return this
}
var value = this._value.getIn(keyPath, NOT_SET)
return value === NOT_SET ? notSetValue : wrappedValue(this, keyPath, value)
}
IndexedCursorPrototype.set = KeyedCursorPrototype.set = function(key, value) {
return updateCursor(this, this._value.set(key, value))
}
IndexedCursorPrototype.push = function(/* values */) {
var args = arguments
var m = this._value
return updateCursor(this, m.push.apply(m, args))
}
IndexedCursorPrototype.pop = function() {
return updateCursor(this, this._value.pop())
}
IndexedCursorPrototype.unshift = function(/* values */) {
var args = arguments
var m = this._value
return updateCursor(this, m.unshift.apply(m, args))
}
IndexedCursorPrototype.shift = function() {
return updateCursor(this, this._value.shift())
}
IndexedCursorPrototype.setIn = KeyedCursorPrototype.setIn = Map.prototype.setIn
KeyedCursorPrototype.remove = KeyedCursorPrototype[
'delete'
] = IndexedCursorPrototype.remove = IndexedCursorPrototype['delete'] = function(
key
) {
return updateCursor(this, this._value.remove(key))
}
IndexedCursorPrototype.removeIn = IndexedCursorPrototype.deleteIn = KeyedCursorPrototype.removeIn = KeyedCursorPrototype.deleteIn =
Map.prototype.deleteIn
KeyedCursorPrototype.clear = IndexedCursorPrototype.clear = function() {
return updateCursor(this, this._value.clear())
}
// TODO maybe remove?
IndexedCursorPrototype.update = KeyedCursorPrototype.update = function(
keyOrFn,
notSetValue,
updater
) {
return arguments.length === 1
? updateCursor(this, this._value.update(keyOrFn))
: this.updateIn([keyOrFn], notSetValue, updater)
}
// TODO maybe remove?
IndexedCursorPrototype.updateIn = KeyedCursorPrototype.updateIn = function(
keyPath,
notSetValue,
updater
) {
return updateCursor(this, this._value.updateIn(keyPath, notSetValue, updater))
}
IndexedCursorPrototype.merge = KeyedCursorPrototype.merge = function(/*...iters*/) {
var args = arguments
var m = this._value
return updateCursor(this, m.merge.apply(m, args))
}
IndexedCursorPrototype.mergeWith = KeyedCursorPrototype.mergeWith = function(
merger /*, ...iters*/
) {
var args = arguments
var m = this._value
return updateCursor(this, m.mergeWith.apply(m, args))
}
IndexedCursorPrototype.mergeIn = KeyedCursorPrototype.mergeIn =
Map.prototype.mergeIn
IndexedCursorPrototype.mergeDeep = KeyedCursorPrototype.mergeDeep = function(/*...iters*/) {
var args = arguments
return updateCursor(this, this._value.mergeDeep.apply(m, args))
}
IndexedCursorPrototype.mergeDeepWith = KeyedCursorPrototype.mergeDeepWith = function(
merger /*, ...iters*/
) {
var args = arguments
return updateCursor(this, this._value.mergeDeepWith.apply(m, args))
}
IndexedCursorPrototype.mergeDeepIn = KeyedCursorPrototype.mergeDeepIn =
Map.prototype.mergeDeepIn
KeyedCursorPrototype.withMutations = IndexedCursorPrototype.withMutations = function(
fn
) {
return updateCursor(this, (this._value || Map()).withMutations(fn))
}
KeyedCursorPrototype.cursor = IndexedCursorPrototype.cursor = function(
subKeyPath
) {
subKeyPath = valToKeyPath(subKeyPath)
return subKeyPath.length === 0 ? this : subCursor(this, subKeyPath)
}
/**
* All iterables need to implement __iterate
*/
KeyedCursorPrototype.__iterate = IndexedCursorPrototype.__iterate = function(
fn,
reverse
) {
var cursor = this
var deref = cursor.deref()
return deref && deref.__iterate
? deref.__iterate(function(v, k) {
return fn(wrappedValue(cursor, [k], v), k, cursor)
}, reverse)
: 0
}
/**
* All iterables need to implement __iterator
*/
KeyedCursorPrototype.__iterator = IndexedCursorPrototype.__iterator = function(
type,
reverse
) {
var deref = this.deref()
var cursor = this
var iterator =
deref && deref.__iterator && deref.__iterator(Iterator.ENTRIES, reverse)
return new Iterator(function() {
if (!iterator) {
return { value: undefined, done: true }
}
var step = iterator.next()
if (step.done) {
return step
}
var entry = step.value
var k = entry[0]
var v = wrappedValue(cursor, [k], entry[1])
return {
value: type === Iterator.KEYS ? k : type === Iterator.VALUES ? v : [k, v],
done: false,
}
})
}
KeyedCursor.prototype = KeyedCursorPrototype
IndexedCursor.prototype = IndexedCursorPrototype
var NOT_SET = {} // Sentinel value
function makeCursor(rootData, keyPath, value) {
if (arguments.length < 3) {
value = rootData.getIn(keyPath)
}
if (value && value.deref) {
throw new Error('Placing cursors inside cursors is not allowed!')
}
if (!Iterable.isIterable(value)) return value
var CursorClass = Iterable.isIndexed(value) ? IndexedCursor : KeyedCursor
var cursor = new CursorClass(rootData, keyPath, value)
if (value instanceof Record) {
defineRecordProperties(cursor, value)
}
return cursor
}
function defineRecordProperties(cursor, value) {
try {
value._keys.forEach(setProp.bind(undefined, cursor))
} catch (error) {
// Object.defineProperty failed. Probably IE8.
}
}
function setProp(prototype, name) {
Object.defineProperty(prototype, name, {
get: function() {
return this.get(name)
},
set: function(value) {
if (!this.__ownerID) {
throw new Error('Cannot set on an immutable record.')
}
},
})
}
function wrappedValue(cursor, keyPath, value) {
return Iterable.isIterable(value) ? subCursor(cursor, keyPath, value) : value
}
function subCursor(cursor, keyPath, value) {
if (arguments.length < 3) {
return makeCursor(
// call without value
cursor._rootData,
newKeyPath(cursor._keyPath, keyPath)
)
}
return makeCursor(
cursor._rootData,
newKeyPath(cursor._keyPath, keyPath),
value
)
}
function updateCursor(cursor, newVal) {
return makeCursor(cursor._rootData, cursor._keyPath, newVal)
}
function newKeyPath(head, tail) {
return head.concat(listToKeyPath(tail))
}
function listToKeyPath(list) {
return Array.isArray(list) ? list : Iterable(list).toArray()
}
function valToKeyPath(val) {
return Array.isArray(val)
? val
: Iterable.isIterable(val) ? val.toArray() : [val]
}
export default {
from: cursorFrom,
}
export { cursorFrom as from }