malinajs 0.7.2-a9 → 0.7.3

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,18 +1315,28 @@
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
 
@@ -1374,7 +1422,7 @@
1374
1422
  const value = raw.substring(1, raw.length - 1);
1375
1423
  result.push({name, value, raw, content: r.sub(start)});
1376
1424
  } else {
1377
- const value = r.readIf(/^\S+/);
1425
+ const value = r.readIf(/^[^\s<>]+/);
1378
1426
  result.push({name, value, raw: value, content: r.sub(start)});
1379
1427
  }
1380
1428
  } else {
@@ -2175,7 +2223,8 @@
2175
2223
  let bindText = xNode('bindText', {
2176
2224
  $wait: ['apply'],
2177
2225
  el: textNode.bindName(),
2178
- exp: pe.result
2226
+ exp: pe.result,
2227
+ parsedExpression: pe
2179
2228
  }, (ctx, n) => {
2180
2229
  if(this.inuse.apply) {
2181
2230
  ctx.writeLine(`$runtime.bindText(${n.el}, () => ${n.exp});`);
@@ -5722,13 +5771,13 @@
5722
5771
 
5723
5772
  if(node.spreading) return node.spreading.push(`${name}: ${exp}`);
5724
5773
 
5725
- if(node.name == 'option' && name == 'value') {
5774
+ if(node.name == 'option' && name == 'value' && parsed.binding) {
5726
5775
  return {
5727
5776
  bind: xNode('bindOptionValue', {
5728
5777
  el: element.bindName(),
5729
- exp
5778
+ value: parsed.binding
5730
5779
  }, (ctx, n) => {
5731
- ctx.write(true, `$runtime.selectOption(${n.el}, () => (${n.exp}));`);
5780
+ ctx.write(true, `$runtime.selectOption(${n.el}, () => (${n.value}));`);
5732
5781
  })
5733
5782
  }
5734
5783
  }
@@ -5741,6 +5790,7 @@
5741
5790
  selected: true,
5742
5791
  innerHTML: true,
5743
5792
  innerText: true,
5793
+ multiple: node.name == 'select',
5744
5794
  src: true,
5745
5795
  readonly: 'readOnly'
5746
5796
  };
@@ -5748,7 +5798,7 @@
5748
5798
  let n = xNode('bindAttribute', {
5749
5799
  $wait: ['apply'],
5750
5800
  name,
5751
- exp,
5801
+ exp: propList[name] && parsed.binding ? parsed.binding : exp,
5752
5802
  hasElement,
5753
5803
  el: element.bindName()
5754
5804
  }, (ctx, data) => {
@@ -6822,7 +6872,7 @@
6822
6872
  });
6823
6873
  }
6824
6874
 
6825
- const version = '0.7.2-a9';
6875
+ const version = '0.7.2-a12';
6826
6876
 
6827
6877
 
6828
6878
  async function compile(source, config = {}) {
@@ -6902,7 +6952,7 @@
6902
6952
  parseHTML: function() {
6903
6953
  this.DOM = parseHTML(this.source);
6904
6954
  },
6905
- compactDOM,
6955
+ compactDOM: config.compact == 'full' ? compactFull : compactDOM,
6906
6956
 
6907
6957
  script: null,
6908
6958
  scriptNodes: null,
@@ -7003,7 +7053,7 @@
7003
7053
  async function hook(ctx, name) {
7004
7054
  for(let i = 0; i < ctx.config.plugins.length; i++) {
7005
7055
  const fn = ctx.config.plugins[i][name];
7006
- if(fn) await fn(ctx);
7056
+ if(fn) await use_context(ctx, () => fn(ctx));
7007
7057
  }
7008
7058
  }
7009
7059
 
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "malinajs",
3
- "version": "0.7.2-a9",
3
+ "version": "0.7.3",
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
 
@@ -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