@mintjamsinc/ichigojs 0.1.33 → 0.1.35

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.
@@ -8286,12 +8388,21 @@
8286
8388
  const evaluators = matches.map(match => {
8287
8389
  const expression = match[1].trim();
8288
8390
  const ids = ExpressionUtils.extractIdentifiers(expression, functionDependencies);
8289
- // Gather identifiers
8391
+ // Keep chain-style identifiers (a, a.b, a.b.c) for metadata
8290
8392
  this.#identifiers.push(...ids.filter(id => !this.#identifiers.includes(id)));
8291
- 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(", ");
8292
8402
  const funcBody = `return (${expression});`;
8293
8403
  return {
8294
8404
  ids,
8405
+ baseArgs,
8295
8406
  func: new Function(args, funcBody)
8296
8407
  };
8297
8408
  });
@@ -8299,17 +8410,13 @@
8299
8410
  this.#evaluate = (bindings) => {
8300
8411
  let result = text;
8301
8412
  evaluators.forEach((evaluator, i) => {
8302
- // Gather the current values of the identifiers from the bindings
8303
- // Fall back to global objects if not found in bindings
8304
- const values = evaluator.ids.map(id => {
8305
- const value = bindings.get(id);
8306
- 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)
8307
8417
  return value;
8308
- }
8309
- // Check if it's a global object
8310
- if (id in GLOBAL_OBJECTS) {
8311
- return GLOBAL_OBJECTS[id];
8312
- }
8418
+ if (name in GLOBAL_OBJECTS)
8419
+ return GLOBAL_OBJECTS[name];
8313
8420
  return undefined;
8314
8421
  });
8315
8422
  // Evaluate the expression and replace {{...}} in the text
@@ -10113,10 +10220,16 @@
10113
10220
  // Evaluate the options expression
10114
10221
  let options;
10115
10222
  if (optionsDirective && optionsDirective.expression) {
10116
- // Evaluate the options expression
10223
+ // Evaluate the options expression using base identifiers (no dots)
10117
10224
  const identifiers = optionsDirective.dependentIdentifiers;
10118
- const values = identifiers.map(id => this.#vNode.bindings?.get(id));
10119
- 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(", ");
10120
10233
  const funcBody = `return (${optionsDirective.expression});`;
10121
10234
  const func = new Function(args, funcBody);
10122
10235
  options = func(...values);
@@ -10189,8 +10302,14 @@
10189
10302
  }
10190
10303
  // For inline expressions, evaluate normally
10191
10304
  // Note: inline expressions receive entries and $ctx as parameters
10192
- const values = identifiers.map(id => vNode.bindings?.get(id));
10193
- 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(", ");
10194
10313
  const funcBody = `return (${expression});`;
10195
10314
  const func = new Function(args, funcBody);
10196
10315
  return func.call(bindings?.raw, ...values, entries, $ctx);
@@ -10854,24 +10973,31 @@
10854
10973
  const replacements = [];
10855
10974
  const parsedAst = parse(`(${expression})`, { ecmaVersion: 'latest' });
10856
10975
  // Collect all identifier nodes that should be replaced
10857
- // Use walk.full to ensure we visit ALL nodes including assignment LHS
10858
- full(parsedAst, (node) => {
10859
- if (node.type !== 'Identifier') {
10860
- return;
10861
- }
10862
- // Skip if not in our identifier set
10863
- if (!bindingIdentifiers.has(node.name)) {
10864
- return;
10976
+ // Use walk.ancestor to check parent context and skip MemberExpression properties
10977
+ ancestor(parsedAst, {
10978
+ Identifier(node, _state, ancestors) {
10979
+ // Skip if not in our identifier set
10980
+ if (!bindingIdentifiers.has(node.name)) {
10981
+ return;
10982
+ }
10983
+ // Check if this identifier is a property of a MemberExpression
10984
+ // (e.g., in 'obj.prop', we should skip 'prop')
10985
+ if (ancestors.length >= 2) {
10986
+ const parent = ancestors[ancestors.length - 2];
10987
+ if (parent.type === 'MemberExpression') {
10988
+ // Skip if this identifier is the property (not the object) of a non-computed member access
10989
+ if (!parent.computed && parent.property === node) {
10990
+ return;
10991
+ }
10992
+ }
10993
+ }
10994
+ // Add to replacements list (adjust for the wrapping parentheses)
10995
+ replacements.push({
10996
+ start: node.start - 1,
10997
+ end: node.end - 1,
10998
+ name: node.name
10999
+ });
10865
11000
  }
10866
- // Note: We cannot easily determine parent context with walk.full
10867
- // So we'll include all identifiers and rely on position-based replacement
10868
- // This is simpler and works correctly for inline expressions
10869
- // Add to replacements list (adjust for the wrapping parentheses)
10870
- replacements.push({
10871
- start: node.start - 1,
10872
- end: node.end - 1,
10873
- name: node.name
10874
- });
10875
11001
  });
10876
11002
  // Sort replacements by start position (descending) to replace from end to start
10877
11003
  replacements.sort((a, b) => b.start - a.start);
@@ -11013,10 +11139,16 @@
11013
11139
  // Evaluate the options expression
11014
11140
  let options;
11015
11141
  if (optionsDirective && optionsDirective.expression) {
11016
- // Evaluate the options expression
11142
+ // Evaluate the options expression using base identifiers (no dots)
11017
11143
  const identifiers = optionsDirective.dependentIdentifiers;
11018
- const values = identifiers.map(id => this.#vNode.bindings?.get(id));
11019
- const args = identifiers.join(", ");
11144
+ const baseArgs = [];
11145
+ for (const id of identifiers) {
11146
+ const base = id.split('.')[0];
11147
+ if (!baseArgs.includes(base))
11148
+ baseArgs.push(base);
11149
+ }
11150
+ const values = baseArgs.map(name => this.#vNode.bindings?.get(name));
11151
+ const args = baseArgs.join(", ");
11020
11152
  const funcBody = `return (${optionsDirective.expression});`;
11021
11153
  const func = new Function(args, funcBody);
11022
11154
  options = func(...values);
@@ -11097,8 +11229,14 @@
11097
11229
  }
11098
11230
  // For inline expressions, evaluate normally
11099
11231
  // Note: inline expressions receive entries, observer, options, and $ctx as parameters
11100
- const values = identifiers.map(id => vNode.bindings?.get(id));
11101
- const args = [...identifiers, 'entries', 'observer', 'options', '$ctx'].join(", ");
11232
+ const baseArgs = [];
11233
+ for (const id of identifiers) {
11234
+ const base = id.split('.')[0];
11235
+ if (!baseArgs.includes(base))
11236
+ baseArgs.push(base);
11237
+ }
11238
+ const values = baseArgs.map(name => vNode.bindings?.get(name));
11239
+ const args = [...baseArgs, 'entries', 'observer', 'options', '$ctx'].join(", ");
11102
11240
  const funcBody = `return (${expression});`;
11103
11241
  const func = new Function(args, funcBody);
11104
11242
  return func.call(bindings?.raw, ...values, entries, observer, options, $ctx);
@@ -11222,10 +11360,16 @@
11222
11360
  // Evaluate the options expression
11223
11361
  let options;
11224
11362
  if (optionsDirective && optionsDirective.expression) {
11225
- // Evaluate the options expression
11363
+ // Evaluate the options expression using base identifiers (no dots)
11226
11364
  const identifiers = optionsDirective.dependentIdentifiers;
11227
- const values = identifiers.map(id => this.#vNode.bindings?.get(id));
11228
- const args = identifiers.join(", ");
11365
+ const baseArgs = [];
11366
+ for (const id of identifiers) {
11367
+ const base = id.split('.')[0];
11368
+ if (!baseArgs.includes(base))
11369
+ baseArgs.push(base);
11370
+ }
11371
+ const values = baseArgs.map(name => this.#vNode.bindings?.get(name));
11372
+ const args = baseArgs.join(", ");
11229
11373
  const funcBody = `return (${optionsDirective.expression});`;
11230
11374
  const func = new Function(args, funcBody);
11231
11375
  options = func(...values);
@@ -11298,8 +11442,14 @@
11298
11442
  }
11299
11443
  // For inline expressions, evaluate normally
11300
11444
  // Note: inline expressions receive entries and $ctx as parameters
11301
- const values = identifiers.map(id => vNode.bindings?.get(id));
11302
- const args = [...identifiers, 'entries', '$ctx'].join(", ");
11445
+ const baseArgs = [];
11446
+ for (const id of identifiers) {
11447
+ const base = id.split('.')[0];
11448
+ if (!baseArgs.includes(base))
11449
+ baseArgs.push(base);
11450
+ }
11451
+ const values = baseArgs.map(name => vNode.bindings?.get(name));
11452
+ const args = [...baseArgs, 'entries', '$ctx'].join(", ");
11303
11453
  const funcBody = `return (${expression});`;
11304
11454
  const func = new Function(args, funcBody);
11305
11455
  return func.call(bindings?.raw, ...values, entries, $ctx);
@@ -11896,6 +12046,12 @@
11896
12046
  * A dictionary mapping computed property names to their dependencies.
11897
12047
  */
11898
12048
  #computedDependencies;
12049
+ /**
12050
+ * A map tracking source paths for computed property values.
12051
+ * Maps source path (e.g., "model.elements[0]") to computed property name (e.g., "selectedElement").
12052
+ * This allows changes to source paths to be mapped to computed property changes.
12053
+ */
12054
+ #computedSourcePaths = new Map();
11899
12055
  /**
11900
12056
  * Flag to indicate if an update is already scheduled.
11901
12057
  */
@@ -12133,11 +12289,45 @@
12133
12289
  #update() {
12134
12290
  // Re-evaluate computed properties that depend on changed values
12135
12291
  this.#recomputeProperties();
12292
+ // Apply computed source path mappings to changes
12293
+ // This converts paths like "model.elements[0].executionListeners"
12294
+ // to "selectedElement.executionListeners" when selectedElement points to model.elements[0]
12295
+ this.#applyComputedPathMappings();
12136
12296
  // Update the DOM
12137
12297
  this.#vNode?.update();
12138
12298
  // Clear the set of changed identifiers after the update
12139
12299
  this.#bindings?.clearChanges();
12140
12300
  }
12301
+ /**
12302
+ * Applies computed source path mappings to the current changes.
12303
+ * For each changed path, if it starts with a source path that maps to a computed property,
12304
+ * adds the corresponding computed property path to the changes.
12305
+ */
12306
+ #applyComputedPathMappings() {
12307
+ if (this.#computedSourcePaths.size === 0 || !this.#bindings) {
12308
+ return;
12309
+ }
12310
+ const changes = this.#bindings.changes;
12311
+ const mappedPaths = [];
12312
+ for (const changedPath of changes) {
12313
+ for (const [sourcePath, computedName] of this.#computedSourcePaths) {
12314
+ // Check if the changed path starts with or equals the source path
12315
+ if (changedPath === sourcePath) {
12316
+ // Exact match: mark the computed property itself as changed
12317
+ mappedPaths.push(computedName);
12318
+ }
12319
+ else if (changedPath.startsWith(sourcePath + '.') || changedPath.startsWith(sourcePath + '[')) {
12320
+ // Subpath match: convert "model.elements[0].x" to "selectedElement.x"
12321
+ const suffix = changedPath.slice(sourcePath.length);
12322
+ mappedPaths.push(computedName + suffix);
12323
+ }
12324
+ }
12325
+ }
12326
+ // Add all mapped paths to the changes
12327
+ for (const path of mappedPaths) {
12328
+ this.#bindings.markChanged(path);
12329
+ }
12330
+ }
12141
12331
  /**
12142
12332
  * Recursively recomputes computed properties based on changed identifiers.
12143
12333
  * @param isInitialization - If true, computes all computed properties regardless of dependencies
@@ -12199,6 +12389,22 @@
12199
12389
  this.#bindings?.setSilent(key, newValue);
12200
12390
  this.#bindings?.markChanged(key);
12201
12391
  allChanges.add(key);
12392
+ // Track source path mapping for computed property values
12393
+ // This allows changes like "model.elements[0].x" to be mapped to "selectedElement.x"
12394
+ if (typeof newValue === 'object' && newValue !== null) {
12395
+ const sourcePath = ReactiveProxy.getPath(newValue);
12396
+ if (sourcePath) {
12397
+ // Remove old mapping for this computed property
12398
+ for (const [path, name] of this.#computedSourcePaths) {
12399
+ if (name === key) {
12400
+ this.#computedSourcePaths.delete(path);
12401
+ break;
12402
+ }
12403
+ }
12404
+ // Add new mapping
12405
+ this.#computedSourcePaths.set(sourcePath, key);
12406
+ }
12407
+ }
12202
12408
  }
12203
12409
  }
12204
12410
  catch (error) {
@@ -12295,6 +12501,7 @@
12295
12501
  }
12296
12502
  }
12297
12503
 
12504
+ exports.ExpressionUtils = ExpressionUtils;
12298
12505
  exports.ReactiveProxy = ReactiveProxy;
12299
12506
  exports.VComponent = VComponent;
12300
12507
  exports.VComponentRegistry = VComponentRegistry;