@mintjamsinc/ichigojs 0.1.21 → 0.1.23

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/README.md CHANGED
@@ -90,6 +90,51 @@ VDOM.createApp({
90
90
  }).mount('#app');
91
91
  ```
92
92
 
93
+ ### Marking Objects as Non-Reactive
94
+
95
+ Use `$markRaw()` to prevent objects from being wrapped in a reactive proxy. This is useful when storing third-party library instances or large data structures that don't need reactivity.
96
+
97
+ ```javascript
98
+ VDOM.createApp({
99
+ data() {
100
+ return {
101
+ // Regular reactive data
102
+ count: 0,
103
+
104
+ // Mark as non-reactive
105
+ chart: null,
106
+ bigData: null
107
+ };
108
+ },
109
+ methods: {
110
+ initChart($ctx) {
111
+ // Create Chart.js instance and mark as non-reactive
112
+ const chartInstance = new Chart(canvas, { /* ... */ });
113
+ this.chart = this.$markRaw(chartInstance);
114
+
115
+ // Large dataset that doesn't need reactivity
116
+ const data = fetchLargeDataset();
117
+ this.bigData = this.$markRaw(data);
118
+ }
119
+ }
120
+ }).mount('#app');
121
+ ```
122
+
123
+ **When to use `$markRaw()`:**
124
+
125
+ - Third-party library instances (Chart.js, Three.js, etc.)
126
+ - Large arrays or objects that don't need change detection
127
+ - Objects with complex internal state that shouldn't be proxied
128
+ - DOM elements or other built-in objects with internal slots
129
+
130
+ **Note:** Objects marked with `$markRaw()` won't trigger updates when modified. Use reactive properties to trigger updates when needed:
131
+
132
+ ```javascript
133
+ // Update reactive trigger after modifying non-reactive data
134
+ this.bigData.push(newItem); // Won't trigger update
135
+ this.count++; // Triggers update
136
+ ```
137
+
93
138
  ### Computed Properties
94
139
 
95
140
  Computed properties automatically track their dependencies and re-evaluate when dependencies change.
@@ -7417,6 +7417,16 @@ class ReactiveProxy {
7417
7417
  * This prevents creating multiple proxies for the same object accessed from different paths.
7418
7418
  */
7419
7419
  static proxyCache = new WeakMap();
7420
+ /**
7421
+ * A WeakMap to track which objects are proxies, mapping proxy -> original target.
7422
+ * This prevents double-wrapping of already proxied objects.
7423
+ */
7424
+ static proxyToTarget = new WeakMap();
7425
+ /**
7426
+ * A WeakSet to track objects marked as "raw" (non-reactive).
7427
+ * These objects will not be wrapped with Proxy.
7428
+ */
7429
+ static rawObjects = new WeakSet();
7420
7430
  /**
7421
7431
  * Creates a reactive proxy for the given object.
7422
7432
  * The proxy will call the onChange callback whenever a property is modified.
@@ -7431,9 +7441,19 @@ class ReactiveProxy {
7431
7441
  if (typeof target !== 'object' || target === null) {
7432
7442
  return target;
7433
7443
  }
7444
+ // Don't wrap objects marked as raw (non-reactive)
7445
+ if (this.rawObjects.has(target)) {
7446
+ return target;
7447
+ }
7434
7448
  // Don't wrap built-in objects that have internal slots
7435
7449
  // These objects require their methods to be called with the correct 'this' context
7436
- if (target instanceof Date || target instanceof RegExp || target instanceof Error) {
7450
+ // Use Object.prototype.toString for more reliable type checking
7451
+ const typeTag = Object.prototype.toString.call(target);
7452
+ if (typeTag === '[object Date]' || typeTag === '[object RegExp]' || typeTag === '[object Error]') {
7453
+ return target;
7454
+ }
7455
+ // Check if the target is already a proxy - if so, return it as-is to prevent double-wrapping
7456
+ if (this.proxyToTarget.has(target)) {
7437
7457
  return target;
7438
7458
  }
7439
7459
  // Check if we already have a proxy for this target with this path
@@ -7454,8 +7474,14 @@ class ReactiveProxy {
7454
7474
  const value = Reflect.get(obj, key);
7455
7475
  // If the value is an object or array, make it reactive too
7456
7476
  if (typeof value === 'object' && value !== null) {
7477
+ // Don't wrap objects marked as raw (non-reactive)
7478
+ if (ReactiveProxy.rawObjects.has(value)) {
7479
+ return value;
7480
+ }
7457
7481
  // Don't wrap built-in objects that have internal slots
7458
- if (value instanceof Date || value instanceof RegExp || value instanceof Error) {
7482
+ // Use Object.prototype.toString for more reliable type checking
7483
+ const valueTypeTag = Object.prototype.toString.call(value);
7484
+ if (valueTypeTag === '[object Date]' || valueTypeTag === '[object RegExp]' || valueTypeTag === '[object Error]') {
7459
7485
  return value;
7460
7486
  }
7461
7487
  // Build the nested path
@@ -7468,7 +7494,7 @@ class ReactiveProxy {
7468
7494
  const arrayMutationMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
7469
7495
  if (arrayMutationMethods.includes(key)) {
7470
7496
  return function (...args) {
7471
- const result = value.apply(this, args);
7497
+ const result = value.apply(obj, args);
7472
7498
  onChange(path || undefined);
7473
7499
  return result;
7474
7500
  };
@@ -7497,6 +7523,8 @@ class ReactiveProxy {
7497
7523
  });
7498
7524
  // Cache the proxy for this path
7499
7525
  pathMap.set(path, proxy);
7526
+ // Track that this proxy wraps the target to prevent double-wrapping
7527
+ this.proxyToTarget.set(proxy, target);
7500
7528
  return proxy;
7501
7529
  }
7502
7530
  /**
@@ -7520,6 +7548,32 @@ class ReactiveProxy {
7520
7548
  // In a full implementation, we'd need to store a reverse mapping
7521
7549
  return obj;
7522
7550
  }
7551
+ /**
7552
+ * Marks an object as "raw" (non-reactive).
7553
+ * Objects marked as raw will not be wrapped with Proxy when accessed from reactive objects.
7554
+ * This is useful for objects that should not be reactive, such as:
7555
+ * - Objects with private fields (class instances with # fields)
7556
+ * - Third-party library instances
7557
+ * - Objects used only for method calls
7558
+ *
7559
+ * @param obj The object to mark as raw.
7560
+ * @returns The same object (for chaining).
7561
+ */
7562
+ static markRaw(obj) {
7563
+ if (typeof obj === 'object' && obj !== null) {
7564
+ this.rawObjects.add(obj);
7565
+ }
7566
+ return obj;
7567
+ }
7568
+ /**
7569
+ * Checks if an object is marked as raw (non-reactive).
7570
+ *
7571
+ * @param obj The object to check.
7572
+ * @returns True if the object is marked as raw, false otherwise.
7573
+ */
7574
+ static isRaw(obj) {
7575
+ return typeof obj === 'object' && obj !== null && this.rawObjects.has(obj);
7576
+ }
7523
7577
  }
7524
7578
 
7525
7579
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
@@ -7553,6 +7607,10 @@ class VBindings {
7553
7607
  * Cache for array lengths to detect length changes when the same object reference is used.
7554
7608
  */
7555
7609
  #lengthCache = new Map();
7610
+ /**
7611
+ * Flag to suppress onChange callbacks temporarily.
7612
+ */
7613
+ #suppressOnChange = false;
7556
7614
  /**
7557
7615
  * Creates a new instance of VBindings.
7558
7616
  * @param parent The parent bindings, if any.
@@ -7591,7 +7649,9 @@ class VBindings {
7591
7649
  this.#logger?.debug(`Binding changed: ${path}`);
7592
7650
  this.#changes.add(path);
7593
7651
  }
7594
- this.#onChange?.(changedPath);
7652
+ if (!this.#suppressOnChange) {
7653
+ this.#onChange?.(changedPath);
7654
+ }
7595
7655
  }, key);
7596
7656
  }
7597
7657
  const oldValue = Reflect.get(target, key);
@@ -7616,7 +7676,9 @@ class VBindings {
7616
7676
  this.#logger.debug(`Binding set on ${target === obj ? 'local' : 'parent'}: ${key}: ${oldValuePreview} -> ${newValuePreview}`);
7617
7677
  }
7618
7678
  this.#changes.add(key);
7619
- this.#onChange?.(key);
7679
+ if (!this.#suppressOnChange) {
7680
+ this.#onChange?.(key);
7681
+ }
7620
7682
  }
7621
7683
  return result;
7622
7684
  },
@@ -7703,6 +7765,21 @@ class VBindings {
7703
7765
  remove(key) {
7704
7766
  delete this.#local[key];
7705
7767
  }
7768
+ /**
7769
+ * Sets a binding value without triggering onChange callback.
7770
+ * This is useful for internal updates that shouldn't trigger reactivity.
7771
+ * @param key The binding name.
7772
+ * @param value The binding value.
7773
+ */
7774
+ setSilent(key, value) {
7775
+ this.#suppressOnChange = true;
7776
+ try {
7777
+ this.#local[key] = value;
7778
+ }
7779
+ finally {
7780
+ this.#suppressOnChange = false;
7781
+ }
7782
+ }
7706
7783
  }
7707
7784
 
7708
7785
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
@@ -9224,7 +9301,10 @@ class VForDirective {
9224
9301
  vApplication: this.#vNode.vApplication,
9225
9302
  parentVNode: this.#vNode.parentVNode,
9226
9303
  bindings,
9227
- dependentIdentifiers: [`${this.#sourceName}[${context.index}]`]
9304
+ dependentIdentifiers: [
9305
+ `${this.#sourceName}[${context.index}]`,
9306
+ ...this.#vNode.vApplication.resolveDependentIdentifiers(this.#sourceName, context.item)
9307
+ ],
9228
9308
  });
9229
9309
  newRenderedItems.set(key, vNode);
9230
9310
  vNode.forceUpdate();
@@ -11072,6 +11152,10 @@ class VApplication {
11072
11152
  * The application options.
11073
11153
  */
11074
11154
  #options;
11155
+ /**
11156
+ * The parent application, if any.
11157
+ */
11158
+ #parentApplication;
11075
11159
  /**
11076
11160
  * The global directive parser registry.
11077
11161
  */
@@ -11110,12 +11194,12 @@ class VApplication {
11110
11194
  #updateScheduled = false;
11111
11195
  /**
11112
11196
  * Creates an instance of the virtual application.
11113
- * @param options The application options.
11114
- * @param directiveParserRegistry The global directive parser registry.
11115
- * @param componentRegistry The global component registry.
11197
+ * @param args The initialization arguments for the application.
11116
11198
  */
11117
- constructor(options, directiveParserRegistry, componentRegistry) {
11199
+ constructor(args) {
11200
+ const { options, parentApplication: parentApplication, directiveParserRegistry, componentRegistry } = args;
11118
11201
  this.#options = options;
11202
+ this.#parentApplication = parentApplication;
11119
11203
  this.#directiveParserRegistry = directiveParserRegistry;
11120
11204
  this.#componentRegistry = componentRegistry;
11121
11205
  // Initialize log manager and logger
@@ -11128,6 +11212,12 @@ class VApplication {
11128
11212
  // Initialize bindings from data, computed, and methods
11129
11213
  this.#initializeBindings();
11130
11214
  }
11215
+ /**
11216
+ * Gets the parent application, if any.
11217
+ */
11218
+ get parentApplication() {
11219
+ return this.#parentApplication;
11220
+ }
11131
11221
  /**
11132
11222
  * Gets the global directive parser registry.
11133
11223
  */
@@ -11207,7 +11297,12 @@ class VApplication {
11207
11297
  * @returns The created child application instance.
11208
11298
  */
11209
11299
  createChildApp(options) {
11210
- return new VApplication(options, this.#directiveParserRegistry, this.#componentRegistry);
11300
+ return new VApplication({
11301
+ options,
11302
+ parentApplication: this,
11303
+ directiveParserRegistry: this.#directiveParserRegistry,
11304
+ componentRegistry: this.#componentRegistry
11305
+ });
11211
11306
  }
11212
11307
  /**
11213
11308
  * Cleans the element by removing unnecessary whitespace text nodes.
@@ -11239,6 +11334,26 @@ class VApplication {
11239
11334
  }
11240
11335
  }
11241
11336
  }
11337
+ /**
11338
+ * Computes dependent identifiers for a given computed property and value.
11339
+ * This is used to track dependencies in directives like v-for.
11340
+ * @param computedName The name of the computed property.
11341
+ * @param value The value to check for dependencies.
11342
+ * @returns An array of dependent identifiers.
11343
+ */
11344
+ resolveDependentIdentifiers(computedName, value) {
11345
+ const identifiers = [];
11346
+ for (const dep of this.#computedDependencies[computedName] || []) {
11347
+ const depValue = this.#bindings?.get(dep);
11348
+ if (Array.isArray(depValue)) {
11349
+ const idx = depValue.indexOf(value);
11350
+ if (idx !== -1) {
11351
+ identifiers.push(`${dep}[${idx}]`);
11352
+ }
11353
+ }
11354
+ }
11355
+ return identifiers;
11356
+ }
11242
11357
  /**
11243
11358
  * Initializes bindings from data, computed properties, and methods.
11244
11359
  * @returns The initialized bindings object.
@@ -11253,6 +11368,7 @@ class VApplication {
11253
11368
  });
11254
11369
  // Inject utility methods into bindings
11255
11370
  this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
11371
+ this.#bindings.set('$markRaw', (obj) => ReactiveProxy.markRaw(obj));
11256
11372
  // Add methods
11257
11373
  if (this.#options.methods) {
11258
11374
  for (const [key, method] of Object.entries(this.#options.methods)) {
@@ -11267,7 +11383,13 @@ class VApplication {
11267
11383
  }
11268
11384
  // Add data properties
11269
11385
  if (this.#options.data) {
11270
- const data = this.#options.data();
11386
+ // Create a $ctx context object with utility functions for data()
11387
+ // This provides the same $markRaw access as in lifecycle hooks (@mount, etc.)
11388
+ const $ctx = {
11389
+ $markRaw: (obj) => ReactiveProxy.markRaw(obj)
11390
+ };
11391
+ // Call data() with $ctx as 'this'
11392
+ const data = this.#options.data.call($ctx);
11271
11393
  if (data && typeof data === 'object') {
11272
11394
  for (const [key, value] of Object.entries(data)) {
11273
11395
  this.#bindings.set(key, value);
@@ -11350,9 +11472,15 @@ class VApplication {
11350
11472
  try {
11351
11473
  const oldValue = this.#bindings?.get(key);
11352
11474
  const newValue = computedFn.call(this.#bindings?.raw);
11353
- // Track if the computed value actually changed
11354
- if (oldValue !== newValue) {
11355
- this.#bindings?.set(key, newValue);
11475
+ // Check if the value actually changed
11476
+ let hasChanged = oldValue !== newValue;
11477
+ // For arrays, always update (VBindings will detect length changes via its length cache)
11478
+ if (!hasChanged && Array.isArray(newValue)) {
11479
+ hasChanged = true;
11480
+ }
11481
+ if (hasChanged) {
11482
+ // Use setSilent to avoid triggering onChange during computed property updates
11483
+ this.#bindings?.setSilent(key, newValue);
11356
11484
  allChanges.add(key);
11357
11485
  }
11358
11486
  }
@@ -11441,9 +11569,13 @@ class VDOM {
11441
11569
  * @returns The created virtual application instance.
11442
11570
  */
11443
11571
  static createApp(options) {
11444
- return new VApplication(options, this.#directiveParserRegistry, this.#componentRegistry);
11572
+ return new VApplication({
11573
+ options,
11574
+ directiveParserRegistry: this.#directiveParserRegistry,
11575
+ componentRegistry: this.#componentRegistry
11576
+ });
11445
11577
  }
11446
11578
  }
11447
11579
 
11448
- export { VComponent, VComponentRegistry, VDOM };
11580
+ export { ReactiveProxy, VComponent, VComponentRegistry, VDOM };
11449
11581
  //# sourceMappingURL=ichigo.esm.js.map