start impl rrdom
This commit is contained in:
173
src/rrdom/index.ts
Normal file
173
src/rrdom/index.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { RRdomTreeNode, AnyObject } from './tree-node';
|
||||
|
||||
class RRdomTree {
|
||||
private readonly symbol = '__rrdom__';
|
||||
|
||||
public initialize(object: AnyObject) {
|
||||
this._node(object);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
public hasChildren(object: AnyObject): boolean {
|
||||
return Boolean(this._node(object).hasChildren);
|
||||
}
|
||||
|
||||
public firstChild(object: AnyObject) {
|
||||
return this._node(object).firstChild || null;
|
||||
}
|
||||
|
||||
public lastChild(object: AnyObject) {
|
||||
return this._node(object).lastChild || null;
|
||||
}
|
||||
|
||||
public previousSibling(object: AnyObject) {
|
||||
return this._node(object).previousSibling || null;
|
||||
}
|
||||
|
||||
public nextSibling(object: AnyObject) {
|
||||
return this._node(object).nextSibling || null;
|
||||
}
|
||||
|
||||
public parent(object: AnyObject) {
|
||||
return this._node(object).parent || null;
|
||||
}
|
||||
|
||||
public insertAfter(referenceObject: AnyObject, newObject: AnyObject) {
|
||||
const referenceNode = this._node(referenceObject);
|
||||
const nextNode = this._node(referenceNode.nextSibling);
|
||||
const newNode = this._node(newObject);
|
||||
const parentNode = this._node(referenceNode.parent);
|
||||
|
||||
if (newNode.isAttached) {
|
||||
throw new Error('Node already attached');
|
||||
}
|
||||
if (!referenceNode) {
|
||||
throw new Error('Reference node not attached');
|
||||
}
|
||||
|
||||
newNode.parent = referenceNode.parent;
|
||||
newNode.previousSibling = referenceObject;
|
||||
newNode.nextSibling = referenceNode.nextSibling;
|
||||
referenceNode.nextSibling = newObject;
|
||||
|
||||
if (nextNode) {
|
||||
nextNode.previousSibling = newObject;
|
||||
}
|
||||
|
||||
if (parentNode && parentNode.lastChild === referenceObject) {
|
||||
parentNode.lastChild = newObject;
|
||||
}
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.childrenChanged();
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
public insertBefore(referenceObject: AnyObject, newObject: AnyObject) {
|
||||
const referenceNode = this._node(referenceObject);
|
||||
const prevNode = this._node(referenceNode.previousSibling);
|
||||
const newNode = this._node(newObject);
|
||||
const parentNode = this._node(referenceNode.parent);
|
||||
|
||||
if (newNode.isAttached) {
|
||||
throw new Error('Node already attached');
|
||||
}
|
||||
if (!referenceNode) {
|
||||
throw new Error('Reference node not attached');
|
||||
}
|
||||
|
||||
newNode.parent = referenceNode.parent;
|
||||
newNode.previousSibling = referenceNode.previousSibling;
|
||||
newNode.nextSibling = referenceObject;
|
||||
referenceNode.previousSibling = newObject;
|
||||
|
||||
if (prevNode) {
|
||||
prevNode.nextSibling = newObject;
|
||||
}
|
||||
|
||||
if (parentNode && parentNode.firstChild === referenceObject) {
|
||||
parentNode.firstChild = newObject;
|
||||
}
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.childrenChanged();
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
public appendChild(referenceObject: AnyObject, newObject: AnyObject) {
|
||||
const referenceNode = this._node(referenceObject);
|
||||
const newNode = this._node(newObject);
|
||||
|
||||
if (newNode.isAttached) {
|
||||
throw new Error('Node already attached');
|
||||
}
|
||||
if (!referenceNode) {
|
||||
throw new Error('Reference node not attached');
|
||||
}
|
||||
|
||||
if (referenceNode.hasChildren) {
|
||||
this.insertAfter(referenceNode.lastChild!, newObject);
|
||||
} else {
|
||||
newNode.parent = referenceObject;
|
||||
referenceNode.firstChild = newObject;
|
||||
referenceNode.lastChild = newObject;
|
||||
referenceNode.childrenChanged();
|
||||
}
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
public remove(removeObject: AnyObject) {
|
||||
const removeNode = this._node(removeObject);
|
||||
const parentNode = this._node(removeNode.parent);
|
||||
const prevNode = this._node(removeNode.previousSibling);
|
||||
const nextNode = this._node(removeNode.nextSibling);
|
||||
|
||||
if (parentNode) {
|
||||
if (parentNode.firstChild === removeObject) {
|
||||
parentNode.firstChild = removeNode.nextSibling;
|
||||
}
|
||||
|
||||
if (parentNode.lastChild === removeObject) {
|
||||
parentNode.lastChild = removeNode.previousSibling;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevNode) {
|
||||
prevNode.nextSibling = removeNode.nextSibling;
|
||||
}
|
||||
|
||||
if (nextNode) {
|
||||
nextNode.previousSibling = removeNode.previousSibling;
|
||||
}
|
||||
|
||||
removeNode.parent = null;
|
||||
removeNode.previousSibling = null;
|
||||
removeNode.nextSibling = null;
|
||||
removeNode.cachedIndex = -1;
|
||||
removeNode.cachedIndexVersion = NaN;
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.childrenChanged();
|
||||
}
|
||||
|
||||
return removeObject;
|
||||
}
|
||||
|
||||
private _node(object: AnyObject | null): RRdomTreeNode {
|
||||
if (!object) {
|
||||
throw new Error('Object is falsy');
|
||||
}
|
||||
|
||||
if (this.symbol in object) {
|
||||
return object[this.symbol] as RRdomTreeNode;
|
||||
}
|
||||
|
||||
return (object[this.symbol] = new RRdomTreeNode());
|
||||
}
|
||||
}
|
||||
52
src/rrdom/tree-node.ts
Normal file
52
src/rrdom/tree-node.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// tslint:disable-next-line: no-any
|
||||
export type AnyObject = { [key: string]: any; __rrdom__?: RRdomTreeNode };
|
||||
|
||||
export class RRdomTreeNode implements AnyObject {
|
||||
public parent: AnyObject | null = null;
|
||||
public previousSibling: AnyObject | null = null;
|
||||
public nextSibling: AnyObject | null = null;
|
||||
|
||||
public firstChild: AnyObject | null = null;
|
||||
public lastChild: AnyObject | null = null;
|
||||
|
||||
// This value is incremented anytime a children is added or removed
|
||||
public childrenVersion = 0;
|
||||
// The last child object which has a cached index
|
||||
public childIndexCachedUpTo: AnyObject | null = null;
|
||||
|
||||
/**
|
||||
* This value represents the cached node index, as long as
|
||||
* cachedIndexVersion matches with the childrenVersion of the parent
|
||||
*/
|
||||
public cachedIndex = -1;
|
||||
public cachedIndexVersion = NaN;
|
||||
|
||||
public get isAttached() {
|
||||
return Boolean(this.parent || this.previousSibling || this.nextSibling);
|
||||
}
|
||||
|
||||
public get hasChildren() {
|
||||
return Boolean(this.firstChild);
|
||||
}
|
||||
|
||||
public childrenChanged() {
|
||||
// tslint:disable-next-line: no-bitwise
|
||||
this.childrenVersion = (this.childrenVersion + 1) & 0xffffffff;
|
||||
this.childIndexCachedUpTo = null;
|
||||
}
|
||||
|
||||
public getCachedIndex(parentNode: AnyObject) {
|
||||
if (this.cachedIndexVersion !== parentNode.childrenVersion) {
|
||||
this.cachedIndexVersion = NaN;
|
||||
// cachedIndex is no longer valid
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.cachedIndex;
|
||||
}
|
||||
|
||||
public setCachedIndex(parentNode: AnyObject, index: number) {
|
||||
this.cachedIndexVersion = parentNode.childrenVersion;
|
||||
this.cachedIndex = index;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user