vasille 2.3.9 → 3.0.2

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