vasille 2.3.1 → 2.3.3

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 CHANGED
@@ -1,356 +1,743 @@
1
1
  (function(){
2
- // ./lib/models/model.js
3
-
4
-
5
-
6
- // ./lib/models/listener.js
2
+ // ./lib/core/destroyable.js
7
3
  /**
8
- * Represent a listener for a model
9
- * @class Listener
4
+ * Mark an object which can be destroyed
5
+ * @class Destroyable
10
6
  */
11
- class Listener {
12
- constructor() {
13
- Object.defineProperties(this, {
14
- onAdded: {
15
- value: new Set,
16
- writable: false,
17
- configurable: false
18
- },
19
- onRemoved: {
20
- value: new Set,
21
- writable: false,
22
- configurable: false
23
- },
24
- frozen: {
25
- value: false,
26
- writable: true,
27
- configurable: false
28
- },
29
- queue: {
30
- value: [],
31
- writable: false,
32
- configurable: false
33
- }
34
- });
35
- }
7
+ class Destroyable {
36
8
  /**
37
- * Exclude the repeated operation in queue
38
- * @private
9
+ * Make object fields non configurable
10
+ * @protected
39
11
  */
40
- excludeRepeat(index) {
41
- this.queue.forEach((item, i) => {
42
- if (item.index === index) {
43
- this.queue.splice(i, 1);
44
- return true;
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
+ }
45
38
  }
46
39
  });
47
- return false;
48
40
  }
49
41
  /**
50
- * Emits added event to listeners
51
- * @param index {*} index of value
52
- * @param value {*} value of added item
42
+ * Garbage collector method
53
43
  */
54
- emitAdded(index, value) {
55
- if (this.frozen) {
56
- if (!this.excludeRepeat(index)) {
57
- this.queue.push({ sign: true, index, value });
58
- }
59
- }
60
- else {
61
- this.onAdded.forEach(handler => {
62
- handler(index, value);
63
- });
64
- }
44
+ $destroy() {
45
+ // nothing here
65
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 {
66
76
  /**
67
- * Emits removed event to listeners
68
- * @param index {*} index of removed value
69
- * @param value {*} value of removed item
77
+ * Enable update handlers triggering
70
78
  */
71
- emitRemoved(index, value) {
72
- if (this.frozen) {
73
- if (!this.excludeRepeat(index)) {
74
- this.queue.push({ sign: false, index, value });
75
- }
76
- }
77
- else {
78
- this.onRemoved.forEach(handler => {
79
- handler(index, value);
80
- });
81
- }
79
+ $enable() {
80
+ throw notOverwritten();
82
81
  }
83
82
  /**
84
- * Adds a handler to added event
85
- * @param handler {function} function to run on event emitting
83
+ * disable update handlers triggering
86
84
  */
87
- onAdd(handler) {
88
- this.onAdded.add(handler);
85
+ $disable() {
86
+ throw notOverwritten();
89
87
  }
88
+ }
89
+ /**
90
+ * Interface which describes a value
91
+ * @class IValue
92
+ * @extends Destroyable
93
+ */
94
+ class IValue extends Switchable {
90
95
  /**
91
- * Adds a handler to removed event
92
- * @param handler {function} function to run on event emitting
96
+ * @param isEnabled {boolean} initial is enabled state
93
97
  */
94
- onRemove(handler) {
95
- this.onRemoved.add(handler);
98
+ constructor(isEnabled) {
99
+ super();
100
+ this.isEnabled = isEnabled;
96
101
  }
97
102
  /**
98
- * Removes an handler from added event
99
- * @param handler {function} handler to remove
103
+ * Get the encapsulated value
104
+ * @return {*} the encapsulated value
100
105
  */
101
- offAdd(handler) {
102
- this.onAdded.delete(handler);
106
+ get $() {
107
+ throw notOverwritten();
103
108
  }
104
109
  /**
105
- * Removes an handler form removed event
106
- * @param handler {function} handler to remove
110
+ * Sets the encapsulated value
111
+ * @param value {*} value to encapsulate
107
112
  */
108
- offRemove(handler) {
109
- this.onRemoved.delete(handler);
113
+ set $(value) {
114
+ throw notOverwritten();
110
115
  }
111
116
  /**
112
- * Run all queued operation and enable reactivity
117
+ * Add a new handler to value change
118
+ * @param handler {function(value : *)} the handler to add
113
119
  */
114
- enableReactivity() {
115
- this.queue.forEach(item => {
116
- if (item.sign) {
117
- this.onAdded.forEach(handler => {
118
- handler(item.index, item.value);
119
- });
120
- }
121
- else {
122
- this.onRemoved.forEach(handler => {
123
- handler(item.index, item.value);
124
- });
125
- }
126
- });
127
- this.queue.splice(0);
128
- this.frozen = false;
120
+ $on(handler) {
121
+ throw notOverwritten();
129
122
  }
130
123
  /**
131
- * Disable the reactivity and enable the queue
124
+ * Removes a handler of value change
125
+ * @param handler {function(value : *)} the handler to remove
132
126
  */
133
- disableReactivity() {
134
- this.frozen = true;
127
+ $off(handler) {
128
+ throw notOverwritten();
135
129
  }
136
130
  }
137
131
 
138
- window.Listener = Listener;
132
+ window.Switchable = Switchable;
133
+ window.IValue = IValue;
139
134
 
140
- // ./lib/models/object-model.js
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
+ }
141
145
  /**
142
- * Object based model
143
- * @extends Object
146
+ * Private stuff of a reactive object
147
+ * @class ReactivePrivate
148
+ * @extends Destroyable
144
149
  */
145
- class ObjectModel extends Object {
146
- /**
147
- * Constructs a object model
148
- * @param obj {Object} input data
149
- */
150
- constructor(obj = {}) {
150
+ class ReactivePrivate extends Destroyable {
151
+ constructor() {
151
152
  super();
152
- this.container = Object.create(null);
153
- Object.defineProperty(this, 'listener', {
154
- value: new Listener,
155
- writable: false,
156
- configurable: false
157
- });
158
- for (const i in obj) {
159
- Object.defineProperty(this.container, i, {
160
- value: obj[i],
161
- configurable: true,
162
- writable: true,
163
- enumerable: true
164
- });
165
- this.listener.emitAdded(i, obj[i]);
166
- }
167
- }
168
- /**
169
- * Gets a value of a field
170
- * @param key {string}
171
- * @return {*}
172
- */
173
- get(key) {
174
- return this.container[key];
175
- }
176
- /**
177
- * Sets an object property value
178
- * @param key {string} property name
179
- * @param v {*} property value
180
- * @return {ObjectModel} a pointer to this
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
181
205
  */
182
- set(key, v) {
183
- if (Reflect.has(this.container, key)) {
184
- this.listener.emitRemoved(key, this.container[key]);
185
- this.container[key] = v;
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;
186
290
  }
187
- else {
188
- Object.defineProperty(this.container, key, {
189
- value: v,
190
- configurable: true,
191
- writable: true,
192
- enumerable: true
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();
193
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
+ 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;
194
502
  }
195
- this.listener.emitAdded(key, this.container[key]);
196
503
  return this;
197
504
  }
198
- get values() {
199
- return this.container;
505
+ $destroy() {
506
+ this.$disable();
507
+ this.values.splice(0);
508
+ this.valuesCache.splice(0);
509
+ this.linkedFunc.splice(0);
510
+ super.$destroy();
200
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 {
201
524
  /**
202
- * Deletes an object property
203
- * @param key {string} property name
525
+ * Constructs a notifiable bind to a value
526
+ * @param value {IValue} is initial value
527
+ * @param forwardOnly {boolean} ensure forward only synchronization
204
528
  */
205
- delete(key) {
206
- if (this.container[key]) {
207
- this.listener.emitRemoved(key, this.container[key]);
208
- delete this.container[key];
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;
209
548
  }
549
+ // this is a ts bug
550
+ // eslint-disable-next-line
551
+ // @ts-ignore
552
+ super.$ = v;
210
553
  }
211
- enableReactivity() {
212
- this.listener.enableReactivity();
554
+ $enable() {
555
+ if (!this.isEnabled) {
556
+ this.isEnabled = true;
557
+ this.$pointedValue.$on(this.$handler);
558
+ this.$ = this.$pointedValue.$;
559
+ }
213
560
  }
214
- disableReactivity() {
215
- this.listener.disableReactivity();
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();
216
570
  }
217
571
  }
218
572
 
219
- window.ObjectModel = ObjectModel;
573
+ window.Mirror = Mirror;
220
574
 
221
- // ./lib/models/set-model.js
575
+ // ./lib/value/pointer.js
222
576
  /**
223
- * A Set based model
224
- * @class SetModel
225
- * @extends Set
226
- * @implements IModel
577
+ * r/w pointer to a value
578
+ * @class Pointer
579
+ * @extends Mirror
227
580
  */
228
- class SetModel extends Set {
581
+ class Pointer extends Mirror {
229
582
  /**
230
- * Constructs a set model based on a set
231
- * @param set {Set} input data
583
+ * @param value {IValue} value to point
584
+ * @param forwardOnly {boolean} forward only data flow
232
585
  */
233
- constructor(set = []) {
234
- super();
235
- Object.defineProperty(this, 'listener', {
236
- value: new Listener,
237
- writable: false,
238
- configurable: false
239
- });
240
- set.forEach(item => {
241
- super.add(item);
242
- });
586
+ constructor(value, forwardOnly = false) {
587
+ super(value, forwardOnly);
243
588
  }
244
589
  /**
245
- * Calls Set.add and notify abut changes
246
- * @param value {*} value
247
- * @return {this} a pointer to this
590
+ * Point a new ivalue
591
+ * @param value {IValue} value to point
248
592
  */
249
- add(value) {
250
- if (!super.has(value)) {
251
- this.listener.emitAdded(value, value);
252
- super.add(value);
593
+ set $$(value) {
594
+ if (this.$pointedValue !== value) {
595
+ this.$disable();
596
+ this.$pointedValue = value;
597
+ this.$enable();
253
598
  }
254
- return this;
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
+ });
255
633
  }
256
634
  /**
257
- * Calls Set.clear and notify abut changes
635
+ * Exclude the repeated operation in queue
636
+ * @private
258
637
  */
259
- clear() {
260
- this.forEach(item => {
261
- this.listener.emitRemoved(item, item);
638
+ excludeRepeat(index) {
639
+ this.queue.forEach((item, i) => {
640
+ if (item.index === index) {
641
+ this.queue.splice(i, 1);
642
+ return true;
643
+ }
262
644
  });
263
- super.clear();
645
+ return false;
264
646
  }
265
647
  /**
266
- * Calls Set.delete and notify abut changes
267
- * @param value {*}
268
- * @return {boolean} true if a value was deleted, otherwise false
648
+ * Emits added event to listeners
649
+ * @param index {*} index of value
650
+ * @param value {*} value of added item
269
651
  */
270
- delete(value) {
271
- if (super.has(value)) {
272
- this.listener.emitRemoved(value, value);
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
+ });
273
662
  }
274
- return super.delete(value);
275
- }
276
- enableReactivity() {
277
- this.listener.enableReactivity();
278
- }
279
- disableReactivity() {
280
- this.listener.disableReactivity();
281
663
  }
282
- }
283
-
284
- window.SetModel = SetModel;
285
-
286
- // ./lib/models/map-model.js
287
- /**
288
- * A Map based memory
289
- * @class MapModel
290
- * @extends Map
291
- * @implements IModel
292
- */
293
- class MapModel extends Map {
294
664
  /**
295
- * Constructs a map model
296
- * @param map {[*, *][]} input data
665
+ * Emits removed event to listeners
666
+ * @param index {*} index of removed value
667
+ * @param value {*} value of removed item
297
668
  */
298
- constructor(map = []) {
299
- super();
300
- Object.defineProperty(this, 'listener', {
301
- value: new Listener,
302
- writable: false,
303
- configurable: false
304
- });
305
- map.forEach(([key, value]) => {
306
- super.set(key, value);
307
- });
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
+ }
308
680
  }
309
681
  /**
310
- * Calls Map.clear and notify abut changes
682
+ * Adds a handler to added event
683
+ * @param handler {function} function to run on event emitting
311
684
  */
312
- clear() {
313
- this.forEach((value, key) => {
314
- this.listener.emitRemoved(key, value);
315
- });
316
- super.clear();
685
+ onAdd(handler) {
686
+ this.onAdded.add(handler);
317
687
  }
318
688
  /**
319
- * Calls Map.delete and notify abut changes
320
- * @param key {*} key
321
- * @return {boolean} true if removed something, otherwise false
689
+ * Adds a handler to removed event
690
+ * @param handler {function} function to run on event emitting
322
691
  */
323
- delete(key) {
324
- const tmp = super.get(key);
325
- if (tmp) {
326
- this.listener.emitRemoved(key, tmp);
327
- }
328
- return super.delete(key);
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);
329
701
  }
330
702
  /**
331
- * Calls Map.set and notify abut changes
332
- * @param key {*} key
333
- * @param value {*} value
334
- * @return {MapModel} a pointer to this
703
+ * Removes an handler form removed event
704
+ * @param handler {function} handler to remove
335
705
  */
336
- set(key, value) {
337
- const tmp = super.get(key);
338
- if (tmp) {
339
- this.listener.emitRemoved(key, tmp);
340
- }
341
- super.set(key, value);
342
- this.listener.emitAdded(key, value);
343
- return this;
706
+ offRemove(handler) {
707
+ this.onRemoved.delete(handler);
344
708
  }
709
+ /**
710
+ * Run all queued operation and enable reactivity
711
+ */
345
712
  enableReactivity() {
346
- this.listener.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;
347
727
  }
728
+ /**
729
+ * Disable the reactivity and enable the queue
730
+ */
348
731
  disableReactivity() {
349
- this.listener.disableReactivity();
732
+ this.frozen = true;
350
733
  }
351
734
  }
352
735
 
353
- window.MapModel = MapModel;
736
+ window.Listener = Listener;
737
+
738
+ // ./lib/models/model.js
739
+
740
+
354
741
 
355
742
  // ./lib/models/array-model.js
356
743
  /**
@@ -554,399 +941,247 @@ class ArrayModel extends Array {
554
941
  return this;
555
942
  }
556
943
  replace(at, with_) {
557
- this.listener.emitAdded(this[at], with_);
558
- this.listener.emitRemoved(this[at], this[at]);
559
- this[at] = with_;
560
- return this;
561
- }
562
- enableReactivity() {
563
- this.listener.enableReactivity();
564
- }
565
- disableReactivity() {
566
- this.listener.disableReactivity();
567
- }
568
- }
569
-
570
- window.ArrayModel = ArrayModel;
571
-
572
- // ./lib/core/errors.js
573
- const reportIt = "Report it here: https://gitlab.com/vasille-js/vasille-js/-/issues";
574
- function notOverwritten() {
575
- console.error("Vasille-SFP: Internal error", "Must be overwritten", reportIt);
576
- return "not-overwritten";
577
- }
578
- function internalError(msg) {
579
- console.error("Vasille-SFP: Internal error", msg, reportIt);
580
- return "internal-error";
581
- }
582
- function userError(msg, err) {
583
- console.error("Vasille-SFP: User error", msg);
584
- return err;
585
- }
586
- function wrongBinding(msg) {
587
- return userError(msg, "wrong-binding");
588
- }
589
-
590
- window.notOverwritten = notOverwritten;
591
- window.internalError = internalError;
592
- window.userError = userError;
593
- window.wrongBinding = wrongBinding;
594
-
595
- // ./lib/core/destroyable.js
596
- /**
597
- * Mark an object which can be destroyed
598
- * @class Destroyable
599
- */
600
- class Destroyable {
601
- /**
602
- * Make object fields non configurable
603
- * @protected
604
- */
605
- $seal() {
606
- const $ = this;
607
- Object.keys($).forEach(i => {
608
- // eslint-disable-next-line no-prototype-builtins
609
- if (this.hasOwnProperty(i)) {
610
- const config = Object.getOwnPropertyDescriptor($, i);
611
- if (config.configurable) {
612
- let descriptor;
613
- if (config.set || config.get) {
614
- descriptor = {
615
- configurable: false,
616
- get: config.get,
617
- set: config.set,
618
- enumerable: config.enumerable
619
- };
620
- }
621
- else {
622
- descriptor = {
623
- value: $[i],
624
- configurable: false,
625
- writable: config.writable,
626
- enumerable: config.enumerable
627
- };
628
- }
629
- Object.defineProperty($, i, descriptor);
630
- }
631
- }
632
- });
633
- }
634
- /**
635
- * Garbage collector method
636
- */
637
- $destroy() {
638
- // nothing here
639
- }
640
- }
641
-
642
- window.Destroyable = Destroyable;
643
-
644
- // ./lib/core/ivalue.js
645
- class Switchable extends Destroyable {
646
- /**
647
- * Enable update handlers triggering
648
- */
649
- $enable() {
650
- throw notOverwritten();
651
- }
652
- /**
653
- * disable update handlers triggering
654
- */
655
- $disable() {
656
- throw notOverwritten();
657
- }
658
- }
659
- /**
660
- * Interface which describes a value
661
- * @class IValue
662
- * @extends Destroyable
663
- */
664
- class IValue extends Switchable {
665
- /**
666
- * @param isEnabled {boolean} initial is enabled state
667
- */
668
- constructor(isEnabled) {
669
- super();
670
- this.isEnabled = isEnabled;
671
- }
672
- /**
673
- * Get the encapsulated value
674
- * @return {*} the encapsulated value
675
- */
676
- get $() {
677
- throw notOverwritten();
678
- }
679
- /**
680
- * Sets the encapsulated value
681
- * @param value {*} value to encapsulate
682
- */
683
- set $(value) {
684
- throw notOverwritten();
685
- }
686
- /**
687
- * Add a new handler to value change
688
- * @param handler {function(value : *)} the handler to add
689
- */
690
- $on(handler) {
691
- throw notOverwritten();
692
- }
693
- /**
694
- * Removes a handler of value change
695
- * @param handler {function(value : *)} the handler to remove
696
- */
697
- $off(handler) {
698
- throw notOverwritten();
699
- }
700
- }
701
-
702
- window.Switchable = Switchable;
703
- window.IValue = IValue;
704
-
705
- // ./lib/index.js
706
-
707
-
708
-
709
- // ./lib/spec/svg.js
710
-
711
-
712
-
713
- // ./lib/spec/react.js
714
-
715
-
716
-
717
- // ./lib/spec/html.js
718
-
719
-
720
-
721
- // ./lib/value/expression.js
722
- /**
723
- * Bind some values to one expression
724
- * @class Expression
725
- * @extends IValue
726
- */
727
- class Expression extends IValue {
728
- /**
729
- * Creates a function bounded to N values
730
- * @param func {Function} the function to bound
731
- * @param values
732
- * @param link {Boolean} links immediately if true
733
- */
734
- constructor(func, link, ...values) {
735
- super(false);
736
- /**
737
- * Expression will link different handler for each value of list
738
- */
739
- this.linkedFunc = [];
740
- const handler = (i) => {
741
- if (i != null) {
742
- this.valuesCache[i] = this.values[i].$;
743
- }
744
- this.sync.$ = func.apply(this, this.valuesCache);
745
- };
746
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
747
- // @ts-ignore
748
- this.valuesCache = values.map(item => item.$);
749
- this.sync = new Reference(func.apply(this, this.valuesCache));
750
- let i = 0;
751
- values.forEach(() => {
752
- this.linkedFunc.push(handler.bind(this, Number(i++)));
753
- });
754
- this.values = values;
755
- this.func = handler;
756
- if (link) {
757
- this.$enable();
758
- }
759
- else {
760
- handler();
761
- }
762
- this.$seal();
944
+ this.listener.emitAdded(this[at], with_);
945
+ this.listener.emitRemoved(this[at], this[at]);
946
+ this[at] = with_;
947
+ return this;
763
948
  }
764
- get $() {
765
- return this.sync.$;
949
+ enableReactivity() {
950
+ this.listener.enableReactivity();
766
951
  }
767
- set $(value) {
768
- this.sync.$ = value;
952
+ disableReactivity() {
953
+ this.listener.disableReactivity();
769
954
  }
770
- $on(handler) {
771
- this.sync.$on(handler);
772
- return this;
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
+ });
773
981
  }
774
- $off(handler) {
775
- this.sync.$off(handler);
776
- return this;
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();
777
990
  }
778
- $enable() {
779
- if (!this.isEnabled) {
780
- for (let i = 0; i < this.values.length; i++) {
781
- this.values[i].$on(this.linkedFunc[i]);
782
- this.valuesCache[i] = this.values[i].$;
783
- }
784
- this.func();
785
- this.isEnabled = true;
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);
786
1000
  }
787
- return this;
1001
+ return super.delete(key);
788
1002
  }
789
- $disable() {
790
- if (this.isEnabled) {
791
- for (let i = 0; i < this.values.length; i++) {
792
- this.values[i].$off(this.linkedFunc[i]);
793
- }
794
- this.isEnabled = false;
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);
795
1013
  }
1014
+ super.set(key, value);
1015
+ this.listener.emitAdded(key, value);
796
1016
  return this;
797
1017
  }
798
- $destroy() {
799
- this.$disable();
800
- this.values.splice(0);
801
- this.valuesCache.splice(0);
802
- this.linkedFunc.splice(0);
803
- super.$destroy();
1018
+ enableReactivity() {
1019
+ this.listener.enableReactivity();
1020
+ }
1021
+ disableReactivity() {
1022
+ this.listener.disableReactivity();
804
1023
  }
805
1024
  }
806
1025
 
807
- window.Expression = Expression;
1026
+ window.MapModel = MapModel;
808
1027
 
809
- // ./lib/value/reference.js
1028
+ // ./lib/models/object-model.js
810
1029
  /**
811
- * Declares a notifiable value
812
- * @class Reference
813
- * @extends IValue
1030
+ * Object based model
1031
+ * @extends Object
814
1032
  */
815
- class Reference extends IValue {
1033
+ class ObjectModel extends Object {
816
1034
  /**
817
- * @param value {any} the initial value
1035
+ * Constructs a object model
1036
+ * @param obj {Object} input data
818
1037
  */
819
- constructor(value) {
820
- super(true);
821
- this.$value = value;
822
- this.$onchange = new Set;
823
- this.$seal();
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
+ }
824
1055
  }
825
- get $() {
826
- return this.$value;
1056
+ /**
1057
+ * Gets a value of a field
1058
+ * @param key {string}
1059
+ * @return {*}
1060
+ */
1061
+ get(key) {
1062
+ return this.container[key];
827
1063
  }
828
- set $(value) {
829
- if (this.$value !== value) {
830
- this.$value = value;
831
- if (this.isEnabled) {
832
- this.$onchange.forEach(handler => {
833
- handler(value);
834
- });
835
- }
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;
836
1074
  }
837
- }
838
- $enable() {
839
- if (!this.isEnabled) {
840
- this.$onchange.forEach(handler => {
841
- handler(this.$value);
1075
+ else {
1076
+ Object.defineProperty(this.container, key, {
1077
+ value: v,
1078
+ configurable: true,
1079
+ writable: true,
1080
+ enumerable: true
842
1081
  });
843
- this.isEnabled = true;
844
1082
  }
1083
+ this.listener.emitAdded(key, this.container[key]);
1084
+ return this;
845
1085
  }
846
- $disable() {
847
- this.isEnabled = false;
1086
+ get values() {
1087
+ return this.container;
848
1088
  }
849
- $on(handler) {
850
- this.$onchange.add(handler);
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
+ }
851
1098
  }
852
- $off(handler) {
853
- this.$onchange.delete(handler);
1099
+ enableReactivity() {
1100
+ this.listener.enableReactivity();
854
1101
  }
855
- $destroy() {
856
- super.$destroy();
857
- this.$onchange.clear();
1102
+ disableReactivity() {
1103
+ this.listener.disableReactivity();
858
1104
  }
859
1105
  }
860
1106
 
861
- window.Reference = Reference;
1107
+ window.ObjectModel = ObjectModel;
862
1108
 
863
- // ./lib/value/mirror.js
1109
+ // ./lib/models/set-model.js
864
1110
  /**
865
- * Declares a notifiable bind to a value
866
- * @class Mirror
867
- * @extends IValue
868
- * @version 2
1111
+ * A Set based model
1112
+ * @class SetModel
1113
+ * @extends Set
1114
+ * @implements IModel
869
1115
  */
870
- class Mirror extends Reference {
1116
+ class SetModel extends Set {
871
1117
  /**
872
- * Constructs a notifiable bind to a value
873
- * @param value {IValue} is initial value
874
- * @param forwardOnly {boolean} ensure forward only synchronization
1118
+ * Constructs a set model based on a set
1119
+ * @param set {Set} input data
875
1120
  */
876
- constructor(value, forwardOnly = false) {
877
- super(value.$);
878
- this.$handler = (v) => {
879
- this.$ = v;
880
- };
881
- this.$pointedValue = value;
882
- this.$forwardOnly = forwardOnly;
883
- value.$on(this.$handler);
884
- this.$seal();
885
- }
886
- get $() {
887
- // this is a ts bug
888
- // eslint-disable-next-line
889
- // @ts-ignore
890
- return super.$;
891
- }
892
- set $(v) {
893
- if (!this.$forwardOnly) {
894
- this.$pointedValue.$ = v;
895
- }
896
- // this is a ts bug
897
- // eslint-disable-next-line
898
- // @ts-ignore
899
- super.$ = v;
900
- }
901
- $enable() {
902
- if (!this.isEnabled) {
903
- this.isEnabled = true;
904
- this.$pointedValue.$on(this.$handler);
905
- this.$ = this.$pointedValue.$;
906
- }
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
+ });
907
1131
  }
908
- $disable() {
909
- if (this.isEnabled) {
910
- this.$pointedValue.$off(this.$handler);
911
- this.isEnabled = false;
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);
912
1141
  }
1142
+ return this;
913
1143
  }
914
- $destroy() {
915
- this.$disable();
916
- super.$destroy();
917
- }
918
- }
919
-
920
- window.Mirror = Mirror;
921
-
922
- // ./lib/value/pointer.js
923
- /**
924
- * r/w pointer to a value
925
- * @class Pointer
926
- * @extends Mirror
927
- */
928
- class Pointer extends Mirror {
929
1144
  /**
930
- * @param value {IValue} value to point
931
- * @param forwardOnly {boolean} forward only data flow
1145
+ * Calls Set.clear and notify abut changes
932
1146
  */
933
- constructor(value, forwardOnly = false) {
934
- super(value, forwardOnly);
1147
+ clear() {
1148
+ this.forEach(item => {
1149
+ this.listener.emitRemoved(item, item);
1150
+ });
1151
+ super.clear();
935
1152
  }
936
1153
  /**
937
- * Point a new ivalue
938
- * @param value {IValue} value to point
1154
+ * Calls Set.delete and notify abut changes
1155
+ * @param value {*}
1156
+ * @return {boolean} true if a value was deleted, otherwise false
939
1157
  */
940
- set $$(value) {
941
- if (this.$pointedValue !== value) {
942
- this.$disable();
943
- this.$pointedValue = value;
944
- this.$enable();
1158
+ delete(value) {
1159
+ if (super.has(value)) {
1160
+ this.listener.emitRemoved(value, value);
945
1161
  }
1162
+ return super.delete(value);
1163
+ }
1164
+ enableReactivity() {
1165
+ this.listener.enableReactivity();
1166
+ }
1167
+ disableReactivity() {
1168
+ this.listener.disableReactivity();
946
1169
  }
947
1170
  }
948
1171
 
949
- window.Pointer = Pointer;
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
+
950
1185
 
951
1186
  // ./lib/binding/binding.js
952
1187
  /**
@@ -973,249 +1208,119 @@ class Binding extends Destroyable {
973
1208
  * Just clear bindings
974
1209
  */
975
1210
  $destroy() {
976
- this.binding.$off(this.func);
977
- super.$destroy();
978
- }
979
- }
980
-
981
- window.Binding = Binding;
982
-
983
- // ./lib/core/core.js
984
-
985
- const currentStack = [];
986
- function stack(node) {
987
- currentStack.push(current);
988
- current = node;
989
- }
990
- function unstack() {
991
- current = currentStack.pop();
992
- }
993
- /**
994
- * Private stuff of a reactive object
995
- * @class ReactivePrivate
996
- * @extends Destroyable
997
- */
998
- class ReactivePrivate extends Destroyable {
999
- constructor() {
1000
- super();
1001
- /**
1002
- * A list of user-defined values
1003
- * @type {Set}
1004
- */
1005
- this.watch = new Set;
1006
- /**
1007
- * A list of user-defined bindings
1008
- * @type {Set}
1009
- */
1010
- this.bindings = new Set;
1011
- /**
1012
- * A list of user defined models
1013
- */
1014
- this.models = new Set;
1015
- /**
1016
- * Reactivity switch state
1017
- * @type {boolean}
1018
- */
1019
- this.enabled = true;
1020
- /**
1021
- * The frozen state of object
1022
- * @type {boolean}
1023
- */
1024
- this.frozen = false;
1025
- this.$seal();
1026
- }
1027
- $destroy() {
1028
- this.watch.forEach(value => value.$destroy());
1029
- this.watch.clear();
1030
- this.bindings.forEach(binding => binding.$destroy());
1031
- this.bindings.clear();
1032
- this.models.forEach(model => model.disableReactivity());
1033
- this.models.clear();
1034
- this.freezeExpr && this.freezeExpr.$destroy();
1035
- this.onDestroy && this.onDestroy();
1036
- super.$destroy();
1037
- }
1038
- }
1039
- /**
1040
- * A reactive object
1041
- * @class Reactive
1042
- * @extends Destroyable
1043
- */
1044
- class Reactive extends Destroyable {
1045
- constructor(input, $) {
1046
- super();
1047
- this.input = input;
1048
- this.$ = $ || new ReactivePrivate;
1049
- this.$seal();
1050
- }
1051
- /**
1052
- * Get parent node
1053
- */
1054
- get parent() {
1055
- return this.$.parent;
1056
- }
1057
- /**
1058
- * Create a reference
1059
- * @param value {*} value to reference
1060
- */
1061
- ref(value) {
1062
- const $ = this.$;
1063
- const ref = new Reference(value);
1064
- $.watch.add(ref);
1065
- return ref;
1066
- }
1067
- /**
1068
- * Create a mirror
1069
- * @param value {IValue} value to mirror
1070
- */
1071
- mirror(value) {
1072
- const mirror = new Mirror(value, false);
1073
- this.$.watch.add(mirror);
1074
- return mirror;
1075
- }
1076
- /**
1077
- * Create a forward-only mirror
1078
- * @param value {IValue} value to mirror
1079
- */
1080
- forward(value) {
1081
- const mirror = new Mirror(value, true);
1082
- this.$.watch.add(mirror);
1083
- return mirror;
1084
- }
1085
- /**
1086
- * Creates a pointer
1087
- * @param value {*} default value to point
1088
- * @param forwardOnly {boolean} forward only sync
1089
- */
1090
- point(value, forwardOnly = false) {
1091
- const $ = this.$;
1092
- const pointer = new Pointer(value, forwardOnly);
1093
- $.watch.add(pointer);
1094
- return pointer;
1095
- }
1096
- /**
1097
- * Register a model
1098
- * @param model
1099
- */
1100
- register(model) {
1101
- this.$.models.add(model);
1102
- return model;
1103
- }
1104
- /**
1105
- * Creates a watcher
1106
- * @param func {function} function to run on any argument change
1107
- * @param values
1108
- */
1109
- watch(func, ...values) {
1110
- const $ = this.$;
1111
- $.watch.add(new Expression(func, !this.$.frozen, ...values));
1112
- }
1113
- /**
1114
- * Creates a computed value
1115
- * @param func {function} function to run on any argument change
1116
- * @param values
1117
- * @return {IValue} the created ivalue
1118
- */
1119
- expr(func, ...values) {
1120
- const res = new Expression(func, !this.$.frozen, ...values);
1121
- const $ = this.$;
1122
- $.watch.add(res);
1123
- return res;
1124
- }
1125
- /**
1126
- * Enable reactivity of fields
1127
- */
1128
- enable() {
1129
- const $ = this.$;
1130
- if (!$.enabled) {
1131
- $.watch.forEach(watcher => {
1132
- watcher.$enable();
1133
- });
1134
- $.models.forEach(model => {
1135
- model.enableReactivity();
1136
- });
1137
- $.enabled = true;
1138
- }
1139
- }
1140
- /**
1141
- * Disable reactivity of fields
1142
- */
1143
- disable() {
1144
- const $ = this.$;
1145
- if ($.enabled) {
1146
- $.watch.forEach(watcher => {
1147
- watcher.$disable();
1148
- });
1149
- $.models.forEach(model => {
1150
- model.disableReactivity();
1151
- });
1152
- $.enabled = false;
1153
- }
1211
+ this.binding.$off(this.func);
1212
+ super.$destroy();
1154
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 {
1155
1225
  /**
1156
- * Disable/Enable reactivity of object fields with feedback
1157
- * @param cond {IValue} show condition
1158
- * @param onOff {function} on show feedback
1159
- * @param onOn {function} on hide feedback
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
1160
1230
  */
1161
- bindAlive(cond, onOff, onOn) {
1162
- const $ = this.$;
1163
- if ($.freezeExpr) {
1164
- throw wrongBinding("this component already have a freeze state");
1165
- }
1166
- if ($.watch.has(cond)) {
1167
- throw wrongBinding("freeze state must be bound to an external component");
1168
- }
1169
- $.freezeExpr = new Expression((cond) => {
1170
- $.frozen = !cond;
1171
- if (cond) {
1172
- onOn === null || onOn === void 0 ? void 0 : onOn();
1173
- this.enable();
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
+ }
1174
1241
  }
1175
1242
  else {
1176
- onOff === null || onOff === void 0 ? void 0 : onOff();
1177
- this.disable();
1243
+ node.node.removeAttribute(name);
1178
1244
  }
1179
- }, true, cond);
1180
- return this;
1181
- }
1182
- init() {
1183
- this.applyOptions(this.input);
1184
- return this.compose(this.input);
1185
- }
1186
- applyOptions(input) {
1187
- // empty
1188
- }
1189
- applyOptionsNow() {
1190
- this.applyOptions(this.input);
1191
- }
1192
- compose(input) {
1193
- throw notOverwritten();
1194
- }
1195
- composeNow() {
1196
- this.compose(this.input);
1245
+ });
1246
+ this.$seal();
1197
1247
  }
1198
- runFunctional(f, ...args) {
1199
- stack(this);
1200
- // yet another ts bug
1201
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1202
- // @ts-ignore
1203
- const result = f(...args);
1204
- unstack();
1205
- return result;
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();
1206
1275
  }
1207
- runOnDestroy(func) {
1208
- this.$.onDestroy = func;
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();
1209
1293
  }
1210
- $destroy() {
1211
- super.$destroy();
1212
- this.$.$destroy();
1213
- this.$ = null;
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();
1214
1320
  }
1215
1321
  }
1216
1322
 
1217
- window.ReactivePrivate = ReactivePrivate;
1218
- window.Reactive = Reactive;
1323
+ window.StyleBinding = StyleBinding;
1219
1324
 
1220
1325
  // ./lib/node/node.js
1221
1326
  /**
@@ -1373,7 +1478,9 @@ class Fragment extends Reactive {
1373
1478
  * @param input
1374
1479
  * @param cb {function(Tag, *)} callback
1375
1480
  */
1376
- tag(tagName, input, cb) {
1481
+ tag(tagName, input, cb
1482
+ // @ts-ignore
1483
+ ) {
1377
1484
  const $ = this.$;
1378
1485
  const node = new Tag(input);
1379
1486
  input.slot = cb || input.slot;
@@ -1381,6 +1488,7 @@ class Fragment extends Reactive {
1381
1488
  node.init();
1382
1489
  this.pushNode(node);
1383
1490
  node.ready();
1491
+ // @ts-ignore
1384
1492
  return node.node;
1385
1493
  }
1386
1494
  /**
@@ -2039,220 +2147,82 @@ class DebugPrivate extends FragmentPrivate {
2039
2147
  this.bindings.add(new Expression((v) => {
2040
2148
  this.node.replaceData(0, -1, v);
2041
2149
  }, true, text));
2042
- this.parent.appendNode(this.node);
2043
- }
2044
- /**
2045
- * Clear node data
2046
- */
2047
- $destroy() {
2048
- this.node.remove();
2049
- super.$destroy();
2050
- }
2051
- }
2052
- /**
2053
- * Represents a debug node
2054
- * @class DebugNode
2055
- * @extends Fragment
2056
- */
2057
- class DebugNode extends Fragment {
2058
- constructor() {
2059
- super({});
2060
- /**
2061
- * private data
2062
- * @type {DebugNode}
2063
- */
2064
- this.$ = new DebugPrivate();
2065
- this.$seal();
2066
- }
2067
- preinit(app, parent, text) {
2068
- const $ = this.$;
2069
- if (!text) {
2070
- throw internalError('wrong DebugNode::$preninit call');
2071
- }
2072
- $.preinitComment(app, parent, text);
2073
- }
2074
- /**
2075
- * Runs garbage collector
2076
- */
2077
- $destroy() {
2078
- this.$.$destroy();
2079
- super.$destroy();
2080
- }
2081
- }
2082
-
2083
- window.FragmentPrivate = FragmentPrivate;
2084
- window.Fragment = Fragment;
2085
- window.TextNodePrivate = TextNodePrivate;
2086
- window.TextNode = TextNode;
2087
- window.INodePrivate = INodePrivate;
2088
- window.INode = INode;
2089
- window.Tag = Tag;
2090
- window.Extension = Extension;
2091
- window.Component = Component;
2092
- window.SwitchedNodePrivate = SwitchedNodePrivate;
2093
- window.SwitchedNode = SwitchedNode;
2094
- window.DebugPrivate = DebugPrivate;
2095
- window.DebugNode = DebugNode;
2096
-
2097
- // ./lib/node/app.js
2098
- /**
2099
- * Application Node
2100
- * @class AppNode
2101
- * @extends INode
2102
- */
2103
- class AppNode extends INode {
2104
- /**
2105
- * @param input
2106
- */
2107
- constructor(input) {
2108
- super(input);
2109
- this.debugUi = input.debugUi || false;
2110
- this.$seal();
2111
- }
2112
- }
2113
- /**
2114
- * Represents a Vasille.js application
2115
- * @class App
2116
- * @extends AppNode
2117
- */
2118
- class App extends AppNode {
2119
- /**
2120
- * Constructs an app node
2121
- * @param node {Element} The root of application
2122
- * @param input
2123
- */
2124
- constructor(node, input) {
2125
- super(input);
2126
- this.$.node = node;
2127
- this.preinit(this, this);
2128
- this.init();
2129
- this.$seal();
2130
- }
2131
- appendNode(node) {
2132
- this.$.node.appendChild(node);
2133
- }
2134
- }
2135
- class Portal extends AppNode {
2136
- constructor(input) {
2137
- super(input);
2138
- this.$.node = input.node;
2139
- this.$seal();
2140
- }
2141
- appendNode(node) {
2142
- this.$.node.appendChild(node);
2150
+ this.parent.appendNode(this.node);
2143
2151
  }
2144
- }
2145
-
2146
- window.AppNode = AppNode;
2147
- window.App = App;
2148
- window.Portal = Portal;
2149
-
2150
- // ./lib/binding/attribute.js
2151
- /**
2152
- * Represents an Attribute binding description
2153
- * @class AttributeBinding
2154
- * @extends Binding
2155
- */
2156
- class AttributeBinding extends Binding {
2157
2152
  /**
2158
- * Constructs an attribute binding description
2159
- * @param node {INode} the vasille node
2160
- * @param name {String} the name of attribute
2161
- * @param value {IValue} value to bind
2153
+ * Clear node data
2162
2154
  */
2163
- constructor(node, name, value) {
2164
- super(value);
2165
- this.init((value) => {
2166
- if (value) {
2167
- if (typeof value === 'boolean') {
2168
- node.node.setAttribute(name, "");
2169
- }
2170
- else {
2171
- node.node.setAttribute(name, `${value}`);
2172
- }
2173
- }
2174
- else {
2175
- node.node.removeAttribute(name);
2176
- }
2177
- });
2178
- this.$seal();
2155
+ $destroy() {
2156
+ this.node.remove();
2157
+ super.$destroy();
2179
2158
  }
2180
2159
  }
2181
-
2182
- window.AttributeBinding = AttributeBinding;
2183
-
2184
- // ./lib/binding/style.js
2185
2160
  /**
2186
- * Describes a style attribute binding
2187
- * @class StyleBinding
2188
- * @extends Binding
2161
+ * Represents a debug node
2162
+ * @class DebugNode
2163
+ * @extends Fragment
2189
2164
  */
2190
- class StyleBinding extends Binding {
2165
+ class DebugNode extends Fragment {
2166
+ constructor() {
2167
+ super({});
2168
+ /**
2169
+ * private data
2170
+ * @type {DebugNode}
2171
+ */
2172
+ this.$ = new DebugPrivate();
2173
+ this.$seal();
2174
+ }
2175
+ preinit(app, parent, text) {
2176
+ const $ = this.$;
2177
+ if (!text) {
2178
+ throw internalError('wrong DebugNode::$preninit call');
2179
+ }
2180
+ $.preinitComment(app, parent, text);
2181
+ }
2191
2182
  /**
2192
- * Constructs a style binding attribute
2193
- * @param node {INode} the vasille node
2194
- * @param name {string} the name of style property
2195
- * @param value {IValue} the value to bind
2183
+ * Runs garbage collector
2196
2184
  */
2197
- constructor(node, name, value) {
2198
- super(value);
2199
- this.init((value) => {
2200
- if (node.node instanceof HTMLElement) {
2201
- node.node.style.setProperty(name, value);
2202
- }
2203
- });
2204
- this.$seal();
2185
+ $destroy() {
2186
+ this.$.$destroy();
2187
+ super.$destroy();
2205
2188
  }
2206
2189
  }
2207
2190
 
2208
- window.StyleBinding = StyleBinding;
2191
+ window.FragmentPrivate = FragmentPrivate;
2192
+ window.Fragment = Fragment;
2193
+ window.TextNodePrivate = TextNodePrivate;
2194
+ window.TextNode = TextNode;
2195
+ window.INodePrivate = INodePrivate;
2196
+ window.INode = INode;
2197
+ window.Tag = Tag;
2198
+ window.Extension = Extension;
2199
+ window.Component = Component;
2200
+ window.SwitchedNodePrivate = SwitchedNodePrivate;
2201
+ window.SwitchedNode = SwitchedNode;
2202
+ window.DebugPrivate = DebugPrivate;
2203
+ window.DebugNode = DebugNode;
2209
2204
 
2210
- // ./lib/binding/class.js
2211
- function addClass(node, cl) {
2212
- node.node.classList.add(cl);
2213
- }
2214
- function removeClass(node, cl) {
2215
- node.node.classList.remove(cl);
2216
- }
2217
- class StaticClassBinding extends Binding {
2218
- constructor(node, name, value) {
2219
- super(value);
2220
- this.current = false;
2221
- this.init((value) => {
2222
- if (value !== this.current) {
2223
- if (value) {
2224
- addClass(node, name);
2225
- }
2226
- else {
2227
- removeClass(node, name);
2228
- }
2229
- this.current = value;
2230
- }
2231
- });
2232
- this.$seal();
2233
- }
2234
- }
2235
- class DynamicalClassBinding extends Binding {
2236
- constructor(node, value) {
2237
- super(value);
2238
- this.current = "";
2239
- this.init((value) => {
2240
- if (this.current != value) {
2241
- if (this.current.length) {
2242
- removeClass(node, this.current);
2243
- }
2244
- if (value.length) {
2245
- addClass(node, value);
2246
- }
2247
- this.current = value;
2248
- }
2249
- });
2250
- this.$seal();
2205
+ // ./lib/node/watch.js
2206
+ /**
2207
+ * Watch Node
2208
+ * @class Watch
2209
+ * @extends Fragment
2210
+ */
2211
+ class Watch extends Fragment {
2212
+ compose(input) {
2213
+ this.watch((value) => {
2214
+ this.children.forEach(child => {
2215
+ child.$destroy();
2216
+ });
2217
+ this.children.clear();
2218
+ this.lastChild = null;
2219
+ input.slot && input.slot(this, value);
2220
+ }, input.model);
2221
+ input.slot(this, input.model.$);
2251
2222
  }
2252
2223
  }
2253
2224
 
2254
- window.StaticClassBinding = StaticClassBinding;
2255
- window.DynamicalClassBinding = DynamicalClassBinding;
2225
+ window.Watch = Watch;
2256
2226
 
2257
2227
  // ./lib/views/repeat-node.js
2258
2228
  /**
@@ -2386,27 +2356,22 @@ class ArrayView extends BaseView {
2386
2356
 
2387
2357
  window.ArrayView = ArrayView;
2388
2358
 
2389
- // ./lib/node/watch.js
2359
+ // ./lib/views/map-view.js
2390
2360
  /**
2391
- * Watch Node
2392
- * @class Watch
2393
- * @extends Fragment
2361
+ * Create a children pack for each map value
2362
+ * @class MapView
2363
+ * @extends BaseView
2394
2364
  */
2395
- class Watch extends Fragment {
2365
+ class MapView extends BaseView {
2396
2366
  compose(input) {
2397
- this.watch((value) => {
2398
- this.children.forEach(child => {
2399
- child.$destroy();
2400
- });
2401
- this.children.clear();
2402
- this.lastChild = null;
2403
- input.slot && input.slot(this, value);
2404
- }, input.model);
2405
- input.slot(this, input.model.$);
2367
+ super.compose(input);
2368
+ input.model.forEach((value, key) => {
2369
+ this.createChild(input, key, value);
2370
+ });
2406
2371
  }
2407
2372
  }
2408
2373
 
2409
- window.Watch = Watch;
2374
+ window.MapView = MapView;
2410
2375
 
2411
2376
  // ./lib/views/object-view.js
2412
2377
  /**
@@ -2427,23 +2392,6 @@ class ObjectView extends BaseView {
2427
2392
 
2428
2393
  window.ObjectView = ObjectView;
2429
2394
 
2430
- // ./lib/views/map-view.js
2431
- /**
2432
- * Create a children pack for each map value
2433
- * @class MapView
2434
- * @extends BaseView
2435
- */
2436
- class MapView extends BaseView {
2437
- compose(input) {
2438
- super.compose(input);
2439
- input.model.forEach((value, key) => {
2440
- this.createChild(input, key, value);
2441
- });
2442
- }
2443
- }
2444
-
2445
- window.MapView = MapView;
2446
-
2447
2395
  // ./lib/views/set-view.js
2448
2396
  /**
2449
2397
  * Create a children pack for each set value
@@ -2462,6 +2410,69 @@ class SetView extends BaseView {
2462
2410
 
2463
2411
  window.SetView = SetView;
2464
2412
 
2413
+ // ./lib/index.js
2414
+ const less = {
2415
+ stack,
2416
+ unstack,
2417
+ current,
2418
+ userError,
2419
+ };
2420
+
2421
+
2422
+
2423
+ // ./lib/node/app.js
2424
+ /**
2425
+ * Application Node
2426
+ * @class AppNode
2427
+ * @extends INode
2428
+ */
2429
+ class AppNode extends INode {
2430
+ /**
2431
+ * @param input
2432
+ */
2433
+ constructor(input) {
2434
+ super(input);
2435
+ this.debugUi = input.debugUi || false;
2436
+ this.$seal();
2437
+ }
2438
+ }
2439
+ /**
2440
+ * Represents a Vasille.js application
2441
+ * @class App
2442
+ * @extends AppNode
2443
+ */
2444
+ class App extends AppNode {
2445
+ /**
2446
+ * Constructs an app node
2447
+ * @param node {Element} The root of application
2448
+ * @param input
2449
+ */
2450
+ constructor(node, input) {
2451
+ super(input);
2452
+ this.$.node = node;
2453
+ this.preinit(this, this);
2454
+ this.init();
2455
+ this.$seal();
2456
+ }
2457
+ appendNode(node) {
2458
+ this.$.node.appendChild(node);
2459
+ }
2460
+ }
2461
+ class Portal extends AppNode {
2462
+ constructor(input) {
2463
+ super(input);
2464
+ this.$.node = input.node;
2465
+ this.$seal();
2466
+ }
2467
+ appendNode(node) {
2468
+ this.$.node.appendChild(node);
2469
+ }
2470
+ }
2471
+
2472
+ window.AppNode = AppNode;
2473
+ window.App = App;
2474
+ window.Portal = Portal;
2475
+
2465
2476
  // ./lib/functional/options.js
2466
2477
 
2467
2478