UNPKG

3.88 kBPlain TextView Raw
1import {VNode} from 'snabbdom';
2import {EventDelegator} from './EventDelegator';
3import {Scope} from './isolate';
4import {isEqualNamespace} from './utils';
5import SymbolTree from './SymbolTree';
6
7export class IsolateModule {
8 private namespaceTree = new SymbolTree<Element, Scope>(x => x.scope);
9 private namespaceByElement: Map<Element, Array<Scope>>;
10 private eventDelegator: EventDelegator | undefined;
11
12 /**
13 * A registry that keeps track of all the nodes that are removed from
14 * the virtual DOM in a single patch. Those nodes are cleaned once snabbdom
15 * has finished patching the DOM.
16 */
17 private vnodesBeingRemoved: Array<VNode>;
18
19 constructor() {
20 this.namespaceByElement = new Map<Element, Array<Scope>>();
21 this.vnodesBeingRemoved = [];
22 }
23
24 public setEventDelegator(del: EventDelegator): void {
25 this.eventDelegator = del;
26 }
27
28 private insertElement(namespace: Array<Scope>, el: Element): void {
29 this.namespaceByElement.set(el, namespace);
30 this.namespaceTree.set(namespace, el);
31 }
32
33 private removeElement(elm: Element): void {
34 this.namespaceByElement.delete(elm);
35 const namespace = this.getNamespace(elm);
36 if (namespace) {
37 this.namespaceTree.delete(namespace);
38 }
39 }
40
41 public getElement(
42 namespace: Array<Scope>,
43 max?: number
44 ): Element | undefined {
45 return this.namespaceTree.get(namespace, undefined, max);
46 }
47
48 public getRootElement(elm: Element): Element | undefined {
49 if (this.namespaceByElement.has(elm)) {
50 return elm;
51 }
52
53 //TODO: Add quick-lru or similar as additional O(1) cache
54
55 let curr = elm;
56 while (!this.namespaceByElement.has(curr)) {
57 curr = curr.parentNode as Element;
58 if (!curr) {
59 return undefined;
60 } else if (curr.tagName === 'HTML') {
61 throw new Error('No root element found, this should not happen at all');
62 }
63 }
64 return curr;
65 }
66
67 public getNamespace(elm: Element): Array<Scope> | undefined {
68 const rootElement = this.getRootElement(elm);
69 if (!rootElement) {
70 return undefined;
71 }
72 return this.namespaceByElement.get(rootElement) as Array<Scope>;
73 }
74
75 public createModule() {
76 const self = this;
77 return {
78 create(emptyVNode: VNode, vNode: VNode) {
79 const {elm, data = {}} = vNode;
80 const namespace: Array<Scope> = (data as any).isolate;
81
82 if (Array.isArray(namespace)) {
83 self.insertElement(namespace, elm as Element);
84 }
85 },
86
87 update(oldVNode: VNode, vNode: VNode) {
88 const {elm: oldElm, data: oldData = {}} = oldVNode;
89 const {elm, data = {}} = vNode;
90 const oldNamespace: Array<Scope> = (oldData as any).isolate;
91 const namespace: Array<Scope> = (data as any).isolate;
92
93 if (!isEqualNamespace(oldNamespace, namespace)) {
94 if (Array.isArray(oldNamespace)) {
95 self.removeElement(oldElm as Element);
96 }
97 }
98 if (Array.isArray(namespace)) {
99 self.insertElement(namespace, elm as Element);
100 }
101 },
102
103 destroy(vNode: VNode) {
104 self.vnodesBeingRemoved.push(vNode);
105 },
106
107 remove(vNode: VNode, cb: Function) {
108 self.vnodesBeingRemoved.push(vNode);
109 cb();
110 },
111
112 post() {
113 const vnodesBeingRemoved = self.vnodesBeingRemoved;
114 for (let i = vnodesBeingRemoved.length - 1; i >= 0; i--) {
115 const vnode = vnodesBeingRemoved[i];
116 const namespace =
117 vnode.data !== undefined
118 ? (vnode.data as any).isolation
119 : undefined;
120 if (namespace !== undefined) {
121 self.removeElement(namespace);
122 }
123 (self.eventDelegator as EventDelegator).removeElement(
124 vnode.elm as Element,
125 namespace
126 );
127 }
128 self.vnodesBeingRemoved = [];
129 },
130 };
131 }
132}