leanweb 1.0.4

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