malinajs 0.7.2-a9 → 0.7.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.
package/malina.js CHANGED
@@ -19,7 +19,7 @@
19
19
  let prev = current_context;
20
20
  try {
21
21
  current_context = context;
22
- fn();
22
+ return fn();
23
23
  } finally {
24
24
  current_context = prev;
25
25
  }
@@ -671,6 +671,42 @@
671
671
  }
672
672
  };
673
673
 
674
+ const walk = (node, fn) => {
675
+ switch(node.type) {
676
+ case 'node':
677
+ case 'slot':
678
+ case 'block':
679
+ case 'fragment':
680
+ case 'root':
681
+ if(node.body) fn(node.body, node);
682
+ break
683
+ case 'each':
684
+ if(node.mainBlock) fn(node.mainBlock, node);
685
+ if(node.elseBlock) fn(node.elseBlock, node);
686
+ break
687
+ case 'await':
688
+ if(node.parts.main) fn(node.parts.main, node);
689
+ if(node.parts.then) fn(node.parts.then, node);
690
+ if(node.parts.catch) fn(node.parts.catch, node);
691
+ break
692
+ case 'if':
693
+ node.parts.forEach(p => {
694
+ if(p.body) fn(p.body, node);
695
+ });
696
+ if(node.elsePart) fn(node.elsePart, node);
697
+ break
698
+ case 'text':
699
+ case 'comment':
700
+ case 'script':
701
+ case 'style':
702
+ case 'systag':
703
+ case 'template':
704
+ break
705
+ default:
706
+ throw `Not implemented: ${node.type}`;
707
+ }
708
+ };
709
+
674
710
  function compactDOM() {
675
711
  let data = this.DOM;
676
712
 
@@ -710,29 +746,7 @@
710
746
  }
711
747
  } else {
712
748
  if(node.type == 'node' && (node.name == 'pre' || node.name == 'textarea')) continue;
713
- switch(node.type) {
714
- case 'node':
715
- case 'slot':
716
- case 'block':
717
- case 'fragment':
718
- if(node.body) go(node.body, node);
719
- break
720
- case 'each':
721
- if(node.mainBlock) go(node.mainBlock, node);
722
- if(node.elseBlock) go(node.elseBlock, node);
723
- break
724
- case 'await':
725
- if(node.parts.main) go(node.parts.main, node);
726
- if(node.parts.then) go(node.parts.then, node);
727
- if(node.parts.catch) go(node.parts.catch, node);
728
- break
729
- case 'if':
730
- node.parts.forEach(p => {
731
- if(p.body) go(p.body, node);
732
- });
733
- if(node.elsePart) go(node.elsePart, node);
734
- break
735
- }
749
+ walk(node, go);
736
750
  }
737
751
  }
738
752
 
@@ -763,8 +777,9 @@
763
777
  if(prev && next) {
764
778
  if(prev.type == 'node' && next.type == 'node') {
765
779
  if(isTable(prev) && isTable(next) ||
766
- prev.name == 'li' && next.name == 'li' ||
767
- prev.name == 'div' && next.name == 'div') {
780
+ prev.name == 'li' && next.name == 'li' ||
781
+ parentNode?.type == 'node' && parentNode?.name == 'select' && prev.name == 'option' && next.name == 'option' ||
782
+ prev.name == 'div' && next.name == 'div') {
768
783
  body.splice(i, 1);
769
784
  continue;
770
785
  }
@@ -785,6 +800,10 @@
785
800
  body.splice(i, 1);
786
801
  continue;
787
802
  }
803
+ if((p == 'option' || n == 'option') && (parentNode.type == 'node' && parentNode.name == 'select')) {
804
+ body.splice(i, 1);
805
+ continue;
806
+ }
788
807
  if(parentNode.type == 'node' && ['div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'].includes(parentNode.name)) {
789
808
  body.splice(i, 1);
790
809
  continue;
@@ -836,6 +855,25 @@
836
855
  go(data.body);
837
856
  }
838
857
 
858
+ function compactFull() {
859
+ const go = (body) => {
860
+ let i = 0;
861
+ while (i < body.length) {
862
+ let n = body[i];
863
+ if(n.type == 'text') {
864
+ n.value = n.value.trim();
865
+ if(!n.value) {
866
+ body.splice(i, 1);
867
+ continue;
868
+ }
869
+ } else walk(n, go);
870
+ i++;
871
+ }
872
+ };
873
+
874
+ walk(this.DOM, go);
875
+ }
876
+
839
877
  class Reader {
840
878
  constructor(source) {
841
879
  if(source instanceof Reader) return source;
@@ -1277,22 +1315,33 @@
1277
1315
  assert(step == 0, 'Wrong expression: ' + source);
1278
1316
  let staticText = null;
1279
1317
  if(!parts.some(p => p.type == 'exp')) staticText = parts.map(p => p.type == 'text' ? p.value : '').join('');
1280
- let result = [];
1281
- parts.forEach(p => {
1282
- if(p.type == 'js') return;
1283
- if(p.type == 'exp') result.push(p);
1284
- else {
1285
- let l = last(result);
1286
- if(l?.type == 'text') l.value += p.value;
1287
- else result.push({ ...p });
1318
+
1319
+ let pe = {
1320
+ parts,
1321
+ staticText,
1322
+ binding: parts.length == 1 && parts[0].type == 'exp' ? parts[0].value : null,
1323
+ getResult() {
1324
+ let result = [];
1325
+ this.parts.forEach(p => {
1326
+ if(p.type == 'js') return;
1327
+ if(p.type == 'exp') result.push(p);
1328
+ else {
1329
+ let l = last(result);
1330
+ if(l?.type == 'text') l.value += p.value;
1331
+ else result.push({ ...p });
1332
+ }
1333
+ });
1334
+
1335
+ return '`' + result.map(p => p.type == 'text' ? Q(p.value) : '${' + p.value + '}').join('') + '`';
1288
1336
  }
1289
- });
1290
- result = result.map(p => p.type == 'text' ? '`' + Q(p.value) + '`' : '(' + p.value + ')').join('+');
1291
- return { result, parts, staticText };
1337
+ };
1338
+ pe.result = pe.getResult();
1339
+ return pe;
1292
1340
  }
1293
1341
 
1294
1342
 
1295
- const parseBinding = (reader) => {
1343
+ const parseBinding = (source) => {
1344
+ const reader = new Reader(source);
1296
1345
  let start = reader.index;
1297
1346
 
1298
1347
  assert(reader.read() === '{', 'Bind error');
@@ -1335,7 +1384,7 @@
1335
1384
  const raw = reader.sub(start);
1336
1385
  return {
1337
1386
  raw,
1338
- value: raw.substring(1, raw.length - 1),
1387
+ value: raw.substring(1, raw.length - 1).trim(),
1339
1388
  };
1340
1389
  }
1341
1390
  };
@@ -1374,7 +1423,7 @@
1374
1423
  const value = raw.substring(1, raw.length - 1);
1375
1424
  result.push({name, value, raw, content: r.sub(start)});
1376
1425
  } else {
1377
- const value = r.readIf(/^\S+/);
1426
+ const value = r.readIf(/^[^\s<>]+/);
1378
1427
  result.push({name, value, raw: value, content: r.sub(start)});
1379
1428
  }
1380
1429
  } else {
@@ -2175,7 +2224,8 @@
2175
2224
  let bindText = xNode('bindText', {
2176
2225
  $wait: ['apply'],
2177
2226
  el: textNode.bindName(),
2178
- exp: pe.result
2227
+ exp: pe.result,
2228
+ parsedExpression: pe
2179
2229
  }, (ctx, n) => {
2180
2230
  if(this.inuse.apply) {
2181
2231
  ctx.writeLine(`$runtime.bindText(${n.el}, () => ${n.exp});`);
@@ -2613,7 +2663,7 @@
2613
2663
  each: option.each,
2614
2664
  parentElement: option.parentElement
2615
2665
  }, (ctx, n) => {
2616
- if(n.each && !ctx.isEmpty(n.innerBlock)) {
2666
+ if(n.each) {
2617
2667
  ctx.write('$runtime.makeEachBlock(');
2618
2668
  } else {
2619
2669
  ctx.write('$runtime.makeBlock(');
@@ -5722,13 +5772,13 @@
5722
5772
 
5723
5773
  if(node.spreading) return node.spreading.push(`${name}: ${exp}`);
5724
5774
 
5725
- if(node.name == 'option' && name == 'value') {
5775
+ if(node.name == 'option' && name == 'value' && parsed.binding) {
5726
5776
  return {
5727
5777
  bind: xNode('bindOptionValue', {
5728
5778
  el: element.bindName(),
5729
- exp
5779
+ value: parsed.binding
5730
5780
  }, (ctx, n) => {
5731
- ctx.write(true, `$runtime.selectOption(${n.el}, () => (${n.exp}));`);
5781
+ ctx.write(true, `$runtime.selectOption(${n.el}, () => (${n.value}));`);
5732
5782
  })
5733
5783
  }
5734
5784
  }
@@ -5741,6 +5791,7 @@
5741
5791
  selected: true,
5742
5792
  innerHTML: true,
5743
5793
  innerText: true,
5794
+ multiple: node.name == 'select',
5744
5795
  src: true,
5745
5796
  readonly: 'readOnly'
5746
5797
  };
@@ -5748,7 +5799,7 @@
5748
5799
  let n = xNode('bindAttribute', {
5749
5800
  $wait: ['apply'],
5750
5801
  name,
5751
- exp,
5802
+ exp: propList[name] && parsed.binding ? parsed.binding : exp,
5752
5803
  hasElement,
5753
5804
  el: element.bindName()
5754
5805
  }, (ctx, data) => {
@@ -6566,42 +6617,34 @@
6566
6617
  name = toCamelCase(name);
6567
6618
  if(name == 'class') name = '_class';
6568
6619
 
6569
- let rawValue, statical = false;
6620
+ let statical = false;
6570
6621
 
6571
6622
  if(value && value.includes('{')) {
6572
- const pe = this.parseText(value);
6573
- this.detectDependency(pe);
6574
-
6575
- if(pe.parts.length == 1 && pe.parts[0].type == 'exp') {
6576
- let v = pe.parts[0].value;
6577
-
6578
- if(isNumber(v)) {
6579
- value = v;
6580
- rawValue = Number(v);
6581
- statical = true;
6582
- } else if(v == 'true' || v == 'false') {
6583
- value = v;
6584
- rawValue = v == 'true';
6585
- statical = true;
6586
- } else if(v == 'null') {
6587
- value = 'null';
6588
- rawValue = null;
6589
- statical = true;
6590
- }
6623
+ const pe = parseBinding(value);
6624
+ const v = pe.value;
6625
+ this.detectDependency(v);
6626
+
6627
+ if(isNumber(v)) {
6628
+ value = v;
6629
+ statical = true;
6630
+ } else if(v == 'true' || v == 'false') {
6631
+ value = v;
6632
+ statical = true;
6633
+ } else if(v == 'null') {
6634
+ value = 'null';
6635
+ statical = true;
6636
+ } else {
6637
+ value = v;
6591
6638
  }
6592
-
6593
- if(!statical) value = pe.result;
6594
6639
  } else if(value) {
6595
- rawValue = value;
6596
6640
  value = '`' + Q(value) + '`';
6597
6641
  statical = true;
6598
6642
  } else {
6599
- rawValue = true;
6600
6643
  value = 'true';
6601
6644
  statical = true;
6602
6645
  }
6603
6646
 
6604
- return { name, value, rawValue, static: statical, mod };
6647
+ return { name, value, static: statical, mod };
6605
6648
  }
6606
6649
 
6607
6650
  function attachPortal(node) {
@@ -6822,7 +6865,7 @@
6822
6865
  });
6823
6866
  }
6824
6867
 
6825
- const version = '0.7.2-a9';
6868
+ const version = '0.7.4';
6826
6869
 
6827
6870
 
6828
6871
  async function compile(source, config = {}) {
@@ -6902,7 +6945,7 @@
6902
6945
  parseHTML: function() {
6903
6946
  this.DOM = parseHTML(this.source);
6904
6947
  },
6905
- compactDOM,
6948
+ compactDOM: config.compact == 'full' ? compactFull : compactDOM,
6906
6949
 
6907
6950
  script: null,
6908
6951
  scriptNodes: null,
@@ -7003,7 +7046,7 @@
7003
7046
  async function hook(ctx, name) {
7004
7047
  for(let i = 0; i < ctx.config.plugins.length; i++) {
7005
7048
  const fn = ctx.config.plugins[i][name];
7006
- if(fn) await fn(ctx);
7049
+ if(fn) await use_context(ctx, () => fn(ctx));
7007
7050
  }
7008
7051
  }
7009
7052
 
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "malinajs",
3
- "version": "0.7.2-a9",
3
+ "version": "0.7.4",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
- "prepare": "npm run build",
7
6
  "build": "npm run build_runtime && rollup -c",
8
7
  "build_runtime": "rollup -c ./rollup.config.runtime.js",
9
8
  "build_app": "NODE_PATH=.. rollup -c rollup.config.app.js && uglifyjs ./example/public/app.js -o ./example/public/app.min.js -m -c",
@@ -39,6 +38,7 @@
39
38
  "runtime.js",
40
39
  "malina-rollup.js",
41
40
  "malina-esbuild.js",
42
- "plugins/sass.js"
41
+ "plugins/sass.js",
42
+ "plugins/static-text.js"
43
43
  ]
44
44
  }
package/plugins/sass.js CHANGED
@@ -8,17 +8,35 @@ module.exports = function sassPlugin() {
8
8
  let type = node.attributes.filter(a => a.name == 'type' || a.name == 'lang')[0];
9
9
  if(!type || type.value != 'sass' && type.value != 'scss') continue;
10
10
 
11
- const sass = require('sass');
11
+ let sass;
12
+ try {
13
+ sass = require('sass');
14
+ } catch (e) {
15
+ if(e.code == 'MODULE_NOT_FOUND') sass = require('node-sass');
16
+ else throw e;
17
+ }
12
18
  node.content = await (new Promise((resolve, reject) => {
13
- sass.render({
14
- file: ctx.config.path,
15
- data: node.content,
16
- indentedSyntax: type.value == 'sass'
17
- }, function(e, result) {
18
- if(e) return reject(e);
19
- resolve(result.css.toString());
20
- type.value = 'css';
21
- });
19
+ if(sass.render) {
20
+ sass.render({ // node-sass
21
+ file: ctx.config.path,
22
+ data: node.content,
23
+ indentedSyntax: type.value == 'sass'
24
+ }, function(e, result) {
25
+ if(e) return reject(e);
26
+ resolve(result.css.toString());
27
+ type.value = 'css';
28
+ });
29
+ } else {
30
+ sass.compileStringAsync(node.content, {
31
+ syntax: type.value == 'sass' ? 'indented' : 'scss',
32
+ url: 'file://' + ctx.config.path
33
+ }).then((r) => {
34
+ resolve(r.css);
35
+ }, (e) => {
36
+ console.error('SCSS Error', e);
37
+ reject(e);
38
+ })
39
+ }
22
40
  }));
23
41
  };
24
42
  }
@@ -0,0 +1,46 @@
1
+
2
+ module.exports = function staticTextPlugin() {
3
+ /*
4
+ Build static text for text binding
5
+ Usage: {=variable}
6
+ */
7
+ return {
8
+ name: 'static-text',
9
+ 'build:before': (ctx) => {
10
+ const walk = (n) => {
11
+ if (!n) return;
12
+
13
+ // Modify AST-node
14
+ if (n.$type == 'bindText') {
15
+ let hasStatic, hasDynamic;
16
+ n.parsedExpression.parts.forEach(p => {
17
+ if(p.type == 'exp') {
18
+ if(p.value[0] == '=') {
19
+ p.value = p.value.substring(1);
20
+ hasStatic = true;
21
+ } else hasDynamic = true;
22
+ }
23
+ });
24
+
25
+ if(hasStatic) n.exp = n.parsedExpression.getResult();
26
+
27
+ if(hasStatic && !hasDynamic) {
28
+ n.$handler = function (ctx, n) {
29
+ ctx.write(true, `${n.el}.textContent = ${n.exp};`);
30
+ }
31
+ }
32
+ }
33
+
34
+ // traversal AST
35
+ if (Array.isArray(n)) n.forEach(walk);
36
+ else if (n.$type) {
37
+ for (let k in n) {
38
+ if (k[0] == '$') continue;
39
+ walk(n[k]);
40
+ }
41
+ }
42
+ }
43
+ walk(ctx.module.body);
44
+ }
45
+ };
46
+ };
package/runtime.js CHANGED
@@ -28,10 +28,10 @@ const safeGroupCall = list => {
28
28
  }
29
29
  };
30
30
 
31
- const safeGroupCall2 = (list, resultList) => {
31
+ const safeGroupCall2 = (list, resultList, onlyFunction) => {
32
32
  list?.forEach(fn => {
33
33
  let r = safeCall(fn);
34
- r && resultList.push(r);
34
+ r && (!onlyFunction || isFunction(r)) && resultList.push(r);
35
35
  });
36
36
  };
37
37
 
@@ -895,7 +895,7 @@ function ifBlock(label, fn, parts, parentLabel) {
895
895
  } else first = last = $dom;
896
896
  if(parentLabel) label.appendChild($dom);
897
897
  else label.parentNode.insertBefore($dom, label);
898
- safeGroupCall2(mountList, destroyList);
898
+ safeGroupCall2(mountList, destroyList, 1);
899
899
  }
900
900
 
901
901
  function destroyBlock() {
@@ -969,7 +969,7 @@ function awaitBlock(label, parentLabel, relation, fn, build_main, build_then, bu
969
969
  } else first = last = $dom;
970
970
  if(parentLabel) label.appendChild($dom);
971
971
  else label.parentNode.insertBefore($dom, label);
972
- safeGroupCall2(mountList, destroyList);
972
+ safeGroupCall2(mountList, destroyList, 1);
973
973
  cd_component(parentCD).$apply();
974
974
  }
975
975
 
@@ -998,7 +998,7 @@ const eachDefaultKey = (item, index, array) => isObject(array[0]) ? item : index
998
998
  const makeEachBlock = (fr, fn) => {
999
999
  return (item, index) => {
1000
1000
  let $dom = fr.cloneNode(true);
1001
- return [$dom, fn($dom, item, index)];
1001
+ return [$dom, fn?.($dom, item, index)];
1002
1002
  };
1003
1003
  };
1004
1004
 
@@ -1026,7 +1026,7 @@ const makeEachElseBlock = (fn) => {
1026
1026
  } else first = last = $dom;
1027
1027
  cd_attach(parentCD, $cd);
1028
1028
  parentNode.insertBefore($dom, mode ? null : label);
1029
- safeGroupCall2(current_mountList, destroyList);
1029
+ safeGroupCall2(current_mountList, destroyList, 1);
1030
1030
  } finally {
1031
1031
  current_destroyList = current_mountList = current_cd = null;
1032
1032
  }
@@ -1187,7 +1187,7 @@ function $$eachBlock(label, mode, fn, getKey, bind, buildElseBlock) {
1187
1187
  } else ctx.first = ctx.last = $dom;
1188
1188
  parentNode.insertBefore($dom, nextNode);
1189
1189
  nextNode = ctx.first;
1190
- safeGroupCall2(m, d);
1190
+ safeGroupCall2(m, d, 1);
1191
1191
  if(d.length) {
1192
1192
  ctx.d = d;
1193
1193
  p_destroy = 1;
@@ -1287,26 +1287,51 @@ const keepAlive = (store, keyFn, builder) => {
1287
1287
 
1288
1288
  const selectElement = (el, getter, setter) => {
1289
1289
  addEvent(el, 'change', () => {
1290
- let op = el.querySelector(':checked');
1291
- if(op?.$$value) {
1292
- let value = op.$$value();
1293
- setter(value);
1294
- w.value = value;
1295
- }
1290
+ let value = [];
1291
+ el.querySelectorAll(':checked').forEach(o => {
1292
+ value.push(o.$$value ? o.$$value() : o.value);
1293
+ });
1294
+ value = el.multiple ? value : value[0];
1295
+ setter(value);
1296
+ w.value = value;
1296
1297
  });
1297
- let w = $watch(getter, (value) => {
1298
- for(let op of el.options) {
1299
- if(op.$$value?.() === value) {
1300
- op.selected = true;
1298
+ const update = () => {
1299
+ let value = w.value;
1300
+ if(el.multiple) {
1301
+ if(isArray(value)) {
1302
+ for(let o of el.options) {
1303
+ const option_value = o.$$value ? o.$$value() : o.value;
1304
+ o.selected = value.indexOf(option_value) != -1;
1305
+ }
1301
1306
  return;
1302
1307
  }
1308
+ } else {
1309
+ for(let o of el.options) {
1310
+ if((o.$$value ? o.$$value() : o.value) === value) {
1311
+ o.selected = true;
1312
+ return;
1313
+ }
1314
+ }
1303
1315
  }
1304
1316
  el.selectedIndex = -1;
1305
- });
1317
+ };
1318
+ const w = $watch(getter, update);
1319
+
1320
+ let debounce = 0;
1321
+ el.$$update = () => {
1322
+ if(debounce) return;
1323
+ debounce = 1;
1324
+ $tick(() => {
1325
+ debounce = 0;
1326
+ update();
1327
+ });
1328
+ };
1306
1329
  };
1307
1330
 
1308
1331
  const selectOption = (op, getter) => {
1309
1332
  op.$$value = getter;
1333
+ if(op.parentElement?.$$update) op.parentElement.$$update();
1334
+ else $tick(() => op.parentElement?.$$update?.());
1310
1335
  };
1311
1336
 
1312
1337
  const radioButton = (el, getValue, getter, setter) => {
package/CHANGELOG.md DELETED
@@ -1,79 +0,0 @@
1
-
2
- ## 0.7.x
3
- * refactoring, optimization, fixes
4
- * export function
5
- * manual event delegation @click|root
6
- * able to delay destroying block (for animations)
7
- * be able to off autosubscribe for import: !no-autosubscribe
8
- * destructuring array/object for each
9
- * functions mount, mountStatic
10
- * each, index variable is not included by default
11
- * reference to element is removed on destroying
12
- * config.useGroupReferencing
13
- * action can return destroy function (not only object)
14
- * each-else
15
- * else-if
16
- * refactoring $onMount
17
- * optional deep checking for passed props: prop={value} prop|deep={value}
18
- * keep-alive
19
- * malina:self
20
- * handler for element "select"
21
- * handler for "radio" input
22
-
23
- ## 0.6.x
24
-
25
- * style's attribute "global"
26
- * compound classes (handle mix of class and class directives)
27
- * new passing class
28
- * mark a class as external "$className"
29
- * Deprecated: passing class from 0.5
30
- * plugins
31
- * esbuild-plugin (by AlexxNB)
32
- * $context
33
- * onError
34
- * $onMount
35
- * $onDestroy
36
- * alias for import: malinajs -> malinajs/runtime.js
37
- * alias @click -> @click={click}, @@click is forwarding
38
- * local config "malina.config.js"
39
- * plugin sass/scss
40
- * key "auto" for each-block
41
- * anchors for components
42
- * style templates, e.g. style:color={color}, style:border-color="red", style:border="1px solid {color}"
43
- * compile option "css"
44
- * event modifier: stop, prevent
45
- * script option "read-only"
46
- * constant props - "export const prop;"
47
- * compile option: debugLabel
48
- * slot for fragment
49
- * exported fragments (inverted slots)
50
- * <malina:head>, <malina:body>, <malina:window>
51
- * option: passClass
52
- * option: immutable
53
- * event modifiers: prevent, stop, ctrl, alt, shift, meta. key-events: enter, tab, esc, space, up, down, left, right, delete
54
- * inline actions for text-node and elements
55
- * portals: <malina:portal>
56
- * autoimport
57
-
58
- ## 0.5.x
59
-
60
- * input with type "range"/"number" - value as number
61
- * improve reactive expression
62
- * unwrap object in each
63
- * dynamic component
64
- * named slot
65
- * fix for dynamic import
66
- * option.onerror
67
- * autosubscribe for imported objects
68
- * compile time optinoption !no-check
69
- * fragments
70
- * event modifier: stopPropagation
71
- * $props, $attributes, $restProps
72
- * await-then-catch
73
- * :global classes
74
- * spreading props and objects {...obj}
75
- * forwarding events, forward all @@
76
- * onMount, onDestroy, $onDestroy
77
- * shortcuts for bindings and actions
78
- * scoped-css
79
- * conrol directives: each, if