vasille 3.2.0 → 4.0.0

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 (53) hide show
  1. package/README.md +5 -14
  2. package/eslint.config.js +25 -0
  3. package/lib/core/core.js +8 -94
  4. package/lib/core/destroyable.js +1 -10
  5. package/lib/core/ivalue.js +3 -5
  6. package/lib/functional/safety.js +1 -1
  7. package/lib/index.js +18 -20
  8. package/lib/models/array-model.js +119 -0
  9. package/lib/models/listener.js +64 -0
  10. package/lib/models/map-model.js +63 -0
  11. package/lib/models/model.js +1 -0
  12. package/lib/models/set-model.js +57 -0
  13. package/lib/node/app.js +7 -5
  14. package/lib/node/node.js +80 -95
  15. package/lib/node/watch.js +24 -8
  16. package/lib/runner/web/binding/binding.js +4 -6
  17. package/lib/runner/web/binding/class.js +19 -6
  18. package/lib/runner/web/binding/property.js +20 -0
  19. package/lib/runner/web/runner.js +31 -22
  20. package/lib/value/expression.js +29 -21
  21. package/lib/value/pointer.js +45 -54
  22. package/lib/value/reference.js +21 -17
  23. package/lib/views/array-view.js +4 -4
  24. package/lib/views/base-view.js +19 -8
  25. package/lib/views/map-view.js +2 -2
  26. package/lib/views/repeat-node.js +12 -11
  27. package/lib/views/set-view.js +3 -3
  28. package/package.json +18 -9
  29. package/types/core/core.d.ts +3 -47
  30. package/types/core/destroyable.d.ts +2 -12
  31. package/types/core/ivalue.d.ts +3 -5
  32. package/types/index.d.ts +21 -22
  33. package/types/models/array-model.d.ts +55 -0
  34. package/types/models/listener.d.ts +48 -0
  35. package/types/models/map-model.d.ts +36 -0
  36. package/types/models/model.d.ts +9 -0
  37. package/types/models/set-model.d.ts +33 -0
  38. package/types/node/app.d.ts +6 -6
  39. package/types/node/node.d.ts +21 -49
  40. package/types/node/runner.d.ts +3 -0
  41. package/types/node/watch.d.ts +5 -1
  42. package/types/runner/web/binding/binding.d.ts +1 -2
  43. package/types/runner/web/binding/class.d.ts +2 -0
  44. package/types/runner/web/binding/property.d.ts +17 -0
  45. package/types/runner/web/runner.d.ts +1 -1
  46. package/types/value/expression.d.ts +7 -12
  47. package/types/value/pointer.d.ts +32 -22
  48. package/types/value/reference.d.ts +2 -4
  49. package/types/views/array-view.d.ts +2 -2
  50. package/types/views/base-view.d.ts +2 -3
  51. package/types/views/repeat-node.d.ts +4 -3
  52. package/lib/tsconfig.tsbuildinfo +0 -1
  53. package/types/tsconfig-types.tsbuildinfo +0 -1
package/lib/node/node.js CHANGED
@@ -2,15 +2,20 @@ import { Reactive } from "../core/core.js";
2
2
  import { IValue } from "../core/ivalue.js";
3
3
  import { SetModel } from "../models/set-model.js";
4
4
  import { Reference } from "../value/reference.js";
5
- import { userError } from "../core/errors.js";
6
5
  /**
7
6
  * This class is symbolic
8
7
  * @extends Reactive
9
8
  */
10
9
  export class Root extends Reactive {
11
- constructor(input, runner) {
12
- super(input);
13
- this.lastChild = undefined;
10
+ /**
11
+ * The children list
12
+ * @type Array
13
+ */
14
+ children;
15
+ runner;
16
+ lastChild = undefined;
17
+ constructor(runner) {
18
+ super();
14
19
  this.runner = runner;
15
20
  this.children = runner.debugUi ? new SetModel() : new Set();
16
21
  }
@@ -31,19 +36,14 @@ export class Root extends Reactive {
31
36
  */
32
37
  findFirstChild() {
33
38
  let first;
34
- for (const child of this.children) {
35
- first = child.findFirstChild();
36
- /* istanbul ignore else */
37
- if (first) {
38
- break;
39
- }
40
- }
39
+ this.children.forEach(child => {
40
+ first = first ?? child.findFirstChild();
41
+ });
41
42
  return first;
42
43
  }
43
44
  /**
44
45
  * Defines a text fragment
45
46
  * @param text {String | IValue} A text fragment string
46
- * @param cb {function (TextNode)} Callback if previous is slot name
47
47
  */
48
48
  text(text) {
49
49
  const node = this.runner.textNode(text);
@@ -76,49 +76,6 @@ export class Root extends Reactive {
76
76
  node.compose();
77
77
  callback?.(node);
78
78
  }
79
- /**
80
- * Defines an if node
81
- * @param cond {IValue} condition
82
- * @param cb {function(Fragment)} callback to run on true
83
- * @return {this}
84
- */
85
- if(cond, cb) {
86
- const node = new SwitchedNode(this.runner);
87
- this.pushNode(node);
88
- node.addCase(this.case(cond, cb));
89
- }
90
- else(cb) {
91
- if (this.lastChild instanceof SwitchedNode) {
92
- this.lastChild.addCase(this.default(cb));
93
- }
94
- else {
95
- throw userError("wrong `else` function use", "logic-error");
96
- }
97
- }
98
- elif(cond, cb) {
99
- if (this.lastChild instanceof SwitchedNode) {
100
- this.lastChild.addCase(this.case(cond, cb));
101
- }
102
- else {
103
- throw userError("wrong `elif` function use", "logic-error");
104
- }
105
- }
106
- /**
107
- * Create a case for switch
108
- * @param cond {IValue<boolean>}
109
- * @param cb {function(Fragment) : void}
110
- * @return {{cond : IValue, cb : (function(Fragment) : void)}}
111
- */
112
- case(cond, cb) {
113
- return { cond, cb };
114
- }
115
- /**
116
- * @param cb {(function(Fragment) : void)}
117
- * @return {{cond : IValue, cb : (function(Fragment) : void)}}
118
- */
119
- default(cb) {
120
- return { cond: trueIValue, cb };
121
- }
122
79
  destroy() {
123
80
  this.children.forEach(child => child.destroy());
124
81
  this.children.clear();
@@ -127,10 +84,20 @@ export class Root extends Reactive {
127
84
  }
128
85
  }
129
86
  export class Fragment extends Root {
130
- constructor(input, runner, name) {
131
- super(input, runner);
132
- this.name = name;
87
+ parent;
88
+ constructor(runner) {
89
+ super(runner);
133
90
  }
91
+ /**
92
+ * Next node
93
+ * @type {?Fragment}
94
+ */
95
+ next;
96
+ /**
97
+ * Previous node
98
+ * @type {?Fragment}
99
+ */
100
+ prev;
134
101
  /**
135
102
  * Pushes a node to children immediately
136
103
  * @param node {Fragment} A node to push
@@ -204,18 +171,20 @@ export class Fragment extends Root {
204
171
  super.destroy();
205
172
  }
206
173
  }
207
- const trueIValue = new Reference(true);
208
174
  /**
209
175
  * Represents a text node
210
176
  * @class TextNode
211
177
  * @extends Fragment
212
178
  */
213
179
  export class TextNode extends Fragment {
180
+ handler;
181
+ data;
214
182
  constructor(input, runner) {
215
- super(input, runner, ":text");
183
+ super(runner);
184
+ this.data = input.text;
216
185
  }
217
186
  destroy() {
218
- const text = this.input.text;
187
+ const text = this.data;
219
188
  if (text instanceof IValue && this.handler) {
220
189
  text.off(this.handler);
221
190
  }
@@ -228,6 +197,11 @@ export class TextNode extends Fragment {
228
197
  * @extends Fragment
229
198
  */
230
199
  export class INode extends Fragment {
200
+ /**
201
+ * The element of vasille node
202
+ * @type Element
203
+ */
204
+ node;
231
205
  get element() {
232
206
  return this.node;
233
207
  }
@@ -241,8 +215,12 @@ export class INode extends Fragment {
241
215
  * @extends INode
242
216
  */
243
217
  export class Tag extends INode {
244
- constructor(input, runner, tagName) {
245
- super(input, runner, tagName);
218
+ name;
219
+ options;
220
+ constructor(options, runner, tagName) {
221
+ super(runner);
222
+ this.options = options;
223
+ this.name = tagName;
246
224
  }
247
225
  findFirstChild() {
248
226
  return this.node;
@@ -251,27 +229,37 @@ export class Tag extends INode {
251
229
  this.runner.appendChild(this.node, node);
252
230
  }
253
231
  }
232
+ const alwaysTrue = new Reference(true);
254
233
  /**
255
234
  * Defines a node which can switch its children conditionally
256
235
  */
257
236
  export class SwitchedNode extends Fragment {
237
+ /**
238
+ * Index of current true condition
239
+ * @type number
240
+ */
241
+ index;
242
+ /**
243
+ * Array of possible cases
244
+ * @type {Array<{cond : IValue<unknown>, cb : function(Fragment)}>}
245
+ */
246
+ cases;
247
+ /**
248
+ * A function that syncs index and content will be bounded to each condition
249
+ * @type {Function}
250
+ */
251
+ sync;
258
252
  /**
259
253
  * Constructs a switch node and define a sync function
260
254
  */
261
- constructor(runner) {
262
- super({}, runner, ":switch");
263
- /**
264
- * Array of possible cases
265
- * @type {Array<{cond : IValue<unknown>, cb : function(Fragment)}>}
266
- */
267
- this.cases = [];
255
+ constructor(runner, cases, _default) {
256
+ super(runner);
257
+ if (_default) {
258
+ cases.push({ $case: alwaysTrue, slot: _default });
259
+ }
260
+ this.cases = cases;
268
261
  this.sync = () => {
269
- let i = 0;
270
- for (; i < this.cases.length; i++) {
271
- if (this.cases[i].cond.$) {
272
- break;
273
- }
274
- }
262
+ let i = this.cases.findIndex(item => item.$case.V);
275
263
  if (i === this.index) {
276
264
  return;
277
265
  }
@@ -280,34 +268,28 @@ export class SwitchedNode extends Fragment {
280
268
  this.children.clear();
281
269
  this.lastChild = undefined;
282
270
  }
283
- if (i !== this.cases.length) {
271
+ if (i !== -1) {
272
+ const node = new Fragment(this.runner);
273
+ node.parent = this;
274
+ this.lastChild = node;
275
+ this.children.add(node);
284
276
  this.index = i;
285
- this.createChild(this.cases[i].cb);
277
+ this.cases[i].slot(node);
286
278
  }
287
279
  else {
288
280
  this.index = -1;
289
281
  }
290
282
  };
283
+ cases.forEach(_case => {
284
+ _case.$case.on(this.sync);
285
+ });
291
286
  }
292
- addCase(case_) {
293
- this.cases.push(case_);
294
- case_.cond.on(this.sync);
287
+ compose() {
295
288
  this.sync();
296
289
  }
297
- /**
298
- * Creates a child node
299
- * @param cb {function(Fragment)} Call-back
300
- */
301
- createChild(cb) {
302
- const node = new Fragment({}, this.runner, ":case");
303
- node.parent = this;
304
- this.lastChild = node;
305
- this.children.add(node);
306
- cb(node);
307
- }
308
290
  destroy() {
309
291
  this.cases.forEach(c => {
310
- c.cond.off(this.sync);
292
+ c.$case.off(this.sync);
311
293
  });
312
294
  this.cases.splice(0);
313
295
  super.destroy();
@@ -319,13 +301,16 @@ export class SwitchedNode extends Fragment {
319
301
  * @extends Fragment
320
302
  */
321
303
  export class DebugNode extends Fragment {
304
+ handler;
305
+ data;
322
306
  constructor(input, runner) {
323
- super(input, runner, ":debug");
307
+ super(runner);
308
+ this.data = input.text;
324
309
  }
325
310
  destroy() {
326
311
  /* istanbul ignore else */
327
312
  if (this.handler) {
328
- this.input.text.off(this.handler);
313
+ this.data.off(this.handler);
329
314
  }
330
315
  super.destroy();
331
316
  }
package/lib/node/watch.js CHANGED
@@ -5,17 +5,33 @@ import { Fragment } from "./node.js";
5
5
  * @extends Fragment
6
6
  */
7
7
  export class Watch extends Fragment {
8
+ model;
9
+ slot;
10
+ handler;
8
11
  constructor(input, runner) {
9
- super(input, runner, ":watch");
12
+ super(runner);
13
+ this.model = input.model;
14
+ this.slot = input.slot;
10
15
  }
11
16
  compose() {
12
- this.watch(value => {
13
- this.children.forEach(child => {
14
- child.destroy();
17
+ const slot = this.slot;
18
+ if (slot) {
19
+ const handler = (this.handler = value => {
20
+ this.children.forEach(child => {
21
+ child.destroy();
22
+ });
23
+ this.children.clear();
24
+ this.lastChild = undefined;
25
+ slot(this, value);
15
26
  });
16
- this.children.clear();
17
- this.lastChild = undefined;
18
- this.input.slot?.(this, value);
19
- }, [this.input.model]);
27
+ this.model.on(handler);
28
+ handler(this.model.V);
29
+ }
30
+ }
31
+ destroy() {
32
+ if (this.handler) {
33
+ this.model.off(this.handler);
34
+ }
35
+ super.destroy();
20
36
  }
21
37
  }
@@ -1,28 +1,26 @@
1
- import { Destroyable } from "../../../core/destroyable.js";
2
1
  /**
3
2
  * Describe a common binding logic
4
3
  * @class Binding
5
- * @extends Destroyable
6
4
  */
7
- export class Binding extends Destroyable {
5
+ export class Binding {
6
+ binding;
7
+ func;
8
8
  /**
9
9
  * Constructs a common binding logic
10
10
  * @param value {IValue} the value to bind
11
11
  */
12
12
  constructor(value) {
13
- super();
14
13
  this.binding = value;
15
14
  }
16
15
  init(bounded) {
17
16
  this.func = bounded;
18
17
  this.binding.on(this.func);
19
- this.func(this.binding.$);
18
+ this.func(this.binding.V);
20
19
  }
21
20
  /**
22
21
  * Just clear bindings
23
22
  */
24
23
  destroy() {
25
24
  this.binding.off(this.func);
26
- super.destroy();
27
25
  }
28
26
  }
@@ -1,14 +1,27 @@
1
1
  import { Binding } from "./binding.js";
2
- function addClass(node, cl) {
3
- node.element.classList.add(cl);
2
+ export function addClass(node, cl) {
3
+ if (process.env.VASILLE_TARGET === "es5" && !node.element.classList) {
4
+ node.element.className = [...node.element.className.split(" "), cl].filter(item => !!item).join(" ");
5
+ }
6
+ else {
7
+ node.element.classList.add(cl);
8
+ }
4
9
  }
5
- function removeClass(node, cl) {
6
- node.element.classList.remove(cl);
10
+ export function removeClass(node, cl) {
11
+ if (process.env.VASILLE_TARGET === "es5" && !node.element.classList) {
12
+ node.element.className = node.element.className
13
+ .split(" ")
14
+ .filter(name => name !== cl)
15
+ .join(" ");
16
+ }
17
+ else {
18
+ node.element.classList.remove(cl);
19
+ }
7
20
  }
8
21
  export class StaticClassBinding extends Binding {
22
+ current = false;
9
23
  constructor(node, name, value) {
10
24
  super(value);
11
- this.current = false;
12
25
  this.init((value) => {
13
26
  if (value !== this.current) {
14
27
  if (value) {
@@ -23,9 +36,9 @@ export class StaticClassBinding extends Binding {
23
36
  }
24
37
  }
25
38
  export class DynamicalClassBinding extends Binding {
39
+ current = "";
26
40
  constructor(node, value) {
27
41
  super(value);
28
- this.current = "";
29
42
  this.init((value) => {
30
43
  /* istanbul ignore else */
31
44
  if (this.current != value) {
@@ -0,0 +1,20 @@
1
+ import { Binding } from "./binding.js";
2
+ /**
3
+ * Represents a property binding
4
+ * @class PropertyBinding
5
+ * @extends Binding
6
+ */
7
+ export class PropertyBinding extends Binding {
8
+ /**
9
+ * Constructs a property binding description
10
+ * @param node the vasille node
11
+ * @param name the name of property
12
+ * @param value the value of property
13
+ */
14
+ constructor(node, name, value) {
15
+ super(value);
16
+ this.init(value => {
17
+ node.element[name] = value;
18
+ });
19
+ }
20
+ }
@@ -1,12 +1,14 @@
1
1
  import { TextNode as AbstractTextNode, DebugNode as AbstractDebugNode, Tag as AbstractTag, IValue, } from "../../index.js";
2
2
  import { internalError } from "../../core/errors.js";
3
3
  import { AttributeBinding } from "./binding/attribute.js";
4
- import { DynamicalClassBinding, StaticClassBinding } from "./binding/class.js";
4
+ import { addClass, DynamicalClassBinding, removeClass, StaticClassBinding } from "./binding/class.js";
5
+ import { PropertyBinding } from "./binding/property.js";
5
6
  import { stringifyStyleValue, StyleBinding } from "./binding/style.js";
6
7
  export class TextNode extends AbstractTextNode {
8
+ node;
7
9
  compose() {
8
- const text = this.input.text;
9
- this.node = this.runner.document.createTextNode((text instanceof IValue ? text.$ : text)?.toString() ?? "");
10
+ const text = this.data;
11
+ this.node = this.runner.document.createTextNode((text instanceof IValue ? text.V : text)?.toString() ?? "");
10
12
  if (text instanceof IValue) {
11
13
  this.handler = (v) => {
12
14
  this.node.replaceData(0, -1, v?.toString() ?? "");
@@ -24,9 +26,10 @@ export class TextNode extends AbstractTextNode {
24
26
  }
25
27
  }
26
28
  export class DebugNode extends AbstractDebugNode {
29
+ node;
27
30
  compose() {
28
- const text = this.input.text;
29
- this.node = this.runner.document.createComment(text.$?.toString() ?? "");
31
+ const text = this.data;
32
+ this.node = this.runner.document.createComment(text.V?.toString() ?? "");
30
33
  this.handler = (v) => {
31
34
  this.node.replaceData(0, -1, v?.toString() ?? "");
32
35
  };
@@ -48,10 +51,10 @@ export class Tag extends AbstractTag {
48
51
  }
49
52
  const node = this.runner.document.createElement(this.name);
50
53
  this.node = node;
51
- this.applyOptions(this.input);
54
+ this.applyOptions(this.options);
52
55
  this.parent.appendNode(node);
53
- this.input.callback?.(this.node);
54
- this.input.slot?.(this);
56
+ this.options.callback?.(this.node);
57
+ this.options.slot?.(this);
55
58
  }
56
59
  destroy() {
57
60
  this.node.remove();
@@ -62,7 +65,7 @@ export class Tag extends AbstractTag {
62
65
  for (const name in options.attr) {
63
66
  const value = options.attr[name];
64
67
  if (value instanceof IValue) {
65
- this.register(new AttributeBinding(this, name, value));
68
+ this.bind(new AttributeBinding(this, name, value));
66
69
  }
67
70
  else {
68
71
  /* istanbul ignore else */
@@ -79,34 +82,34 @@ export class Tag extends AbstractTag {
79
82
  }
80
83
  }
81
84
  if (options.class) {
82
- for (const item of options.class) {
85
+ options.class.forEach(item => {
83
86
  if (item instanceof IValue) {
84
- this.register(new DynamicalClassBinding(this, item));
87
+ this.bind(new DynamicalClassBinding(this, item));
85
88
  }
86
89
  else if (typeof item == "string") {
87
- this.node.classList.add(item);
90
+ addClass(this, item);
88
91
  }
89
92
  else {
90
93
  for (const name in item) {
91
94
  const value = item[name];
92
95
  if (value instanceof IValue) {
93
- this.register(new StaticClassBinding(this, name, value));
96
+ this.bind(new StaticClassBinding(this, name, value));
94
97
  }
95
98
  else if (value) {
96
- this.node.classList.add(name);
99
+ addClass(this, name);
97
100
  }
98
101
  else {
99
- this.node.classList.remove(name);
102
+ removeClass(this, name);
100
103
  }
101
104
  }
102
105
  }
103
- }
106
+ });
104
107
  }
105
108
  if (options.style && this.node instanceof HTMLElement) {
106
109
  for (const name in options.style) {
107
110
  const value = options.style[name];
108
111
  if (value instanceof IValue) {
109
- this.register(new StyleBinding(this, name, value));
112
+ this.bind(new StyleBinding(this, name, value));
110
113
  }
111
114
  else {
112
115
  this.node.style.setProperty(name, stringifyStyleValue(value));
@@ -115,7 +118,13 @@ export class Tag extends AbstractTag {
115
118
  }
116
119
  if (options.events) {
117
120
  for (const name in options.events) {
118
- this.node.addEventListener(name, options.events[name]);
121
+ const event = options.events[name];
122
+ if (event instanceof Array) {
123
+ this.node.addEventListener(name, event[0], event[1]);
124
+ }
125
+ else {
126
+ this.node.addEventListener(name, event);
127
+ }
119
128
  }
120
129
  }
121
130
  if (options.bind) {
@@ -126,16 +135,16 @@ export class Tag extends AbstractTag {
126
135
  node[k] = value;
127
136
  }
128
137
  else {
129
- node[k] = value.$;
130
- this.watch((v) => {
131
- node[k] = v;
132
- }, [value]);
138
+ node[k] = value.V;
139
+ this.bind(new PropertyBinding(this, k, value));
133
140
  }
134
141
  }
135
142
  }
136
143
  }
137
144
  }
138
145
  export class Runner {
146
+ debugUi;
147
+ document;
139
148
  constructor(debugUi, document) {
140
149
  this.debugUi = debugUi;
141
150
  this.document = document;
@@ -6,43 +6,52 @@ import { IValue } from "../core/ivalue.js";
6
6
  * @extends IValue
7
7
  */
8
8
  export class Expression extends IValue {
9
+ /**
10
+ * The array of value which will trigger recalculation
11
+ * @type {Array}
12
+ */
13
+ values;
14
+ /**
15
+ * Cache the values of expression variables
16
+ * @type {Array}
17
+ */
18
+ valuesCache;
19
+ /**
20
+ * Expression will link different handler for each value of the list
21
+ */
22
+ linkedFunc = [];
23
+ /**
24
+ * The buffer to keep the last calculated value
25
+ */
26
+ sync;
9
27
  /**
10
28
  * Creates a function bounded to N values
11
- * @param func {Function} the function to bound
12
- * @param values
13
- * @param link {Boolean} links immediately if true
14
29
  */
15
- constructor(func, values) {
30
+ constructor(func, values, ctx) {
16
31
  super();
17
- /**
18
- * Expression will link different handler for each value of the list
19
- */
20
- this.linkedFunc = [];
21
32
  const handler = (i) => {
22
33
  /* istanbul ignore else */
23
34
  if (typeof i === "number") {
24
- this.valuesCache[i] = this.values[i].$;
35
+ this.valuesCache[i] = this.values[i]?.V;
25
36
  }
26
- this.sync.$ = func.apply(this, this.valuesCache);
37
+ this.sync.V = func.apply(this, this.valuesCache);
27
38
  };
28
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
29
- // @ts-ignore
30
- this.valuesCache = values.map(item => item.$);
39
+ this.valuesCache = values.map(item => item?.V);
31
40
  this.sync = new Reference(func.apply(this, this.valuesCache));
32
41
  let i = 0;
33
42
  values.forEach(value => {
34
43
  const updater = handler.bind(this, Number(i++));
35
44
  this.linkedFunc.push(updater);
36
- value.on(updater);
45
+ value?.on(updater);
37
46
  });
38
47
  this.values = values;
39
- this.func = handler;
48
+ ctx?.bind(this);
40
49
  }
41
- get $() {
42
- return this.sync.$;
50
+ get V() {
51
+ return this.sync.V;
43
52
  }
44
- set $(value) {
45
- this.sync.$ = value;
53
+ set V(value) {
54
+ this.sync.V = value;
46
55
  }
47
56
  on(handler) {
48
57
  this.sync.on(handler);
@@ -52,11 +61,10 @@ export class Expression extends IValue {
52
61
  }
53
62
  destroy() {
54
63
  for (let i = 0; i < this.values.length; i++) {
55
- this.values[i].off(this.linkedFunc[i]);
64
+ this.values[i]?.off(this.linkedFunc[i]);
56
65
  }
57
66
  this.values.splice(0);
58
67
  this.valuesCache.splice(0);
59
68
  this.linkedFunc.splice(0);
60
- super.destroy();
61
69
  }
62
70
  }