vasille 2.3.5 → 2.3.7
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/cdn/es2015.js +2483 -0
- package/cdn/es5.js +3458 -0
- package/flow-typed/vasille.js +13 -11
- package/lib/core/core.js +1 -1
- package/lib/node/node.js +4 -1
- package/lib/node/watch.js +1 -0
- package/lib/views/array-view.js +1 -0
- package/lib/views/base-view.js +1 -0
- package/lib/views/map-view.js +1 -0
- package/lib/views/object-view.js +1 -0
- package/lib/views/set-view.js +1 -0
- package/lib-node/core/core.js +1 -1
- package/lib-node/node/node.js +4 -1
- package/lib-node/node/watch.js +1 -0
- package/lib-node/views/array-view.js +1 -0
- package/lib-node/views/base-view.js +1 -0
- package/lib-node/views/map-view.js +1 -0
- package/lib-node/views/object-view.js +1 -0
- package/lib-node/views/set-view.js +1 -0
- package/package.json +1 -1
- package/types/core/core.d.ts +1 -1
- package/types/functional/options.d.ts +3 -1
- package/types/node/node.d.ts +3 -3
- package/types/node/watch.d.ts +1 -1
- package/types/views/array-view.d.ts +1 -1
- package/types/views/base-view.d.ts +1 -1
- package/types/views/map-view.d.ts +1 -1
- package/types/views/object-view.d.ts +1 -1
- package/types/views/set-view.d.ts +1 -1
package/cdn/es2015.js
ADDED
|
@@ -0,0 +1,2483 @@
|
|
|
1
|
+
(function(){
|
|
2
|
+
// ./lib/core/destroyable.js
|
|
3
|
+
/**
|
|
4
|
+
* Mark an object which can be destroyed
|
|
5
|
+
* @class Destroyable
|
|
6
|
+
*/
|
|
7
|
+
class Destroyable {
|
|
8
|
+
/**
|
|
9
|
+
* Make object fields non configurable
|
|
10
|
+
* @protected
|
|
11
|
+
*/
|
|
12
|
+
$seal() {
|
|
13
|
+
const $ = this;
|
|
14
|
+
Object.keys($).forEach(i => {
|
|
15
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
16
|
+
if (this.hasOwnProperty(i)) {
|
|
17
|
+
const config = Object.getOwnPropertyDescriptor($, i);
|
|
18
|
+
if (config.configurable) {
|
|
19
|
+
let descriptor;
|
|
20
|
+
if (config.set || config.get) {
|
|
21
|
+
descriptor = {
|
|
22
|
+
configurable: false,
|
|
23
|
+
get: config.get,
|
|
24
|
+
set: config.set,
|
|
25
|
+
enumerable: config.enumerable
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
descriptor = {
|
|
30
|
+
value: $[i],
|
|
31
|
+
configurable: false,
|
|
32
|
+
writable: config.writable,
|
|
33
|
+
enumerable: config.enumerable
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty($, i, descriptor);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Garbage collector method
|
|
43
|
+
*/
|
|
44
|
+
$destroy() {
|
|
45
|
+
// nothing here
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
window.Destroyable = Destroyable;
|
|
50
|
+
|
|
51
|
+
// ./lib/core/errors.js
|
|
52
|
+
const reportIt = "Report it here: https://gitlab.com/vasille-js/vasille-js/-/issues";
|
|
53
|
+
function notOverwritten() {
|
|
54
|
+
console.error("Vasille-SFP: Internal error", "Must be overwritten", reportIt);
|
|
55
|
+
return "not-overwritten";
|
|
56
|
+
}
|
|
57
|
+
function internalError(msg) {
|
|
58
|
+
console.error("Vasille-SFP: Internal error", msg, reportIt);
|
|
59
|
+
return "internal-error";
|
|
60
|
+
}
|
|
61
|
+
function userError(msg, err) {
|
|
62
|
+
console.error("Vasille-SFP: User error", msg);
|
|
63
|
+
return err;
|
|
64
|
+
}
|
|
65
|
+
function wrongBinding(msg) {
|
|
66
|
+
return userError(msg, "wrong-binding");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
window.notOverwritten = notOverwritten;
|
|
70
|
+
window.internalError = internalError;
|
|
71
|
+
window.userError = userError;
|
|
72
|
+
window.wrongBinding = wrongBinding;
|
|
73
|
+
|
|
74
|
+
// ./lib/core/ivalue.js
|
|
75
|
+
class Switchable extends Destroyable {
|
|
76
|
+
/**
|
|
77
|
+
* Enable update handlers triggering
|
|
78
|
+
*/
|
|
79
|
+
$enable() {
|
|
80
|
+
throw notOverwritten();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* disable update handlers triggering
|
|
84
|
+
*/
|
|
85
|
+
$disable() {
|
|
86
|
+
throw notOverwritten();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Interface which describes a value
|
|
91
|
+
* @class IValue
|
|
92
|
+
* @extends Destroyable
|
|
93
|
+
*/
|
|
94
|
+
class IValue extends Switchable {
|
|
95
|
+
/**
|
|
96
|
+
* @param isEnabled {boolean} initial is enabled state
|
|
97
|
+
*/
|
|
98
|
+
constructor(isEnabled) {
|
|
99
|
+
super();
|
|
100
|
+
this.isEnabled = isEnabled;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the encapsulated value
|
|
104
|
+
* @return {*} the encapsulated value
|
|
105
|
+
*/
|
|
106
|
+
get $() {
|
|
107
|
+
throw notOverwritten();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Sets the encapsulated value
|
|
111
|
+
* @param value {*} value to encapsulate
|
|
112
|
+
*/
|
|
113
|
+
set $(value) {
|
|
114
|
+
throw notOverwritten();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Add a new handler to value change
|
|
118
|
+
* @param handler {function(value : *)} the handler to add
|
|
119
|
+
*/
|
|
120
|
+
$on(handler) {
|
|
121
|
+
throw notOverwritten();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Removes a handler of value change
|
|
125
|
+
* @param handler {function(value : *)} the handler to remove
|
|
126
|
+
*/
|
|
127
|
+
$off(handler) {
|
|
128
|
+
throw notOverwritten();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
window.Switchable = Switchable;
|
|
133
|
+
window.IValue = IValue;
|
|
134
|
+
|
|
135
|
+
// ./lib/core/core.js
|
|
136
|
+
|
|
137
|
+
const currentStack = [];
|
|
138
|
+
function stack(node) {
|
|
139
|
+
currentStack.push(current);
|
|
140
|
+
current = node;
|
|
141
|
+
}
|
|
142
|
+
function unstack() {
|
|
143
|
+
current = currentStack.pop();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Private stuff of a reactive object
|
|
147
|
+
* @class ReactivePrivate
|
|
148
|
+
* @extends Destroyable
|
|
149
|
+
*/
|
|
150
|
+
class ReactivePrivate extends Destroyable {
|
|
151
|
+
constructor() {
|
|
152
|
+
super();
|
|
153
|
+
/**
|
|
154
|
+
* A list of user-defined values
|
|
155
|
+
* @type {Set}
|
|
156
|
+
*/
|
|
157
|
+
this.watch = new Set;
|
|
158
|
+
/**
|
|
159
|
+
* A list of user-defined bindings
|
|
160
|
+
* @type {Set}
|
|
161
|
+
*/
|
|
162
|
+
this.bindings = new Set;
|
|
163
|
+
/**
|
|
164
|
+
* A list of user defined models
|
|
165
|
+
*/
|
|
166
|
+
this.models = new Set;
|
|
167
|
+
/**
|
|
168
|
+
* Reactivity switch state
|
|
169
|
+
* @type {boolean}
|
|
170
|
+
*/
|
|
171
|
+
this.enabled = true;
|
|
172
|
+
/**
|
|
173
|
+
* The frozen state of object
|
|
174
|
+
* @type {boolean}
|
|
175
|
+
*/
|
|
176
|
+
this.frozen = false;
|
|
177
|
+
this.$seal();
|
|
178
|
+
}
|
|
179
|
+
$destroy() {
|
|
180
|
+
this.watch.forEach(value => value.$destroy());
|
|
181
|
+
this.watch.clear();
|
|
182
|
+
this.bindings.forEach(binding => binding.$destroy());
|
|
183
|
+
this.bindings.clear();
|
|
184
|
+
this.models.forEach(model => model.disableReactivity());
|
|
185
|
+
this.models.clear();
|
|
186
|
+
this.freezeExpr && this.freezeExpr.$destroy();
|
|
187
|
+
this.onDestroy && this.onDestroy();
|
|
188
|
+
super.$destroy();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A reactive object
|
|
193
|
+
* @class Reactive
|
|
194
|
+
* @extends Destroyable
|
|
195
|
+
*/
|
|
196
|
+
class Reactive extends Destroyable {
|
|
197
|
+
constructor(input, $) {
|
|
198
|
+
super();
|
|
199
|
+
this.input = input;
|
|
200
|
+
this.$ = $ || new ReactivePrivate;
|
|
201
|
+
this.$seal();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get parent node
|
|
205
|
+
*/
|
|
206
|
+
get parent() {
|
|
207
|
+
return this.$.parent;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Create a reference
|
|
211
|
+
* @param value {*} value to reference
|
|
212
|
+
*/
|
|
213
|
+
ref(value) {
|
|
214
|
+
const $ = this.$;
|
|
215
|
+
const ref = new Reference(value);
|
|
216
|
+
$.watch.add(ref);
|
|
217
|
+
return ref;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Create a mirror
|
|
221
|
+
* @param value {IValue} value to mirror
|
|
222
|
+
*/
|
|
223
|
+
mirror(value) {
|
|
224
|
+
const mirror = new Mirror(value, false);
|
|
225
|
+
this.$.watch.add(mirror);
|
|
226
|
+
return mirror;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Create a forward-only mirror
|
|
230
|
+
* @param value {IValue} value to mirror
|
|
231
|
+
*/
|
|
232
|
+
forward(value) {
|
|
233
|
+
const mirror = new Mirror(value, true);
|
|
234
|
+
this.$.watch.add(mirror);
|
|
235
|
+
return mirror;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Creates a pointer
|
|
239
|
+
* @param value {*} default value to point
|
|
240
|
+
* @param forwardOnly {boolean} forward only sync
|
|
241
|
+
*/
|
|
242
|
+
point(value, forwardOnly = false) {
|
|
243
|
+
const $ = this.$;
|
|
244
|
+
const pointer = new Pointer(value, forwardOnly);
|
|
245
|
+
$.watch.add(pointer);
|
|
246
|
+
return pointer;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Register a model
|
|
250
|
+
* @param model
|
|
251
|
+
*/
|
|
252
|
+
register(model) {
|
|
253
|
+
this.$.models.add(model);
|
|
254
|
+
return model;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Creates a watcher
|
|
258
|
+
* @param func {function} function to run on any argument change
|
|
259
|
+
* @param values
|
|
260
|
+
*/
|
|
261
|
+
watch(func, ...values) {
|
|
262
|
+
const $ = this.$;
|
|
263
|
+
$.watch.add(new Expression(func, !this.$.frozen, ...values));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Creates a computed value
|
|
267
|
+
* @param func {function} function to run on any argument change
|
|
268
|
+
* @param values
|
|
269
|
+
* @return {IValue} the created ivalue
|
|
270
|
+
*/
|
|
271
|
+
expr(func, ...values) {
|
|
272
|
+
const res = new Expression(func, !this.$.frozen, ...values);
|
|
273
|
+
const $ = this.$;
|
|
274
|
+
$.watch.add(res);
|
|
275
|
+
return res;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Enable reactivity of fields
|
|
279
|
+
*/
|
|
280
|
+
enable() {
|
|
281
|
+
const $ = this.$;
|
|
282
|
+
if (!$.enabled) {
|
|
283
|
+
$.watch.forEach(watcher => {
|
|
284
|
+
watcher.$enable();
|
|
285
|
+
});
|
|
286
|
+
$.models.forEach(model => {
|
|
287
|
+
model.enableReactivity();
|
|
288
|
+
});
|
|
289
|
+
$.enabled = true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Disable reactivity of fields
|
|
294
|
+
*/
|
|
295
|
+
disable() {
|
|
296
|
+
const $ = this.$;
|
|
297
|
+
if ($.enabled) {
|
|
298
|
+
$.watch.forEach(watcher => {
|
|
299
|
+
watcher.$disable();
|
|
300
|
+
});
|
|
301
|
+
$.models.forEach(model => {
|
|
302
|
+
model.disableReactivity();
|
|
303
|
+
});
|
|
304
|
+
$.enabled = false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Disable/Enable reactivity of object fields with feedback
|
|
309
|
+
* @param cond {IValue} show condition
|
|
310
|
+
* @param onOff {function} on show feedback
|
|
311
|
+
* @param onOn {function} on hide feedback
|
|
312
|
+
*/
|
|
313
|
+
bindAlive(cond, onOff, onOn) {
|
|
314
|
+
const $ = this.$;
|
|
315
|
+
if ($.freezeExpr) {
|
|
316
|
+
throw wrongBinding("this component already have a freeze state");
|
|
317
|
+
}
|
|
318
|
+
if ($.watch.has(cond)) {
|
|
319
|
+
throw wrongBinding("freeze state must be bound to an external component");
|
|
320
|
+
}
|
|
321
|
+
$.freezeExpr = new Expression((cond) => {
|
|
322
|
+
$.frozen = !cond;
|
|
323
|
+
if (cond) {
|
|
324
|
+
onOn === null || onOn === void 0 ? void 0 : onOn();
|
|
325
|
+
this.enable();
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
onOff === null || onOff === void 0 ? void 0 : onOff();
|
|
329
|
+
this.disable();
|
|
330
|
+
}
|
|
331
|
+
}, true, cond);
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
init() {
|
|
335
|
+
this.applyOptions(this.input);
|
|
336
|
+
return this.compose(this.input);
|
|
337
|
+
}
|
|
338
|
+
applyOptions(input) {
|
|
339
|
+
// empty
|
|
340
|
+
}
|
|
341
|
+
applyOptionsNow() {
|
|
342
|
+
this.applyOptions(this.input);
|
|
343
|
+
}
|
|
344
|
+
compose(input) {
|
|
345
|
+
throw notOverwritten();
|
|
346
|
+
}
|
|
347
|
+
composeNow() {
|
|
348
|
+
return this.compose(this.input);
|
|
349
|
+
}
|
|
350
|
+
runFunctional(f, ...args) {
|
|
351
|
+
stack(this);
|
|
352
|
+
// yet another ts bug
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
354
|
+
// @ts-ignore
|
|
355
|
+
const result = f(...args);
|
|
356
|
+
unstack();
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
runOnDestroy(func) {
|
|
360
|
+
this.$.onDestroy = func;
|
|
361
|
+
}
|
|
362
|
+
$destroy() {
|
|
363
|
+
super.$destroy();
|
|
364
|
+
this.$.$destroy();
|
|
365
|
+
this.$ = null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
window.stack = stack;
|
|
370
|
+
window.unstack = unstack;
|
|
371
|
+
window.ReactivePrivate = ReactivePrivate;
|
|
372
|
+
window.Reactive = Reactive;
|
|
373
|
+
|
|
374
|
+
// ./lib/value/reference.js
|
|
375
|
+
/**
|
|
376
|
+
* Declares a notifiable value
|
|
377
|
+
* @class Reference
|
|
378
|
+
* @extends IValue
|
|
379
|
+
*/
|
|
380
|
+
class Reference extends IValue {
|
|
381
|
+
/**
|
|
382
|
+
* @param value {any} the initial value
|
|
383
|
+
*/
|
|
384
|
+
constructor(value) {
|
|
385
|
+
super(true);
|
|
386
|
+
this.$value = value;
|
|
387
|
+
this.$onchange = new Set;
|
|
388
|
+
this.$seal();
|
|
389
|
+
}
|
|
390
|
+
get $() {
|
|
391
|
+
return this.$value;
|
|
392
|
+
}
|
|
393
|
+
set $(value) {
|
|
394
|
+
if (this.$value !== value) {
|
|
395
|
+
this.$value = value;
|
|
396
|
+
if (this.isEnabled) {
|
|
397
|
+
this.$onchange.forEach(handler => {
|
|
398
|
+
handler(value);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
$enable() {
|
|
404
|
+
if (!this.isEnabled) {
|
|
405
|
+
this.$onchange.forEach(handler => {
|
|
406
|
+
handler(this.$value);
|
|
407
|
+
});
|
|
408
|
+
this.isEnabled = true;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
$disable() {
|
|
412
|
+
this.isEnabled = false;
|
|
413
|
+
}
|
|
414
|
+
$on(handler) {
|
|
415
|
+
this.$onchange.add(handler);
|
|
416
|
+
}
|
|
417
|
+
$off(handler) {
|
|
418
|
+
this.$onchange.delete(handler);
|
|
419
|
+
}
|
|
420
|
+
$destroy() {
|
|
421
|
+
super.$destroy();
|
|
422
|
+
this.$onchange.clear();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
window.Reference = Reference;
|
|
427
|
+
|
|
428
|
+
// ./lib/value/expression.js
|
|
429
|
+
/**
|
|
430
|
+
* Bind some values to one expression
|
|
431
|
+
* @class Expression
|
|
432
|
+
* @extends IValue
|
|
433
|
+
*/
|
|
434
|
+
class Expression extends IValue {
|
|
435
|
+
/**
|
|
436
|
+
* Creates a function bounded to N values
|
|
437
|
+
* @param func {Function} the function to bound
|
|
438
|
+
* @param values
|
|
439
|
+
* @param link {Boolean} links immediately if true
|
|
440
|
+
*/
|
|
441
|
+
constructor(func, link, ...values) {
|
|
442
|
+
super(false);
|
|
443
|
+
/**
|
|
444
|
+
* Expression will link different handler for each value of list
|
|
445
|
+
*/
|
|
446
|
+
this.linkedFunc = [];
|
|
447
|
+
const handler = (i) => {
|
|
448
|
+
if (i != null) {
|
|
449
|
+
this.valuesCache[i] = this.values[i].$;
|
|
450
|
+
}
|
|
451
|
+
this.sync.$ = func.apply(this, this.valuesCache);
|
|
452
|
+
};
|
|
453
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
454
|
+
// @ts-ignore
|
|
455
|
+
this.valuesCache = values.map(item => item.$);
|
|
456
|
+
this.sync = new Reference(func.apply(this, this.valuesCache));
|
|
457
|
+
let i = 0;
|
|
458
|
+
values.forEach(() => {
|
|
459
|
+
this.linkedFunc.push(handler.bind(this, Number(i++)));
|
|
460
|
+
});
|
|
461
|
+
this.values = values;
|
|
462
|
+
this.func = handler;
|
|
463
|
+
if (link) {
|
|
464
|
+
this.$enable();
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
handler();
|
|
468
|
+
}
|
|
469
|
+
this.$seal();
|
|
470
|
+
}
|
|
471
|
+
get $() {
|
|
472
|
+
return this.sync.$;
|
|
473
|
+
}
|
|
474
|
+
set $(value) {
|
|
475
|
+
this.sync.$ = value;
|
|
476
|
+
}
|
|
477
|
+
$on(handler) {
|
|
478
|
+
this.sync.$on(handler);
|
|
479
|
+
return this;
|
|
480
|
+
}
|
|
481
|
+
$off(handler) {
|
|
482
|
+
this.sync.$off(handler);
|
|
483
|
+
return this;
|
|
484
|
+
}
|
|
485
|
+
$enable() {
|
|
486
|
+
if (!this.isEnabled) {
|
|
487
|
+
for (let i = 0; i < this.values.length; i++) {
|
|
488
|
+
this.values[i].$on(this.linkedFunc[i]);
|
|
489
|
+
this.valuesCache[i] = this.values[i].$;
|
|
490
|
+
}
|
|
491
|
+
this.func();
|
|
492
|
+
this.isEnabled = true;
|
|
493
|
+
}
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
$disable() {
|
|
497
|
+
if (this.isEnabled) {
|
|
498
|
+
for (let i = 0; i < this.values.length; i++) {
|
|
499
|
+
this.values[i].$off(this.linkedFunc[i]);
|
|
500
|
+
}
|
|
501
|
+
this.isEnabled = false;
|
|
502
|
+
}
|
|
503
|
+
return this;
|
|
504
|
+
}
|
|
505
|
+
$destroy() {
|
|
506
|
+
this.$disable();
|
|
507
|
+
this.values.splice(0);
|
|
508
|
+
this.valuesCache.splice(0);
|
|
509
|
+
this.linkedFunc.splice(0);
|
|
510
|
+
super.$destroy();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
window.Expression = Expression;
|
|
515
|
+
|
|
516
|
+
// ./lib/value/mirror.js
|
|
517
|
+
/**
|
|
518
|
+
* Declares a notifiable bind to a value
|
|
519
|
+
* @class Mirror
|
|
520
|
+
* @extends IValue
|
|
521
|
+
* @version 2
|
|
522
|
+
*/
|
|
523
|
+
class Mirror extends Reference {
|
|
524
|
+
/**
|
|
525
|
+
* Constructs a notifiable bind to a value
|
|
526
|
+
* @param value {IValue} is initial value
|
|
527
|
+
* @param forwardOnly {boolean} ensure forward only synchronization
|
|
528
|
+
*/
|
|
529
|
+
constructor(value, forwardOnly = false) {
|
|
530
|
+
super(value.$);
|
|
531
|
+
this.$handler = (v) => {
|
|
532
|
+
this.$ = v;
|
|
533
|
+
};
|
|
534
|
+
this.$pointedValue = value;
|
|
535
|
+
this.$forwardOnly = forwardOnly;
|
|
536
|
+
value.$on(this.$handler);
|
|
537
|
+
this.$seal();
|
|
538
|
+
}
|
|
539
|
+
get $() {
|
|
540
|
+
// this is a ts bug
|
|
541
|
+
// eslint-disable-next-line
|
|
542
|
+
// @ts-ignore
|
|
543
|
+
return super.$;
|
|
544
|
+
}
|
|
545
|
+
set $(v) {
|
|
546
|
+
if (!this.$forwardOnly) {
|
|
547
|
+
this.$pointedValue.$ = v;
|
|
548
|
+
}
|
|
549
|
+
// this is a ts bug
|
|
550
|
+
// eslint-disable-next-line
|
|
551
|
+
// @ts-ignore
|
|
552
|
+
super.$ = v;
|
|
553
|
+
}
|
|
554
|
+
$enable() {
|
|
555
|
+
if (!this.isEnabled) {
|
|
556
|
+
this.isEnabled = true;
|
|
557
|
+
this.$pointedValue.$on(this.$handler);
|
|
558
|
+
this.$ = this.$pointedValue.$;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
$disable() {
|
|
562
|
+
if (this.isEnabled) {
|
|
563
|
+
this.$pointedValue.$off(this.$handler);
|
|
564
|
+
this.isEnabled = false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
$destroy() {
|
|
568
|
+
this.$disable();
|
|
569
|
+
super.$destroy();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
window.Mirror = Mirror;
|
|
574
|
+
|
|
575
|
+
// ./lib/value/pointer.js
|
|
576
|
+
/**
|
|
577
|
+
* r/w pointer to a value
|
|
578
|
+
* @class Pointer
|
|
579
|
+
* @extends Mirror
|
|
580
|
+
*/
|
|
581
|
+
class Pointer extends Mirror {
|
|
582
|
+
/**
|
|
583
|
+
* @param value {IValue} value to point
|
|
584
|
+
* @param forwardOnly {boolean} forward only data flow
|
|
585
|
+
*/
|
|
586
|
+
constructor(value, forwardOnly = false) {
|
|
587
|
+
super(value, forwardOnly);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Point a new ivalue
|
|
591
|
+
* @param value {IValue} value to point
|
|
592
|
+
*/
|
|
593
|
+
set $$(value) {
|
|
594
|
+
if (this.$pointedValue !== value) {
|
|
595
|
+
this.$disable();
|
|
596
|
+
this.$pointedValue = value;
|
|
597
|
+
this.$enable();
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
window.Pointer = Pointer;
|
|
603
|
+
|
|
604
|
+
// ./lib/models/listener.js
|
|
605
|
+
/**
|
|
606
|
+
* Represent a listener for a model
|
|
607
|
+
* @class Listener
|
|
608
|
+
*/
|
|
609
|
+
class Listener {
|
|
610
|
+
constructor() {
|
|
611
|
+
Object.defineProperties(this, {
|
|
612
|
+
onAdded: {
|
|
613
|
+
value: new Set,
|
|
614
|
+
writable: false,
|
|
615
|
+
configurable: false
|
|
616
|
+
},
|
|
617
|
+
onRemoved: {
|
|
618
|
+
value: new Set,
|
|
619
|
+
writable: false,
|
|
620
|
+
configurable: false
|
|
621
|
+
},
|
|
622
|
+
frozen: {
|
|
623
|
+
value: false,
|
|
624
|
+
writable: true,
|
|
625
|
+
configurable: false
|
|
626
|
+
},
|
|
627
|
+
queue: {
|
|
628
|
+
value: [],
|
|
629
|
+
writable: false,
|
|
630
|
+
configurable: false
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Exclude the repeated operation in queue
|
|
636
|
+
* @private
|
|
637
|
+
*/
|
|
638
|
+
excludeRepeat(index) {
|
|
639
|
+
this.queue.forEach((item, i) => {
|
|
640
|
+
if (item.index === index) {
|
|
641
|
+
this.queue.splice(i, 1);
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Emits added event to listeners
|
|
649
|
+
* @param index {*} index of value
|
|
650
|
+
* @param value {*} value of added item
|
|
651
|
+
*/
|
|
652
|
+
emitAdded(index, value) {
|
|
653
|
+
if (this.frozen) {
|
|
654
|
+
if (!this.excludeRepeat(index)) {
|
|
655
|
+
this.queue.push({ sign: true, index, value });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
this.onAdded.forEach(handler => {
|
|
660
|
+
handler(index, value);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Emits removed event to listeners
|
|
666
|
+
* @param index {*} index of removed value
|
|
667
|
+
* @param value {*} value of removed item
|
|
668
|
+
*/
|
|
669
|
+
emitRemoved(index, value) {
|
|
670
|
+
if (this.frozen) {
|
|
671
|
+
if (!this.excludeRepeat(index)) {
|
|
672
|
+
this.queue.push({ sign: false, index, value });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
this.onRemoved.forEach(handler => {
|
|
677
|
+
handler(index, value);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Adds a handler to added event
|
|
683
|
+
* @param handler {function} function to run on event emitting
|
|
684
|
+
*/
|
|
685
|
+
onAdd(handler) {
|
|
686
|
+
this.onAdded.add(handler);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Adds a handler to removed event
|
|
690
|
+
* @param handler {function} function to run on event emitting
|
|
691
|
+
*/
|
|
692
|
+
onRemove(handler) {
|
|
693
|
+
this.onRemoved.add(handler);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Removes an handler from added event
|
|
697
|
+
* @param handler {function} handler to remove
|
|
698
|
+
*/
|
|
699
|
+
offAdd(handler) {
|
|
700
|
+
this.onAdded.delete(handler);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Removes an handler form removed event
|
|
704
|
+
* @param handler {function} handler to remove
|
|
705
|
+
*/
|
|
706
|
+
offRemove(handler) {
|
|
707
|
+
this.onRemoved.delete(handler);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Run all queued operation and enable reactivity
|
|
711
|
+
*/
|
|
712
|
+
enableReactivity() {
|
|
713
|
+
this.queue.forEach(item => {
|
|
714
|
+
if (item.sign) {
|
|
715
|
+
this.onAdded.forEach(handler => {
|
|
716
|
+
handler(item.index, item.value);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
this.onRemoved.forEach(handler => {
|
|
721
|
+
handler(item.index, item.value);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
this.queue.splice(0);
|
|
726
|
+
this.frozen = false;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Disable the reactivity and enable the queue
|
|
730
|
+
*/
|
|
731
|
+
disableReactivity() {
|
|
732
|
+
this.frozen = true;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
window.Listener = Listener;
|
|
737
|
+
|
|
738
|
+
// ./lib/models/model.js
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
// ./lib/models/array-model.js
|
|
743
|
+
/**
|
|
744
|
+
* Model based on Array class
|
|
745
|
+
* @extends Array
|
|
746
|
+
* @implements IModel
|
|
747
|
+
*/
|
|
748
|
+
class ArrayModel extends Array {
|
|
749
|
+
/**
|
|
750
|
+
* @param data {Array} input data
|
|
751
|
+
*/
|
|
752
|
+
constructor(data = []) {
|
|
753
|
+
super();
|
|
754
|
+
Object.defineProperty(this, 'listener', {
|
|
755
|
+
value: new Listener,
|
|
756
|
+
writable: false,
|
|
757
|
+
configurable: false
|
|
758
|
+
});
|
|
759
|
+
for (let i = 0; i < data.length; i++) {
|
|
760
|
+
super.push(data[i]);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/* Array members */
|
|
764
|
+
/**
|
|
765
|
+
* Gets the last item of array
|
|
766
|
+
* @return {*} the last item of array
|
|
767
|
+
*/
|
|
768
|
+
get last() {
|
|
769
|
+
return this.length ? this[this.length - 1] : null;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Calls Array.fill and notify about changes
|
|
773
|
+
* @param value {*} value to fill with
|
|
774
|
+
* @param start {?number} begin index
|
|
775
|
+
* @param end {?number} end index
|
|
776
|
+
*/
|
|
777
|
+
fill(value, start, end) {
|
|
778
|
+
if (!start) {
|
|
779
|
+
start = 0;
|
|
780
|
+
}
|
|
781
|
+
if (!end) {
|
|
782
|
+
end = this.length;
|
|
783
|
+
}
|
|
784
|
+
for (let i = start; i < end; i++) {
|
|
785
|
+
this.listener.emitRemoved(this[i], this[i]);
|
|
786
|
+
this[i] = value;
|
|
787
|
+
this.listener.emitAdded(value, value);
|
|
788
|
+
}
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Calls Array.pop and notify about changes
|
|
793
|
+
* @return {*} removed value
|
|
794
|
+
*/
|
|
795
|
+
pop() {
|
|
796
|
+
const v = super.pop();
|
|
797
|
+
if (v !== undefined) {
|
|
798
|
+
this.listener.emitRemoved(v, v);
|
|
799
|
+
}
|
|
800
|
+
return v;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Calls Array.push and notify about changes
|
|
804
|
+
* @param items {...*} values to push
|
|
805
|
+
* @return {number} new length of array
|
|
806
|
+
*/
|
|
807
|
+
push(...items) {
|
|
808
|
+
items.forEach(item => {
|
|
809
|
+
this.listener.emitAdded(item, item);
|
|
810
|
+
super.push(item);
|
|
811
|
+
});
|
|
812
|
+
return this.length;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Calls Array.shift and notify about changed
|
|
816
|
+
* @return {*} the shifted value
|
|
817
|
+
*/
|
|
818
|
+
shift() {
|
|
819
|
+
const v = super.shift();
|
|
820
|
+
if (v !== undefined) {
|
|
821
|
+
this.listener.emitRemoved(v, v);
|
|
822
|
+
}
|
|
823
|
+
return v;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Calls Array.splice and notify about changed
|
|
827
|
+
* @param start {number} start index
|
|
828
|
+
* @param deleteCount {?number} delete count
|
|
829
|
+
* @param items {...*}
|
|
830
|
+
* @return {ArrayModel} a pointer to this
|
|
831
|
+
*/
|
|
832
|
+
splice(start, deleteCount, ...items) {
|
|
833
|
+
start = Math.min(start, this.length);
|
|
834
|
+
deleteCount = deleteCount || this.length - start;
|
|
835
|
+
const before = this[start + deleteCount];
|
|
836
|
+
for (let i = 0; i < deleteCount; i++) {
|
|
837
|
+
const index = start + deleteCount - i - 1;
|
|
838
|
+
if (this[index] !== undefined) {
|
|
839
|
+
this.listener.emitRemoved(this[index], this[index]);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
for (let i = 0; i < items.length; i++) {
|
|
843
|
+
this.listener.emitAdded(before, items[i]);
|
|
844
|
+
}
|
|
845
|
+
return new ArrayModel(super.splice(start, deleteCount, ...items));
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Calls Array.unshift and notify about changed
|
|
849
|
+
* @param items {...*} values to insert
|
|
850
|
+
* @return {number} the length after prepend
|
|
851
|
+
*/
|
|
852
|
+
unshift(...items) {
|
|
853
|
+
for (let i = 0; i < items.length; i++) {
|
|
854
|
+
this.listener.emitAdded(this[i], items[i]);
|
|
855
|
+
}
|
|
856
|
+
return super.unshift(...items);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Inserts a value to the end of array
|
|
860
|
+
* @param v {*} value to insert
|
|
861
|
+
*/
|
|
862
|
+
append(v) {
|
|
863
|
+
this.listener.emitAdded(null, v);
|
|
864
|
+
super.push(v);
|
|
865
|
+
return this;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Clears array
|
|
869
|
+
* @return {this} a pointer to this
|
|
870
|
+
*/
|
|
871
|
+
clear() {
|
|
872
|
+
this.forEach(v => {
|
|
873
|
+
this.listener.emitRemoved(v, v);
|
|
874
|
+
});
|
|
875
|
+
super.splice(0);
|
|
876
|
+
return this;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Inserts a value to position `index`
|
|
880
|
+
* @param index {number} index to insert value
|
|
881
|
+
* @param v {*} value to insert
|
|
882
|
+
* @return {this} a pointer to this
|
|
883
|
+
*/
|
|
884
|
+
insert(index, v) {
|
|
885
|
+
this.listener.emitAdded(this[index], v);
|
|
886
|
+
super.splice(index, 0, v);
|
|
887
|
+
return this;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Inserts a value to the beginning of array
|
|
891
|
+
* @param v {*} value to insert
|
|
892
|
+
* @return {this} a pointer to this
|
|
893
|
+
*/
|
|
894
|
+
prepend(v) {
|
|
895
|
+
this.listener.emitAdded(this[0], v);
|
|
896
|
+
super.unshift(v);
|
|
897
|
+
return this;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Removes a value from an index
|
|
901
|
+
* @param index {number} index of value to remove
|
|
902
|
+
* @return {this} a pointer to this
|
|
903
|
+
*/
|
|
904
|
+
removeAt(index) {
|
|
905
|
+
if (index > 0 && index < this.length) {
|
|
906
|
+
this.listener.emitRemoved(this[index], this[index]);
|
|
907
|
+
super.splice(index, 1);
|
|
908
|
+
}
|
|
909
|
+
return this;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Removes the first value of array
|
|
913
|
+
* @return {this} a pointer to this
|
|
914
|
+
*/
|
|
915
|
+
removeFirst() {
|
|
916
|
+
if (this.length) {
|
|
917
|
+
this.listener.emitRemoved(this[0], this[0]);
|
|
918
|
+
super.shift();
|
|
919
|
+
}
|
|
920
|
+
return this;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Removes the ast value of array
|
|
924
|
+
* @return {this} a pointer to this
|
|
925
|
+
*/
|
|
926
|
+
removeLast() {
|
|
927
|
+
const last = this.last;
|
|
928
|
+
if (last != null) {
|
|
929
|
+
this.listener.emitRemoved(this[this.length - 1], last);
|
|
930
|
+
super.pop();
|
|
931
|
+
}
|
|
932
|
+
return this;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Remove the first occurrence of value
|
|
936
|
+
* @param v {*} value to remove
|
|
937
|
+
* @return {this}
|
|
938
|
+
*/
|
|
939
|
+
removeOne(v) {
|
|
940
|
+
this.removeAt(this.indexOf(v));
|
|
941
|
+
return this;
|
|
942
|
+
}
|
|
943
|
+
replace(at, with_) {
|
|
944
|
+
this.listener.emitAdded(this[at], with_);
|
|
945
|
+
this.listener.emitRemoved(this[at], this[at]);
|
|
946
|
+
this[at] = with_;
|
|
947
|
+
return this;
|
|
948
|
+
}
|
|
949
|
+
enableReactivity() {
|
|
950
|
+
this.listener.enableReactivity();
|
|
951
|
+
}
|
|
952
|
+
disableReactivity() {
|
|
953
|
+
this.listener.disableReactivity();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
window.ArrayModel = ArrayModel;
|
|
958
|
+
|
|
959
|
+
// ./lib/models/map-model.js
|
|
960
|
+
/**
|
|
961
|
+
* A Map based memory
|
|
962
|
+
* @class MapModel
|
|
963
|
+
* @extends Map
|
|
964
|
+
* @implements IModel
|
|
965
|
+
*/
|
|
966
|
+
class MapModel extends Map {
|
|
967
|
+
/**
|
|
968
|
+
* Constructs a map model
|
|
969
|
+
* @param map {[*, *][]} input data
|
|
970
|
+
*/
|
|
971
|
+
constructor(map = []) {
|
|
972
|
+
super();
|
|
973
|
+
Object.defineProperty(this, 'listener', {
|
|
974
|
+
value: new Listener,
|
|
975
|
+
writable: false,
|
|
976
|
+
configurable: false
|
|
977
|
+
});
|
|
978
|
+
map.forEach(([key, value]) => {
|
|
979
|
+
super.set(key, value);
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Calls Map.clear and notify abut changes
|
|
984
|
+
*/
|
|
985
|
+
clear() {
|
|
986
|
+
this.forEach((value, key) => {
|
|
987
|
+
this.listener.emitRemoved(key, value);
|
|
988
|
+
});
|
|
989
|
+
super.clear();
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Calls Map.delete and notify abut changes
|
|
993
|
+
* @param key {*} key
|
|
994
|
+
* @return {boolean} true if removed something, otherwise false
|
|
995
|
+
*/
|
|
996
|
+
delete(key) {
|
|
997
|
+
const tmp = super.get(key);
|
|
998
|
+
if (tmp) {
|
|
999
|
+
this.listener.emitRemoved(key, tmp);
|
|
1000
|
+
}
|
|
1001
|
+
return super.delete(key);
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Calls Map.set and notify abut changes
|
|
1005
|
+
* @param key {*} key
|
|
1006
|
+
* @param value {*} value
|
|
1007
|
+
* @return {MapModel} a pointer to this
|
|
1008
|
+
*/
|
|
1009
|
+
set(key, value) {
|
|
1010
|
+
const tmp = super.get(key);
|
|
1011
|
+
if (tmp) {
|
|
1012
|
+
this.listener.emitRemoved(key, tmp);
|
|
1013
|
+
}
|
|
1014
|
+
super.set(key, value);
|
|
1015
|
+
this.listener.emitAdded(key, value);
|
|
1016
|
+
return this;
|
|
1017
|
+
}
|
|
1018
|
+
enableReactivity() {
|
|
1019
|
+
this.listener.enableReactivity();
|
|
1020
|
+
}
|
|
1021
|
+
disableReactivity() {
|
|
1022
|
+
this.listener.disableReactivity();
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
window.MapModel = MapModel;
|
|
1027
|
+
|
|
1028
|
+
// ./lib/models/object-model.js
|
|
1029
|
+
/**
|
|
1030
|
+
* Object based model
|
|
1031
|
+
* @extends Object
|
|
1032
|
+
*/
|
|
1033
|
+
class ObjectModel extends Object {
|
|
1034
|
+
/**
|
|
1035
|
+
* Constructs a object model
|
|
1036
|
+
* @param obj {Object} input data
|
|
1037
|
+
*/
|
|
1038
|
+
constructor(obj = {}) {
|
|
1039
|
+
super();
|
|
1040
|
+
this.container = Object.create(null);
|
|
1041
|
+
Object.defineProperty(this, 'listener', {
|
|
1042
|
+
value: new Listener,
|
|
1043
|
+
writable: false,
|
|
1044
|
+
configurable: false
|
|
1045
|
+
});
|
|
1046
|
+
for (const i in obj) {
|
|
1047
|
+
Object.defineProperty(this.container, i, {
|
|
1048
|
+
value: obj[i],
|
|
1049
|
+
configurable: true,
|
|
1050
|
+
writable: true,
|
|
1051
|
+
enumerable: true
|
|
1052
|
+
});
|
|
1053
|
+
this.listener.emitAdded(i, obj[i]);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Gets a value of a field
|
|
1058
|
+
* @param key {string}
|
|
1059
|
+
* @return {*}
|
|
1060
|
+
*/
|
|
1061
|
+
get(key) {
|
|
1062
|
+
return this.container[key];
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Sets an object property value
|
|
1066
|
+
* @param key {string} property name
|
|
1067
|
+
* @param v {*} property value
|
|
1068
|
+
* @return {ObjectModel} a pointer to this
|
|
1069
|
+
*/
|
|
1070
|
+
set(key, v) {
|
|
1071
|
+
if (Reflect.has(this.container, key)) {
|
|
1072
|
+
this.listener.emitRemoved(key, this.container[key]);
|
|
1073
|
+
this.container[key] = v;
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
Object.defineProperty(this.container, key, {
|
|
1077
|
+
value: v,
|
|
1078
|
+
configurable: true,
|
|
1079
|
+
writable: true,
|
|
1080
|
+
enumerable: true
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
this.listener.emitAdded(key, this.container[key]);
|
|
1084
|
+
return this;
|
|
1085
|
+
}
|
|
1086
|
+
get values() {
|
|
1087
|
+
return this.container;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Deletes an object property
|
|
1091
|
+
* @param key {string} property name
|
|
1092
|
+
*/
|
|
1093
|
+
delete(key) {
|
|
1094
|
+
if (this.container[key]) {
|
|
1095
|
+
this.listener.emitRemoved(key, this.container[key]);
|
|
1096
|
+
delete this.container[key];
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
enableReactivity() {
|
|
1100
|
+
this.listener.enableReactivity();
|
|
1101
|
+
}
|
|
1102
|
+
disableReactivity() {
|
|
1103
|
+
this.listener.disableReactivity();
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
window.ObjectModel = ObjectModel;
|
|
1108
|
+
|
|
1109
|
+
// ./lib/models/set-model.js
|
|
1110
|
+
/**
|
|
1111
|
+
* A Set based model
|
|
1112
|
+
* @class SetModel
|
|
1113
|
+
* @extends Set
|
|
1114
|
+
* @implements IModel
|
|
1115
|
+
*/
|
|
1116
|
+
class SetModel extends Set {
|
|
1117
|
+
/**
|
|
1118
|
+
* Constructs a set model based on a set
|
|
1119
|
+
* @param set {Set} input data
|
|
1120
|
+
*/
|
|
1121
|
+
constructor(set = []) {
|
|
1122
|
+
super();
|
|
1123
|
+
Object.defineProperty(this, 'listener', {
|
|
1124
|
+
value: new Listener,
|
|
1125
|
+
writable: false,
|
|
1126
|
+
configurable: false
|
|
1127
|
+
});
|
|
1128
|
+
set.forEach(item => {
|
|
1129
|
+
super.add(item);
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Calls Set.add and notify abut changes
|
|
1134
|
+
* @param value {*} value
|
|
1135
|
+
* @return {this} a pointer to this
|
|
1136
|
+
*/
|
|
1137
|
+
add(value) {
|
|
1138
|
+
if (!super.has(value)) {
|
|
1139
|
+
this.listener.emitAdded(value, value);
|
|
1140
|
+
super.add(value);
|
|
1141
|
+
}
|
|
1142
|
+
return this;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Calls Set.clear and notify abut changes
|
|
1146
|
+
*/
|
|
1147
|
+
clear() {
|
|
1148
|
+
this.forEach(item => {
|
|
1149
|
+
this.listener.emitRemoved(item, item);
|
|
1150
|
+
});
|
|
1151
|
+
super.clear();
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Calls Set.delete and notify abut changes
|
|
1155
|
+
* @param value {*}
|
|
1156
|
+
* @return {boolean} true if a value was deleted, otherwise false
|
|
1157
|
+
*/
|
|
1158
|
+
delete(value) {
|
|
1159
|
+
if (super.has(value)) {
|
|
1160
|
+
this.listener.emitRemoved(value, value);
|
|
1161
|
+
}
|
|
1162
|
+
return super.delete(value);
|
|
1163
|
+
}
|
|
1164
|
+
enableReactivity() {
|
|
1165
|
+
this.listener.enableReactivity();
|
|
1166
|
+
}
|
|
1167
|
+
disableReactivity() {
|
|
1168
|
+
this.listener.disableReactivity();
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
window.SetModel = SetModel;
|
|
1173
|
+
|
|
1174
|
+
// ./lib/spec/html.js
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
// ./lib/spec/svg.js
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
// ./lib/spec/react.js
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
// ./lib/binding/binding.js
|
|
1187
|
+
/**
|
|
1188
|
+
* Describe a common binding logic
|
|
1189
|
+
* @class Binding
|
|
1190
|
+
* @extends Destroyable
|
|
1191
|
+
*/
|
|
1192
|
+
class Binding extends Destroyable {
|
|
1193
|
+
/**
|
|
1194
|
+
* Constructs a common binding logic
|
|
1195
|
+
* @param value {IValue} the value to bind
|
|
1196
|
+
*/
|
|
1197
|
+
constructor(value) {
|
|
1198
|
+
super();
|
|
1199
|
+
this.binding = value;
|
|
1200
|
+
this.$seal();
|
|
1201
|
+
}
|
|
1202
|
+
init(bounded) {
|
|
1203
|
+
this.func = bounded;
|
|
1204
|
+
this.binding.$on(this.func);
|
|
1205
|
+
this.func(this.binding.$);
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Just clear bindings
|
|
1209
|
+
*/
|
|
1210
|
+
$destroy() {
|
|
1211
|
+
this.binding.$off(this.func);
|
|
1212
|
+
super.$destroy();
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
window.Binding = Binding;
|
|
1217
|
+
|
|
1218
|
+
// ./lib/binding/attribute.js
|
|
1219
|
+
/**
|
|
1220
|
+
* Represents an Attribute binding description
|
|
1221
|
+
* @class AttributeBinding
|
|
1222
|
+
* @extends Binding
|
|
1223
|
+
*/
|
|
1224
|
+
class AttributeBinding extends Binding {
|
|
1225
|
+
/**
|
|
1226
|
+
* Constructs an attribute binding description
|
|
1227
|
+
* @param node {INode} the vasille node
|
|
1228
|
+
* @param name {String} the name of attribute
|
|
1229
|
+
* @param value {IValue} value to bind
|
|
1230
|
+
*/
|
|
1231
|
+
constructor(node, name, value) {
|
|
1232
|
+
super(value);
|
|
1233
|
+
this.init((value) => {
|
|
1234
|
+
if (value) {
|
|
1235
|
+
if (typeof value === 'boolean') {
|
|
1236
|
+
node.node.setAttribute(name, "");
|
|
1237
|
+
}
|
|
1238
|
+
else {
|
|
1239
|
+
node.node.setAttribute(name, `${value}`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
else {
|
|
1243
|
+
node.node.removeAttribute(name);
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
this.$seal();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
window.AttributeBinding = AttributeBinding;
|
|
1251
|
+
|
|
1252
|
+
// ./lib/binding/class.js
|
|
1253
|
+
function addClass(node, cl) {
|
|
1254
|
+
node.node.classList.add(cl);
|
|
1255
|
+
}
|
|
1256
|
+
function removeClass(node, cl) {
|
|
1257
|
+
node.node.classList.remove(cl);
|
|
1258
|
+
}
|
|
1259
|
+
class StaticClassBinding extends Binding {
|
|
1260
|
+
constructor(node, name, value) {
|
|
1261
|
+
super(value);
|
|
1262
|
+
this.current = false;
|
|
1263
|
+
this.init((value) => {
|
|
1264
|
+
if (value !== this.current) {
|
|
1265
|
+
if (value) {
|
|
1266
|
+
addClass(node, name);
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
removeClass(node, name);
|
|
1270
|
+
}
|
|
1271
|
+
this.current = value;
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
this.$seal();
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
class DynamicalClassBinding extends Binding {
|
|
1278
|
+
constructor(node, value) {
|
|
1279
|
+
super(value);
|
|
1280
|
+
this.current = "";
|
|
1281
|
+
this.init((value) => {
|
|
1282
|
+
if (this.current != value) {
|
|
1283
|
+
if (this.current.length) {
|
|
1284
|
+
removeClass(node, this.current);
|
|
1285
|
+
}
|
|
1286
|
+
if (value.length) {
|
|
1287
|
+
addClass(node, value);
|
|
1288
|
+
}
|
|
1289
|
+
this.current = value;
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
this.$seal();
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
window.StaticClassBinding = StaticClassBinding;
|
|
1297
|
+
window.DynamicalClassBinding = DynamicalClassBinding;
|
|
1298
|
+
|
|
1299
|
+
// ./lib/binding/style.js
|
|
1300
|
+
/**
|
|
1301
|
+
* Describes a style attribute binding
|
|
1302
|
+
* @class StyleBinding
|
|
1303
|
+
* @extends Binding
|
|
1304
|
+
*/
|
|
1305
|
+
class StyleBinding extends Binding {
|
|
1306
|
+
/**
|
|
1307
|
+
* Constructs a style binding attribute
|
|
1308
|
+
* @param node {INode} the vasille node
|
|
1309
|
+
* @param name {string} the name of style property
|
|
1310
|
+
* @param value {IValue} the value to bind
|
|
1311
|
+
*/
|
|
1312
|
+
constructor(node, name, value) {
|
|
1313
|
+
super(value);
|
|
1314
|
+
this.init((value) => {
|
|
1315
|
+
if (node.node instanceof HTMLElement) {
|
|
1316
|
+
node.node.style.setProperty(name, value);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
this.$seal();
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
window.StyleBinding = StyleBinding;
|
|
1324
|
+
|
|
1325
|
+
// ./lib/node/node.js
|
|
1326
|
+
/**
|
|
1327
|
+
* Represents a Vasille.js node
|
|
1328
|
+
* @class FragmentPrivate
|
|
1329
|
+
* @extends ReactivePrivate
|
|
1330
|
+
*/
|
|
1331
|
+
class FragmentPrivate extends ReactivePrivate {
|
|
1332
|
+
constructor() {
|
|
1333
|
+
super();
|
|
1334
|
+
this.$seal();
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Pre-initializes the base of a fragment
|
|
1338
|
+
* @param app {App} the app node
|
|
1339
|
+
* @param parent {Fragment} the parent node
|
|
1340
|
+
*/
|
|
1341
|
+
preinit(app, parent) {
|
|
1342
|
+
this.app = app;
|
|
1343
|
+
this.parent = parent;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Unlinks all bindings
|
|
1347
|
+
*/
|
|
1348
|
+
$destroy() {
|
|
1349
|
+
this.next = null;
|
|
1350
|
+
this.prev = null;
|
|
1351
|
+
super.$destroy();
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* This class is symbolic
|
|
1356
|
+
* @extends Reactive
|
|
1357
|
+
*/
|
|
1358
|
+
class Fragment extends Reactive {
|
|
1359
|
+
/**
|
|
1360
|
+
* Constructs a Vasille Node
|
|
1361
|
+
* @param input
|
|
1362
|
+
* @param $ {FragmentPrivate}
|
|
1363
|
+
*/
|
|
1364
|
+
constructor(input, $) {
|
|
1365
|
+
super(input, $ || new FragmentPrivate);
|
|
1366
|
+
/**
|
|
1367
|
+
* The children list
|
|
1368
|
+
* @type Array
|
|
1369
|
+
*/
|
|
1370
|
+
this.children = new Set;
|
|
1371
|
+
this.lastChild = null;
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Gets the app of node
|
|
1375
|
+
*/
|
|
1376
|
+
get app() {
|
|
1377
|
+
return this.$.app;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Prepare to init fragment
|
|
1381
|
+
* @param app {AppNode} app of node
|
|
1382
|
+
* @param parent {Fragment} parent of node
|
|
1383
|
+
* @param data {*} additional data
|
|
1384
|
+
*/
|
|
1385
|
+
preinit(app, parent, data) {
|
|
1386
|
+
const $ = this.$;
|
|
1387
|
+
$.preinit(app, parent);
|
|
1388
|
+
}
|
|
1389
|
+
init() {
|
|
1390
|
+
const ret = super.init();
|
|
1391
|
+
this.ready();
|
|
1392
|
+
return ret;
|
|
1393
|
+
}
|
|
1394
|
+
compose(input) {
|
|
1395
|
+
input.slot && input.slot(this);
|
|
1396
|
+
return {};
|
|
1397
|
+
}
|
|
1398
|
+
/** To be overloaded: ready event handler */
|
|
1399
|
+
ready() {
|
|
1400
|
+
// empty
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Pushes a node to children immediately
|
|
1404
|
+
* @param node {Fragment} A node to push
|
|
1405
|
+
* @protected
|
|
1406
|
+
*/
|
|
1407
|
+
pushNode(node) {
|
|
1408
|
+
if (this.lastChild) {
|
|
1409
|
+
this.lastChild.$.next = node;
|
|
1410
|
+
}
|
|
1411
|
+
node.$.prev = this.lastChild;
|
|
1412
|
+
this.lastChild = node;
|
|
1413
|
+
this.children.add(node);
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Find first node in element if so exists
|
|
1417
|
+
* @return {?Element}
|
|
1418
|
+
* @protected
|
|
1419
|
+
*/
|
|
1420
|
+
findFirstChild() {
|
|
1421
|
+
let first;
|
|
1422
|
+
this.children.forEach(child => {
|
|
1423
|
+
first = first || child.findFirstChild();
|
|
1424
|
+
});
|
|
1425
|
+
return first;
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Append a node to end of element
|
|
1429
|
+
* @param node {Node} node to insert
|
|
1430
|
+
*/
|
|
1431
|
+
appendNode(node) {
|
|
1432
|
+
const $ = this.$;
|
|
1433
|
+
if ($.next) {
|
|
1434
|
+
$.next.insertAdjacent(node);
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
$.parent.appendNode(node);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Insert a node as a sibling of this
|
|
1442
|
+
* @param node {Node} node to insert
|
|
1443
|
+
*/
|
|
1444
|
+
insertAdjacent(node) {
|
|
1445
|
+
const child = this.findFirstChild();
|
|
1446
|
+
const $ = this.$;
|
|
1447
|
+
if (child) {
|
|
1448
|
+
child.parentElement.insertBefore(node, child);
|
|
1449
|
+
}
|
|
1450
|
+
else if ($.next) {
|
|
1451
|
+
$.next.insertAdjacent(node);
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
$.parent.appendNode(node);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Defines a text fragment
|
|
1459
|
+
* @param text {String | IValue} A text fragment string
|
|
1460
|
+
* @param cb {function (TextNode)} Callback if previous is slot name
|
|
1461
|
+
*/
|
|
1462
|
+
text(text, cb) {
|
|
1463
|
+
const $ = this.$;
|
|
1464
|
+
const node = new TextNode();
|
|
1465
|
+
node.preinit($.app, this, text);
|
|
1466
|
+
this.pushNode(node);
|
|
1467
|
+
cb && cb(node);
|
|
1468
|
+
}
|
|
1469
|
+
debug(text) {
|
|
1470
|
+
if (this.$.app.debugUi) {
|
|
1471
|
+
const node = new DebugNode();
|
|
1472
|
+
node.preinit(this.$.app, this, text);
|
|
1473
|
+
this.pushNode(node);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Defines a tag element
|
|
1478
|
+
* @param tagName {String} the tag name
|
|
1479
|
+
* @param input
|
|
1480
|
+
* @param cb {function(Tag, *)} callback
|
|
1481
|
+
*/
|
|
1482
|
+
tag(tagName, input, cb
|
|
1483
|
+
// @ts-ignore
|
|
1484
|
+
) {
|
|
1485
|
+
const $ = this.$;
|
|
1486
|
+
const node = new Tag(input);
|
|
1487
|
+
input.slot = cb || input.slot;
|
|
1488
|
+
node.preinit($.app, this, tagName);
|
|
1489
|
+
node.init();
|
|
1490
|
+
this.pushNode(node);
|
|
1491
|
+
node.ready();
|
|
1492
|
+
// @ts-ignore
|
|
1493
|
+
return node.node;
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Defines a custom element
|
|
1497
|
+
* @param node {Fragment} vasille element to insert
|
|
1498
|
+
* @param callback {function($ : *)}
|
|
1499
|
+
*/
|
|
1500
|
+
create(node, callback) {
|
|
1501
|
+
const $ = this.$;
|
|
1502
|
+
node.$.parent = this;
|
|
1503
|
+
node.preinit($.app, this);
|
|
1504
|
+
node.input.slot = callback || node.input.slot;
|
|
1505
|
+
this.pushNode(node);
|
|
1506
|
+
return node.init();
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Defines an if node
|
|
1510
|
+
* @param cond {IValue} condition
|
|
1511
|
+
* @param cb {function(Fragment)} callback to run on true
|
|
1512
|
+
* @return {this}
|
|
1513
|
+
*/
|
|
1514
|
+
if(cond, cb) {
|
|
1515
|
+
const node = new SwitchedNode();
|
|
1516
|
+
node.preinit(this.$.app, this);
|
|
1517
|
+
node.init();
|
|
1518
|
+
this.pushNode(node);
|
|
1519
|
+
node.addCase(this.case(cond, cb));
|
|
1520
|
+
node.ready();
|
|
1521
|
+
}
|
|
1522
|
+
else(cb) {
|
|
1523
|
+
if (this.lastChild instanceof SwitchedNode) {
|
|
1524
|
+
this.lastChild.addCase(this.default(cb));
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
throw userError('wrong `else` function use', 'logic-error');
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
elif(cond, cb) {
|
|
1531
|
+
if (this.lastChild instanceof SwitchedNode) {
|
|
1532
|
+
this.lastChild.addCase(this.case(cond, cb));
|
|
1533
|
+
}
|
|
1534
|
+
else {
|
|
1535
|
+
throw userError('wrong `elif` function use', 'logic-error');
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Create a case for switch
|
|
1540
|
+
* @param cond {IValue<boolean>}
|
|
1541
|
+
* @param cb {function(Fragment) : void}
|
|
1542
|
+
* @return {{cond : IValue, cb : (function(Fragment) : void)}}
|
|
1543
|
+
*/
|
|
1544
|
+
case(cond, cb) {
|
|
1545
|
+
return { cond, cb };
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* @param cb {(function(Fragment) : void)}
|
|
1549
|
+
* @return {{cond : IValue, cb : (function(Fragment) : void)}}
|
|
1550
|
+
*/
|
|
1551
|
+
default(cb) {
|
|
1552
|
+
return { cond: trueIValue, cb };
|
|
1553
|
+
}
|
|
1554
|
+
insertBefore(node) {
|
|
1555
|
+
const $ = this.$;
|
|
1556
|
+
node.$.prev = $.prev;
|
|
1557
|
+
node.$.next = this;
|
|
1558
|
+
if ($.prev) {
|
|
1559
|
+
$.prev.$.next = node;
|
|
1560
|
+
}
|
|
1561
|
+
$.prev = node;
|
|
1562
|
+
}
|
|
1563
|
+
insertAfter(node) {
|
|
1564
|
+
const $ = this.$;
|
|
1565
|
+
node.$.prev = this;
|
|
1566
|
+
node.$.next = $.next;
|
|
1567
|
+
$.next = node;
|
|
1568
|
+
}
|
|
1569
|
+
remove() {
|
|
1570
|
+
const $ = this.$;
|
|
1571
|
+
if ($.next) {
|
|
1572
|
+
$.next.$.prev = $.prev;
|
|
1573
|
+
}
|
|
1574
|
+
if ($.prev) {
|
|
1575
|
+
$.prev.$.next = $.next;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
$destroy() {
|
|
1579
|
+
this.children.forEach(child => child.$destroy());
|
|
1580
|
+
this.children.clear();
|
|
1581
|
+
this.lastChild = null;
|
|
1582
|
+
if (this.$.parent.lastChild === this) {
|
|
1583
|
+
this.$.parent.lastChild = this.$.prev;
|
|
1584
|
+
}
|
|
1585
|
+
super.$destroy();
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
const trueIValue = new Reference(true);
|
|
1589
|
+
/**
|
|
1590
|
+
* The private part of a text node
|
|
1591
|
+
* @class TextNodePrivate
|
|
1592
|
+
* @extends FragmentPrivate
|
|
1593
|
+
*/
|
|
1594
|
+
class TextNodePrivate extends FragmentPrivate {
|
|
1595
|
+
constructor() {
|
|
1596
|
+
super();
|
|
1597
|
+
this.$seal();
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Pre-initializes a text node
|
|
1601
|
+
* @param app {AppNode} the app node
|
|
1602
|
+
* @param parent
|
|
1603
|
+
* @param text {IValue}
|
|
1604
|
+
*/
|
|
1605
|
+
preinitText(app, parent, text) {
|
|
1606
|
+
super.preinit(app, parent);
|
|
1607
|
+
this.node = document.createTextNode(text instanceof IValue ? text.$ : text);
|
|
1608
|
+
if (text instanceof IValue) {
|
|
1609
|
+
this.bindings.add(new Expression((v) => {
|
|
1610
|
+
this.node.replaceData(0, -1, v);
|
|
1611
|
+
}, true, text));
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Clear node data
|
|
1616
|
+
*/
|
|
1617
|
+
$destroy() {
|
|
1618
|
+
super.$destroy();
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Represents a text node
|
|
1623
|
+
* @class TextNode
|
|
1624
|
+
* @extends Fragment
|
|
1625
|
+
*/
|
|
1626
|
+
class TextNode extends Fragment {
|
|
1627
|
+
constructor($ = new TextNodePrivate()) {
|
|
1628
|
+
super({}, $);
|
|
1629
|
+
this.$seal();
|
|
1630
|
+
}
|
|
1631
|
+
preinit(app, parent, text) {
|
|
1632
|
+
const $ = this.$;
|
|
1633
|
+
if (!text) {
|
|
1634
|
+
throw internalError('wrong TextNode::$preninit call');
|
|
1635
|
+
}
|
|
1636
|
+
$.preinitText(app, parent, text);
|
|
1637
|
+
$.parent.appendNode($.node);
|
|
1638
|
+
}
|
|
1639
|
+
findFirstChild() {
|
|
1640
|
+
return this.$.node;
|
|
1641
|
+
}
|
|
1642
|
+
$destroy() {
|
|
1643
|
+
this.$.node.remove();
|
|
1644
|
+
this.$.$destroy();
|
|
1645
|
+
super.$destroy();
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* The private part of a base node
|
|
1650
|
+
* @class INodePrivate
|
|
1651
|
+
* @extends FragmentPrivate
|
|
1652
|
+
*/
|
|
1653
|
+
class INodePrivate extends FragmentPrivate {
|
|
1654
|
+
constructor() {
|
|
1655
|
+
super();
|
|
1656
|
+
/**
|
|
1657
|
+
* Defines if node is unmounted
|
|
1658
|
+
* @type {boolean}
|
|
1659
|
+
*/
|
|
1660
|
+
this.unmounted = false;
|
|
1661
|
+
this.$seal();
|
|
1662
|
+
}
|
|
1663
|
+
$destroy() {
|
|
1664
|
+
super.$destroy();
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Vasille node which can manipulate an element node
|
|
1669
|
+
* @class INode
|
|
1670
|
+
* @extends Fragment
|
|
1671
|
+
*/
|
|
1672
|
+
class INode extends Fragment {
|
|
1673
|
+
/**
|
|
1674
|
+
* Constructs a base node
|
|
1675
|
+
* @param input
|
|
1676
|
+
* @param $ {?INodePrivate}
|
|
1677
|
+
*/
|
|
1678
|
+
constructor(input, $) {
|
|
1679
|
+
super(input, $ || new INodePrivate);
|
|
1680
|
+
this.$seal();
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Get the bound node
|
|
1684
|
+
*/
|
|
1685
|
+
get node() {
|
|
1686
|
+
return this.$.node;
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Bind attribute value
|
|
1690
|
+
* @param name {String} name of attribute
|
|
1691
|
+
* @param value {IValue} value
|
|
1692
|
+
*/
|
|
1693
|
+
attr(name, value) {
|
|
1694
|
+
const $ = this.$;
|
|
1695
|
+
const attr = new AttributeBinding(this, name, value);
|
|
1696
|
+
$.bindings.add(attr);
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Set attribute value
|
|
1700
|
+
* @param name {string} name of attribute
|
|
1701
|
+
* @param value {string} value
|
|
1702
|
+
*/
|
|
1703
|
+
setAttr(name, value) {
|
|
1704
|
+
if (typeof value === 'boolean') {
|
|
1705
|
+
if (value) {
|
|
1706
|
+
this.$.node.setAttribute(name, "");
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
else {
|
|
1710
|
+
this.$.node.setAttribute(name, `${value}`);
|
|
1711
|
+
}
|
|
1712
|
+
return this;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Adds a CSS class
|
|
1716
|
+
* @param cl {string} Class name
|
|
1717
|
+
*/
|
|
1718
|
+
addClass(cl) {
|
|
1719
|
+
this.$.node.classList.add(cl);
|
|
1720
|
+
return this;
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Adds some CSS classes
|
|
1724
|
+
* @param cls {...string} classes names
|
|
1725
|
+
*/
|
|
1726
|
+
removeClasse(cl) {
|
|
1727
|
+
this.$.node.classList.remove(cl);
|
|
1728
|
+
return this;
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Bind a CSS class
|
|
1732
|
+
* @param className {IValue}
|
|
1733
|
+
*/
|
|
1734
|
+
bindClass(className) {
|
|
1735
|
+
const $ = this.$;
|
|
1736
|
+
$.bindings.add(new DynamicalClassBinding(this, className));
|
|
1737
|
+
return this;
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Bind a floating class name
|
|
1741
|
+
* @param cond {IValue} condition
|
|
1742
|
+
* @param className {string} class name
|
|
1743
|
+
*/
|
|
1744
|
+
floatingClass(cond, className) {
|
|
1745
|
+
this.$.bindings.add(new StaticClassBinding(this, className, cond));
|
|
1746
|
+
return this;
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Defines a style attribute
|
|
1750
|
+
* @param name {String} name of style attribute
|
|
1751
|
+
* @param value {IValue} value
|
|
1752
|
+
*/
|
|
1753
|
+
style(name, value) {
|
|
1754
|
+
const $ = this.$;
|
|
1755
|
+
if ($.node instanceof HTMLElement) {
|
|
1756
|
+
$.bindings.add(new StyleBinding(this, name, value));
|
|
1757
|
+
}
|
|
1758
|
+
else {
|
|
1759
|
+
throw userError('style can be applied to HTML elements only', 'non-html-element');
|
|
1760
|
+
}
|
|
1761
|
+
return this;
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Sets a style property value
|
|
1765
|
+
* @param prop {string} Property name
|
|
1766
|
+
* @param value {string} Property value
|
|
1767
|
+
*/
|
|
1768
|
+
setStyle(prop, value) {
|
|
1769
|
+
if (this.$.node instanceof HTMLElement) {
|
|
1770
|
+
this.$.node.style.setProperty(prop, value);
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
throw userError("Style can be set for HTML elements only", "non-html-element");
|
|
1774
|
+
}
|
|
1775
|
+
return this;
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Add a listener for an event
|
|
1779
|
+
* @param name {string} Event name
|
|
1780
|
+
* @param handler {function (Event)} Event handler
|
|
1781
|
+
* @param options {Object | boolean} addEventListener options
|
|
1782
|
+
*/
|
|
1783
|
+
listen(name, handler, options) {
|
|
1784
|
+
this.$.node.addEventListener(name, handler, options);
|
|
1785
|
+
return this;
|
|
1786
|
+
}
|
|
1787
|
+
insertAdjacent(node) {
|
|
1788
|
+
this.$.node.parentNode.insertBefore(node, this.$.node);
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* A v-show & ngShow alternative
|
|
1792
|
+
* @param cond {IValue} show condition
|
|
1793
|
+
*/
|
|
1794
|
+
bindShow(cond) {
|
|
1795
|
+
const $ = this.$;
|
|
1796
|
+
const node = $.node;
|
|
1797
|
+
if (node instanceof HTMLElement) {
|
|
1798
|
+
let lastDisplay = node.style.display;
|
|
1799
|
+
const htmlNode = node;
|
|
1800
|
+
return this.bindAlive(cond, () => {
|
|
1801
|
+
lastDisplay = htmlNode.style.display;
|
|
1802
|
+
htmlNode.style.display = 'none';
|
|
1803
|
+
}, () => {
|
|
1804
|
+
htmlNode.style.display = lastDisplay;
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
else {
|
|
1808
|
+
throw userError('the element must be a html element', 'bind-show');
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* bind HTML
|
|
1813
|
+
* @param value {IValue}
|
|
1814
|
+
*/
|
|
1815
|
+
bindDomApi(name, value) {
|
|
1816
|
+
const $ = this.$;
|
|
1817
|
+
const node = $.node;
|
|
1818
|
+
if (node instanceof HTMLElement) {
|
|
1819
|
+
node[name] = value.$;
|
|
1820
|
+
this.watch((v) => {
|
|
1821
|
+
node[name] = v;
|
|
1822
|
+
}, value);
|
|
1823
|
+
}
|
|
1824
|
+
else {
|
|
1825
|
+
throw userError("HTML can be bound for HTML nodes only", "dom-error");
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
applyOptions(options) {
|
|
1829
|
+
options["v:attr"] && Object.keys(options["v:attr"]).forEach(name => {
|
|
1830
|
+
const value = options["v:attr"][name];
|
|
1831
|
+
if (value instanceof IValue) {
|
|
1832
|
+
this.attr(name, value);
|
|
1833
|
+
}
|
|
1834
|
+
else {
|
|
1835
|
+
this.setAttr(name, value);
|
|
1836
|
+
}
|
|
1837
|
+
});
|
|
1838
|
+
if (options.class) {
|
|
1839
|
+
const handleClass = (name, value) => {
|
|
1840
|
+
if (value instanceof IValue) {
|
|
1841
|
+
this.floatingClass(value, name);
|
|
1842
|
+
}
|
|
1843
|
+
else if (value && name !== '$') {
|
|
1844
|
+
this.addClass(name);
|
|
1845
|
+
}
|
|
1846
|
+
else {
|
|
1847
|
+
this.removeClasse(name);
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
if (Array.isArray(options.class)) {
|
|
1851
|
+
options.class.forEach(item => {
|
|
1852
|
+
if (item instanceof IValue) {
|
|
1853
|
+
this.bindClass(item);
|
|
1854
|
+
}
|
|
1855
|
+
else if (typeof item == "string") {
|
|
1856
|
+
this.addClass(item);
|
|
1857
|
+
}
|
|
1858
|
+
else {
|
|
1859
|
+
Reflect.ownKeys(item).forEach((name) => {
|
|
1860
|
+
handleClass(name, item[name]);
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
else {
|
|
1866
|
+
options.class.$.forEach(item => {
|
|
1867
|
+
this.bindClass(item);
|
|
1868
|
+
});
|
|
1869
|
+
Reflect.ownKeys(options.class).forEach((name) => {
|
|
1870
|
+
handleClass(name, options.class[name]);
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
options.style && Object.keys(options.style).forEach(name => {
|
|
1875
|
+
const value = options.style[name];
|
|
1876
|
+
if (value instanceof IValue) {
|
|
1877
|
+
this.style(name, value);
|
|
1878
|
+
}
|
|
1879
|
+
else if (typeof value === "string") {
|
|
1880
|
+
this.setStyle(name, value);
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
if (value[0] instanceof IValue) {
|
|
1884
|
+
this.style(name, this.expr((v) => v + value[1], value[0]));
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
this.setStyle(name, value[0] + value[1]);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
});
|
|
1891
|
+
options["v:events"] && Object.keys(options["v:events"]).forEach(name => {
|
|
1892
|
+
this.listen(name, options["v:events"][name]);
|
|
1893
|
+
});
|
|
1894
|
+
if (options["v:bind"]) {
|
|
1895
|
+
const inode = this.node;
|
|
1896
|
+
Reflect.ownKeys(options["v:bind"]).forEach((k) => {
|
|
1897
|
+
const value = options["v:bind"][k];
|
|
1898
|
+
if (k === 'value' && (inode instanceof HTMLInputElement || inode instanceof HTMLTextAreaElement)) {
|
|
1899
|
+
inode.oninput = () => value.$ = inode.value;
|
|
1900
|
+
}
|
|
1901
|
+
else if (k === 'checked' && inode instanceof HTMLInputElement) {
|
|
1902
|
+
inode.oninput = () => value.$ = inode.checked;
|
|
1903
|
+
}
|
|
1904
|
+
else if (k === 'volume' && inode instanceof HTMLMediaElement) {
|
|
1905
|
+
inode.onvolumechange = () => value.$ = inode.volume;
|
|
1906
|
+
}
|
|
1907
|
+
this.bindDomApi(k, value);
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
options["v:set"] && Object.keys(options["v:set"]).forEach(key => {
|
|
1911
|
+
this.node[key] = options["v:set"][key];
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Represents an Vasille.js HTML element node
|
|
1917
|
+
* @class Tag
|
|
1918
|
+
* @extends INode
|
|
1919
|
+
*/
|
|
1920
|
+
class Tag extends INode {
|
|
1921
|
+
constructor(input) {
|
|
1922
|
+
super(input);
|
|
1923
|
+
this.$seal();
|
|
1924
|
+
}
|
|
1925
|
+
preinit(app, parent, tagName) {
|
|
1926
|
+
if (!tagName || typeof tagName !== "string") {
|
|
1927
|
+
throw internalError('wrong Tag::$preinit call');
|
|
1928
|
+
}
|
|
1929
|
+
const node = document.createElement(tagName);
|
|
1930
|
+
const $ = this.$;
|
|
1931
|
+
$.preinit(app, parent);
|
|
1932
|
+
$.node = node;
|
|
1933
|
+
$.parent.appendNode(node);
|
|
1934
|
+
}
|
|
1935
|
+
compose(input) {
|
|
1936
|
+
input.slot && input.slot(this);
|
|
1937
|
+
return {};
|
|
1938
|
+
}
|
|
1939
|
+
findFirstChild() {
|
|
1940
|
+
return this.$.unmounted ? null : this.$.node;
|
|
1941
|
+
}
|
|
1942
|
+
insertAdjacent(node) {
|
|
1943
|
+
if (this.$.unmounted) {
|
|
1944
|
+
if (this.$.next) {
|
|
1945
|
+
this.$.next.insertAdjacent(node);
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
this.$.parent.appendNode(node);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
else {
|
|
1952
|
+
super.insertAdjacent(node);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
appendNode(node) {
|
|
1956
|
+
this.$.node.appendChild(node);
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Mount/Unmount a node
|
|
1960
|
+
* @param cond {IValue} show condition
|
|
1961
|
+
*/
|
|
1962
|
+
bindMount(cond) {
|
|
1963
|
+
const $ = this.$;
|
|
1964
|
+
this.bindAlive(cond, () => {
|
|
1965
|
+
$.node.remove();
|
|
1966
|
+
$.unmounted = true;
|
|
1967
|
+
}, () => {
|
|
1968
|
+
if ($.unmounted) {
|
|
1969
|
+
this.insertAdjacent($.node);
|
|
1970
|
+
$.unmounted = false;
|
|
1971
|
+
}
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Runs GC
|
|
1976
|
+
*/
|
|
1977
|
+
$destroy() {
|
|
1978
|
+
this.node.remove();
|
|
1979
|
+
super.$destroy();
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Represents a vasille extension node
|
|
1984
|
+
* @class Extension
|
|
1985
|
+
* @extends INode
|
|
1986
|
+
*/
|
|
1987
|
+
class Extension extends INode {
|
|
1988
|
+
preinit(app, parent) {
|
|
1989
|
+
const $ = this.$;
|
|
1990
|
+
let it = parent;
|
|
1991
|
+
while (it && !(it instanceof INode)) {
|
|
1992
|
+
it = it.parent;
|
|
1993
|
+
}
|
|
1994
|
+
if (it && it instanceof INode) {
|
|
1995
|
+
$.node = it.node;
|
|
1996
|
+
}
|
|
1997
|
+
$.preinit(app, parent);
|
|
1998
|
+
if (!it) {
|
|
1999
|
+
throw userError("A extension node can be encapsulated only in a tag/extension/component", "virtual-dom");
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
extend(options) {
|
|
2003
|
+
this.applyOptions(options);
|
|
2004
|
+
}
|
|
2005
|
+
$destroy() {
|
|
2006
|
+
super.$destroy();
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Node which cas has just a child
|
|
2011
|
+
* @class Component
|
|
2012
|
+
* @extends Extension
|
|
2013
|
+
*/
|
|
2014
|
+
class Component extends Extension {
|
|
2015
|
+
init() {
|
|
2016
|
+
const ret = super.composeNow();
|
|
2017
|
+
this.ready();
|
|
2018
|
+
super.applyOptionsNow();
|
|
2019
|
+
return ret;
|
|
2020
|
+
}
|
|
2021
|
+
ready() {
|
|
2022
|
+
super.ready();
|
|
2023
|
+
if (this.children.size !== 1) {
|
|
2024
|
+
throw userError("Component must have a child only", "dom-error");
|
|
2025
|
+
}
|
|
2026
|
+
const child = this.lastChild;
|
|
2027
|
+
if (child instanceof Tag || child instanceof Component) {
|
|
2028
|
+
const $ = this.$;
|
|
2029
|
+
$.node = child.node;
|
|
2030
|
+
}
|
|
2031
|
+
else {
|
|
2032
|
+
throw userError("Component child must be Tag or Component", "dom-error");
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
preinit(app, parent) {
|
|
2036
|
+
this.$.preinit(app, parent);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Private part of switch node
|
|
2041
|
+
* @class SwitchedNodePrivate
|
|
2042
|
+
* @extends INodePrivate
|
|
2043
|
+
*/
|
|
2044
|
+
class SwitchedNodePrivate extends FragmentPrivate {
|
|
2045
|
+
constructor() {
|
|
2046
|
+
super();
|
|
2047
|
+
/**
|
|
2048
|
+
* Array of possible cases
|
|
2049
|
+
* @type {Array<{cond : IValue<boolean>, cb : function(Fragment)}>}
|
|
2050
|
+
*/
|
|
2051
|
+
this.cases = [];
|
|
2052
|
+
this.$seal();
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Runs GC
|
|
2056
|
+
*/
|
|
2057
|
+
$destroy() {
|
|
2058
|
+
this.cases.forEach(c => {
|
|
2059
|
+
delete c.cond;
|
|
2060
|
+
delete c.cb;
|
|
2061
|
+
});
|
|
2062
|
+
this.cases.splice(0);
|
|
2063
|
+
super.$destroy();
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Defines a node witch can switch its children conditionally
|
|
2068
|
+
*/
|
|
2069
|
+
class SwitchedNode extends Fragment {
|
|
2070
|
+
/**
|
|
2071
|
+
* Constructs a switch node and define a sync function
|
|
2072
|
+
*/
|
|
2073
|
+
constructor() {
|
|
2074
|
+
super({}, new SwitchedNodePrivate);
|
|
2075
|
+
this.$.sync = () => {
|
|
2076
|
+
const $ = this.$;
|
|
2077
|
+
let i = 0;
|
|
2078
|
+
for (; i < $.cases.length; i++) {
|
|
2079
|
+
if ($.cases[i].cond.$) {
|
|
2080
|
+
break;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (i === $.index) {
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
if (this.lastChild) {
|
|
2087
|
+
this.lastChild.$destroy();
|
|
2088
|
+
this.children.clear();
|
|
2089
|
+
this.lastChild = null;
|
|
2090
|
+
}
|
|
2091
|
+
if (i !== $.cases.length) {
|
|
2092
|
+
$.index = i;
|
|
2093
|
+
this.createChild($.cases[i].cb);
|
|
2094
|
+
}
|
|
2095
|
+
else {
|
|
2096
|
+
$.index = -1;
|
|
2097
|
+
}
|
|
2098
|
+
};
|
|
2099
|
+
this.$seal();
|
|
2100
|
+
}
|
|
2101
|
+
addCase(case_) {
|
|
2102
|
+
this.$.cases.push(case_);
|
|
2103
|
+
case_.cond.$on(this.$.sync);
|
|
2104
|
+
this.$.sync();
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Creates a child node
|
|
2108
|
+
* @param cb {function(Fragment)} Call-back
|
|
2109
|
+
*/
|
|
2110
|
+
createChild(cb) {
|
|
2111
|
+
const node = new Fragment({});
|
|
2112
|
+
node.preinit(this.$.app, this);
|
|
2113
|
+
node.init();
|
|
2114
|
+
this.lastChild = node;
|
|
2115
|
+
this.children.add(node);
|
|
2116
|
+
cb(node);
|
|
2117
|
+
}
|
|
2118
|
+
ready() {
|
|
2119
|
+
const $ = this.$;
|
|
2120
|
+
$.cases.forEach(c => {
|
|
2121
|
+
c.cond.$on($.sync);
|
|
2122
|
+
});
|
|
2123
|
+
$.sync();
|
|
2124
|
+
}
|
|
2125
|
+
$destroy() {
|
|
2126
|
+
const $ = this.$;
|
|
2127
|
+
$.cases.forEach(c => {
|
|
2128
|
+
c.cond.$off($.sync);
|
|
2129
|
+
});
|
|
2130
|
+
super.$destroy();
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* The private part of a text node
|
|
2135
|
+
*/
|
|
2136
|
+
class DebugPrivate extends FragmentPrivate {
|
|
2137
|
+
constructor() {
|
|
2138
|
+
super();
|
|
2139
|
+
this.$seal();
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Pre-initializes a text node
|
|
2143
|
+
* @param app {App} the app node
|
|
2144
|
+
* @param parent {Fragment} parent node
|
|
2145
|
+
* @param text {String | IValue}
|
|
2146
|
+
*/
|
|
2147
|
+
preinitComment(app, parent, text) {
|
|
2148
|
+
super.preinit(app, parent);
|
|
2149
|
+
this.node = document.createComment(text.$);
|
|
2150
|
+
this.bindings.add(new Expression((v) => {
|
|
2151
|
+
this.node.replaceData(0, -1, v);
|
|
2152
|
+
}, true, text));
|
|
2153
|
+
this.parent.appendNode(this.node);
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Clear node data
|
|
2157
|
+
*/
|
|
2158
|
+
$destroy() {
|
|
2159
|
+
this.node.remove();
|
|
2160
|
+
super.$destroy();
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Represents a debug node
|
|
2165
|
+
* @class DebugNode
|
|
2166
|
+
* @extends Fragment
|
|
2167
|
+
*/
|
|
2168
|
+
class DebugNode extends Fragment {
|
|
2169
|
+
constructor() {
|
|
2170
|
+
super({});
|
|
2171
|
+
/**
|
|
2172
|
+
* private data
|
|
2173
|
+
* @type {DebugNode}
|
|
2174
|
+
*/
|
|
2175
|
+
this.$ = new DebugPrivate();
|
|
2176
|
+
this.$seal();
|
|
2177
|
+
}
|
|
2178
|
+
preinit(app, parent, text) {
|
|
2179
|
+
const $ = this.$;
|
|
2180
|
+
if (!text) {
|
|
2181
|
+
throw internalError('wrong DebugNode::$preninit call');
|
|
2182
|
+
}
|
|
2183
|
+
$.preinitComment(app, parent, text);
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Runs garbage collector
|
|
2187
|
+
*/
|
|
2188
|
+
$destroy() {
|
|
2189
|
+
this.$.$destroy();
|
|
2190
|
+
super.$destroy();
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
window.FragmentPrivate = FragmentPrivate;
|
|
2195
|
+
window.Fragment = Fragment;
|
|
2196
|
+
window.TextNodePrivate = TextNodePrivate;
|
|
2197
|
+
window.TextNode = TextNode;
|
|
2198
|
+
window.INodePrivate = INodePrivate;
|
|
2199
|
+
window.INode = INode;
|
|
2200
|
+
window.Tag = Tag;
|
|
2201
|
+
window.Extension = Extension;
|
|
2202
|
+
window.Component = Component;
|
|
2203
|
+
window.SwitchedNodePrivate = SwitchedNodePrivate;
|
|
2204
|
+
window.SwitchedNode = SwitchedNode;
|
|
2205
|
+
window.DebugPrivate = DebugPrivate;
|
|
2206
|
+
window.DebugNode = DebugNode;
|
|
2207
|
+
|
|
2208
|
+
// ./lib/node/watch.js
|
|
2209
|
+
/**
|
|
2210
|
+
* Watch Node
|
|
2211
|
+
* @class Watch
|
|
2212
|
+
* @extends Fragment
|
|
2213
|
+
*/
|
|
2214
|
+
class Watch extends Fragment {
|
|
2215
|
+
compose(input) {
|
|
2216
|
+
this.watch((value) => {
|
|
2217
|
+
this.children.forEach(child => {
|
|
2218
|
+
child.$destroy();
|
|
2219
|
+
});
|
|
2220
|
+
this.children.clear();
|
|
2221
|
+
this.lastChild = null;
|
|
2222
|
+
input.slot && input.slot(this, value);
|
|
2223
|
+
}, input.model);
|
|
2224
|
+
input.slot(this, input.model.$);
|
|
2225
|
+
return {};
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
window.Watch = Watch;
|
|
2230
|
+
|
|
2231
|
+
// ./lib/views/repeat-node.js
|
|
2232
|
+
/**
|
|
2233
|
+
* Private part of repeat node
|
|
2234
|
+
* @class RepeatNodePrivate
|
|
2235
|
+
* @extends INodePrivate
|
|
2236
|
+
*/
|
|
2237
|
+
class RepeatNodePrivate extends INodePrivate {
|
|
2238
|
+
constructor() {
|
|
2239
|
+
super();
|
|
2240
|
+
/**
|
|
2241
|
+
* Children node hash
|
|
2242
|
+
* @type {Map}
|
|
2243
|
+
*/
|
|
2244
|
+
this.nodes = new Map();
|
|
2245
|
+
this.$seal();
|
|
2246
|
+
}
|
|
2247
|
+
$destroy() {
|
|
2248
|
+
this.nodes.clear();
|
|
2249
|
+
super.$destroy();
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Repeat node repeats its children
|
|
2254
|
+
* @class RepeatNode
|
|
2255
|
+
* @extends Fragment
|
|
2256
|
+
*/
|
|
2257
|
+
class RepeatNode extends Fragment {
|
|
2258
|
+
constructor(input, $) {
|
|
2259
|
+
super(input, $);
|
|
2260
|
+
/**
|
|
2261
|
+
* If false will use timeout executor, otherwise the app executor
|
|
2262
|
+
*/
|
|
2263
|
+
this.freezeUi = true;
|
|
2264
|
+
}
|
|
2265
|
+
createChild(opts, id, item, before) {
|
|
2266
|
+
const node = new Fragment({});
|
|
2267
|
+
this.destroyChild(id, item);
|
|
2268
|
+
if (before) {
|
|
2269
|
+
this.children.add(node);
|
|
2270
|
+
before.insertBefore(node);
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
const lastChild = this.lastChild;
|
|
2274
|
+
if (lastChild) {
|
|
2275
|
+
lastChild.insertAfter(node);
|
|
2276
|
+
}
|
|
2277
|
+
this.children.add(node);
|
|
2278
|
+
}
|
|
2279
|
+
this.lastChild = node;
|
|
2280
|
+
node.preinit(this.$.app, this);
|
|
2281
|
+
node.init();
|
|
2282
|
+
opts.slot && opts.slot(node, item, id);
|
|
2283
|
+
node.ready();
|
|
2284
|
+
this.$.nodes.set(id, node);
|
|
2285
|
+
}
|
|
2286
|
+
destroyChild(id, item) {
|
|
2287
|
+
const $ = this.$;
|
|
2288
|
+
const child = $.nodes.get(id);
|
|
2289
|
+
if (child) {
|
|
2290
|
+
child.remove();
|
|
2291
|
+
child.$destroy();
|
|
2292
|
+
this.$.nodes.delete(id);
|
|
2293
|
+
this.children.delete(child);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
window.RepeatNodePrivate = RepeatNodePrivate;
|
|
2299
|
+
window.RepeatNode = RepeatNode;
|
|
2300
|
+
|
|
2301
|
+
// ./lib/views/base-view.js
|
|
2302
|
+
/**
|
|
2303
|
+
* Private part of BaseView
|
|
2304
|
+
* @class BaseViewPrivate
|
|
2305
|
+
* @extends RepeatNodePrivate
|
|
2306
|
+
*/
|
|
2307
|
+
class BaseViewPrivate extends RepeatNodePrivate {
|
|
2308
|
+
constructor() {
|
|
2309
|
+
super();
|
|
2310
|
+
this.$seal();
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Base class of default views
|
|
2315
|
+
* @class BaseView
|
|
2316
|
+
* @extends RepeatNode
|
|
2317
|
+
* @implements IModel
|
|
2318
|
+
*/
|
|
2319
|
+
class BaseView extends RepeatNode {
|
|
2320
|
+
constructor(input, $) {
|
|
2321
|
+
super(input, $ || new BaseViewPrivate);
|
|
2322
|
+
}
|
|
2323
|
+
compose(input) {
|
|
2324
|
+
const $ = this.$;
|
|
2325
|
+
$.addHandler = (id, item) => {
|
|
2326
|
+
this.createChild(input, id, item);
|
|
2327
|
+
};
|
|
2328
|
+
$.removeHandler = (id, item) => {
|
|
2329
|
+
this.destroyChild(id, item);
|
|
2330
|
+
};
|
|
2331
|
+
input.model.listener.onAdd($.addHandler);
|
|
2332
|
+
input.model.listener.onRemove($.removeHandler);
|
|
2333
|
+
this.runOnDestroy(() => {
|
|
2334
|
+
input.model.listener.offAdd($.addHandler);
|
|
2335
|
+
input.model.listener.offRemove($.removeHandler);
|
|
2336
|
+
});
|
|
2337
|
+
return {};
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
window.BaseViewPrivate = BaseViewPrivate;
|
|
2342
|
+
window.BaseView = BaseView;
|
|
2343
|
+
|
|
2344
|
+
// ./lib/views/array-view.js
|
|
2345
|
+
/**
|
|
2346
|
+
* Represents a view of an array model
|
|
2347
|
+
* @class ArrayView
|
|
2348
|
+
* @extends BaseView
|
|
2349
|
+
*/
|
|
2350
|
+
class ArrayView extends BaseView {
|
|
2351
|
+
createChild(input, id, item, before) {
|
|
2352
|
+
super.createChild(input, item, item, before || this.$.nodes.get(id));
|
|
2353
|
+
}
|
|
2354
|
+
compose(input) {
|
|
2355
|
+
super.compose(input);
|
|
2356
|
+
input.model.forEach(item => {
|
|
2357
|
+
this.createChild(input, item, item);
|
|
2358
|
+
});
|
|
2359
|
+
return {};
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
window.ArrayView = ArrayView;
|
|
2364
|
+
|
|
2365
|
+
// ./lib/views/map-view.js
|
|
2366
|
+
/**
|
|
2367
|
+
* Create a children pack for each map value
|
|
2368
|
+
* @class MapView
|
|
2369
|
+
* @extends BaseView
|
|
2370
|
+
*/
|
|
2371
|
+
class MapView extends BaseView {
|
|
2372
|
+
compose(input) {
|
|
2373
|
+
super.compose(input);
|
|
2374
|
+
input.model.forEach((value, key) => {
|
|
2375
|
+
this.createChild(input, key, value);
|
|
2376
|
+
});
|
|
2377
|
+
return {};
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
window.MapView = MapView;
|
|
2382
|
+
|
|
2383
|
+
// ./lib/views/object-view.js
|
|
2384
|
+
/**
|
|
2385
|
+
* Create a children pack for each object field
|
|
2386
|
+
* @class ObjectView
|
|
2387
|
+
* @extends BaseView
|
|
2388
|
+
*/
|
|
2389
|
+
class ObjectView extends BaseView {
|
|
2390
|
+
compose(input) {
|
|
2391
|
+
super.compose(input);
|
|
2392
|
+
const obj = input.model.values;
|
|
2393
|
+
for (const key in obj) {
|
|
2394
|
+
this.createChild(input, key, obj[key]);
|
|
2395
|
+
}
|
|
2396
|
+
super.ready();
|
|
2397
|
+
return {};
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
window.ObjectView = ObjectView;
|
|
2402
|
+
|
|
2403
|
+
// ./lib/views/set-view.js
|
|
2404
|
+
/**
|
|
2405
|
+
* Create a children pack for each set value
|
|
2406
|
+
* @class SetView
|
|
2407
|
+
* @extends BaseView
|
|
2408
|
+
*/
|
|
2409
|
+
class SetView extends BaseView {
|
|
2410
|
+
compose(input) {
|
|
2411
|
+
super.compose(input);
|
|
2412
|
+
const set = input.model;
|
|
2413
|
+
set.forEach(item => {
|
|
2414
|
+
this.createChild(input, item, item);
|
|
2415
|
+
});
|
|
2416
|
+
return {};
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
window.SetView = SetView;
|
|
2421
|
+
|
|
2422
|
+
// ./lib/index.js
|
|
2423
|
+
|
|
2424
|
+
|
|
2425
|
+
|
|
2426
|
+
// ./lib/node/app.js
|
|
2427
|
+
/**
|
|
2428
|
+
* Application Node
|
|
2429
|
+
* @class AppNode
|
|
2430
|
+
* @extends INode
|
|
2431
|
+
*/
|
|
2432
|
+
class AppNode extends INode {
|
|
2433
|
+
/**
|
|
2434
|
+
* @param input
|
|
2435
|
+
*/
|
|
2436
|
+
constructor(input) {
|
|
2437
|
+
super(input);
|
|
2438
|
+
this.debugUi = input.debugUi || false;
|
|
2439
|
+
this.$seal();
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Represents a Vasille.js application
|
|
2444
|
+
* @class App
|
|
2445
|
+
* @extends AppNode
|
|
2446
|
+
*/
|
|
2447
|
+
class App extends AppNode {
|
|
2448
|
+
/**
|
|
2449
|
+
* Constructs an app node
|
|
2450
|
+
* @param node {Element} The root of application
|
|
2451
|
+
* @param input
|
|
2452
|
+
*/
|
|
2453
|
+
constructor(node, input) {
|
|
2454
|
+
super(input);
|
|
2455
|
+
this.$.node = node;
|
|
2456
|
+
this.preinit(this, this);
|
|
2457
|
+
this.init();
|
|
2458
|
+
this.$seal();
|
|
2459
|
+
}
|
|
2460
|
+
appendNode(node) {
|
|
2461
|
+
this.$.node.appendChild(node);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
class Portal extends AppNode {
|
|
2465
|
+
constructor(input) {
|
|
2466
|
+
super(input);
|
|
2467
|
+
this.$.node = input.node;
|
|
2468
|
+
this.$seal();
|
|
2469
|
+
}
|
|
2470
|
+
appendNode(node) {
|
|
2471
|
+
this.$.node.appendChild(node);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
window.AppNode = AppNode;
|
|
2476
|
+
window.App = App;
|
|
2477
|
+
window.Portal = Portal;
|
|
2478
|
+
|
|
2479
|
+
// ./lib/functional/options.js
|
|
2480
|
+
|
|
2481
|
+
|
|
2482
|
+
|
|
2483
|
+
})();
|