leanweb 3.0.7 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/commands/init.js CHANGED
@@ -23,7 +23,7 @@ import * as utils from './utils.js';
23
23
  return;
24
24
  }
25
25
 
26
- const projectName = path.basename(path.resolve());
26
+ let projectName = path.basename(path.resolve());
27
27
 
28
28
  if (args.length >= 3) {
29
29
  projectName = args[2];
package/leanweb.js CHANGED
@@ -42,7 +42,7 @@ import * as utils from './commands/utils.js';
42
42
  return;
43
43
  }
44
44
 
45
- if (leanwebJSONExisted && target === 'version' || target === 'serve' || target === 'dist') {
45
+ if (leanwebJSONExisted && (target === 'version' || target === 'serve' || target === 'dist')) {
46
46
  const leanwebPackageJSON = require(`${__dirname}/package.json`);
47
47
  const projectLeanwebJSON = require(`${process.cwd()}/${utils.dirs.src}/leanweb.json`);
48
48
  const upgradeAvailable = semver.gt(leanwebPackageJSON.version, projectLeanwebJSON.version);
@@ -6,7 +6,7 @@ let astKey = 0;
6
6
  const removeASTLocation = ast => {
7
7
  if (Array.isArray(ast)) {
8
8
  ast.forEach(a => removeASTLocation(a));
9
- } else if (typeof ast === 'object') {
9
+ } else if (ast !== null && typeof ast === 'object') {
10
10
  delete ast['loc'];
11
11
  delete ast['start'];
12
12
  delete ast['end'];
@@ -77,7 +77,7 @@ const walkNode = (node, interpolation) => {
77
77
 
78
78
  if (attr.name === 'lw-bind:class') {
79
79
  const classAttr = node.attrs.find(a => a.name === 'class');
80
- node.attrs.push({ name: 'lw-init-class', value: classAttr.value });
80
+ node.attrs.push({ name: 'lw-init-class', value: classAttr?.value ?? '' });
81
81
  }
82
82
 
83
83
  const ast = getAST(attr.value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leanweb",
3
- "version": "3.0.7",
3
+ "version": "3.0.9",
4
4
  "description": "Builds framework agnostic web components.",
5
5
  "bin": {
6
6
  "leanweb": "leanweb.js",
@@ -20,15 +20,15 @@
20
20
  "author": "Qian Chen",
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
- "@babel/parser": "^7.28.4",
24
- "esbuild": "^0.25.9",
25
- "fs-extra": "^11.3.1",
26
- "globby": "^14.1.0",
23
+ "@babel/parser": "^7.29.0",
24
+ "esbuild": "^0.27.3",
25
+ "fs-extra": "^11.3.4",
26
+ "globby": "^16.1.1",
27
27
  "html-minifier": "^4.0.0",
28
- "isomorphic-git": "^1.33.1",
28
+ "isomorphic-git": "^1.37.2",
29
29
  "live-server": "^1.2.2",
30
30
  "node-watch": "^0.7.4",
31
31
  "parse5": "^8.0.0",
32
- "semver": "^7.7.2"
32
+ "semver": "^7.7.4"
33
33
  }
34
34
  }
@@ -111,10 +111,14 @@ export default class LWElement extends HTMLElement {
111
111
 
112
112
  this._bindMethods();
113
113
  setTimeout(() => {
114
- this.update(this.shadowRoot);
115
- setTimeout(() => {
116
- this.domReady?.call(this);
117
- });
114
+ const result = this.domReady?.call(this);
115
+ if (result && typeof result.then === 'function') {
116
+ // domReady is async
117
+ result.then(() => this.update());
118
+ } else {
119
+ // domReady is sync
120
+ this.update();
121
+ }
118
122
  });
119
123
 
120
124
  if (this.urlHashChanged && typeof this.urlHashChanged === 'function') {
@@ -286,7 +290,9 @@ export default class LWElement extends HTMLElement {
286
290
  } else if (modelNode.type === 'checkbox') {
287
291
  if (Array.isArray(object[propertyExpr])) {
288
292
  if (modelNode.checked) {
289
- object[propertyExpr].push(modelNode.value);
293
+ if (!object[propertyExpr].includes(modelNode.value)) {
294
+ object[propertyExpr].push(modelNode.value);
295
+ }
290
296
  } else {
291
297
  const index = object[propertyExpr].indexOf(modelNode.value);
292
298
  if (index > -1) {
@@ -316,7 +322,7 @@ export default class LWElement extends HTMLElement {
316
322
  }
317
323
 
318
324
  updateModel(modelNode) {
319
- if (modelNode.do_not_update && modelNode.type === 'number') {
325
+ if (modelNode.do_not_update && (modelNode.type === 'number' || modelNode.type === 'range')) {
320
326
  return;
321
327
  }
322
328
  const key = modelNode.getAttribute('lw-model');
@@ -335,9 +341,14 @@ export default class LWElement extends HTMLElement {
335
341
  } else if (modelNode.type === 'radio') {
336
342
  modelNode.checked = parsed[0] === modelNode.value;
337
343
  } else if (modelNode.type === 'select-multiple') {
344
+ // First, clear all selections
338
345
  for (let i = 0; i < modelNode.options.length; ++i) {
339
- const option = modelNode.options[i];
340
- if (parsed[0]) {
346
+ modelNode.options[i].selected = false;
347
+ }
348
+ // Then, set selected options
349
+ if (parsed[0]) {
350
+ for (let i = 0; i < modelNode.options.length; ++i) {
351
+ const option = modelNode.options[i];
341
352
  option.selected = parsed[0].includes(option.value);
342
353
  }
343
354
  }
@@ -377,7 +388,7 @@ export default class LWElement extends HTMLElement {
377
388
  const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
378
389
 
379
390
  const hasLwFalse = ifNode.hasAttribute('lw-false');
380
- if (parsed[0] !== false && parsed[0] !== undefined && parsed[0] !== null) {
391
+ if (parsed[0]) {
381
392
  hasLwFalse && ifNode.removeAttribute('lw-false');
382
393
  setTimeout(() => {
383
394
  ifNode.turnedOn?.call(ifNode);
@@ -419,11 +430,25 @@ export default class LWElement extends HTMLElement {
419
430
  const parsed = parser.evaluate(interpolation.ast, context, interpolation.loc);
420
431
 
421
432
  if (interpolation.lwValue === 'class') {
422
- const initClass = bindNode.getAttribute('lw-init-class');
423
- if (!parsed[0]) {
424
- bindNode.classList.remove(parsed[0]);
433
+ const dynamicClass = parsed[0];
434
+ const initClass = bindNode.getAttribute('lw-init-class') || '';
435
+ // Ensure initial classes are present
436
+ if (initClass) {
437
+ initClass.split(' ').forEach(cls => {
438
+ if (cls) bindNode.classList.add(cls);
439
+ });
440
+ }
441
+ // Remove previously applied dynamic class
442
+ const prevClass = bindNode['lw-prev-class-' + attrValue];
443
+ if (prevClass && prevClass !== dynamicClass) {
444
+ bindNode.classList.remove(prevClass);
445
+ }
446
+ // Add or record the dynamic class
447
+ if (dynamicClass) {
448
+ bindNode.classList.add(dynamicClass);
449
+ bindNode['lw-prev-class-' + attrValue] = dynamicClass;
425
450
  } else {
426
- bindNode.classList = initClass + ' ' + parsed[0];
451
+ bindNode['lw-prev-class-' + attrValue] = null;
427
452
  }
428
453
  } else {
429
454
  if (parsed[0] !== false && parsed[0] !== undefined && parsed[0] !== null) {
@@ -43,11 +43,6 @@ const assignmentOperations = {
43
43
  '^=': (c, a, b) => { c[a] ^= b; },
44
44
  };
45
45
 
46
- const logicalOperators = {
47
- '||': (a, b) => a || b,
48
- '&&': (a, b) => a && b,
49
- '??': (a, b) => a ?? b,
50
- };
51
46
 
52
47
  const unaryOperators = {
53
48
  '-': a => -a,
@@ -96,20 +91,44 @@ const nodeHandlers = {
96
91
  'ExpressionStatement': (node, context) => evalNode(node.expression, context),
97
92
  'BinaryExpression': (node, context) => binaryOperations[node.operator](evalNode(node.left, context), evalNode(node.right, context)),
98
93
  'AssignmentExpression': (node, context) => {
99
- const immediateCtx = immediateContext(node.left, context);
100
- assignmentOperations[node.operator](immediateCtx, node.left.name, evalNode(node.right, context));
94
+ // Support complex left-hand sides (e.g., obj.prop = 1, obj[expr] = 1)
95
+ let obj, prop;
96
+ if (node.left.type === 'MemberExpression' || node.left.type === 'OptionalMemberExpression') {
97
+ obj = evalNode(node.left.object, context);
98
+ prop = node.left.computed ? evalNode(node.left.property, context) : node.left.property.name;
99
+ } else if (node.left.type === 'Identifier') {
100
+ // Simple variable assignment
101
+ obj = immediateContext(node.left, context);
102
+ prop = node.left.name;
103
+ } else {
104
+ throw new Error('Unsupported assignment left-hand side');
105
+ }
106
+ assignmentOperations[node.operator](obj, prop, evalNode(node.right, context));
107
+ },
108
+ 'LogicalExpression': (node, context) => {
109
+ const left = evalNode(node.left, context);
110
+ if (node.operator === '&&') return left ? evalNode(node.right, context) : left;
111
+ if (node.operator === '||') return left ? left : evalNode(node.right, context);
112
+ if (node.operator === '??') return left ?? evalNode(node.right, context);
101
113
  },
102
- 'LogicalExpression': (node, context) => logicalOperators[node.operator](evalNode(node.left, context), evalNode(node.right, context)),
103
114
  'UnaryExpression': (node, context) => unaryOperators[node.operator](evalNode(node.argument, context)),
104
115
  'UpdateExpression': (node, context) => {
105
- const immediateCtx = immediateContext(node.argument, context);
106
- updateOperators(node.operator, node.prefix)(immediateCtx, node.argument.name, evalNode(node.argument, context));
116
+ // Support complex left-hand sides (e.g., ++obj.prop, ++obj[expr])
117
+ let obj, prop;
118
+ if (node.argument.type === 'MemberExpression' || node.argument.type === 'OptionalMemberExpression') {
119
+ obj = evalNode(node.argument.object, context);
120
+ prop = node.argument.computed ? evalNode(node.argument.property, context) : node.argument.property.name;
121
+ } else if (node.argument.type === 'Identifier') {
122
+ obj = immediateContext(node.argument, context);
123
+ prop = node.argument.name;
124
+ } else {
125
+ throw new Error('Unsupported update left-hand side');
126
+ }
127
+ return updateOperators(node.operator, node.prefix)(obj, prop);
107
128
  },
108
129
  'ConditionalExpression': (node, context) => {
109
130
  const test = evalNode(node.test, context);
110
- const consequent = evalNode(node.consequent, context);
111
- const alternate = evalNode(node.alternate, context);
112
- return test ? consequent : alternate;
131
+ return test ? evalNode(node.consequent, context) : evalNode(node.alternate, context);
113
132
  },
114
133
  'MemberExpression': (node, context) => {
115
134
  const object = evalNode(node.object, context);
@@ -143,7 +162,7 @@ const nodeHandlers = {
143
162
  return arr;
144
163
  },
145
164
  'ObjectExpression': (node, context) => node.properties.reduce((acc, prop) => ({ ...acc, ...evalNode(prop, context) }), {}),
146
- 'ObjectProperty': (node, context) => ({ [evalNode(node.key, context)]: evalNode(node.value, context) }),
165
+ 'ObjectProperty': (node, context) => ({ [node.computed ? evalNode(node.key, context) : (node.key.name ?? node.key.value)]: evalNode(node.value, context) }),
147
166
  'SpreadElement': (node, context) => evalNode(node.argument, context),
148
167
 
149
168
  'Identifier': (node, context) => {
@@ -160,7 +179,18 @@ const nodeHandlers = {
160
179
 
161
180
  'CallExpression': (node, context) => callFunction(node, context),
162
181
  'OptionalCallExpression': (node, context) => callFunction(node, context),
163
- 'NewExpression': (node, context) => callFunction(node, context),
182
+ 'NewExpression': (node, context) => {
183
+ const callee = evalNode(node.callee, context);
184
+ const args = [];
185
+ node.arguments.map(argument => {
186
+ if (argument.type === 'SpreadElement') {
187
+ args.push(...evalNode(argument, context));
188
+ } else {
189
+ args.push(evalNode(argument, context));
190
+ }
191
+ });
192
+ return new callee(...args);
193
+ },
164
194
 
165
195
  'Directive': (node, context) => evalNode(node.value, context),
166
196
  'DirectiveLiteral': (node, context) => node.value,