cdom 0.0.14 → 0.0.16

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.
@@ -88,8 +88,8 @@
88
88
  "div": {
89
89
  "class": "test-section",
90
90
  "children": [
91
- { "h2": { "children": ["Test 1: Define Macro"] } },
92
- { "p": { "children": ["Defining a macro called 'adjusted_price' with schema validation..."] } },
91
+ { "h2": "Test 1: Define Macro" },
92
+ { "p": "Defining a macro called 'adjusted_price' with schema validation..." },
93
93
  { "div": { "class": "json-label", "children": ["Macro Definition:"] } },
94
94
  {
95
95
  "pre": {
@@ -108,9 +108,9 @@
108
108
  },
109
109
  "body": {
110
110
  "*": [
111
- "$.basePrice",
112
- { "+": [1, "$.taxRate"] },
113
- { "-": [1, "$.discount"] }
111
+ "=$.basePrice",
112
+ { "+": [1, "=$.taxRate"] },
113
+ { "-": [1, "=$.discount"] }
114
114
  ]
115
115
  }
116
116
  }
@@ -132,9 +132,9 @@
132
132
  },
133
133
  "body": {
134
134
  "*": [
135
- "$.basePrice",
136
- { "+": [1, "$.taxRate"] },
137
- { "-": [1, "$.discount"] }
135
+ "=$.basePrice",
136
+ { "+": [1, "=$.taxRate"] },
137
+ { "-": [1, "=$.discount"] }
138
138
  ]
139
139
  }
140
140
  }
@@ -152,8 +152,8 @@
152
152
  "div": {
153
153
  "class": "test-section",
154
154
  "children": [
155
- { "h2": { "children": ["Test 2: Call Macro with Literal Values"] } },
156
- { "p": { "children": ["Calling macro with basePrice=100, taxRate=0.08, discount=0.10"] } },
155
+ { "h2": "Test 2: Call Macro with Literal Values" },
156
+ { "p": "Calling macro with basePrice=100, taxRate=0.08, discount=0.10" },
157
157
  { "div": { "class": "json-label", "children": ["Macro Invocation:"] } },
158
158
  {
159
159
  "pre": {
@@ -183,7 +183,7 @@
183
183
  ]
184
184
  }
185
185
  },
186
- { "p": { "children": ["Expected: $97.20 (100 × 1.08 × 0.90)"] } }
186
+ { "p": "Expected: $97.20 (100 × 1.08 × 0.90)" }
187
187
  ]
188
188
  }
189
189
  },
@@ -191,8 +191,8 @@
191
191
  "div": {
192
192
  "class": "test-section",
193
193
  "children": [
194
- { "h2": { "children": ["Test 3: Call Macro with State References"] } },
195
- { "p": { "children": ["Calling macro with values from state..."] } },
194
+ { "h2": "Test 3: Call Macro with State References" },
195
+ { "p": "Calling macro with values from state..." },
196
196
  { "div": { "class": "json-label", "children": ["Macro Invocation (State References):"] } },
197
197
  {
198
198
  "pre": {
@@ -229,8 +229,8 @@
229
229
  "div": {
230
230
  "class": "test-section",
231
231
  "children": [
232
- { "h2": { "children": ["Test 4: Macro Without Schema"] } },
233
- { "p": { "children": ["Defining a simple macro without schema validation..."] } },
232
+ { "h2": "Test 4: Macro Without Schema" },
233
+ { "p": "Defining a simple macro without schema validation..." },
234
234
  { "div": { "class": "json-label", "children": ["Macro Definition:"] } },
235
235
  {
236
236
  "pre": {
@@ -238,7 +238,7 @@
238
238
  JSON.stringify({
239
239
  "=macro": {
240
240
  "name": "simple_sum",
241
- "body": { "+": ["$.a", "$.b"] }
241
+ "body": { "+": ["=$.a", "=$.b"] }
242
242
  }
243
243
  }, null, 4)
244
244
  ]
@@ -247,11 +247,11 @@
247
247
  {
248
248
  "=macro": {
249
249
  "name": "simple_sum",
250
- "body": { "+": ["$.a", "$.b"] }
250
+ "body": { "+": ["=$.a", "=$.b"] }
251
251
  }
252
252
  },
253
253
  { "p": { "class": "success", "children": ["✓ simple_sum macro registered (no schema)"] } },
254
- { "p": { "children": ["Calling: simple_sum(a=10, b=25)"] } },
254
+ { "p": "Calling: simple_sum(a=10, b=25)" },
255
255
  { "div": { "class": "json-label", "children": ["Macro Invocation:"] } },
256
256
  {
257
257
  "pre": {
@@ -287,8 +287,8 @@
287
287
  "div": {
288
288
  "class": "test-section",
289
289
  "children": [
290
- { "h2": { "children": ["Test 5: Macro With String Operations"] } },
291
- { "p": { "children": ["A macro that formats a greeting..."] } },
290
+ { "h2": "Test 5: Macro With String Operations" },
291
+ { "p": "A macro that formats a greeting..." },
292
292
  { "div": { "class": "json-label", "children": ["Macro Definition:"] } },
293
293
  {
294
294
  "pre": {
@@ -296,7 +296,7 @@
296
296
  JSON.stringify({
297
297
  "=macro": {
298
298
  "name": "greeting",
299
- "body": { "=concat": ["Hello, ", "$.name", "!"] }
299
+ "body": { "=concat": ["Hello, ", "=$.name", "!"] }
300
300
  }
301
301
  }, null, 4)
302
302
  ]
@@ -305,7 +305,7 @@
305
305
  {
306
306
  "=macro": {
307
307
  "name": "greeting",
308
- "body": { "=concat": ["Hello, ", "$.name", "!"] }
308
+ "body": { "=concat": ["Hello, ", "=$.name", "!"] }
309
309
  }
310
310
  },
311
311
  { "p": { "class": "success", "children": ["✓ greeting macro registered"] } },
package/index.js CHANGED
@@ -35,20 +35,20 @@
35
35
  };
36
36
 
37
37
 
38
+ function tokenize(src) {
39
+ const tokenRegex = /\s*(\d*\.\d+|\d+|"([^"\\]|\\.)*"|'([^'\\]|\\.)*'|=\/|=\$this|=\$event|=\$query|=\$|[a-zA-Z_$][\w$]*|==|!=|<=|>=|&&|\|\||[-+*/%^<>!?:.,(){}[\]])\s*/g;
40
+ const results = [];
41
+ let match;
42
+ while ((match = tokenRegex.exec(src)) !== null) {
43
+ results.push(match[1]);
44
+ }
45
+ return results;
46
+ }
47
+
38
48
  function createExpression(text) {
39
49
  let at = 0;
40
50
  const tokens = tokenize(text);
41
51
 
42
- function tokenize(src) {
43
- const results = [];
44
- const tokenRegex = /\s*(\d*\.\d+|\d+|"([^"\\]|\\.)*"|'([^'\\]|\\.)*'|=\/|=\$this|=\$event|=\$query|=\$|\$this|\$event|\$query|[a-zA-Z_$][\w$]*|==|!=|<=|>=|&&|\|\||[-+*/%^<>!?:.,(){}[\]])\s*/g;
45
- let match;
46
- while ((match = tokenRegex.exec(src)) !== null) {
47
- results.push(match[1]);
48
- }
49
- return results;
50
- }
51
-
52
52
  function peek(offset = 0) { return tokens[at + offset]; }
53
53
  function next() { return tokens[at++]; }
54
54
  function consume(expected) {
@@ -272,11 +272,9 @@
272
272
  return createPathFunction(path, 'state');
273
273
  }
274
274
 
275
- // Handle $this and $event (and deprecated $this/$event)
276
- if (t === '=$this' || t === '=$event' || t === '$this' || t === '$event') {
277
- const isDeprecated = !t.startsWith('=');
278
- let path = t;
279
- if (!isDeprecated) path = t.slice(1);
275
+ // Handle =$this and =$event
276
+ if (t === '=$this' || t === '=$event') {
277
+ let path = t.slice(1); // Remove the '=' prefix
280
278
  // Check for property access
281
279
  while (true) {
282
280
  const p = peek();
@@ -301,8 +299,8 @@
301
299
  if (path.startsWith('$event.') || path.startsWith('$event/')) return createPathFunction(path.slice(7), '$event');
302
300
  }
303
301
 
304
- // Handle $.propertyName and =$$.propertyName
305
- if (t === '$' || t === '=$') {
302
+ // Handle =$.propertyName
303
+ if (t === '=$') {
306
304
  const p = peek();
307
305
  if (p === '.') {
308
306
  next(); // consume '.'
@@ -327,8 +325,8 @@
327
325
  }
328
326
  }
329
327
 
330
- // Handle $query.propertyName and =$query.propertyName
331
- if (t === '$query' || t === '=$query') {
328
+ // Handle =$query.propertyName
329
+ if (t === '=$query') {
332
330
  const p = peek();
333
331
  if (p === '.') {
334
332
  next(); // consume '.'
@@ -854,6 +852,7 @@
854
852
  }
855
853
 
856
854
  async function loadHelper(name) {
855
+ if (helpers.has(name)) return Promise.resolve(helpers.get(name));
857
856
  if (pendingHelpers.has(name)) return pendingHelpers.get(name);
858
857
 
859
858
  const promise = (async () => {
@@ -908,6 +907,42 @@
908
907
  set.add(sub);
909
908
  }
910
909
 
910
+ function findNeededHelpers(obj, found = new Set()) {
911
+ if (!obj) return found;
912
+
913
+ if (typeof obj === 'string') {
914
+ if (obj.startsWith('=') && obj.length > 1) {
915
+ const tokens = tokenize(obj);
916
+ for (let i = 0; i < tokens.length; i++) {
917
+ const t = tokens[i];
918
+ if (/^[a-zA-Z_$][\w$]*$/.test(t) && !['true', 'false', 'null', 'undefined'].includes(t)) {
919
+ if (i > 0 && tokens[i - 1] === '.') continue;
920
+ found.add(t);
921
+ }
922
+ }
923
+ }
924
+ return found;
925
+ }
926
+
927
+ if (typeof obj !== 'object' || obj.nodeType) return found;
928
+
929
+ if (Array.isArray(obj)) {
930
+ for (const item of obj) findNeededHelpers(item, found);
931
+ return found;
932
+ }
933
+
934
+ for (const key in obj) {
935
+ const alias = symbolicOperators.get(key);
936
+ if (key.startsWith('=') && key.length > 1) {
937
+ found.add(key.slice(1));
938
+ } else if (alias) {
939
+ found.add(alias);
940
+ }
941
+ findNeededHelpers(obj[key], found);
942
+ }
943
+ return found;
944
+ }
945
+
911
946
 
912
947
  function evaluateStructural(obj, context, event, unsafe) {
913
948
  if (typeof obj !== 'object' || obj === null || obj.nodeType) {
@@ -915,10 +950,6 @@
915
950
  if (typeof obj === 'string' && obj.startsWith('=')) {
916
951
  return evaluateStateExpression(obj, context, event);
917
952
  }
918
- // Temporarily support deprecated bare $. for backward compatibility
919
- if (typeof obj === 'string' && obj.startsWith('$.')) {
920
- return evaluateStateExpression(obj, context, event);
921
- }
922
953
  return obj;
923
954
  }
924
955
  obj = unwrap(obj);
@@ -966,9 +997,10 @@
966
997
  const v = val[k];
967
998
  const isPath = typeof v === 'string' && (
968
999
  v.startsWith('=/') ||
969
- v.startsWith('$.') ||
970
- v === '$this' || v.startsWith('$this/') || v.startsWith('$this.') ||
971
- v === '$event' || v.startsWith('$event/') || v.startsWith('$event.')
1000
+ v.startsWith('=$.') ||
1001
+ v.startsWith('=$this') ||
1002
+ v.startsWith('=$event') ||
1003
+ v.startsWith('=$query')
972
1004
  );
973
1005
 
974
1006
  if (isPath) {
@@ -987,9 +1019,10 @@
987
1019
  const resolvedArgs = args.map(arg => {
988
1020
  const isPath = typeof arg === 'string' && (
989
1021
  arg.startsWith('=/') ||
990
- arg.startsWith('$.') ||
991
- arg === '$this' || arg.startsWith('$this/') || arg.startsWith('$this.') ||
992
- arg === '$event' || arg.startsWith('$event/') || arg.startsWith('$event.')
1022
+ arg.startsWith('=$.') ||
1023
+ arg.startsWith('=$this') ||
1024
+ arg.startsWith('=$event') ||
1025
+ arg.startsWith('=$query')
993
1026
  );
994
1027
 
995
1028
  if (isPath) {
@@ -1501,6 +1534,13 @@
1501
1534
  const query = Object.fromEntries(url.searchParams.entries());
1502
1535
  element._queryContext = query;
1503
1536
  if (!element._macroContext) element._macroContext = query;
1537
+
1538
+ // Pre-load all needed helpers to avoid "suspense" flickering during hypermedia loads
1539
+ const helpersToLoad = findNeededHelpers(cdomData);
1540
+ if (helpersToLoad.size > 0) {
1541
+ await Promise.all(Array.from(helpersToLoad).map(name => loadHelper(name)));
1542
+ }
1543
+
1504
1544
  const dom = cdomToDOM(cdomData, true, false, element);
1505
1545
  element.replaceChildren(dom);
1506
1546
  } else if (isHTML) {
@@ -1558,6 +1598,65 @@
1558
1598
  }
1559
1599
  }
1560
1600
 
1601
+ // Core Library Functions (Inlined for performance/reliability)
1602
+ const _sum = (...args) => args.flat(Infinity).reduce((a, b) => a + (Number(b) || 0), 0);
1603
+ const _sub = (...args) => {
1604
+ const flat = args.flat(Infinity).map(v => Number(v) || 0);
1605
+ if (flat.length === 0) return 0;
1606
+ if (flat.length === 1) return -flat[0];
1607
+ return flat.reduce((a, b) => a - b);
1608
+ };
1609
+ const _mul = (...args) => args.flat(Infinity).reduce((a, b) => a * (Number(b) || 1), 1);
1610
+ const _div = (a, b) => (Number(a) || 0) / (Number(b) || 1);
1611
+
1612
+ const _set = function (target, val) {
1613
+ if (target && typeof target === 'object' && 'value' in target) {
1614
+ target.value = val;
1615
+ } else if (target && typeof target === 'function' && 'value' in target) {
1616
+ target.value = val;
1617
+ } else if (target && typeof target === 'object' && val && typeof val === 'object') {
1618
+ Object.assign(target, val);
1619
+ }
1620
+ return val;
1621
+ };
1622
+ _set.mutates = true;
1623
+
1624
+ const _inc = function (target, by = 1) {
1625
+ const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
1626
+ const current = hasValue ? target.value : 0;
1627
+ return _set(target, Number(current) + Number(by));
1628
+ };
1629
+ _inc.mutates = true;
1630
+
1631
+ const _dec = function (target, by = 1) {
1632
+ const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
1633
+ const current = hasValue ? target.value : 0;
1634
+ return _set(target, Number(current) - Number(by));
1635
+ };
1636
+ _dec.mutates = true;
1637
+
1638
+ const _toggle = function (target) {
1639
+ const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
1640
+ const current = hasValue ? target.value : false;
1641
+ return _set(target, !current);
1642
+ };
1643
+ _toggle.mutates = true;
1644
+
1645
+ const _if = (c, t, e) => c ? t : e;
1646
+ const _ifs = (...args) => {
1647
+ for (let i = 0; i < args.length; i += 2) {
1648
+ if (i + 1 >= args.length) return args[i]; // Default case
1649
+ if (args[i]) return args[i + 1];
1650
+ }
1651
+ };
1652
+
1653
+ const _switch = (exp, ...args) => {
1654
+ for (let i = 0; i < args.length - 1; i += 2) {
1655
+ if (exp === args[i]) return args[i + 1];
1656
+ }
1657
+ return args.length % 2 === 1 ? args[args.length - 1] : undefined;
1658
+ };
1659
+
1561
1660
  // Export to global scope
1562
1661
  cDOM.signal = signal;
1563
1662
  cDOM.state = state;
@@ -1575,6 +1674,27 @@
1575
1674
  // Register core helpers
1576
1675
  helpers.set('state', state);
1577
1676
  helpers.set('signal', signal);
1677
+ helpers.set('sum', _sum);
1678
+ helpers.set('add', _sum);
1679
+ helpers.set('subtract', _sub);
1680
+ helpers.set('multiply', _mul);
1681
+ helpers.set('divide', _div);
1682
+ helpers.set('eq', (a, b) => a == b);
1683
+ helpers.set('neq', (a, b) => a != b);
1684
+ helpers.set('gt', (a, b) => a > b);
1685
+ helpers.set('lt', (a, b) => a < b);
1686
+ helpers.set('gte', (a, b) => a >= b);
1687
+ helpers.set('lte', (a, b) => a <= b);
1688
+ helpers.set('and', (...args) => args.every(Boolean));
1689
+ helpers.set('or', (...args) => args.some(Boolean));
1690
+ helpers.set('not', (a) => !a);
1691
+ helpers.set('set', _set);
1692
+ helpers.set('increment', _inc);
1693
+ helpers.set('decrement', _dec);
1694
+ helpers.set('toggle', _toggle);
1695
+ helpers.set('if', _if);
1696
+ helpers.set('ifs', _ifs);
1697
+ helpers.set('switch', _switch);
1578
1698
 
1579
1699
  // Default Operator Mappings
1580
1700
  cDOM.operator('+', 'add');
@@ -1582,8 +1702,11 @@
1582
1702
  cDOM.operator('*', 'multiply');
1583
1703
  cDOM.operator('/', 'divide');
1584
1704
  cDOM.operator('==', 'eq');
1705
+ cDOM.operator('!=', 'neq');
1585
1706
  cDOM.operator('>', 'gt');
1586
1707
  cDOM.operator('<', 'lt');
1708
+ cDOM.operator('>=', 'gte');
1709
+ cDOM.operator('<=', 'lte');
1587
1710
  cDOM.operator('&&', 'and');
1588
1711
  cDOM.operator('||', 'or');
1589
1712
  cDOM.operator('!', 'not');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdom",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "Safe, reactive UIs based on JSON Pointer, XPath, JSON Schema",
5
5
  "keywords": [
6
6
  "JPRX",
package/helpers/add.js DELETED
@@ -1,3 +0,0 @@
1
- // Example: { p: { "=add": [10, 20] } }
2
- import sum from './sum.js';
3
- export default sum;
package/helpers/and.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=and": [true, true] } }
2
- export default function (...args) {
3
- return args.every(Boolean);
4
- }
@@ -1,10 +0,0 @@
1
- // Example: { onclick: { "=decrement": ["/counter/count", 1] } }
2
- import set from './set.js';
3
- const decrement = function (target, by = 1) {
4
- const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
5
- const current = hasValue ? target.value : 0;
6
- const next = Number(current) - Number(by);
7
- return set(target, next);
8
- }
9
- decrement.mutates = true;
10
- export default decrement;
package/helpers/divide.js DELETED
@@ -1,7 +0,0 @@
1
- // Example: { p: { "=divide": [10, 2] } }
2
- export default function (...args) {
3
- const flat = args.flat(Infinity).map(v => Number(v) || 0);
4
- if (flat.length === 0) return 0;
5
- if (flat.length === 1) return 1 / flat[0];
6
- return flat.reduce((a, b) => a / b);
7
- }
package/helpers/eq.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=eq": [1, 1] } }
2
- export default function (a, b) {
3
- return a == b;
4
- }
package/helpers/gt.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=gt": [10, 5] } }
2
- export default function (a, b) {
3
- return Number(a) > Number(b);
4
- }
package/helpers/if.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=if": [true, "Yes", "No"] } }
2
- export default function (condition, thenVal, elseVal) {
3
- return condition ? thenVal : elseVal;
4
- }
package/helpers/ifs.js DELETED
@@ -1,7 +0,0 @@
1
- // Example: { p: { "=ifs": [true, "A", false, "B", "Default"] } }
2
- export default function (...args) {
3
- for (let i = 0; i < args.length - 1; i += 2) {
4
- if (args[i]) return args[i + 1];
5
- }
6
- return args.length % 2 === 1 ? args[args.length - 1] : undefined;
7
- }
@@ -1,10 +0,0 @@
1
- // Example: { onclick: { "=increment": ["/counter/count", 1] } }
2
- import set from './set.js';
3
- const increment = function (target, by = 1) {
4
- const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
5
- const current = hasValue ? target.value : 0;
6
- const next = Number(current) + Number(by);
7
- return set(target, next);
8
- }
9
- increment.mutates = true;
10
- export default increment;
package/helpers/lt.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=lt": [5, 10] } }
2
- export default function (a, b) {
3
- return Number(a) < Number(b);
4
- }
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=multiply": [2, 3, 4] } }
2
- export default function (...args) {
3
- return args.flat(Infinity).reduce((a, b) => a * (Number(b) || 0), 1);
4
- }
package/helpers/not.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=not": [true] } }
2
- export default function (val) {
3
- return !val;
4
- }
package/helpers/or.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=or": [true, false] } }
2
- export default function (...args) {
3
- return args.some(Boolean);
4
- }
package/helpers/set.js DELETED
@@ -1,13 +0,0 @@
1
- // Example: { onclick: { "=set": ["/app/title", "New Title"] } }
2
- const set = function (target, val) {
3
- if (target && typeof target === 'object' && 'value' in target) {
4
- target.value = val;
5
- } else if (target && typeof target === 'function' && 'value' in target) {
6
- target.value = val;
7
- } else if (target && typeof target === 'object' && val && typeof val === 'object') {
8
- Object.assign(target, val);
9
- }
10
- return val;
11
- }
12
- set.mutates = true;
13
- export default set;
@@ -1,7 +0,0 @@
1
- // Example: { p: { "=subtract": [10, 5, 2] } }
2
- export default function (...args) {
3
- const flat = args.flat(Infinity).map(v => Number(v) || 0);
4
- if (flat.length === 0) return 0;
5
- if (flat.length === 1) return -flat[0];
6
- return flat.reduce((a, b) => a - b);
7
- }
package/helpers/sum.js DELETED
@@ -1,4 +0,0 @@
1
- // Example: { p: { "=sum": [10, 20, 30] } }
2
- export default function (...args) {
3
- return args.flat(Infinity).reduce((a, b) => a + (Number(b) || 0), 0);
4
- }
package/helpers/switch.js DELETED
@@ -1,7 +0,0 @@
1
- // Example: { p: { "=switch": [2, 1, "One", 2, "Two", "Default"] } }
2
- export default function (expression, ...args) {
3
- for (let i = 0; i < args.length - 1; i += 2) {
4
- if (expression === args[i]) return args[i + 1];
5
- }
6
- return args.length % 2 === 1 ? args[args.length - 1] : undefined;
7
- }
package/helpers/toggle.js DELETED
@@ -1,9 +0,0 @@
1
- // Example: { onclick: { "=toggle": ["/user/isActive"] } }
2
- import set from './set.js';
3
- const toggle = function (target) {
4
- const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
5
- const current = hasValue ? target.value : false;
6
- return set(target, !current);
7
- }
8
- toggle.mutates = true;
9
- export default toggle;