vsn 0.1.71 → 0.1.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/demo.html +5 -1
- package/demo/vsn.js +2 -2
- package/demo/xhr.html +27 -5
- package/dist/AST/ArithmeticAssignmentNode.js +1 -1
- package/dist/AST/ArithmeticAssignmentNode.js.map +1 -1
- package/dist/AST/ClassNode.d.ts +24 -7
- package/dist/AST/ClassNode.js +220 -57
- package/dist/AST/ClassNode.js.map +1 -1
- package/dist/AST/ElementAttributeNode.js +13 -4
- package/dist/AST/ElementAttributeNode.js.map +1 -1
- package/dist/AST/FunctionCallNode.js +14 -3
- package/dist/AST/FunctionCallNode.js.map +1 -1
- package/dist/AST/Node.d.ts +4 -1
- package/dist/AST/Node.js.map +1 -1
- package/dist/AST/ScopeMemberNode.js.map +1 -1
- package/dist/AST.js +3 -1
- package/dist/AST.js.map +1 -1
- package/dist/DOM.d.ts +2 -0
- package/dist/DOM.js +16 -3
- package/dist/DOM.js.map +1 -1
- package/dist/EventDispatcher.d.ts +1 -0
- package/dist/EventDispatcher.js +16 -0
- package/dist/EventDispatcher.js.map +1 -1
- package/dist/Registry.js +1 -1
- package/dist/Registry.js.map +1 -1
- package/dist/Tag/List.d.ts +1 -1
- package/dist/Tag/List.js +4 -14
- package/dist/Tag/List.js.map +1 -1
- package/dist/Tag.d.ts +0 -1
- package/dist/Tag.js +0 -1
- package/dist/Tag.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vsn.js +2 -1
- package/dist/vsn.js.map +1 -1
- package/dist/vsn.min.js +2 -2
- package/package.json +3 -2
- package/src/AST/ArithmeticAssignmentNode.ts +1 -1
- package/src/AST/ClassNode.ts +138 -33
- package/src/AST/ElementAttributeNode.ts +9 -4
- package/src/AST/FunctionCallNode.ts +11 -2
- package/src/AST/Node.ts +5 -1
- package/src/AST/ScopeMemberNode.ts +0 -1
- package/src/AST.ts +3 -1
- package/src/DOM.ts +15 -3
- package/src/EventDispatcher.ts +8 -0
- package/src/Registry.ts +1 -1
- package/src/Tag/List.ts +5 -13
- package/src/Tag.ts +0 -1
- package/src/version.ts +1 -0
- package/src/vsn.ts +2 -1
- package/test/AST/ClassNode.spec.ts +66 -11
- package/test/attributes/ListItem.spec.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vsn",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"description": "SEO Friendly Javascript/Typescript Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
],
|
|
14
14
|
"main": "./dist/vsn.js",
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "rm -rf ./dist/ && tsc",
|
|
16
|
+
"build": "rm -rf ./dist/ && npm run-script version && tsc",
|
|
17
|
+
"version": "echo -n \"export const VERSION = '${npm_package_version}';\n\" > src/version.ts",
|
|
17
18
|
"build_dev": "rm -rf ./dist/ && webpack --env BUILD=development BENCHMARK=1",
|
|
18
19
|
"demo": "webpack --env BUILD=production BENCHMARK=1 && cp ./dist/vsn.min.js ./demo/vsn.js",
|
|
19
20
|
"test": "karma start --single-run",
|
|
@@ -48,7 +48,7 @@ export class ArithmeticAssignmentNode extends Node implements TreeNode {
|
|
|
48
48
|
}
|
|
49
49
|
} else if ((this.left instanceof ElementAttributeNode || this.left instanceof ElementStyleNode) && this.left.elementRef) {
|
|
50
50
|
const elements = await this.left.elementRef.evaluate(scope, dom, tag);
|
|
51
|
-
if (this.left.elementRef.first) {
|
|
51
|
+
if (this.left.elementRef.first || elements instanceof DOMObject) {
|
|
52
52
|
scopes.push(elements);
|
|
53
53
|
} else {
|
|
54
54
|
scopes = elements;
|
package/src/AST/ClassNode.ts
CHANGED
|
@@ -2,45 +2,89 @@ import {Scope} from "../Scope";
|
|
|
2
2
|
import {DOM} from "../DOM";
|
|
3
3
|
import {Tag} from "../Tag";
|
|
4
4
|
import {Token, TokenType, Tree, TreeNode} from "../AST";
|
|
5
|
-
import {Node} from "./Node";
|
|
5
|
+
import {INodeMeta, Node} from "./Node";
|
|
6
6
|
import {BlockNode} from "./BlockNode";
|
|
7
7
|
import {Registry} from "../Registry";
|
|
8
8
|
import {OnNode} from "./OnNode";
|
|
9
9
|
import {FunctionNode} from "./FunctionNode";
|
|
10
10
|
|
|
11
11
|
export class ClassNode extends Node implements TreeNode {
|
|
12
|
+
public static readonly ClassesVariable = '_vsn_classes';
|
|
12
13
|
public static readonly classes: {[name: string]: ClassNode} = {};
|
|
14
|
+
public static readonly classParents: {[name: string]: string[]} = {};
|
|
15
|
+
public static readonly classChildren: {[name: string]: string[]} = {}; // List of child class selectors for a given class selector
|
|
16
|
+
public static readonly preppedTags: {[name: string]: Tag[]} = {};
|
|
17
|
+
|
|
13
18
|
protected requiresPrep: boolean = true;
|
|
14
19
|
public readonly classScope: Scope = new Scope();
|
|
15
|
-
protected
|
|
20
|
+
protected _fullSelector: string;
|
|
16
21
|
|
|
17
22
|
constructor(
|
|
18
|
-
public readonly
|
|
23
|
+
public readonly selector: string,
|
|
19
24
|
public readonly block: BlockNode
|
|
20
25
|
) {
|
|
21
26
|
super();
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
public get fullSelector(): string {
|
|
30
|
+
return this._fullSelector;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
public updateMeta(meta?: any) {
|
|
25
34
|
meta = meta || {};
|
|
26
35
|
meta['ClassNode'] = this;
|
|
27
36
|
return meta;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
public async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta?:
|
|
31
|
-
meta = meta || {};
|
|
32
|
-
meta['
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
public async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta?: INodeMeta): Promise<void> {
|
|
40
|
+
meta = Object.assign({}, meta) || {};
|
|
41
|
+
const initial = !!meta['initial'];
|
|
42
|
+
let root: boolean = false;
|
|
43
|
+
meta['ClassNodePrepare'] = initial;
|
|
44
|
+
|
|
45
|
+
// Only prepare once during the initial prep, all subsequent prepares are on tag class blocks
|
|
46
|
+
if (initial) {
|
|
47
|
+
if (meta['ClassNodeSelector']) {
|
|
48
|
+
ClassNode.classChildren[meta['ClassNodeSelector'] as string].push(this.selector);
|
|
49
|
+
meta['ClassNodeSelector'] = `${meta['ClassNodeSelector']} ${this.selector}`;
|
|
50
|
+
} else {
|
|
51
|
+
root = true;
|
|
52
|
+
meta['ClassNodeSelector'] = this.selector;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this._fullSelector = meta['ClassNodeSelector'];
|
|
56
|
+
if (ClassNode.classes[this._fullSelector]) return; // Don't re-prepare same classes
|
|
57
|
+
ClassNode.classes[this._fullSelector] = this;
|
|
58
|
+
ClassNode.classChildren[this._fullSelector] = [];
|
|
59
|
+
ClassNode.preppedTags[this._fullSelector] = [];
|
|
60
|
+
|
|
61
|
+
if (ClassNode.classParents[this.selector] === undefined)
|
|
62
|
+
ClassNode.classParents[this.selector] = [];
|
|
63
|
+
|
|
64
|
+
ClassNode.classParents[this.selector].push(this._fullSelector);
|
|
65
|
+
await this.block.prepare(this.classScope, dom, tag, meta);
|
|
66
|
+
Registry.class(this);
|
|
67
|
+
|
|
68
|
+
if (root) {
|
|
69
|
+
await this.findClassElements(dom);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
await this.block.prepare(this.classScope, dom, tag, meta);
|
|
40
73
|
}
|
|
41
74
|
}
|
|
42
75
|
|
|
43
|
-
public async
|
|
76
|
+
public async findClassElements(dom) {
|
|
77
|
+
for (const element of Array.from(dom.querySelectorAll(this._fullSelector))) {
|
|
78
|
+
await ClassNode.addElementClass(this._fullSelector, element as HTMLElement, dom, element[Tag.TaggedVariable] || null);
|
|
79
|
+
}
|
|
80
|
+
for (const childSelector of ClassNode.classChildren[this._fullSelector]) {
|
|
81
|
+
const node = ClassNode.classes[`${this._fullSelector} ${childSelector}`];
|
|
82
|
+
if (!node) continue;
|
|
83
|
+
await node.findClassElements(dom);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public async constructTag(tag: Tag, dom: DOM, hasConstructor: boolean | null = null) {
|
|
44
88
|
if (hasConstructor === null)
|
|
45
89
|
hasConstructor = this.classScope.has('construct');
|
|
46
90
|
|
|
@@ -52,10 +96,12 @@ export class ClassNode extends Node implements TreeNode {
|
|
|
52
96
|
const fnc = await fncCls.getFunction(tag.scope, dom, tag, false);
|
|
53
97
|
await fnc();
|
|
54
98
|
}
|
|
55
|
-
tag.
|
|
99
|
+
tag.dispatch(`${this.fullSelector}.construct`, tag.element.id);
|
|
100
|
+
ClassNode.preppedTags[this.fullSelector].push(tag);
|
|
101
|
+
ClassNode.addPreparedClassToElement(tag.element, this.fullSelector);
|
|
56
102
|
}
|
|
57
103
|
|
|
58
|
-
public async
|
|
104
|
+
public async deconstructTag(tag: Tag, dom: DOM, hasDeconstructor: boolean | null = null) {
|
|
59
105
|
if (hasDeconstructor === null)
|
|
60
106
|
hasDeconstructor = this.classScope.has('deconstruct');
|
|
61
107
|
|
|
@@ -70,7 +116,9 @@ export class ClassNode extends Node implements TreeNode {
|
|
|
70
116
|
tag.removeContextEventHandlers(on);
|
|
71
117
|
}
|
|
72
118
|
}
|
|
73
|
-
tag.
|
|
119
|
+
tag.dispatch(`${this.fullSelector}.deconstruct`);
|
|
120
|
+
ClassNode.preppedTags[this.fullSelector].splice(ClassNode.preppedTags[this.fullSelector].indexOf(tag), 1);
|
|
121
|
+
await ClassNode.removePreparedClassFromElement(tag.element, this.fullSelector);
|
|
74
122
|
}
|
|
75
123
|
|
|
76
124
|
public async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
|
|
@@ -84,35 +132,92 @@ export class ClassNode extends Node implements TreeNode {
|
|
|
84
132
|
if (t.type === TokenType.L_BRACE) break;
|
|
85
133
|
nameParts.push(t.value);
|
|
86
134
|
}
|
|
87
|
-
const
|
|
135
|
+
const selector = nameParts.join('').trim();
|
|
88
136
|
tokens.splice(0, nameParts.length);
|
|
89
137
|
const block = Tree.processTokens(Tree.getNextStatementTokens(tokens, true, true));
|
|
90
|
-
return new ClassNode(
|
|
138
|
+
return new ClassNode(selector, block);
|
|
91
139
|
}
|
|
92
140
|
|
|
93
141
|
public static async checkForClassChanges(element: HTMLElement, dom: DOM, tag: Tag = null) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
142
|
+
const localSelectors: string[] = [element.tagName.toLowerCase(), ...Array.from(element.classList).map(c => `.${c}`)];
|
|
143
|
+
const fullSelectors: string[] = [...ClassNode.getClassesForElement(element)];
|
|
144
|
+
if (element.id)
|
|
145
|
+
localSelectors.push(`#${element.id}`);
|
|
146
|
+
|
|
147
|
+
for (const selector in localSelectors) {
|
|
148
|
+
if (ClassNode.classParents[selector])
|
|
149
|
+
fullSelectors.push(...ClassNode.classParents[selector]);
|
|
150
|
+
}
|
|
97
151
|
|
|
98
152
|
if (!tag) {
|
|
99
153
|
tag = await dom.getTagForElement(element, true);
|
|
100
154
|
}
|
|
101
|
-
addedClasses = addedClasses.filter(c => !tag.preppedClasses.includes(c));
|
|
102
|
-
removedClasses = tag.preppedClasses.filter(c => !classes.includes(c));
|
|
103
155
|
|
|
104
|
-
for (const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
156
|
+
for (const selector of fullSelectors) {
|
|
157
|
+
const isPrepped = ClassNode.getClassesForElement(element).includes(selector);
|
|
158
|
+
const elements = Array.from(dom.querySelectorAll(selector));
|
|
159
|
+
const inElements = elements.includes(element);
|
|
160
|
+
let changed: boolean = false;
|
|
161
|
+
|
|
162
|
+
if (inElements && !isPrepped) {
|
|
163
|
+
await ClassNode.addElementClass(selector, element, dom, tag);
|
|
164
|
+
changed = true;
|
|
165
|
+
} else if (!inElements && isPrepped) {
|
|
166
|
+
await ClassNode.removeElementClass(selector, element, dom, tag);
|
|
167
|
+
changed = true;
|
|
108
168
|
}
|
|
109
|
-
}
|
|
110
169
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
170
|
+
if (changed && ClassNode.classChildren[selector].length > 0) {
|
|
171
|
+
for (const childSelector of ClassNode.classChildren[selector]) {
|
|
172
|
+
for (const childElement of Array.from(dom.querySelectorAll(childSelector, tag)) as HTMLElement[]) {
|
|
173
|
+
await ClassNode.checkForClassChanges(childElement, dom, childElement[Tag.TaggedVariable] || null);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
115
176
|
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public static getClassesForElement(element: HTMLElement): string[] {
|
|
182
|
+
if (!element[ClassNode.ClassesVariable])
|
|
183
|
+
element[ClassNode.ClassesVariable] = [];
|
|
184
|
+
return element[ClassNode.ClassesVariable];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public static addPreparedClassToElement(element: HTMLElement, selector: string) {
|
|
188
|
+
ClassNode.getClassesForElement(element).push(selector);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public static removePreparedClassFromElement(element: HTMLElement, selector: string) {
|
|
192
|
+
const classes = ClassNode.getClassesForElement(element);
|
|
193
|
+
classes.splice(classes.indexOf(selector), 1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public static async addElementClass(selector: string, element: HTMLElement, dom: DOM, tag: Tag = null) {
|
|
197
|
+
const classes = ClassNode.getClassesForElement(element);
|
|
198
|
+
if (classes.includes(selector)) return;
|
|
199
|
+
|
|
200
|
+
if (!tag) {
|
|
201
|
+
tag = await dom.getTagForElement(element, true);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const classNode: ClassNode = Registry.instance.classes.getSynchronous(selector);
|
|
205
|
+
if (classNode) {
|
|
206
|
+
await classNode.constructTag(tag, dom);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public static async removeElementClass(selector: string, element: HTMLElement, dom: DOM, tag: Tag = null) {
|
|
211
|
+
const classes = ClassNode.getClassesForElement(element);
|
|
212
|
+
if (!classes.includes(selector)) return;
|
|
213
|
+
|
|
214
|
+
if (!tag) {
|
|
215
|
+
tag = await dom.getTagForElement(element, true);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const classNode: ClassNode = Registry.instance.classes.getSynchronous(selector);
|
|
219
|
+
if (classNode) {
|
|
220
|
+
await classNode.deconstructTag(tag, dom);
|
|
116
221
|
}
|
|
117
222
|
}
|
|
118
223
|
|
|
@@ -6,6 +6,7 @@ import {TreeNode} from "../AST";
|
|
|
6
6
|
import {Node} from "./Node";
|
|
7
7
|
import {ElementQueryNode} from "./ElementQueryNode";
|
|
8
8
|
import {LiteralNode} from "./LiteralNode";
|
|
9
|
+
import {DOMObject} from "../DOM/DOMObject";
|
|
9
10
|
|
|
10
11
|
export class ElementAttributeNode extends Node implements TreeNode {
|
|
11
12
|
protected requiresPrep: boolean = true;
|
|
@@ -53,10 +54,14 @@ export class ElementAttributeNode extends Node implements TreeNode {
|
|
|
53
54
|
async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta: any = null) {
|
|
54
55
|
if (this.elementRef) {
|
|
55
56
|
await this.elementRef.prepare(scope, dom, tag, meta);
|
|
56
|
-
const tags:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const tags: any = await this.elementRef.evaluate(scope, dom, tag, true);
|
|
58
|
+
if (tags instanceof TagList) {
|
|
59
|
+
for (const t of tags)
|
|
60
|
+
await t.watchAttribute(this.attributeName);
|
|
61
|
+
} else if (tags instanceof DOMObject) {
|
|
62
|
+
await (tags as DOMObject).watchAttribute(this.attributeName);
|
|
63
|
+
}
|
|
64
|
+
} else if (tag) {
|
|
60
65
|
await tag.watchAttribute(this.attributeName);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
@@ -8,6 +8,8 @@ import {ScopeMemberNode} from "./ScopeMemberNode";
|
|
|
8
8
|
import {FunctionNode} from "./FunctionNode";
|
|
9
9
|
import {Registry} from "../Registry";
|
|
10
10
|
import {ElementQueryNode} from "./ElementQueryNode";
|
|
11
|
+
import {ClassNode} from "./ClassNode";
|
|
12
|
+
import {TagList} from "../Tag/List";
|
|
11
13
|
|
|
12
14
|
export class FunctionCallNode<T = any> extends Node implements TreeNode {
|
|
13
15
|
constructor(
|
|
@@ -31,7 +33,14 @@ export class FunctionCallNode<T = any> extends Node implements TreeNode {
|
|
|
31
33
|
if (this.fnc instanceof ScopeMemberNode) {
|
|
32
34
|
functionScope = await this.fnc.scope.evaluate(scope, dom, tag);
|
|
33
35
|
if (this.fnc.scope instanceof ElementQueryNode) {
|
|
34
|
-
|
|
36
|
+
const _tags = await this.fnc.scope.evaluate(scope, dom, tag);
|
|
37
|
+
if (_tags instanceof Array) {
|
|
38
|
+
tags = _tags;
|
|
39
|
+
} else if (_tags instanceof Tag) {
|
|
40
|
+
tags = [_tags];
|
|
41
|
+
} else {
|
|
42
|
+
throw new Error('Invalid element query result');
|
|
43
|
+
}
|
|
35
44
|
} else {
|
|
36
45
|
tags = [tag];
|
|
37
46
|
}
|
|
@@ -46,7 +55,7 @@ export class FunctionCallNode<T = any> extends Node implements TreeNode {
|
|
|
46
55
|
let calls = 0;
|
|
47
56
|
for (const _tag of tags) {
|
|
48
57
|
let tagNum = 0;
|
|
49
|
-
for (const className of _tag.
|
|
58
|
+
for (const className of _tag.element[ClassNode.ClassesVariable]) {
|
|
50
59
|
tagNum++;
|
|
51
60
|
const cls = Registry.instance.classes.getSynchronous(className);
|
|
52
61
|
if (cls) {
|
package/src/AST/Node.ts
CHANGED
|
@@ -3,6 +3,10 @@ import {DOM} from "../DOM";
|
|
|
3
3
|
import {Tag} from "../Tag";
|
|
4
4
|
import {TreeNode} from "../AST";
|
|
5
5
|
|
|
6
|
+
export interface INodeMeta {
|
|
7
|
+
[key: string]: string | number | boolean | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export abstract class Node implements TreeNode {
|
|
7
11
|
protected requiresPrep: boolean = false;
|
|
8
12
|
protected _isPreparationRequired: boolean;
|
|
@@ -28,7 +32,7 @@ export abstract class Node implements TreeNode {
|
|
|
28
32
|
return false;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta:
|
|
35
|
+
async prepare(scope: Scope, dom: DOM, tag: Tag = null, meta: INodeMeta = null): Promise<void> {
|
|
32
36
|
for (const node of this.getChildNodes()) {
|
|
33
37
|
await node.prepare(scope, dom, tag, meta);
|
|
34
38
|
}
|
|
@@ -26,7 +26,6 @@ export class ScopeMemberNode extends ScopeNodeAbstract implements TreeNode {
|
|
|
26
26
|
async evaluate(scope: Scope, dom: DOM, tag: Tag = null) {
|
|
27
27
|
let scopes = [];
|
|
28
28
|
const values = [];
|
|
29
|
-
|
|
30
29
|
if (this.scope instanceof ElementQueryNode) {
|
|
31
30
|
const elements = await this.scope.evaluate(scope, dom, tag);
|
|
32
31
|
if (this.scope.first) {
|
package/src/AST.ts
CHANGED
|
@@ -420,7 +420,9 @@ export class Tree {
|
|
|
420
420
|
async prepare(scope: Scope, dom: DOM, tag: Tag = null) {
|
|
421
421
|
if (!this._root.isPreparationRequired())
|
|
422
422
|
return;
|
|
423
|
-
return await this._root.prepare(scope, dom, tag
|
|
423
|
+
return await this._root.prepare(scope, dom, tag, {
|
|
424
|
+
initial: true
|
|
425
|
+
});
|
|
424
426
|
}
|
|
425
427
|
|
|
426
428
|
async bindToScopeChanges(scope, fnc, dom: DOM, tag: Tag = null) {
|
package/src/DOM.ts
CHANGED
|
@@ -18,6 +18,7 @@ export enum EQuerySelectDirection {
|
|
|
18
18
|
export class DOM extends EventDispatcher {
|
|
19
19
|
protected static _instance: DOM;
|
|
20
20
|
protected _root: Tag;
|
|
21
|
+
protected _ready: Promise<boolean>;
|
|
21
22
|
protected tags: Tag[];
|
|
22
23
|
protected observer: MutationObserver;
|
|
23
24
|
protected evaluateTimeout: any;
|
|
@@ -32,6 +33,12 @@ export class DOM extends EventDispatcher {
|
|
|
32
33
|
protected debug: boolean = false
|
|
33
34
|
) {
|
|
34
35
|
super();
|
|
36
|
+
this._ready = new Promise((resolve) => {
|
|
37
|
+
this.once('built', () => {
|
|
38
|
+
resolve(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
35
42
|
this.observer = new MutationObserver(this.mutation.bind(this));
|
|
36
43
|
this.tags = [];
|
|
37
44
|
|
|
@@ -49,6 +56,10 @@ export class DOM extends EventDispatcher {
|
|
|
49
56
|
return this._root;
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
public get ready(): Promise<boolean> {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
public async get(selector: string, create: boolean = false, tag: Tag = null, direction: EQuerySelectDirection = EQuerySelectDirection.DOWN): Promise<TagList> {
|
|
53
64
|
switch (selector) {
|
|
54
65
|
case 'window':
|
|
@@ -232,7 +243,7 @@ export class DOM extends EventDispatcher {
|
|
|
232
243
|
const found: Element[] = [];
|
|
233
244
|
for (const tag of this.tags)
|
|
234
245
|
{
|
|
235
|
-
if (elements.indexOf(tag.element) > -1) {
|
|
246
|
+
if (!found.includes(tag.element) && elements.indexOf(tag.element) > -1) {
|
|
236
247
|
tags.push(tag);
|
|
237
248
|
found.push(tag.element);
|
|
238
249
|
}
|
|
@@ -242,8 +253,9 @@ export class DOM extends EventDispatcher {
|
|
|
242
253
|
const notFound: Element[] = [...elements];
|
|
243
254
|
for (let i = notFound.length; i >= 0; i--) {
|
|
244
255
|
const element: Element = notFound[i];
|
|
245
|
-
if (found.indexOf(element) > -1)
|
|
246
|
-
notFound.
|
|
256
|
+
if (found.indexOf(element) > -1) {
|
|
257
|
+
notFound.splice(i, 1);
|
|
258
|
+
}
|
|
247
259
|
}
|
|
248
260
|
|
|
249
261
|
for (const element of notFound) {
|
package/src/EventDispatcher.ts
CHANGED
|
@@ -70,6 +70,14 @@ export class EventDispatcher {
|
|
|
70
70
|
return this.on(event, fct, context, true);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
promise(event: string, ...args: any[]): Promise<any> {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
this.once(event, (...args) => {
|
|
76
|
+
resolve(args);
|
|
77
|
+
}, null);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
off(event: string, key?: number): boolean {
|
|
74
82
|
if(!(event in this._listeners)) return false;
|
|
75
83
|
if(key) {
|
package/src/Registry.ts
CHANGED
|
@@ -90,7 +90,7 @@ export class Registry extends EventDispatcher {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
public static class(cls: ClassNode) {
|
|
93
|
-
Registry.instance.classes.register(cls.
|
|
93
|
+
Registry.instance.classes.register(cls.fullSelector, cls);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
public static controller(key: string = null, setup = null) {
|
package/src/Tag/List.ts
CHANGED
|
@@ -12,19 +12,6 @@ export class TagList extends Array<DOMObject> {
|
|
|
12
12
|
return this[0].scope
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
on(event, cbOrSelector, cb) {
|
|
16
|
-
if (typeof cbOrSelector === "function") {
|
|
17
|
-
this.forEach(e => e.element.addEventListener(event, cbOrSelector))
|
|
18
|
-
} else {
|
|
19
|
-
this.forEach(elem => {
|
|
20
|
-
elem.element.addEventListener(event, e => {
|
|
21
|
-
if (e.target.matches(cbOrSelector)) cb(e)
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
return this
|
|
26
|
-
}
|
|
27
|
-
|
|
28
15
|
get elements(): HTMLElement[] {
|
|
29
16
|
return this.map(e => e.element);
|
|
30
17
|
}
|
|
@@ -37,6 +24,11 @@ export class TagList extends Array<DOMObject> {
|
|
|
37
24
|
return this[this.length - 1];
|
|
38
25
|
}
|
|
39
26
|
|
|
27
|
+
all(event: string): Promise<number[]> {
|
|
28
|
+
const promises = this.map(e => e.promise(event));
|
|
29
|
+
return Promise.all(promises);
|
|
30
|
+
}
|
|
31
|
+
|
|
40
32
|
removeClass(className) {
|
|
41
33
|
this.forEach(e => e.element.classList.remove(className))
|
|
42
34
|
return this
|
package/src/Tag.ts
CHANGED
|
@@ -26,7 +26,6 @@ export class Tag extends DOMObject {
|
|
|
26
26
|
public readonly rawAttributes: { [key: string]: string; };
|
|
27
27
|
public readonly parsedAttributes: { [key: string]: string[]; };
|
|
28
28
|
public readonly deferredAttributes: Attribute[] = [];
|
|
29
|
-
public readonly preppedClasses: string[] = [];
|
|
30
29
|
protected _state: TagState;
|
|
31
30
|
protected attributes: Attribute[];
|
|
32
31
|
protected _nonDeferredAttributes: Attribute[] = [];
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.1.74';
|
package/src/vsn.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {Query} from "./Query";
|
|
|
8
8
|
import {EventDispatcher} from "./EventDispatcher";
|
|
9
9
|
import {DynamicScopeData} from "./Scope/DynamicScopeData";
|
|
10
10
|
import {Controller} from "./Controller";
|
|
11
|
+
import {VERSION} from "./version";
|
|
11
12
|
|
|
12
13
|
export class Vision extends EventDispatcher {
|
|
13
14
|
protected static _instance: Vision;
|
|
@@ -59,7 +60,7 @@ export class Vision extends EventDispatcher {
|
|
|
59
60
|
await this._dom.buildFrom(document, true);
|
|
60
61
|
const now = (new Date()).getTime();
|
|
61
62
|
const setupTime = now - startTime;
|
|
62
|
-
console.info(`Took ${setupTime}ms to start up VisionJS. https://www.vsnjs.com/`,
|
|
63
|
+
console.info(`Took ${setupTime}ms to start up VisionJS. https://www.vsnjs.com/`, `v${VERSION}`);
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
public static get instance() {
|
|
@@ -1,29 +1,84 @@
|
|
|
1
1
|
import {DOM} from "../../src/DOM";
|
|
2
|
+
import {ClassNode} from "../../src/AST/ClassNode";
|
|
3
|
+
import {Registry} from "../../src/Registry";
|
|
4
|
+
import {TagList} from "../../src/Tag/List";
|
|
5
|
+
import {Tag} from "../../src/Tag";
|
|
2
6
|
|
|
3
7
|
|
|
4
8
|
describe('ClassNode', () => {
|
|
5
|
-
it("properly
|
|
9
|
+
it("properly combine nested classes", async () => {
|
|
6
10
|
document.body.innerHTML = `
|
|
7
11
|
<script type="text/vsn" vsn-script>
|
|
8
|
-
class simple {
|
|
12
|
+
class .simple {
|
|
13
|
+
func construct() {}
|
|
14
|
+
class input {
|
|
15
|
+
func construct() {}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
<div class="simple"><input /></div>
|
|
20
|
+
`;
|
|
21
|
+
const dom = new DOM(document);
|
|
22
|
+
await dom.ready;
|
|
23
|
+
await Registry.instance.classes.get('.simple input');
|
|
24
|
+
expect(ClassNode.classParents['input']).toBeInstanceOf(Array);
|
|
25
|
+
expect(ClassNode.classParents['input'].includes('.simple input')).toBe(true);
|
|
26
|
+
expect(ClassNode.classes['.simple input']).toBeInstanceOf(ClassNode);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("properly define a simple class", async () => {
|
|
30
|
+
document.body.innerHTML = `
|
|
31
|
+
<script type="text/vsn" vsn-script>
|
|
32
|
+
class .simple-construct {
|
|
9
33
|
func construct() {
|
|
10
|
-
a|integer = "15";
|
|
34
|
+
a|integer = "15";
|
|
35
|
+
log('####### construct', a);
|
|
11
36
|
}
|
|
12
37
|
|
|
13
38
|
func test() {
|
|
14
39
|
a += 1;
|
|
40
|
+
log('####### testing', a);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
<div class="simple-construct" id="ele-1"></div>
|
|
45
|
+
<div class="simple-construct" id="ele-2"></div>
|
|
46
|
+
`;
|
|
47
|
+
const dom = new DOM(document);
|
|
48
|
+
await dom.ready;
|
|
49
|
+
await Registry.instance.classes.get('.simple-construct');
|
|
50
|
+
const t: TagList = await dom.exec('?(.simple-construct)');
|
|
51
|
+
expect(t).toBeInstanceOf(TagList);
|
|
52
|
+
expect(t.length).toBe(2);
|
|
53
|
+
await t.all('.simple-construct.construct');
|
|
54
|
+
console.log('####### hmm?', await dom.exec('?(.simple-construct).a'));
|
|
55
|
+
expect(await dom.exec('?(.simple-construct).a')).toEqual([15, 15]);
|
|
56
|
+
await dom.exec('?(.simple-construct).test()');
|
|
57
|
+
expect(await dom.exec('?(.simple-construct).a')).toEqual([16, 16]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("properly define a simple class with a parent", async () => {
|
|
61
|
+
document.body.innerHTML = `
|
|
62
|
+
<script type="text/vsn" vsn-script>
|
|
63
|
+
class .testing {
|
|
64
|
+
func construct() {
|
|
65
|
+
a|integer = 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class .test {
|
|
69
|
+
func construct() {
|
|
70
|
+
?<(.testing).a += 1;
|
|
71
|
+
}
|
|
15
72
|
}
|
|
16
73
|
}
|
|
17
74
|
</script>
|
|
18
|
-
<div class="
|
|
19
|
-
<div class="simple"></div>
|
|
75
|
+
<div class="testing"><span class="test"></span></div>
|
|
20
76
|
`;
|
|
21
77
|
const dom = new DOM(document);
|
|
22
|
-
dom.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
78
|
+
await dom.ready;
|
|
79
|
+
await Registry.instance.classes.get('.testing .test');
|
|
80
|
+
const t = await dom.exec('?(.testing .test)[0]');
|
|
81
|
+
await t.promise('.testing .test.construct');
|
|
82
|
+
expect(await dom.exec('?(.testing)[0].a')).toEqual(1);
|
|
28
83
|
});
|
|
29
84
|
});
|