leanweb 1.3.6 → 2.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.
@@ -2,481 +2,480 @@ import * as parser from './lw-expr-parser.js';
2
2
  import LWEventBus from './lw-event-bus.js';
3
3
 
4
4
  globalThis.leanweb = globalThis.leanweb ?? {
5
- componentsListeningOnUrlChanges: [],
6
- eventBus: new LWEventBus(),
7
- updateComponents(...tagNames) {
8
- if (tagNames?.length) {
9
- tagNames.forEach(tagName => {
10
- leanweb.eventBus.dispatchEvent(tagName);
11
- });
5
+ componentsListeningOnUrlChanges: [],
6
+ eventBus: new LWEventBus(),
7
+ updateComponents(...tagNames) {
8
+ if (tagNames?.length) {
9
+ tagNames.forEach(tagName => {
10
+ leanweb.eventBus.dispatchEvent(tagName);
11
+ });
12
+ } else {
13
+ leanweb.eventBus.dispatchEvent('update');
14
+ }
15
+ },
16
+
17
+ set urlHash(hash) {
18
+ location.hash = hash;
19
+ },
20
+
21
+ get urlHash() {
22
+ return location.hash;
23
+ },
24
+
25
+ set urlHashPath(hashPath) {
26
+ const s = this.urlHash.split('?');
27
+ if (s.length === 1) {
28
+ this.urlHash = hashPath;
29
+ } else if (s.length > 1) {
30
+ this.urlHash = hashPath + '?' + s[1];
31
+ }
32
+ },
33
+
34
+ get urlHashPath() {
35
+ return this.urlHash.split('?')[0];
36
+ },
37
+
38
+ set urlHashParams(hashParams) {
39
+ if (!hashParams) {
40
+ return;
41
+ }
42
+
43
+ const paramArray = [];
44
+ Object.keys(hashParams).forEach(key => {
45
+ const value = hashParams[key];
46
+ if (Array.isArray(value)) {
47
+ value.forEach(v => {
48
+ paramArray.push(key + '=' + encodeURIComponent(v));
49
+ });
12
50
  } else {
13
- leanweb.eventBus.dispatchEvent('update');
14
- }
15
- },
16
-
17
- set urlHash(hash) {
18
- location.hash = hash;
19
- },
20
-
21
- get urlHash() {
22
- return location.hash;
23
- },
24
-
25
- set urlHashPath(hashPath) {
26
- const s = this.urlHash.split('?');
27
- if (s.length === 1) {
28
- this.urlHash = hashPath;
29
- } else if (s.length > 1) {
30
- this.urlHash = hashPath + '?' + s[1];
51
+ paramArray.push(key + '=' + encodeURIComponent(value));
31
52
  }
32
- },
33
-
34
- get urlHashPath() {
35
- return this.urlHash.split('?')[0];
36
- },
37
-
38
- set urlHashParams(hashParams) {
39
- if (!hashParams) {
40
- return;
41
- }
42
-
43
- const paramArray = [];
44
- Object.keys(hashParams).forEach(key => {
45
- const value = hashParams[key];
46
- if (Array.isArray(value)) {
47
- value.forEach(v => {
48
- paramArray.push(key + '=' + encodeURIComponent(v));
49
- });
50
- } else {
51
- paramArray.push(key + '=' + encodeURIComponent(value));
52
- }
53
+ });
54
+ this.urlHash = this.urlHashPath + '?' + paramArray.join('&');
55
+ },
56
+
57
+ get urlHashParams() {
58
+ const ret = {};
59
+ const s = this.urlHash.split('?');
60
+ if (s.length > 1) {
61
+ const p = new URLSearchParams(s[1]);
62
+ p.forEach((v, k) => {
63
+ if (ret[k] === undefined) {
64
+ ret[k] = v;
65
+ } else if (Array.isArray(ret[k])) {
66
+ ret[k].push(v);
67
+ } else {
68
+ ret[k] = [ret[k], v];
69
+ }
53
70
  });
54
- this.urlHash = this.urlHashPath + '?' + paramArray.join('&');
55
- },
56
-
57
- get urlHashParams() {
58
- const ret = {};
59
- const s = this.urlHash.split('?');
60
- if (s.length > 1) {
61
- const p = new URLSearchParams(s[1]);
62
- p.forEach((v, k) => {
63
- if (ret[k] === undefined) {
64
- ret[k] = v;
65
- } else if (Array.isArray(ret[k])) {
66
- ret[k].push(v);
67
- } else {
68
- ret[k] = [ret[k], v];
69
- }
70
- });
71
- }
72
- return ret;
73
- }
71
+ }
72
+ return ret;
73
+ }
74
74
  };
75
75
 
76
76
  globalThis.addEventListener('hashchange', () => {
77
- leanweb.componentsListeningOnUrlChanges.forEach(component => {
78
- setTimeout(() => {
79
- component?.urlHashChanged?.call(component);
80
- component?.update?.call(component);
81
- });
82
- });
77
+ leanweb.componentsListeningOnUrlChanges.forEach(component => {
78
+ setTimeout(() => {
79
+ component?.urlHashChanged?.call(component);
80
+ component?.update?.call(component);
81
+ });
82
+ });
83
83
  }, false);
84
84
 
85
85
  const hasMethod = (obj, name) => {
86
- const desc = Object.getOwnPropertyDescriptor(obj, name);
87
- return !!desc && typeof desc.value === 'function';
86
+ const desc = Object.getOwnPropertyDescriptor(obj, name);
87
+ return !!desc && typeof desc.value === 'function';
88
88
  }
89
89
 
90
90
  const nextAllSiblings = (el, selector) => {
91
- const siblings = [];
92
- while (el = el.nextSibling) {
93
- if (el.nodeType === Node.ELEMENT_NODE && (!selector || el.matches(selector))) {
94
- siblings.push(el);
95
- }
96
- }
97
- return siblings;
91
+ const siblings = [];
92
+ while (el = el.nextSibling) {
93
+ if (el.nodeType === Node.ELEMENT_NODE && (!selector || el.matches(selector))) {
94
+ siblings.push(el);
95
+ }
96
+ }
97
+ return siblings;
98
98
  };
99
99
 
100
100
  export default class LWElement extends HTMLElement {
101
- constructor(ast) {
102
- super();
103
- this.ast = ast;
101
+ constructor(ast) {
102
+ super();
103
+ this.ast = ast;
104
104
 
105
- leanweb.runtimeVersion = ast.runtimeVersion;
106
- leanweb.builderVersion = ast.builderVersion;
105
+ leanweb.runtimeVersion = ast.runtimeVersion;
106
+ leanweb.builderVersion = ast.builderVersion;
107
107
 
108
- const node = document.createElement('template');
109
- node.innerHTML = '<style>' + ast.css + '</style>' +
110
- ast.html;
111
- this.attachShadow({ mode: 'open' }).appendChild(node.content);
108
+ const node = document.createElement('template');
109
+ node.innerHTML = `<style>${ast.css}</style>${ast.html}`;
110
+ this.attachShadow({ mode: 'open' }).appendChild(node.content);
112
111
 
113
- this._bindMethods();
112
+ this._bindMethods();
113
+ setTimeout(() => {
114
+ this.update(this.shadowRoot);
114
115
  setTimeout(() => {
115
- this.update(this.shadowRoot);
116
- setTimeout(() => {
117
- this.domReady?.call(this);
118
- });
119
- });
120
-
121
- if (this.urlHashChanged && typeof this.urlHashChanged === 'function') {
122
- leanweb.componentsListeningOnUrlChanges.push(this);
123
- }
124
-
125
- leanweb.eventBus.addEventListener('update', _ => {
126
- this.update();
127
- });
128
-
129
- leanweb.eventBus.addEventListener(ast.componentFullName, _ => {
130
- this.update();
131
- });
132
- }
133
-
134
- _getNodeContext(node) {
135
- const contextNode = node.closest('[lw-context]');
136
- return contextNode?.['lw-context'] ?? [{ 'this': this }, this, globalThis];
137
- }
138
-
139
- update(rootNode = this.shadowRoot) {
140
- if (rootNode !== this.shadowRoot) {
141
- if (rootNode.hasAttribute('lw-elem')) {
142
- if (rootNode.hasAttribute('lw-elem-bind')) {
143
- this._bindModels(rootNode);
144
- this._bindEvents(rootNode);
145
- this._bindInputs(rootNode);
146
- }
147
- if (rootNode.hasAttribute('lw-if')) {
148
- this.updateIf(rootNode);
149
- }
150
- if (!rootNode.hasAttribute('lw-false')) {
151
- this.updateEval(rootNode);
152
- this.updateClass(rootNode);
153
- this.updateBind(rootNode);
154
- this.updateModel(rootNode);
155
- if (rootNode.hasAttribute('lw-for')) {
156
- this.updateFor(rootNode);
157
- }
158
- }
159
- }
160
- }
161
- const treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, {
162
- acceptNode: node => {
163
- if (node.hasAttribute('lw-elem')) {
164
- if (node.hasAttribute('lw-for')) {
165
- this.updateFor(node);
166
- return NodeFilter.FILTER_REJECT;
167
- }
168
- if (node.hasAttribute('lw-for-parent')) {
169
- return NodeFilter.FILTER_REJECT;
170
- }
171
- if (node.hasAttribute('lw-elem-bind')) {
172
- this._bindModels(node);
173
- this._bindEvents(node);
174
- this._bindInputs(node);
175
- }
176
- if (node.hasAttribute('lw-if')) {
177
- this.updateIf(node);
178
- }
179
- if (node.hasAttribute('lw-false')) {
180
- return NodeFilter.FILTER_REJECT;
181
- }
182
- this.updateEval(node);
183
- this.updateClass(node);
184
- this.updateBind(node);
185
- this.updateModel(node);
186
- }
187
- return NodeFilter.FILTER_ACCEPT;
188
- }
189
- });
190
- while (treeWalker.nextNode()) { }
191
- }
192
-
193
- _bindMethods() {
194
- const methodNames = ['update'];
195
- const proto = Object.getPrototypeOf(this);
196
- methodNames.push(...Object.getOwnPropertyNames(proto).filter(name => hasMethod(proto, name)));
197
- methodNames.push(...Object.getOwnPropertyNames(this).filter(name => hasMethod(this, name)));
198
- methodNames.filter(name => name !== 'constructor').forEach(name => {
199
- this[name] = this[name].bind(this);
116
+ this.domReady?.call(this);
200
117
  });
201
- }
202
-
203
- // properties:
204
- // lw_input_bound: boolean
205
- _bindInputs(inputNode) {
206
- if (inputNode['lw_input_bound']) {
207
- return;
118
+ });
119
+
120
+ if (this.urlHashChanged && typeof this.urlHashChanged === 'function') {
121
+ leanweb.componentsListeningOnUrlChanges.push(this);
122
+ }
123
+
124
+ leanweb.eventBus.addEventListener('update', _ => {
125
+ this.update();
126
+ });
127
+
128
+ leanweb.eventBus.addEventListener(ast.componentFullName, _ => {
129
+ this.update();
130
+ });
131
+ }
132
+
133
+ _getNodeContext(node) {
134
+ const contextNode = node.closest('[lw-context]');
135
+ return contextNode?.['lw-context'] ?? [{ 'this': this }, this, globalThis];
136
+ }
137
+
138
+ update(rootNode = this.shadowRoot) {
139
+ if (rootNode !== this.shadowRoot) {
140
+ if (rootNode.hasAttribute('lw-elem')) {
141
+ if (rootNode.hasAttribute('lw-elem-bind')) {
142
+ this._bindModels(rootNode);
143
+ this._bindEvents(rootNode);
144
+ this._bindInputs(rootNode);
145
+ }
146
+ if (rootNode.hasAttribute('lw-if')) {
147
+ this.updateIf(rootNode);
148
+ }
149
+ if (!rootNode.hasAttribute('lw-false')) {
150
+ this.updateEval(rootNode);
151
+ this.updateClass(rootNode);
152
+ this.updateBind(rootNode);
153
+ this.updateModel(rootNode);
154
+ if (rootNode.hasAttribute('lw-for')) {
155
+ this.updateFor(rootNode);
156
+ }
157
+ }
208
158
  }
209
- inputNode['lw_input_bound'] = true;
210
- for (const attr of inputNode.attributes) {
211
- const attrName = attr.name;
212
- const attrValue = attr.value;
213
- if (attrName.startsWith('lw-input:')) {
214
- const interpolation = this.ast[attrValue];
215
- const context = this._getNodeContext(inputNode);
216
- const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
217
- inputNode[interpolation.lwValue] = parsed[0];
218
- }
159
+ }
160
+ const treeWalker = document.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT, {
161
+ acceptNode: node => {
162
+ if (node.hasAttribute('lw-elem')) {
163
+ if (node.hasAttribute('lw-for')) {
164
+ this.updateFor(node);
165
+ return NodeFilter.FILTER_REJECT;
166
+ }
167
+ if (node.hasAttribute('lw-for-parent')) {
168
+ return NodeFilter.FILTER_REJECT;
169
+ }
170
+ if (node.hasAttribute('lw-elem-bind')) {
171
+ this._bindModels(node);
172
+ this._bindEvents(node);
173
+ this._bindInputs(node);
174
+ }
175
+ if (node.hasAttribute('lw-if')) {
176
+ this.updateIf(node);
177
+ }
178
+ if (node.hasAttribute('lw-false')) {
179
+ return NodeFilter.FILTER_REJECT;
180
+ }
181
+ this.updateEval(node);
182
+ this.updateClass(node);
183
+ this.updateBind(node);
184
+ this.updateModel(node);
185
+ }
186
+ return NodeFilter.FILTER_ACCEPT;
219
187
  }
220
- inputNode?.inputReady?.call(this);
221
- inputNode?.update?.call(this);
222
- }
223
-
224
- // properties:
225
- // lw_event_bound: boolean
226
- _bindEvents(eventNode) {
227
- if (eventNode['lw_event_bound']) {
228
- return;
188
+ });
189
+ while (treeWalker.nextNode()) { }
190
+ }
191
+
192
+ _bindMethods() {
193
+ const methodNames = ['update'];
194
+ const proto = Object.getPrototypeOf(this);
195
+ methodNames.push(...Object.getOwnPropertyNames(proto).filter(name => hasMethod(proto, name)));
196
+ methodNames.push(...Object.getOwnPropertyNames(this).filter(name => hasMethod(this, name)));
197
+ methodNames.filter(name => name !== 'constructor').forEach(name => {
198
+ this[name] = this[name].bind(this);
199
+ });
200
+ }
201
+
202
+ // properties:
203
+ // lw_input_bound: boolean
204
+ _bindInputs(inputNode) {
205
+ if (inputNode['lw_input_bound']) {
206
+ return;
207
+ }
208
+ inputNode['lw_input_bound'] = true;
209
+ for (const attr of inputNode.attributes) {
210
+ const attrName = attr.name;
211
+ const attrValue = attr.value;
212
+ if (attrName.startsWith('lw-input:')) {
213
+ const interpolation = this.ast[attrValue];
214
+ const context = this._getNodeContext(inputNode);
215
+ const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
216
+ inputNode[interpolation.lwValue] = parsed[0];
229
217
  }
230
- eventNode['lw_event_bound'] = true;
231
- const me = this;
232
- for (const attr of eventNode.attributes) {
233
- const attrName = attr.name;
234
- const attrValue = attr.value;
235
- if (attrName.startsWith('lw-on:')) {
236
- const interpolation = this.ast[attrValue];
237
- interpolation.lwValue.split(',').forEach(eventType => {
238
- eventNode.addEventListener(eventType.trim(), (event => {
239
- const context = this._getNodeContext(eventNode);
240
- const eventContext = { '$event': event, '$node': eventNode };
241
- const parsed = parser.evaluate(interpolation.ast, [eventContext, ...context], interpolation.loc);
242
- const promises = parsed.filter(p => typeof p?.then === 'function' && typeof p?.finally === 'function');
243
- if (parsed.length > promises.length) {
244
- me.update();
245
- }
246
- promises.forEach(p => {
247
- p?.finally(() => {
248
- me.update();
249
- });
250
- });
251
- }).bind(me));
218
+ }
219
+ inputNode?.inputReady?.call(this);
220
+ inputNode?.update?.call(this);
221
+ }
222
+
223
+ // properties:
224
+ // lw_event_bound: boolean
225
+ _bindEvents(eventNode) {
226
+ if (eventNode['lw_event_bound']) {
227
+ return;
228
+ }
229
+ eventNode['lw_event_bound'] = true;
230
+ const me = this;
231
+ for (const attr of eventNode.attributes) {
232
+ const attrName = attr.name;
233
+ const attrValue = attr.value;
234
+ if (attrName.startsWith('lw-on:')) {
235
+ const interpolation = this.ast[attrValue];
236
+ interpolation.lwValue.split(',').forEach(eventType => {
237
+ eventNode.addEventListener(eventType.trim(), (event => {
238
+ const context = this._getNodeContext(eventNode);
239
+ const eventContext = { '$event': event, '$node': eventNode };
240
+ const parsed = parser.evaluate(interpolation.ast, [eventContext, ...context], interpolation.loc);
241
+ const promises = parsed.filter(p => typeof p?.then === 'function' && typeof p?.finally === 'function');
242
+ if (parsed.length > promises.length) {
243
+ me.update();
244
+ }
245
+ promises.forEach(p => {
246
+ p?.finally(() => {
247
+ me.update();
248
+ });
252
249
  });
253
- }
250
+ }).bind(me));
251
+ });
254
252
  }
255
- }
256
-
257
- // properties:
258
- // lw_model_bound: boolean
259
- _bindModels(modelNode) {
260
- const key = modelNode.getAttribute('lw-model');
261
- if (!key) {
262
- return;
263
- }
264
- if (modelNode['lw_model_bound']) {
265
- return;
253
+ }
254
+ }
255
+
256
+ // properties:
257
+ // lw_model_bound: boolean
258
+ _bindModels(modelNode) {
259
+ const key = modelNode.getAttribute('lw-model');
260
+ if (!key) {
261
+ return;
262
+ }
263
+ if (modelNode['lw_model_bound']) {
264
+ return;
265
+ }
266
+ modelNode['lw_model_bound'] = true;
267
+ const interpolation = this.ast[key];
268
+ modelNode.addEventListener('input', (event => {
269
+ const context = this._getNodeContext(modelNode);
270
+ const astModel = interpolation.ast[0].expression;
271
+ let object;
272
+ let propertyExpr;
273
+ if (astModel.type === 'MemberExpression') {
274
+ propertyExpr = astModel.property.name;
275
+ if (astModel.computed) {
276
+ // . false and [] true
277
+ propertyExpr = parser.evaluate([astModel.property], context, interpolation.loc)[0];
278
+ }
279
+ object = parser.evaluate([astModel.object], context, interpolation.loc)[0];
280
+ } else if (astModel.type === 'Identifier') {
281
+ object = this;
282
+ propertyExpr = astModel.name;
266
283
  }
267
- modelNode['lw_model_bound'] = true;
268
- const interpolation = this.ast[key];
269
- modelNode.addEventListener('input', (event => {
270
- const context = this._getNodeContext(modelNode);
271
- const astModel = interpolation.ast[0].expression;
272
- let object;
273
- let propertyExpr;
274
- if (astModel.type === 'MemberExpression') {
275
- propertyExpr = astModel.property.name;
276
- if (astModel.computed) {
277
- // . false and [] true
278
- propertyExpr = parser.evaluate([astModel.property], context, interpolation.loc)[0];
279
- }
280
- object = parser.evaluate([astModel.object], context, interpolation.loc)[0];
281
- } else if (astModel.type === 'Identifier') {
282
- object = this;
283
- propertyExpr = astModel.name;
284
- }
285
-
286
- if (modelNode.type === 'number') {
287
- // set do_not_update mark for cases when user inputs 0.01, 0.0 will not be evaluated prematurely
288
- modelNode.do_not_update = true;
289
- object[propertyExpr] = modelNode.value * 1;
290
- } else if (modelNode.type === 'checkbox') {
291
- if (Array.isArray(object[propertyExpr])) {
292
- if (modelNode.checked) {
293
- object[propertyExpr].push(modelNode.value);
294
- } else {
295
- const index = object[propertyExpr].indexOf(modelNode.value);
296
- if (index > -1) {
297
- object[propertyExpr].splice(index, 1);
298
- }
299
- }
300
- } else {
301
- object[propertyExpr] = modelNode.checked;
302
- }
303
- } else if (modelNode.type === 'select-multiple') {
304
- if (!Array.isArray(object[propertyExpr])) {
305
- object[propertyExpr] = [];
306
- }
307
- object[propertyExpr].length = 0;
308
- for (let i = 0; i < modelNode.options.length; ++i) {
309
- const option = modelNode.options[i];
310
- if (option.selected) {
311
- object[propertyExpr].push(option.value);
312
- }
284
+
285
+ if (modelNode.type === 'number') {
286
+ // set do_not_update mark for cases when user inputs 0.01, 0.0 will not be evaluated prematurely
287
+ modelNode.do_not_update = true;
288
+ object[propertyExpr] = modelNode.value * 1;
289
+ } else if (modelNode.type === 'checkbox') {
290
+ if (Array.isArray(object[propertyExpr])) {
291
+ if (modelNode.checked) {
292
+ object[propertyExpr].push(modelNode.value);
293
+ } else {
294
+ const index = object[propertyExpr].indexOf(modelNode.value);
295
+ if (index > -1) {
296
+ object[propertyExpr].splice(index, 1);
313
297
  }
314
- } else {
315
- object[propertyExpr] = modelNode.value;
316
- }
317
- this.update();
318
- delete modelNode.do_not_update;
319
- }).bind(this));
320
- }
321
-
322
- updateModel(modelNode) {
323
- if (modelNode.do_not_update && modelNode.type === 'number') {
324
- return;
325
- }
326
- const key = modelNode.getAttribute('lw-model');
327
- if (!key) {
328
- return;
329
- }
330
- const context = this._getNodeContext(modelNode);
331
- const interpolation = this.ast[key];
332
- const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
333
- if (modelNode.type === 'checkbox') {
334
- if (Array.isArray(parsed[0])) {
335
- modelNode.checked = parsed[0].includes?.(modelNode.value);
336
- } else {
337
- modelNode.checked = !!parsed[0];
338
- }
339
- } else if (modelNode.type === 'radio') {
340
- modelNode.checked = parsed[0] === modelNode.value;
298
+ }
299
+ } else {
300
+ object[propertyExpr] = modelNode.checked;
301
+ }
341
302
  } else if (modelNode.type === 'select-multiple') {
342
- for (let i = 0; i < modelNode.options.length; ++i) {
343
- const option = modelNode.options[i];
344
- if (parsed[0]) {
345
- option.selected = parsed[0].includes(option.value);
346
- }
347
- }
303
+ if (!Array.isArray(object[propertyExpr])) {
304
+ object[propertyExpr] = [];
305
+ }
306
+ object[propertyExpr].length = 0;
307
+ for (let i = 0; i < modelNode.options.length; ++i) {
308
+ const option = modelNode.options[i];
309
+ if (option.selected) {
310
+ object[propertyExpr].push(option.value);
311
+ }
312
+ }
348
313
  } else {
349
- const newValue = parsed[0] ?? '';
350
- if (modelNode.value !== newValue) {
351
- modelNode.value = newValue;
352
- }
314
+ object[propertyExpr] = modelNode.value;
353
315
  }
354
- }
355
-
356
- // attribute: lw: astKey
357
- // property: lw-eval-value-$key
358
- updateEval(evalNode) {
359
- const key = evalNode.getAttribute('lw');
360
- if (!key) {
361
- return;
316
+ this.update();
317
+ delete modelNode.do_not_update;
318
+ }).bind(this));
319
+ }
320
+
321
+ updateModel(modelNode) {
322
+ if (modelNode.do_not_update && modelNode.type === 'number') {
323
+ return;
324
+ }
325
+ const key = modelNode.getAttribute('lw-model');
326
+ if (!key) {
327
+ return;
328
+ }
329
+ const context = this._getNodeContext(modelNode);
330
+ const interpolation = this.ast[key];
331
+ const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
332
+ if (modelNode.type === 'checkbox') {
333
+ if (Array.isArray(parsed[0])) {
334
+ modelNode.checked = parsed[0].includes?.(modelNode.value);
335
+ } else {
336
+ modelNode.checked = !!parsed[0];
362
337
  }
363
- const context = this._getNodeContext(evalNode);
364
- const interpolation = this.ast[key];
365
- const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
366
- if (evalNode['lw-eval-value-' + key] !== parsed[0] || typeof parsed[0] === 'object') {
367
- evalNode['lw-eval-value-' + key] = parsed[0];
368
- evalNode.innerText = parsed[0] ?? '';
338
+ } else if (modelNode.type === 'radio') {
339
+ modelNode.checked = parsed[0] === modelNode.value;
340
+ } else if (modelNode.type === 'select-multiple') {
341
+ for (let i = 0; i < modelNode.options.length; ++i) {
342
+ const option = modelNode.options[i];
343
+ if (parsed[0]) {
344
+ option.selected = parsed[0].includes(option.value);
345
+ }
369
346
  }
370
- }
371
-
372
- // attribute: lw-if: astKey
373
- // lw-false: '' (if false)
374
- updateIf(ifNode) {
375
- const key = ifNode.getAttribute('lw-if');
376
- if (!key) {
377
- return;
347
+ } else {
348
+ const newValue = parsed[0] ?? '';
349
+ if (modelNode.value !== newValue) {
350
+ modelNode.value = newValue;
378
351
  }
379
- const context = this._getNodeContext(ifNode);
380
- const interpolation = this.ast[key];
381
- const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
382
-
383
- const hasLwFalse = ifNode.hasAttribute('lw-false');
384
- if (parsed[0] !== false && parsed[0] !== undefined && parsed[0] !== null) {
385
- hasLwFalse && ifNode.removeAttribute('lw-false');
386
- setTimeout(() => {
387
- ifNode.turnedOn?.call(ifNode);
388
- });
389
- } else {
390
- !hasLwFalse && ifNode.setAttribute('lw-false', '');
391
- setTimeout(() => {
392
- ifNode.turnedOff?.call(ifNode);
393
- });
352
+ }
353
+ }
354
+
355
+ // attribute: lw: astKey
356
+ // property: lw-eval-value-$key
357
+ updateEval(evalNode) {
358
+ const key = evalNode.getAttribute('lw');
359
+ if (!key) {
360
+ return;
361
+ }
362
+ const context = this._getNodeContext(evalNode);
363
+ const interpolation = this.ast[key];
364
+ const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
365
+ if (evalNode['lw-eval-value-' + key] !== parsed[0] || typeof parsed[0] === 'object') {
366
+ evalNode['lw-eval-value-' + key] = parsed[0];
367
+ evalNode.innerText = parsed[0] ?? '';
368
+ }
369
+ }
370
+
371
+ // attribute: lw-if: astKey
372
+ // lw-false: '' (if false)
373
+ updateIf(ifNode) {
374
+ const key = ifNode.getAttribute('lw-if');
375
+ if (!key) {
376
+ return;
377
+ }
378
+ const context = this._getNodeContext(ifNode);
379
+ const interpolation = this.ast[key];
380
+ const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
381
+
382
+ const hasLwFalse = ifNode.hasAttribute('lw-false');
383
+ if (parsed[0] !== false && parsed[0] !== undefined && parsed[0] !== null) {
384
+ hasLwFalse && ifNode.removeAttribute('lw-false');
385
+ setTimeout(() => {
386
+ ifNode.turnedOn?.call(ifNode);
387
+ });
388
+ } else {
389
+ !hasLwFalse && ifNode.setAttribute('lw-false', '');
390
+ setTimeout(() => {
391
+ ifNode.turnedOff?.call(ifNode);
392
+ });
393
+ }
394
+ }
395
+
396
+ // attribute: lw-class: astKey
397
+ updateClass(classNode) {
398
+ const context = this._getNodeContext(classNode);
399
+ for (const attr of classNode.attributes) {
400
+ const attrName = attr.name;
401
+ const attrValue = attr.value;
402
+ if (attrName.startsWith('lw-class:')) {
403
+ const interpolation = this.ast[attrValue];
404
+ const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
405
+
406
+ if (!parsed[0]) {
407
+ classNode.classList.remove(interpolation.lwValue);
408
+ } else {
409
+ classNode.classList.add(interpolation.lwValue);
410
+ }
394
411
  }
395
- }
396
-
397
- // attribute: lw-class: astKey
398
- updateClass(classNode) {
399
- const context = this._getNodeContext(classNode);
400
- for (const attr of classNode.attributes) {
401
- const attrName = attr.name;
402
- const attrValue = attr.value;
403
- if (attrName.startsWith('lw-class:')) {
404
- const interpolation = this.ast[attrValue];
405
- const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
406
-
407
- if (!parsed[0]) {
408
- classNode.classList.remove(interpolation.lwValue);
409
- } else {
410
- classNode.classList.add(interpolation.lwValue);
411
- }
412
- }
412
+ }
413
+ }
414
+
415
+ updateBind(bindNode) {
416
+ const context = this._getNodeContext(bindNode);
417
+ for (const attr of bindNode.attributes) {
418
+ const attrName = attr.name;
419
+ const attrValue = attr.value;
420
+ if (attrName.startsWith('lw-bind:')) {
421
+ const interpolation = this.ast[attrValue];
422
+ const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
423
+
424
+ if (parsed[0] !== false && parsed[0] !== undefined && parsed[0] !== null) {
425
+ bindNode.setAttribute(interpolation.lwValue, parsed[0]);
426
+ } else {
427
+ bindNode.removeAttribute(interpolation.lwValue);
428
+ }
413
429
  }
414
- }
415
-
416
- updateBind(bindNode) {
417
- const context = this._getNodeContext(bindNode);
418
- for (const attr of bindNode.attributes) {
419
- const attrName = attr.name;
420
- const attrValue = attr.value;
421
- if (attrName.startsWith('lw-bind:')) {
422
- const interpolation = this.ast[attrValue];
423
- const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
424
-
425
- if (parsed[0] !== false && parsed[0] !== undefined && parsed[0] !== null) {
426
- bindNode.setAttribute(interpolation.lwValue, parsed[0]);
427
- } else {
428
- bindNode.removeAttribute(interpolation.lwValue);
429
- }
430
- }
430
+ }
431
+ }
432
+
433
+ // parent attribytes:
434
+ // lw-for: $astKey
435
+
436
+ // child attributes:
437
+ // lw-context: ''
438
+ // lw-for-parent: $astKey
439
+
440
+ // child propery:
441
+ // lw-context: localContext
442
+ updateFor(forNode) {
443
+ const key = forNode.getAttribute('lw-for');
444
+ if (!key) {
445
+ return;
446
+ }
447
+ const context = this._getNodeContext(forNode);
448
+ const interpolation = this.ast[key];
449
+ const items = parser.evaluate(interpolation.astItems, context, interpolation.loc)[0] ?? [];
450
+ const rendered = nextAllSiblings(forNode, `[lw-for-parent="${key}"]`);
451
+ for (let i = items.length; i < rendered.length; ++i) {
452
+ rendered[i].remove();
453
+ }
454
+
455
+ let currentNode = forNode;
456
+ items.forEach((item, index) => {
457
+ let node;
458
+ if (rendered.length > index) {
459
+ node = rendered[index];
460
+ } else {
461
+ node = forNode.cloneNode(true);
462
+ node.removeAttribute('lw-for');
463
+ // node.removeAttribute('lw-elem');
464
+ node.setAttribute('lw-for-parent', key);
465
+ node.setAttribute('lw-context', '');
466
+ currentNode.insertAdjacentElement('afterend', node);
431
467
  }
432
- }
433
-
434
- // parent attribytes:
435
- // lw-for: $astKey
436
-
437
- // child attributes:
438
- // lw-context: ''
439
- // lw-for-parent: $astKey
440
-
441
- // child propery:
442
- // lw-context: localContext
443
- updateFor(forNode) {
444
- const key = forNode.getAttribute('lw-for');
445
- if (!key) {
446
- return;
468
+ if (item && typeof item === 'object') {
469
+ item.getDom = () => node;
447
470
  }
448
- const context = this._getNodeContext(forNode);
449
- const interpolation = this.ast[key];
450
- const items = parser.evaluate(interpolation.astItems, context, interpolation.loc)[0] ?? [];
451
- const rendered = nextAllSiblings(forNode, `[lw-for-parent="${key}"]`);
452
- for (let i = items.length; i < rendered.length; ++i) {
453
- rendered[i].remove();
471
+ currentNode = node;
472
+ const itemContext = { [interpolation.itemExpr]: item };
473
+ if (interpolation.indexExpr) {
474
+ itemContext[interpolation.indexExpr] = index;
454
475
  }
455
476
 
456
- let currentNode = forNode;
457
- items.forEach((item, index) => {
458
- let node;
459
- if (rendered.length > index) {
460
- node = rendered[index];
461
- } else {
462
- node = forNode.cloneNode(true);
463
- node.removeAttribute('lw-for');
464
- // node.removeAttribute('lw-elem');
465
- node.setAttribute('lw-for-parent', key);
466
- node.setAttribute('lw-context', '');
467
- currentNode.insertAdjacentElement('afterend', node);
468
- }
469
- if (item && typeof item === 'object') {
470
- item.getDom = () => node;
471
- }
472
- currentNode = node;
473
- const itemContext = { [interpolation.itemExpr]: item };
474
- if (interpolation.indexExpr) {
475
- itemContext[interpolation.indexExpr] = index;
476
- }
477
-
478
- node['lw-context'] = [itemContext, ...context];
479
- this.update(node);
480
- });
481
- }
477
+ node['lw-context'] = [itemContext, ...context];
478
+ this.update(node);
479
+ });
480
+ }
482
481
  }