1 | import {VNode} from 'snabbdom';
|
2 | import {EventDelegator} from './EventDelegator';
|
3 | import {Scope} from './isolate';
|
4 | import {isEqualNamespace} from './utils';
|
5 | import SymbolTree from './SymbolTree';
|
6 |
|
7 | export 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 |
|
14 |
|
15 |
|
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 |
|
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 | }
|