vasille 2.2.2 → 2.3.2
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/README.md +34 -28
- package/cdn/es2015.js +18 -724
- package/cdn/es5.js +687 -957
- package/flow-typed/vasille.js +14 -59
- package/lib/core/core.js +3 -3
- package/lib/index.js +5 -4
- package/lib/models/array-model.js +6 -9
- package/lib/models/object-model.js +3 -17
- package/lib/node/node.js +6 -3
- package/lib/views/object-view.js +1 -1
- package/lib-node/binding/attribute.js +35 -0
- package/lib-node/binding/binding.js +33 -0
- package/lib-node/binding/class.js +48 -0
- package/lib-node/binding/style.js +27 -0
- package/lib-node/core/core.js +243 -0
- package/lib-node/core/destroyable.js +49 -0
- package/lib-node/core/errors.js +23 -0
- package/lib-node/core/ivalue.js +63 -0
- package/lib-node/functional/options.js +2 -0
- package/lib-node/index.js +54 -0
- package/lib-node/models/array-model.js +218 -0
- package/lib-node/models/listener.js +134 -0
- package/lib-node/models/map-model.js +70 -0
- package/lib-node/models/model.js +2 -0
- package/lib-node/models/object-model.js +82 -0
- package/lib-node/models/set-model.js +66 -0
- package/lib-node/node/app.js +54 -0
- package/lib-node/node/node.js +885 -0
- package/lib-node/node/watch.js +23 -0
- package/lib-node/spec/html.js +2 -0
- package/lib-node/spec/react.js +2 -0
- package/lib-node/spec/svg.js +2 -0
- package/lib-node/value/expression.js +90 -0
- package/lib-node/value/mirror.js +60 -0
- package/lib-node/value/pointer.js +30 -0
- package/lib-node/value/reference.js +55 -0
- package/lib-node/views/array-view.js +21 -0
- package/lib-node/views/base-view.js +43 -0
- package/lib-node/views/map-view.js +18 -0
- package/lib-node/views/object-view.js +20 -0
- package/lib-node/views/repeat-node.js +71 -0
- package/lib-node/views/set-view.js +19 -0
- package/package.json +21 -17
- package/types/core/core.d.ts +4 -4
- package/types/functional/options.d.ts +2 -2
- package/types/index.d.ts +10 -7
- package/types/models/array-model.d.ts +1 -1
- package/types/models/object-model.d.ts +1 -1
- package/types/node/node.d.ts +5 -4
- package/types/node/watch.d.ts +2 -2
- package/types/views/repeat-node.d.ts +2 -2
- package/lib/core/executor.js +0 -154
- package/lib/core/signal.js +0 -50
- package/lib/core/slot.js +0 -47
- package/lib/functional/components.js +0 -17
- package/lib/functional/merge.js +0 -41
- package/lib/functional/models.js +0 -26
- package/lib/functional/reactivity.js +0 -33
- package/lib/functional/stack.js +0 -127
- package/lib/node/interceptor.js +0 -83
- package/lib/v/index.js +0 -23
- package/lib/views/repeater.js +0 -63
- package/types/functional/components.d.ts +0 -4
- package/types/functional/merge.d.ts +0 -1
- package/types/functional/models.d.ts +0 -10
- package/types/functional/reactivity.d.ts +0 -11
- package/types/functional/stack.d.ts +0 -24
- package/types/v/index.d.ts +0 -36
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DebugNode = exports.DebugPrivate = exports.SwitchedNode = exports.SwitchedNodePrivate = exports.Component = exports.Extension = exports.Tag = exports.INode = exports.INodePrivate = exports.TextNode = exports.TextNodePrivate = exports.Fragment = exports.FragmentPrivate = void 0;
|
|
4
|
+
const core_1 = require("../core/core");
|
|
5
|
+
const ivalue_1 = require("../core/ivalue");
|
|
6
|
+
const reference_1 = require("../value/reference");
|
|
7
|
+
const expression_1 = require("../value/expression");
|
|
8
|
+
const attribute_1 = require("../binding/attribute");
|
|
9
|
+
const class_1 = require("../binding/class");
|
|
10
|
+
const style_1 = require("../binding/style");
|
|
11
|
+
const errors_1 = require("../core/errors");
|
|
12
|
+
/**
|
|
13
|
+
* Represents a Vasille.js node
|
|
14
|
+
* @class FragmentPrivate
|
|
15
|
+
* @extends ReactivePrivate
|
|
16
|
+
*/
|
|
17
|
+
class FragmentPrivate extends core_1.ReactivePrivate {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.$seal();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pre-initializes the base of a fragment
|
|
24
|
+
* @param app {App} the app node
|
|
25
|
+
* @param parent {Fragment} the parent node
|
|
26
|
+
*/
|
|
27
|
+
preinit(app, parent) {
|
|
28
|
+
this.app = app;
|
|
29
|
+
this.parent = parent;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Unlinks all bindings
|
|
33
|
+
*/
|
|
34
|
+
$destroy() {
|
|
35
|
+
this.next = null;
|
|
36
|
+
this.prev = null;
|
|
37
|
+
super.$destroy();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.FragmentPrivate = FragmentPrivate;
|
|
41
|
+
/**
|
|
42
|
+
* This class is symbolic
|
|
43
|
+
* @extends Reactive
|
|
44
|
+
*/
|
|
45
|
+
class Fragment extends core_1.Reactive {
|
|
46
|
+
/**
|
|
47
|
+
* Constructs a Vasille Node
|
|
48
|
+
* @param input
|
|
49
|
+
* @param $ {FragmentPrivate}
|
|
50
|
+
*/
|
|
51
|
+
constructor(input, $) {
|
|
52
|
+
super(input, $ || new FragmentPrivate);
|
|
53
|
+
/**
|
|
54
|
+
* The children list
|
|
55
|
+
* @type Array
|
|
56
|
+
*/
|
|
57
|
+
this.children = new Set;
|
|
58
|
+
this.lastChild = null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Gets the app of node
|
|
62
|
+
*/
|
|
63
|
+
get app() {
|
|
64
|
+
return this.$.app;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Prepare to init fragment
|
|
68
|
+
* @param app {AppNode} app of node
|
|
69
|
+
* @param parent {Fragment} parent of node
|
|
70
|
+
* @param data {*} additional data
|
|
71
|
+
*/
|
|
72
|
+
preinit(app, parent, data) {
|
|
73
|
+
const $ = this.$;
|
|
74
|
+
$.preinit(app, parent);
|
|
75
|
+
}
|
|
76
|
+
init() {
|
|
77
|
+
const ret = super.init();
|
|
78
|
+
this.ready();
|
|
79
|
+
return ret;
|
|
80
|
+
}
|
|
81
|
+
compose(input) {
|
|
82
|
+
input.slot && input.slot(this);
|
|
83
|
+
}
|
|
84
|
+
/** To be overloaded: ready event handler */
|
|
85
|
+
ready() {
|
|
86
|
+
// empty
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Pushes a node to children immediately
|
|
90
|
+
* @param node {Fragment} A node to push
|
|
91
|
+
* @protected
|
|
92
|
+
*/
|
|
93
|
+
pushNode(node) {
|
|
94
|
+
if (this.lastChild) {
|
|
95
|
+
this.lastChild.$.next = node;
|
|
96
|
+
}
|
|
97
|
+
node.$.prev = this.lastChild;
|
|
98
|
+
this.lastChild = node;
|
|
99
|
+
this.children.add(node);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Find first node in element if so exists
|
|
103
|
+
* @return {?Element}
|
|
104
|
+
* @protected
|
|
105
|
+
*/
|
|
106
|
+
findFirstChild() {
|
|
107
|
+
let first;
|
|
108
|
+
this.children.forEach(child => {
|
|
109
|
+
first = first || child.findFirstChild();
|
|
110
|
+
});
|
|
111
|
+
return first;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Append a node to end of element
|
|
115
|
+
* @param node {Node} node to insert
|
|
116
|
+
*/
|
|
117
|
+
appendNode(node) {
|
|
118
|
+
const $ = this.$;
|
|
119
|
+
if ($.next) {
|
|
120
|
+
$.next.insertAdjacent(node);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
$.parent.appendNode(node);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Insert a node as a sibling of this
|
|
128
|
+
* @param node {Node} node to insert
|
|
129
|
+
*/
|
|
130
|
+
insertAdjacent(node) {
|
|
131
|
+
const child = this.findFirstChild();
|
|
132
|
+
const $ = this.$;
|
|
133
|
+
if (child) {
|
|
134
|
+
child.parentElement.insertBefore(node, child);
|
|
135
|
+
}
|
|
136
|
+
else if ($.next) {
|
|
137
|
+
$.next.insertAdjacent(node);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
$.parent.appendNode(node);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Defines a text fragment
|
|
145
|
+
* @param text {String | IValue} A text fragment string
|
|
146
|
+
* @param cb {function (TextNode)} Callback if previous is slot name
|
|
147
|
+
*/
|
|
148
|
+
text(text, cb) {
|
|
149
|
+
const $ = this.$;
|
|
150
|
+
const node = new TextNode();
|
|
151
|
+
node.preinit($.app, this, text);
|
|
152
|
+
this.pushNode(node);
|
|
153
|
+
cb && cb(node);
|
|
154
|
+
}
|
|
155
|
+
debug(text) {
|
|
156
|
+
if (this.$.app.debugUi) {
|
|
157
|
+
const node = new DebugNode();
|
|
158
|
+
node.preinit(this.$.app, this, text);
|
|
159
|
+
this.pushNode(node);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Defines a tag element
|
|
164
|
+
* @param tagName {String} the tag name
|
|
165
|
+
* @param input
|
|
166
|
+
* @param cb {function(Tag, *)} callback
|
|
167
|
+
*/
|
|
168
|
+
tag(tagName, input, cb) {
|
|
169
|
+
const $ = this.$;
|
|
170
|
+
const node = new Tag(input);
|
|
171
|
+
input.slot = cb || input.slot;
|
|
172
|
+
node.preinit($.app, this, tagName);
|
|
173
|
+
node.init();
|
|
174
|
+
this.pushNode(node);
|
|
175
|
+
node.ready();
|
|
176
|
+
return node.node;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Defines a custom element
|
|
180
|
+
* @param node {Fragment} vasille element to insert
|
|
181
|
+
* @param callback {function($ : *)}
|
|
182
|
+
*/
|
|
183
|
+
create(node, callback) {
|
|
184
|
+
const $ = this.$;
|
|
185
|
+
node.$.parent = this;
|
|
186
|
+
node.preinit($.app, this);
|
|
187
|
+
node.input.slot = callback || node.input.slot;
|
|
188
|
+
this.pushNode(node);
|
|
189
|
+
return node.init();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Defines an if node
|
|
193
|
+
* @param cond {IValue} condition
|
|
194
|
+
* @param cb {function(Fragment)} callback to run on true
|
|
195
|
+
* @return {this}
|
|
196
|
+
*/
|
|
197
|
+
if(cond, cb) {
|
|
198
|
+
const node = new SwitchedNode();
|
|
199
|
+
node.preinit(this.$.app, this);
|
|
200
|
+
node.init();
|
|
201
|
+
this.pushNode(node);
|
|
202
|
+
node.addCase(this.case(cond, cb));
|
|
203
|
+
node.ready();
|
|
204
|
+
}
|
|
205
|
+
else(cb) {
|
|
206
|
+
if (this.lastChild instanceof SwitchedNode) {
|
|
207
|
+
this.lastChild.addCase(this.default(cb));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
throw (0, errors_1.userError)('wrong `else` function use', 'logic-error');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
elif(cond, cb) {
|
|
214
|
+
if (this.lastChild instanceof SwitchedNode) {
|
|
215
|
+
this.lastChild.addCase(this.case(cond, cb));
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
throw (0, errors_1.userError)('wrong `elif` function use', 'logic-error');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Create a case for switch
|
|
223
|
+
* @param cond {IValue<boolean>}
|
|
224
|
+
* @param cb {function(Fragment) : void}
|
|
225
|
+
* @return {{cond : IValue, cb : (function(Fragment) : void)}}
|
|
226
|
+
*/
|
|
227
|
+
case(cond, cb) {
|
|
228
|
+
return { cond, cb };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* @param cb {(function(Fragment) : void)}
|
|
232
|
+
* @return {{cond : IValue, cb : (function(Fragment) : void)}}
|
|
233
|
+
*/
|
|
234
|
+
default(cb) {
|
|
235
|
+
return { cond: trueIValue, cb };
|
|
236
|
+
}
|
|
237
|
+
insertBefore(node) {
|
|
238
|
+
const $ = this.$;
|
|
239
|
+
node.$.prev = $.prev;
|
|
240
|
+
node.$.next = this;
|
|
241
|
+
if ($.prev) {
|
|
242
|
+
$.prev.$.next = node;
|
|
243
|
+
}
|
|
244
|
+
$.prev = node;
|
|
245
|
+
}
|
|
246
|
+
insertAfter(node) {
|
|
247
|
+
const $ = this.$;
|
|
248
|
+
node.$.prev = this;
|
|
249
|
+
node.$.next = $.next;
|
|
250
|
+
$.next = node;
|
|
251
|
+
}
|
|
252
|
+
remove() {
|
|
253
|
+
const $ = this.$;
|
|
254
|
+
if ($.next) {
|
|
255
|
+
$.next.$.prev = $.prev;
|
|
256
|
+
}
|
|
257
|
+
if ($.prev) {
|
|
258
|
+
$.prev.$.next = $.next;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
$destroy() {
|
|
262
|
+
this.children.forEach(child => child.$destroy());
|
|
263
|
+
this.children.clear();
|
|
264
|
+
this.lastChild = null;
|
|
265
|
+
if (this.$.parent.lastChild === this) {
|
|
266
|
+
this.$.parent.lastChild = this.$.prev;
|
|
267
|
+
}
|
|
268
|
+
super.$destroy();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.Fragment = Fragment;
|
|
272
|
+
const trueIValue = new reference_1.Reference(true);
|
|
273
|
+
/**
|
|
274
|
+
* The private part of a text node
|
|
275
|
+
* @class TextNodePrivate
|
|
276
|
+
* @extends FragmentPrivate
|
|
277
|
+
*/
|
|
278
|
+
class TextNodePrivate extends FragmentPrivate {
|
|
279
|
+
constructor() {
|
|
280
|
+
super();
|
|
281
|
+
this.$seal();
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Pre-initializes a text node
|
|
285
|
+
* @param app {AppNode} the app node
|
|
286
|
+
* @param parent
|
|
287
|
+
* @param text {IValue}
|
|
288
|
+
*/
|
|
289
|
+
preinitText(app, parent, text) {
|
|
290
|
+
super.preinit(app, parent);
|
|
291
|
+
this.node = document.createTextNode(text instanceof ivalue_1.IValue ? text.$ : text);
|
|
292
|
+
if (text instanceof ivalue_1.IValue) {
|
|
293
|
+
this.bindings.add(new expression_1.Expression((v) => {
|
|
294
|
+
this.node.replaceData(0, -1, v);
|
|
295
|
+
}, true, text));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Clear node data
|
|
300
|
+
*/
|
|
301
|
+
$destroy() {
|
|
302
|
+
super.$destroy();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
exports.TextNodePrivate = TextNodePrivate;
|
|
306
|
+
/**
|
|
307
|
+
* Represents a text node
|
|
308
|
+
* @class TextNode
|
|
309
|
+
* @extends Fragment
|
|
310
|
+
*/
|
|
311
|
+
class TextNode extends Fragment {
|
|
312
|
+
constructor($ = new TextNodePrivate()) {
|
|
313
|
+
super({}, $);
|
|
314
|
+
this.$seal();
|
|
315
|
+
}
|
|
316
|
+
preinit(app, parent, text) {
|
|
317
|
+
const $ = this.$;
|
|
318
|
+
if (!text) {
|
|
319
|
+
throw (0, errors_1.internalError)('wrong TextNode::$preninit call');
|
|
320
|
+
}
|
|
321
|
+
$.preinitText(app, parent, text);
|
|
322
|
+
$.parent.appendNode($.node);
|
|
323
|
+
}
|
|
324
|
+
findFirstChild() {
|
|
325
|
+
return this.$.node;
|
|
326
|
+
}
|
|
327
|
+
$destroy() {
|
|
328
|
+
this.$.node.remove();
|
|
329
|
+
this.$.$destroy();
|
|
330
|
+
super.$destroy();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
exports.TextNode = TextNode;
|
|
334
|
+
/**
|
|
335
|
+
* The private part of a base node
|
|
336
|
+
* @class INodePrivate
|
|
337
|
+
* @extends FragmentPrivate
|
|
338
|
+
*/
|
|
339
|
+
class INodePrivate extends FragmentPrivate {
|
|
340
|
+
constructor() {
|
|
341
|
+
super();
|
|
342
|
+
/**
|
|
343
|
+
* Defines if node is unmounted
|
|
344
|
+
* @type {boolean}
|
|
345
|
+
*/
|
|
346
|
+
this.unmounted = false;
|
|
347
|
+
this.$seal();
|
|
348
|
+
}
|
|
349
|
+
$destroy() {
|
|
350
|
+
super.$destroy();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
exports.INodePrivate = INodePrivate;
|
|
354
|
+
/**
|
|
355
|
+
* Vasille node which can manipulate an element node
|
|
356
|
+
* @class INode
|
|
357
|
+
* @extends Fragment
|
|
358
|
+
*/
|
|
359
|
+
class INode extends Fragment {
|
|
360
|
+
/**
|
|
361
|
+
* Constructs a base node
|
|
362
|
+
* @param input
|
|
363
|
+
* @param $ {?INodePrivate}
|
|
364
|
+
*/
|
|
365
|
+
constructor(input, $) {
|
|
366
|
+
super(input, $ || new INodePrivate);
|
|
367
|
+
this.$seal();
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get the bound node
|
|
371
|
+
*/
|
|
372
|
+
get node() {
|
|
373
|
+
return this.$.node;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Bind attribute value
|
|
377
|
+
* @param name {String} name of attribute
|
|
378
|
+
* @param value {IValue} value
|
|
379
|
+
*/
|
|
380
|
+
attr(name, value) {
|
|
381
|
+
const $ = this.$;
|
|
382
|
+
const attr = new attribute_1.AttributeBinding(this, name, value);
|
|
383
|
+
$.bindings.add(attr);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Set attribute value
|
|
387
|
+
* @param name {string} name of attribute
|
|
388
|
+
* @param value {string} value
|
|
389
|
+
*/
|
|
390
|
+
setAttr(name, value) {
|
|
391
|
+
if (typeof value === 'boolean') {
|
|
392
|
+
if (value) {
|
|
393
|
+
this.$.node.setAttribute(name, "");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
this.$.node.setAttribute(name, `${value}`);
|
|
398
|
+
}
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Adds a CSS class
|
|
403
|
+
* @param cl {string} Class name
|
|
404
|
+
*/
|
|
405
|
+
addClass(cl) {
|
|
406
|
+
this.$.node.classList.add(cl);
|
|
407
|
+
return this;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Adds some CSS classes
|
|
411
|
+
* @param cls {...string} classes names
|
|
412
|
+
*/
|
|
413
|
+
removeClasse(cl) {
|
|
414
|
+
this.$.node.classList.remove(cl);
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Bind a CSS class
|
|
419
|
+
* @param className {IValue}
|
|
420
|
+
*/
|
|
421
|
+
bindClass(className) {
|
|
422
|
+
const $ = this.$;
|
|
423
|
+
$.bindings.add(new class_1.DynamicalClassBinding(this, className));
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Bind a floating class name
|
|
428
|
+
* @param cond {IValue} condition
|
|
429
|
+
* @param className {string} class name
|
|
430
|
+
*/
|
|
431
|
+
floatingClass(cond, className) {
|
|
432
|
+
this.$.bindings.add(new class_1.StaticClassBinding(this, className, cond));
|
|
433
|
+
return this;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Defines a style attribute
|
|
437
|
+
* @param name {String} name of style attribute
|
|
438
|
+
* @param value {IValue} value
|
|
439
|
+
*/
|
|
440
|
+
style(name, value) {
|
|
441
|
+
const $ = this.$;
|
|
442
|
+
if ($.node instanceof HTMLElement) {
|
|
443
|
+
$.bindings.add(new style_1.StyleBinding(this, name, value));
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
throw (0, errors_1.userError)('style can be applied to HTML elements only', 'non-html-element');
|
|
447
|
+
}
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Sets a style property value
|
|
452
|
+
* @param prop {string} Property name
|
|
453
|
+
* @param value {string} Property value
|
|
454
|
+
*/
|
|
455
|
+
setStyle(prop, value) {
|
|
456
|
+
if (this.$.node instanceof HTMLElement) {
|
|
457
|
+
this.$.node.style.setProperty(prop, value);
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
throw (0, errors_1.userError)("Style can be set for HTML elements only", "non-html-element");
|
|
461
|
+
}
|
|
462
|
+
return this;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Add a listener for an event
|
|
466
|
+
* @param name {string} Event name
|
|
467
|
+
* @param handler {function (Event)} Event handler
|
|
468
|
+
* @param options {Object | boolean} addEventListener options
|
|
469
|
+
*/
|
|
470
|
+
listen(name, handler, options) {
|
|
471
|
+
this.$.node.addEventListener(name, handler, options);
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
insertAdjacent(node) {
|
|
475
|
+
this.$.node.parentNode.insertBefore(node, this.$.node);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* A v-show & ngShow alternative
|
|
479
|
+
* @param cond {IValue} show condition
|
|
480
|
+
*/
|
|
481
|
+
bindShow(cond) {
|
|
482
|
+
const $ = this.$;
|
|
483
|
+
const node = $.node;
|
|
484
|
+
if (node instanceof HTMLElement) {
|
|
485
|
+
let lastDisplay = node.style.display;
|
|
486
|
+
const htmlNode = node;
|
|
487
|
+
return this.bindAlive(cond, () => {
|
|
488
|
+
lastDisplay = htmlNode.style.display;
|
|
489
|
+
htmlNode.style.display = 'none';
|
|
490
|
+
}, () => {
|
|
491
|
+
htmlNode.style.display = lastDisplay;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
throw (0, errors_1.userError)('the element must be a html element', 'bind-show');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* bind HTML
|
|
500
|
+
* @param value {IValue}
|
|
501
|
+
*/
|
|
502
|
+
bindDomApi(name, value) {
|
|
503
|
+
const $ = this.$;
|
|
504
|
+
const node = $.node;
|
|
505
|
+
if (node instanceof HTMLElement) {
|
|
506
|
+
node[name] = value.$;
|
|
507
|
+
this.watch((v) => {
|
|
508
|
+
node[name] = v;
|
|
509
|
+
}, value);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
throw (0, errors_1.userError)("HTML can be bound for HTML nodes only", "dom-error");
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
applyOptions(options) {
|
|
516
|
+
options["v:attr"] && Object.keys(options["v:attr"]).forEach(name => {
|
|
517
|
+
const value = options["v:attr"][name];
|
|
518
|
+
if (value instanceof ivalue_1.IValue) {
|
|
519
|
+
this.attr(name, value);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
this.setAttr(name, value);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
if (options.class) {
|
|
526
|
+
const handleClass = (name, value) => {
|
|
527
|
+
if (value instanceof ivalue_1.IValue) {
|
|
528
|
+
this.floatingClass(value, name);
|
|
529
|
+
}
|
|
530
|
+
else if (value && name !== '$') {
|
|
531
|
+
this.addClass(name);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
this.removeClasse(name);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
if (Array.isArray(options.class)) {
|
|
538
|
+
options.class.forEach(item => {
|
|
539
|
+
if (item instanceof ivalue_1.IValue) {
|
|
540
|
+
this.bindClass(item);
|
|
541
|
+
}
|
|
542
|
+
else if (typeof item == "string") {
|
|
543
|
+
this.addClass(item);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
Reflect.ownKeys(item).forEach((name) => {
|
|
547
|
+
handleClass(name, item[name]);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
options.class.$.forEach(item => {
|
|
554
|
+
this.bindClass(item);
|
|
555
|
+
});
|
|
556
|
+
Reflect.ownKeys(options.class).forEach((name) => {
|
|
557
|
+
handleClass(name, options.class[name]);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
options.style && Object.keys(options.style).forEach(name => {
|
|
562
|
+
const value = options.style[name];
|
|
563
|
+
if (value instanceof ivalue_1.IValue) {
|
|
564
|
+
this.style(name, value);
|
|
565
|
+
}
|
|
566
|
+
else if (typeof value === "string") {
|
|
567
|
+
this.setStyle(name, value);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
if (value[0] instanceof ivalue_1.IValue) {
|
|
571
|
+
this.style(name, this.expr((v) => v + value[1], value[0]));
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
this.setStyle(name, value[0] + value[1]);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
options["v:events"] && Object.keys(options["v:events"]).forEach(name => {
|
|
579
|
+
this.listen(name, options["v:events"][name]);
|
|
580
|
+
});
|
|
581
|
+
if (options["v:bind"]) {
|
|
582
|
+
const inode = this.node;
|
|
583
|
+
Reflect.ownKeys(options["v:bind"]).forEach((k) => {
|
|
584
|
+
const value = options["v:bind"][k];
|
|
585
|
+
if (k === 'value' && (inode instanceof HTMLInputElement || inode instanceof HTMLTextAreaElement)) {
|
|
586
|
+
inode.oninput = () => value.$ = inode.value;
|
|
587
|
+
}
|
|
588
|
+
else if (k === 'checked' && inode instanceof HTMLInputElement) {
|
|
589
|
+
inode.oninput = () => value.$ = inode.checked;
|
|
590
|
+
}
|
|
591
|
+
else if (k === 'volume' && inode instanceof HTMLMediaElement) {
|
|
592
|
+
inode.onvolumechange = () => value.$ = inode.volume;
|
|
593
|
+
}
|
|
594
|
+
this.bindDomApi(k, value);
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
options["v:set"] && Object.keys(options["v:set"]).forEach(key => {
|
|
598
|
+
this.node[key] = options["v:set"][key];
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
exports.INode = INode;
|
|
603
|
+
/**
|
|
604
|
+
* Represents an Vasille.js HTML element node
|
|
605
|
+
* @class Tag
|
|
606
|
+
* @extends INode
|
|
607
|
+
*/
|
|
608
|
+
class Tag extends INode {
|
|
609
|
+
constructor(input) {
|
|
610
|
+
super(input);
|
|
611
|
+
this.$seal();
|
|
612
|
+
}
|
|
613
|
+
preinit(app, parent, tagName) {
|
|
614
|
+
if (!tagName || typeof tagName !== "string") {
|
|
615
|
+
throw (0, errors_1.internalError)('wrong Tag::$preinit call');
|
|
616
|
+
}
|
|
617
|
+
const node = document.createElement(tagName);
|
|
618
|
+
const $ = this.$;
|
|
619
|
+
$.preinit(app, parent);
|
|
620
|
+
$.node = node;
|
|
621
|
+
$.parent.appendNode(node);
|
|
622
|
+
}
|
|
623
|
+
compose(input) {
|
|
624
|
+
input.slot && input.slot(this);
|
|
625
|
+
}
|
|
626
|
+
findFirstChild() {
|
|
627
|
+
return this.$.unmounted ? null : this.$.node;
|
|
628
|
+
}
|
|
629
|
+
insertAdjacent(node) {
|
|
630
|
+
if (this.$.unmounted) {
|
|
631
|
+
if (this.$.next) {
|
|
632
|
+
this.$.next.insertAdjacent(node);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
this.$.parent.appendNode(node);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
super.insertAdjacent(node);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
appendNode(node) {
|
|
643
|
+
this.$.node.appendChild(node);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Mount/Unmount a node
|
|
647
|
+
* @param cond {IValue} show condition
|
|
648
|
+
*/
|
|
649
|
+
bindMount(cond) {
|
|
650
|
+
const $ = this.$;
|
|
651
|
+
this.bindAlive(cond, () => {
|
|
652
|
+
$.node.remove();
|
|
653
|
+
$.unmounted = true;
|
|
654
|
+
}, () => {
|
|
655
|
+
if ($.unmounted) {
|
|
656
|
+
this.insertAdjacent($.node);
|
|
657
|
+
$.unmounted = false;
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Runs GC
|
|
663
|
+
*/
|
|
664
|
+
$destroy() {
|
|
665
|
+
this.node.remove();
|
|
666
|
+
super.$destroy();
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
exports.Tag = Tag;
|
|
670
|
+
/**
|
|
671
|
+
* Represents a vasille extension node
|
|
672
|
+
* @class Extension
|
|
673
|
+
* @extends INode
|
|
674
|
+
*/
|
|
675
|
+
class Extension extends INode {
|
|
676
|
+
preinit(app, parent) {
|
|
677
|
+
const $ = this.$;
|
|
678
|
+
let it = parent;
|
|
679
|
+
while (it && !(it instanceof INode)) {
|
|
680
|
+
it = it.parent;
|
|
681
|
+
}
|
|
682
|
+
if (it && it instanceof INode) {
|
|
683
|
+
$.node = it.node;
|
|
684
|
+
}
|
|
685
|
+
$.preinit(app, parent);
|
|
686
|
+
if (!it) {
|
|
687
|
+
throw (0, errors_1.userError)("A extension node can be encapsulated only in a tag/extension/component", "virtual-dom");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
extend(options) {
|
|
691
|
+
this.applyOptions(options);
|
|
692
|
+
}
|
|
693
|
+
$destroy() {
|
|
694
|
+
super.$destroy();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
exports.Extension = Extension;
|
|
698
|
+
/**
|
|
699
|
+
* Node which cas has just a child
|
|
700
|
+
* @class Component
|
|
701
|
+
* @extends Extension
|
|
702
|
+
*/
|
|
703
|
+
class Component extends Extension {
|
|
704
|
+
init() {
|
|
705
|
+
super.composeNow();
|
|
706
|
+
this.ready();
|
|
707
|
+
super.applyOptionsNow();
|
|
708
|
+
}
|
|
709
|
+
ready() {
|
|
710
|
+
super.ready();
|
|
711
|
+
if (this.children.size !== 1) {
|
|
712
|
+
throw (0, errors_1.userError)("Component must have a child only", "dom-error");
|
|
713
|
+
}
|
|
714
|
+
const child = this.lastChild;
|
|
715
|
+
if (child instanceof Tag || child instanceof Component) {
|
|
716
|
+
const $ = this.$;
|
|
717
|
+
$.node = child.node;
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
throw (0, errors_1.userError)("Component child must be Tag or Component", "dom-error");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
preinit(app, parent) {
|
|
724
|
+
this.$.preinit(app, parent);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
exports.Component = Component;
|
|
728
|
+
/**
|
|
729
|
+
* Private part of switch node
|
|
730
|
+
* @class SwitchedNodePrivate
|
|
731
|
+
* @extends INodePrivate
|
|
732
|
+
*/
|
|
733
|
+
class SwitchedNodePrivate extends FragmentPrivate {
|
|
734
|
+
constructor() {
|
|
735
|
+
super();
|
|
736
|
+
/**
|
|
737
|
+
* Array of possible cases
|
|
738
|
+
* @type {Array<{cond : IValue<boolean>, cb : function(Fragment)}>}
|
|
739
|
+
*/
|
|
740
|
+
this.cases = [];
|
|
741
|
+
this.$seal();
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Runs GC
|
|
745
|
+
*/
|
|
746
|
+
$destroy() {
|
|
747
|
+
this.cases.forEach(c => {
|
|
748
|
+
delete c.cond;
|
|
749
|
+
delete c.cb;
|
|
750
|
+
});
|
|
751
|
+
this.cases.splice(0);
|
|
752
|
+
super.$destroy();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
exports.SwitchedNodePrivate = SwitchedNodePrivate;
|
|
756
|
+
/**
|
|
757
|
+
* Defines a node witch can switch its children conditionally
|
|
758
|
+
*/
|
|
759
|
+
class SwitchedNode extends Fragment {
|
|
760
|
+
/**
|
|
761
|
+
* Constructs a switch node and define a sync function
|
|
762
|
+
*/
|
|
763
|
+
constructor() {
|
|
764
|
+
super({}, new SwitchedNodePrivate);
|
|
765
|
+
this.$.sync = () => {
|
|
766
|
+
const $ = this.$;
|
|
767
|
+
let i = 0;
|
|
768
|
+
for (; i < $.cases.length; i++) {
|
|
769
|
+
if ($.cases[i].cond.$) {
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (i === $.index) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (this.lastChild) {
|
|
777
|
+
this.lastChild.$destroy();
|
|
778
|
+
this.children.clear();
|
|
779
|
+
this.lastChild = null;
|
|
780
|
+
}
|
|
781
|
+
if (i !== $.cases.length) {
|
|
782
|
+
$.index = i;
|
|
783
|
+
this.createChild($.cases[i].cb);
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
$.index = -1;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
this.$seal();
|
|
790
|
+
}
|
|
791
|
+
addCase(case_) {
|
|
792
|
+
this.$.cases.push(case_);
|
|
793
|
+
case_.cond.$on(this.$.sync);
|
|
794
|
+
this.$.sync();
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Creates a child node
|
|
798
|
+
* @param cb {function(Fragment)} Call-back
|
|
799
|
+
*/
|
|
800
|
+
createChild(cb) {
|
|
801
|
+
const node = new Fragment({});
|
|
802
|
+
node.preinit(this.$.app, this);
|
|
803
|
+
node.init();
|
|
804
|
+
this.lastChild = node;
|
|
805
|
+
this.children.add(node);
|
|
806
|
+
cb(node);
|
|
807
|
+
}
|
|
808
|
+
ready() {
|
|
809
|
+
const $ = this.$;
|
|
810
|
+
$.cases.forEach(c => {
|
|
811
|
+
c.cond.$on($.sync);
|
|
812
|
+
});
|
|
813
|
+
$.sync();
|
|
814
|
+
}
|
|
815
|
+
$destroy() {
|
|
816
|
+
const $ = this.$;
|
|
817
|
+
$.cases.forEach(c => {
|
|
818
|
+
c.cond.$off($.sync);
|
|
819
|
+
});
|
|
820
|
+
super.$destroy();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
exports.SwitchedNode = SwitchedNode;
|
|
824
|
+
/**
|
|
825
|
+
* The private part of a text node
|
|
826
|
+
*/
|
|
827
|
+
class DebugPrivate extends FragmentPrivate {
|
|
828
|
+
constructor() {
|
|
829
|
+
super();
|
|
830
|
+
this.$seal();
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Pre-initializes a text node
|
|
834
|
+
* @param app {App} the app node
|
|
835
|
+
* @param parent {Fragment} parent node
|
|
836
|
+
* @param text {String | IValue}
|
|
837
|
+
*/
|
|
838
|
+
preinitComment(app, parent, text) {
|
|
839
|
+
super.preinit(app, parent);
|
|
840
|
+
this.node = document.createComment(text.$);
|
|
841
|
+
this.bindings.add(new expression_1.Expression((v) => {
|
|
842
|
+
this.node.replaceData(0, -1, v);
|
|
843
|
+
}, true, text));
|
|
844
|
+
this.parent.appendNode(this.node);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Clear node data
|
|
848
|
+
*/
|
|
849
|
+
$destroy() {
|
|
850
|
+
this.node.remove();
|
|
851
|
+
super.$destroy();
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
exports.DebugPrivate = DebugPrivate;
|
|
855
|
+
/**
|
|
856
|
+
* Represents a debug node
|
|
857
|
+
* @class DebugNode
|
|
858
|
+
* @extends Fragment
|
|
859
|
+
*/
|
|
860
|
+
class DebugNode extends Fragment {
|
|
861
|
+
constructor() {
|
|
862
|
+
super({});
|
|
863
|
+
/**
|
|
864
|
+
* private data
|
|
865
|
+
* @type {DebugNode}
|
|
866
|
+
*/
|
|
867
|
+
this.$ = new DebugPrivate();
|
|
868
|
+
this.$seal();
|
|
869
|
+
}
|
|
870
|
+
preinit(app, parent, text) {
|
|
871
|
+
const $ = this.$;
|
|
872
|
+
if (!text) {
|
|
873
|
+
throw (0, errors_1.internalError)('wrong DebugNode::$preninit call');
|
|
874
|
+
}
|
|
875
|
+
$.preinitComment(app, parent, text);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Runs garbage collector
|
|
879
|
+
*/
|
|
880
|
+
$destroy() {
|
|
881
|
+
this.$.$destroy();
|
|
882
|
+
super.$destroy();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
exports.DebugNode = DebugNode;
|