@mintjamsinc/ichigojs 0.1.32 → 0.1.34

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/dist/ichigo.cjs CHANGED
@@ -6734,6 +6734,70 @@
6734
6734
  }
6735
6735
  }
6736
6736
  }
6737
+ else if (node.type === 'MemberExpression') {
6738
+ // Reconstruct chain parts from a MemberExpression (e.g. a.b.c)
6739
+ const parts = [];
6740
+ let cur = node;
6741
+ let stop = false;
6742
+ while (cur && !stop) {
6743
+ // property part (right side)
6744
+ if (cur.property) {
6745
+ if (!cur.computed && cur.property.type === 'Identifier') {
6746
+ parts.unshift(cur.property.name);
6747
+ }
6748
+ else if (cur.computed && cur.property.type === 'Literal') {
6749
+ parts.unshift(String(cur.property.value));
6750
+ }
6751
+ else if (cur.computed && cur.property.type === 'Identifier') {
6752
+ // e.g. obj[prop] -> include the prop identifier (it will also be added separately)
6753
+ parts.unshift(cur.property.name);
6754
+ }
6755
+ else {
6756
+ // unknown property shape (e.g. expression) — stop adding chain here
6757
+ // inner expressions will be visited separately by walk.full
6758
+ stop = true;
6759
+ break;
6760
+ }
6761
+ }
6762
+ // object part (left side)
6763
+ if (cur.object) {
6764
+ if (cur.object.type === 'Identifier') {
6765
+ parts.unshift(cur.object.name);
6766
+ break;
6767
+ }
6768
+ else if (cur.object.type === 'ThisExpression') {
6769
+ // For `this.x` or `this.a.b` we don't want to include 'this' itself.
6770
+ // Stop unwrapping here; we'll drop a leading 'this' later.
6771
+ break;
6772
+ }
6773
+ else if (cur.object.type === 'MemberExpression') {
6774
+ // continue unwrapping
6775
+ cur = cur.object;
6776
+ }
6777
+ else {
6778
+ // other object types (CallExpression, etc.) — stop here
6779
+ stop = true;
6780
+ break;
6781
+ }
6782
+ }
6783
+ else {
6784
+ break;
6785
+ }
6786
+ }
6787
+ if (parts.length > 0) {
6788
+ // Add progressive chains: 'a', 'a.b', 'a.b.c'
6789
+ for (let i = 0; i < parts.length; i++) {
6790
+ const chain = parts.slice(0, i + 1).join('.');
6791
+ identifiers.add(chain);
6792
+ // Also apply functionDependencies lookup for the chain key
6793
+ if (functionDependencies[chain]) {
6794
+ for (const dependency of functionDependencies[chain]) {
6795
+ identifiers.add(dependency);
6796
+ }
6797
+ }
6798
+ }
6799
+ }
6800
+ }
6737
6801
  });
6738
6802
  return Array.from(identifiers);
6739
6803
  }
@@ -6939,6 +7003,8 @@
6939
7003
  * The identifiers extracted from the expression.
6940
7004
  */
6941
7005
  identifiers;
7006
+ // The original extracted identifiers (may include member chains like 'a.b')
7007
+ originalIdentifiers;
6942
7008
  /**
6943
7009
  * The bindings to use for evaluating the expression.
6944
7010
  */
@@ -6954,10 +7020,11 @@
6954
7020
  /**
6955
7021
  * Private constructor. Use ExpressionEvaluator.create() to create instances.
6956
7022
  */
6957
- constructor(expression, bindings, identifiers, evaluatorFunc, additionalContext) {
7023
+ constructor(expression, bindings, identifiers, originalIdentifiers, evaluatorFunc, additionalContext) {
6958
7024
  this.expression = expression;
6959
7025
  this.bindings = bindings;
6960
7026
  this.identifiers = identifiers;
7027
+ this.originalIdentifiers = originalIdentifiers;
6961
7028
  this.evaluatorFunc = evaluatorFunc;
6962
7029
  this.additionalContext = additionalContext;
6963
7030
  }
@@ -6971,12 +7038,22 @@
6971
7038
  * @returns An ExpressionEvaluator instance.
6972
7039
  */
6973
7040
  static create(expression, bindings, functionDependencies, options) {
6974
- // Extract identifiers from the expression
6975
- const identifiers = ExpressionUtils.extractIdentifiers(expression, functionDependencies);
7041
+ // Extract identifiers from the expression (may include member chains like 'a.b')
7042
+ const extractedIdentifiers = ExpressionUtils.extractIdentifiers(expression, functionDependencies);
7043
+ // For compilation we must only pass simple identifier names (no dots).
7044
+ // Use the base (left-most) segment of member chains and preserve order.
7045
+ const baseIdentifiers = [];
7046
+ for (const id of extractedIdentifiers) {
7047
+ const base = id.split('.')[0];
7048
+ if (!baseIdentifiers.includes(base)) {
7049
+ baseIdentifiers.push(base);
7050
+ }
7051
+ }
6976
7052
  // Apply custom rewrite if provided (used by VOnDirective)
6977
7053
  let processedExpression = expression;
6978
7054
  if (options?.rewriteExpression) {
6979
- processedExpression = options.rewriteExpression(expression, identifiers);
7055
+ // Pass the original extracted identifiers (may include member chains)
7056
+ processedExpression = options.rewriteExpression(expression, extractedIdentifiers);
6980
7057
  }
6981
7058
  // Build cache key including options that affect compilation
6982
7059
  const cacheKey = JSON.stringify({
@@ -6986,12 +7063,12 @@
6986
7063
  // Check cache
6987
7064
  let evaluatorFunc = this.cache.get(cacheKey);
6988
7065
  if (!evaluatorFunc) {
6989
- // Compile the expression
6990
- evaluatorFunc = this.compileExpression(processedExpression, identifiers, options);
7066
+ // Compile the expression using base identifiers (no dots)
7067
+ evaluatorFunc = this.compileExpression(processedExpression, baseIdentifiers, options);
6991
7068
  // Cache the compiled function
6992
7069
  this.cache.set(cacheKey, evaluatorFunc);
6993
7070
  }
6994
- return new ExpressionEvaluator(expression, bindings, identifiers, evaluatorFunc, options?.additionalContext);
7071
+ return new ExpressionEvaluator(expression, bindings, baseIdentifiers, extractedIdentifiers, evaluatorFunc, options?.additionalContext);
6995
7072
  }
6996
7073
  /**
6997
7074
  * Compiles an expression into a function.
@@ -7002,7 +7079,7 @@
7002
7079
  * @returns A compiled function.
7003
7080
  */
7004
7081
  static compileExpression(expression, identifiers, options) {
7005
- // Build parameter list: globals first, then identifiers, then additional context
7082
+ // Build parameter list: globals first, then identifiers (base names), then additional context
7006
7083
  const params = [
7007
7084
  ...Object.keys(ExpressionEvaluator.GLOBAL_WHITELIST),
7008
7085
  ...identifiers
@@ -7059,7 +7136,8 @@
7059
7136
  * Gets the list of identifiers extracted from the expression.
7060
7137
  */
7061
7138
  get dependentIdentifiers() {
7062
- return this.identifiers;
7139
+ // Return the original extracted identifiers (may include member chains like 'a.b')
7140
+ return this.originalIdentifiers;
7063
7141
  }
7064
7142
  /**
7065
7143
  * Clears the expression cache.
@@ -7624,6 +7702,11 @@
7624
7702
  * These objects will not be wrapped with Proxy.
7625
7703
  */
7626
7704
  static rawObjects = new WeakSet();
7705
+ /**
7706
+ * A WeakMap to store the path for each proxy object.
7707
+ * This allows retrieving the source path of an object for computed property mapping.
7708
+ */
7709
+ static proxyPaths = new WeakMap();
7627
7710
  /**
7628
7711
  * Creates a reactive proxy for the given object.
7629
7712
  * The proxy will call the onChange callback whenever a property is modified.
@@ -7722,6 +7805,10 @@
7722
7805
  pathMap.set(path, proxy);
7723
7806
  // Track that this proxy wraps the target to prevent double-wrapping
7724
7807
  this.proxyToTarget.set(proxy, target);
7808
+ // Store the path for this proxy (for computed property mapping)
7809
+ if (path) {
7810
+ this.proxyPaths.set(proxy, path);
7811
+ }
7725
7812
  return proxy;
7726
7813
  }
7727
7814
  /**
@@ -7771,6 +7858,21 @@
7771
7858
  static isRaw(obj) {
7772
7859
  return typeof obj === 'object' && obj !== null && this.rawObjects.has(obj);
7773
7860
  }
7861
+ /**
7862
+ * Gets the source path for a proxy object.
7863
+ * This is used to map computed property values back to their source paths.
7864
+ * For example, if a computed property returns `model.elements[0]`,
7865
+ * this method returns "model.elements[0]" for that object.
7866
+ *
7867
+ * @param obj The proxy object to get the path for.
7868
+ * @returns The source path, or undefined if not found.
7869
+ */
7870
+ static getPath(obj) {
7871
+ if (typeof obj !== 'object' || obj === null) {
7872
+ return undefined;
7873
+ }
7874
+ return this.proxyPaths.get(obj);
7875
+ }
7774
7876
  }
7775
7877
 
7776
7878
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
@@ -7977,6 +8079,15 @@
7977
8079
  this.#suppressOnChange = false;
7978
8080
  }
7979
8081
  }
8082
+ /**
8083
+ * Manually adds an identifier to the set of changed identifiers.
8084
+ * This is useful for computed properties that need to mark themselves as changed
8085
+ * without triggering a new update cycle.
8086
+ * @param key The identifier to mark as changed.
8087
+ */
8088
+ markChanged(key) {
8089
+ this.#changes.add(key);
8090
+ }
7980
8091
  }
7981
8092
 
7982
8093
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
@@ -8277,12 +8388,21 @@
8277
8388
  const evaluators = matches.map(match => {
8278
8389
  const expression = match[1].trim();
8279
8390
  const ids = ExpressionUtils.extractIdentifiers(expression, functionDependencies);
8280
- // Gather identifiers
8391
+ // Keep chain-style identifiers (a, a.b, a.b.c) for metadata
8281
8392
  this.#identifiers.push(...ids.filter(id => !this.#identifiers.includes(id)));
8282
- const args = ids.join(", ");
8393
+ // Build base parameter names (no dots), preserving order of first appearance
8394
+ const baseArgs = [];
8395
+ for (const id of ids) {
8396
+ const base = id.split('.')[0];
8397
+ if (!baseArgs.includes(base)) {
8398
+ baseArgs.push(base);
8399
+ }
8400
+ }
8401
+ const args = baseArgs.join(", ");
8283
8402
  const funcBody = `return (${expression});`;
8284
8403
  return {
8285
8404
  ids,
8405
+ baseArgs,
8286
8406
  func: new Function(args, funcBody)
8287
8407
  };
8288
8408
  });
@@ -8290,17 +8410,13 @@
8290
8410
  this.#evaluate = (bindings) => {
8291
8411
  let result = text;
8292
8412
  evaluators.forEach((evaluator, i) => {
8293
- // Gather the current values of the identifiers from the bindings
8294
- // Fall back to global objects if not found in bindings
8295
- const values = evaluator.ids.map(id => {
8296
- const value = bindings.get(id);
8297
- if (value !== undefined) {
8413
+ // Build values for baseArgs (no dots) in the same order as function parameters
8414
+ const values = evaluator.baseArgs.map((name) => {
8415
+ const value = bindings.get(name);
8416
+ if (value !== undefined)
8298
8417
  return value;
8299
- }
8300
- // Check if it's a global object
8301
- if (id in GLOBAL_OBJECTS) {
8302
- return GLOBAL_OBJECTS[id];
8303
- }
8418
+ if (name in GLOBAL_OBJECTS)
8419
+ return GLOBAL_OBJECTS[name];
8304
8420
  return undefined;
8305
8421
  });
8306
8422
  // Evaluate the expression and replace {{...}} in the text
@@ -10104,10 +10220,16 @@
10104
10220
  // Evaluate the options expression
10105
10221
  let options;
10106
10222
  if (optionsDirective && optionsDirective.expression) {
10107
- // Evaluate the options expression
10223
+ // Evaluate the options expression using base identifiers (no dots)
10108
10224
  const identifiers = optionsDirective.dependentIdentifiers;
10109
- const values = identifiers.map(id => this.#vNode.bindings?.get(id));
10110
- const args = identifiers.join(", ");
10225
+ const baseArgs = [];
10226
+ for (const id of identifiers) {
10227
+ const base = id.split('.')[0];
10228
+ if (!baseArgs.includes(base))
10229
+ baseArgs.push(base);
10230
+ }
10231
+ const values = baseArgs.map(name => this.#vNode.bindings?.get(name));
10232
+ const args = baseArgs.join(", ");
10111
10233
  const funcBody = `return (${optionsDirective.expression});`;
10112
10234
  const func = new Function(args, funcBody);
10113
10235
  options = func(...values);
@@ -10180,8 +10302,14 @@
10180
10302
  }
10181
10303
  // For inline expressions, evaluate normally
10182
10304
  // Note: inline expressions receive entries and $ctx as parameters
10183
- const values = identifiers.map(id => vNode.bindings?.get(id));
10184
- const args = [...identifiers, 'entries', '$ctx'].join(", ");
10305
+ const baseArgs = [];
10306
+ for (const id of identifiers) {
10307
+ const base = id.split('.')[0];
10308
+ if (!baseArgs.includes(base))
10309
+ baseArgs.push(base);
10310
+ }
10311
+ const values = baseArgs.map(name => vNode.bindings?.get(name));
10312
+ const args = [...baseArgs, 'entries', '$ctx'].join(", ");
10185
10313
  const funcBody = `return (${expression});`;
10186
10314
  const func = new Function(args, funcBody);
10187
10315
  return func.call(bindings?.raw, ...values, entries, $ctx);
@@ -11004,10 +11132,16 @@
11004
11132
  // Evaluate the options expression
11005
11133
  let options;
11006
11134
  if (optionsDirective && optionsDirective.expression) {
11007
- // Evaluate the options expression
11135
+ // Evaluate the options expression using base identifiers (no dots)
11008
11136
  const identifiers = optionsDirective.dependentIdentifiers;
11009
- const values = identifiers.map(id => this.#vNode.bindings?.get(id));
11010
- const args = identifiers.join(", ");
11137
+ const baseArgs = [];
11138
+ for (const id of identifiers) {
11139
+ const base = id.split('.')[0];
11140
+ if (!baseArgs.includes(base))
11141
+ baseArgs.push(base);
11142
+ }
11143
+ const values = baseArgs.map(name => this.#vNode.bindings?.get(name));
11144
+ const args = baseArgs.join(", ");
11011
11145
  const funcBody = `return (${optionsDirective.expression});`;
11012
11146
  const func = new Function(args, funcBody);
11013
11147
  options = func(...values);
@@ -11088,8 +11222,14 @@
11088
11222
  }
11089
11223
  // For inline expressions, evaluate normally
11090
11224
  // Note: inline expressions receive entries, observer, options, and $ctx as parameters
11091
- const values = identifiers.map(id => vNode.bindings?.get(id));
11092
- const args = [...identifiers, 'entries', 'observer', 'options', '$ctx'].join(", ");
11225
+ const baseArgs = [];
11226
+ for (const id of identifiers) {
11227
+ const base = id.split('.')[0];
11228
+ if (!baseArgs.includes(base))
11229
+ baseArgs.push(base);
11230
+ }
11231
+ const values = baseArgs.map(name => vNode.bindings?.get(name));
11232
+ const args = [...baseArgs, 'entries', 'observer', 'options', '$ctx'].join(", ");
11093
11233
  const funcBody = `return (${expression});`;
11094
11234
  const func = new Function(args, funcBody);
11095
11235
  return func.call(bindings?.raw, ...values, entries, observer, options, $ctx);
@@ -11213,10 +11353,16 @@
11213
11353
  // Evaluate the options expression
11214
11354
  let options;
11215
11355
  if (optionsDirective && optionsDirective.expression) {
11216
- // Evaluate the options expression
11356
+ // Evaluate the options expression using base identifiers (no dots)
11217
11357
  const identifiers = optionsDirective.dependentIdentifiers;
11218
- const values = identifiers.map(id => this.#vNode.bindings?.get(id));
11219
- const args = identifiers.join(", ");
11358
+ const baseArgs = [];
11359
+ for (const id of identifiers) {
11360
+ const base = id.split('.')[0];
11361
+ if (!baseArgs.includes(base))
11362
+ baseArgs.push(base);
11363
+ }
11364
+ const values = baseArgs.map(name => this.#vNode.bindings?.get(name));
11365
+ const args = baseArgs.join(", ");
11220
11366
  const funcBody = `return (${optionsDirective.expression});`;
11221
11367
  const func = new Function(args, funcBody);
11222
11368
  options = func(...values);
@@ -11289,8 +11435,14 @@
11289
11435
  }
11290
11436
  // For inline expressions, evaluate normally
11291
11437
  // Note: inline expressions receive entries and $ctx as parameters
11292
- const values = identifiers.map(id => vNode.bindings?.get(id));
11293
- const args = [...identifiers, 'entries', '$ctx'].join(", ");
11438
+ const baseArgs = [];
11439
+ for (const id of identifiers) {
11440
+ const base = id.split('.')[0];
11441
+ if (!baseArgs.includes(base))
11442
+ baseArgs.push(base);
11443
+ }
11444
+ const values = baseArgs.map(name => vNode.bindings?.get(name));
11445
+ const args = [...baseArgs, 'entries', '$ctx'].join(", ");
11294
11446
  const funcBody = `return (${expression});`;
11295
11447
  const func = new Function(args, funcBody);
11296
11448
  return func.call(bindings?.raw, ...values, entries, $ctx);
@@ -11887,6 +12039,12 @@
11887
12039
  * A dictionary mapping computed property names to their dependencies.
11888
12040
  */
11889
12041
  #computedDependencies;
12042
+ /**
12043
+ * A map tracking source paths for computed property values.
12044
+ * Maps source path (e.g., "model.elements[0]") to computed property name (e.g., "selectedElement").
12045
+ * This allows changes to source paths to be mapped to computed property changes.
12046
+ */
12047
+ #computedSourcePaths = new Map();
11890
12048
  /**
11891
12049
  * Flag to indicate if an update is already scheduled.
11892
12050
  */
@@ -12124,11 +12282,45 @@
12124
12282
  #update() {
12125
12283
  // Re-evaluate computed properties that depend on changed values
12126
12284
  this.#recomputeProperties();
12285
+ // Apply computed source path mappings to changes
12286
+ // This converts paths like "model.elements[0].executionListeners"
12287
+ // to "selectedElement.executionListeners" when selectedElement points to model.elements[0]
12288
+ this.#applyComputedPathMappings();
12127
12289
  // Update the DOM
12128
12290
  this.#vNode?.update();
12129
12291
  // Clear the set of changed identifiers after the update
12130
12292
  this.#bindings?.clearChanges();
12131
12293
  }
12294
+ /**
12295
+ * Applies computed source path mappings to the current changes.
12296
+ * For each changed path, if it starts with a source path that maps to a computed property,
12297
+ * adds the corresponding computed property path to the changes.
12298
+ */
12299
+ #applyComputedPathMappings() {
12300
+ if (this.#computedSourcePaths.size === 0 || !this.#bindings) {
12301
+ return;
12302
+ }
12303
+ const changes = this.#bindings.changes;
12304
+ const mappedPaths = [];
12305
+ for (const changedPath of changes) {
12306
+ for (const [sourcePath, computedName] of this.#computedSourcePaths) {
12307
+ // Check if the changed path starts with or equals the source path
12308
+ if (changedPath === sourcePath) {
12309
+ // Exact match: mark the computed property itself as changed
12310
+ mappedPaths.push(computedName);
12311
+ }
12312
+ else if (changedPath.startsWith(sourcePath + '.') || changedPath.startsWith(sourcePath + '[')) {
12313
+ // Subpath match: convert "model.elements[0].x" to "selectedElement.x"
12314
+ const suffix = changedPath.slice(sourcePath.length);
12315
+ mappedPaths.push(computedName + suffix);
12316
+ }
12317
+ }
12318
+ }
12319
+ // Add all mapped paths to the changes
12320
+ for (const path of mappedPaths) {
12321
+ this.#bindings.markChanged(path);
12322
+ }
12323
+ }
12132
12324
  /**
12133
12325
  * Recursively recomputes computed properties based on changed identifiers.
12134
12326
  * @param isInitialization - If true, computes all computed properties regardless of dependencies
@@ -12186,8 +12378,26 @@
12186
12378
  }
12187
12379
  if (hasChanged) {
12188
12380
  // Use setSilent to avoid triggering onChange during computed property updates
12381
+ // Then mark the computed property as changed so UI depending on it will update
12189
12382
  this.#bindings?.setSilent(key, newValue);
12383
+ this.#bindings?.markChanged(key);
12190
12384
  allChanges.add(key);
12385
+ // Track source path mapping for computed property values
12386
+ // This allows changes like "model.elements[0].x" to be mapped to "selectedElement.x"
12387
+ if (typeof newValue === 'object' && newValue !== null) {
12388
+ const sourcePath = ReactiveProxy.getPath(newValue);
12389
+ if (sourcePath) {
12390
+ // Remove old mapping for this computed property
12391
+ for (const [path, name] of this.#computedSourcePaths) {
12392
+ if (name === key) {
12393
+ this.#computedSourcePaths.delete(path);
12394
+ break;
12395
+ }
12396
+ }
12397
+ // Add new mapping
12398
+ this.#computedSourcePaths.set(sourcePath, key);
12399
+ }
12400
+ }
12191
12401
  }
12192
12402
  }
12193
12403
  catch (error) {
@@ -12284,6 +12494,7 @@
12284
12494
  }
12285
12495
  }
12286
12496
 
12497
+ exports.ExpressionUtils = ExpressionUtils;
12287
12498
  exports.ReactiveProxy = ReactiveProxy;
12288
12499
  exports.VComponent = VComponent;
12289
12500
  exports.VComponentRegistry = VComponentRegistry;