@mintjamsinc/ichigojs 0.1.1 → 0.1.2
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.esm.js +137 -33
- package/dist/ichigo.esm.js.map +1 -1
- package/dist/ichigo.esm.min.js +1 -1
- package/dist/ichigo.esm.min.js.map +1 -1
- package/dist/ichigo.umd.js +137 -33
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/ichigo.umd.min.js.map +1 -1
- package/dist/types/ichigo/VNode.d.ts +11 -1
- package/dist/types/ichigo/VNodeInit.d.ts +5 -0
- package/dist/types/ichigo/util/ReactiveProxy.d.ts +6 -5
- package/package.json +2 -2
package/dist/ichigo.esm.js
CHANGED
@@ -7044,34 +7044,46 @@ class VBindDirective {
|
|
7044
7044
|
*/
|
7045
7045
|
class ReactiveProxy {
|
7046
7046
|
/**
|
7047
|
-
* A WeakMap to store the
|
7048
|
-
* This
|
7047
|
+
* A WeakMap to store the proxy for each target object and path combination.
|
7048
|
+
* This prevents creating multiple proxies for the same object accessed from different paths.
|
7049
7049
|
*/
|
7050
|
-
static
|
7050
|
+
static proxyCache = new WeakMap();
|
7051
7051
|
/**
|
7052
7052
|
* Creates a reactive proxy for the given object.
|
7053
7053
|
* The proxy will call the onChange callback whenever a property is modified.
|
7054
7054
|
*
|
7055
7055
|
* @param target The object to make reactive.
|
7056
|
-
* @param onChange Callback function to call when the object changes. Receives the changed
|
7056
|
+
* @param onChange Callback function to call when the object changes. Receives the full path of the changed property.
|
7057
|
+
* @param path The current path in the object tree (used internally for nested objects).
|
7057
7058
|
* @returns A reactive proxy of the target object.
|
7058
7059
|
*/
|
7059
|
-
static create(target, onChange) {
|
7060
|
+
static create(target, onChange, path = '') {
|
7060
7061
|
// If the target is not an object or is null, return it as-is
|
7061
7062
|
if (typeof target !== 'object' || target === null) {
|
7062
7063
|
return target;
|
7063
7064
|
}
|
7064
|
-
//
|
7065
|
-
|
7066
|
-
|
7065
|
+
// Check if we already have a proxy for this target with this path
|
7066
|
+
let pathMap = this.proxyCache.get(target);
|
7067
|
+
if (pathMap) {
|
7068
|
+
const existingProxy = pathMap.get(path);
|
7069
|
+
if (existingProxy) {
|
7070
|
+
return existingProxy;
|
7071
|
+
}
|
7072
|
+
}
|
7073
|
+
else {
|
7074
|
+
pathMap = new Map();
|
7075
|
+
this.proxyCache.set(target, pathMap);
|
7067
7076
|
}
|
7068
|
-
// Create the proxy
|
7077
|
+
// Create the proxy with path captured in closure
|
7069
7078
|
const proxy = new Proxy(target, {
|
7070
7079
|
get(obj, key) {
|
7071
7080
|
const value = Reflect.get(obj, key);
|
7072
7081
|
// If the value is an object or array, make it reactive too
|
7073
7082
|
if (typeof value === 'object' && value !== null) {
|
7074
|
-
|
7083
|
+
// Build the nested path
|
7084
|
+
const keyStr = String(key);
|
7085
|
+
const nestedPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7086
|
+
return ReactiveProxy.create(value, onChange, nestedPath);
|
7075
7087
|
}
|
7076
7088
|
// For arrays, intercept mutation methods
|
7077
7089
|
if (Array.isArray(obj) && typeof value === 'function') {
|
@@ -7079,7 +7091,7 @@ class ReactiveProxy {
|
|
7079
7091
|
if (arrayMutationMethods.includes(key)) {
|
7080
7092
|
return function (...args) {
|
7081
7093
|
const result = value.apply(this, args);
|
7082
|
-
onChange();
|
7094
|
+
onChange(path || undefined);
|
7083
7095
|
return result;
|
7084
7096
|
};
|
7085
7097
|
}
|
@@ -7091,18 +7103,22 @@ class ReactiveProxy {
|
|
7091
7103
|
const result = Reflect.set(obj, key, value);
|
7092
7104
|
// Only trigger onChange if the value actually changed
|
7093
7105
|
if (oldValue !== value) {
|
7094
|
-
|
7106
|
+
const keyStr = String(key);
|
7107
|
+
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7108
|
+
onChange(fullPath);
|
7095
7109
|
}
|
7096
7110
|
return result;
|
7097
7111
|
},
|
7098
7112
|
deleteProperty(obj, key) {
|
7099
7113
|
const result = Reflect.deleteProperty(obj, key);
|
7100
|
-
|
7114
|
+
const keyStr = String(key);
|
7115
|
+
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7116
|
+
onChange(fullPath);
|
7101
7117
|
return result;
|
7102
7118
|
}
|
7103
7119
|
});
|
7104
|
-
//
|
7105
|
-
|
7120
|
+
// Cache the proxy for this path
|
7121
|
+
pathMap.set(path, proxy);
|
7106
7122
|
return proxy;
|
7107
7123
|
}
|
7108
7124
|
/**
|
@@ -7112,7 +7128,7 @@ class ReactiveProxy {
|
|
7112
7128
|
* @returns True if the object is a reactive proxy, false otherwise.
|
7113
7129
|
*/
|
7114
7130
|
static isReactive(obj) {
|
7115
|
-
return this.
|
7131
|
+
return this.proxyCache.has(obj);
|
7116
7132
|
}
|
7117
7133
|
/**
|
7118
7134
|
* Unwraps a reactive proxy to get the original object.
|
@@ -7151,6 +7167,10 @@ class VBindings {
|
|
7151
7167
|
* The set of changed identifiers.
|
7152
7168
|
*/
|
7153
7169
|
#changes = new Set();
|
7170
|
+
/**
|
7171
|
+
* Cache for array lengths to detect length changes when the same object reference is used.
|
7172
|
+
*/
|
7173
|
+
#lengthCache = new Map();
|
7154
7174
|
/**
|
7155
7175
|
* Creates a new instance of VBindings.
|
7156
7176
|
* @param parent The parent bindings, if any.
|
@@ -7178,14 +7198,29 @@ class VBindings {
|
|
7178
7198
|
let newValue = value;
|
7179
7199
|
if (typeof value === 'object' && value !== null) {
|
7180
7200
|
// Wrap objects/arrays with reactive proxy, tracking the root key
|
7181
|
-
newValue = ReactiveProxy.create(value, () => {
|
7182
|
-
|
7183
|
-
|
7184
|
-
|
7201
|
+
newValue = ReactiveProxy.create(value, (changedPath) => {
|
7202
|
+
let path = '';
|
7203
|
+
for (const part of changedPath?.split('.') || []) {
|
7204
|
+
path = path ? `${path}.${part}` : part;
|
7205
|
+
this.#changes.add(path);
|
7206
|
+
}
|
7207
|
+
this.#onChange?.(changedPath);
|
7208
|
+
}, key);
|
7185
7209
|
}
|
7186
7210
|
const oldValue = Reflect.get(target, key);
|
7187
7211
|
const result = Reflect.set(target, key, newValue);
|
7188
|
-
|
7212
|
+
// Detect changes
|
7213
|
+
let hasChanged = oldValue !== newValue;
|
7214
|
+
// Special handling for arrays: check length changes even if same object reference
|
7215
|
+
if (!hasChanged && Array.isArray(newValue)) {
|
7216
|
+
const cachedLength = this.#lengthCache.get(key);
|
7217
|
+
const currentLength = newValue.length;
|
7218
|
+
if (cachedLength !== undefined && cachedLength !== currentLength) {
|
7219
|
+
hasChanged = true;
|
7220
|
+
}
|
7221
|
+
this.#lengthCache.set(key, currentLength);
|
7222
|
+
}
|
7223
|
+
if (hasChanged) {
|
7189
7224
|
this.#changes.add(key);
|
7190
7225
|
this.#onChange?.(key);
|
7191
7226
|
}
|
@@ -7541,6 +7576,11 @@ class VNode {
|
|
7541
7576
|
* The data bindings associated with this virtual node, if any.
|
7542
7577
|
*/
|
7543
7578
|
#bindings;
|
7579
|
+
/**
|
7580
|
+
* The initial set of identifiers that this node depends on.
|
7581
|
+
* This is optional and may be undefined if there are no dependent identifiers.
|
7582
|
+
*/
|
7583
|
+
#initDependentIdentifiers;
|
7544
7584
|
/**
|
7545
7585
|
* An evaluator for text nodes that contain expressions in {{...}}.
|
7546
7586
|
* This is used to dynamically update the text content based on data bindings.
|
@@ -7585,6 +7625,7 @@ class VNode {
|
|
7585
7625
|
this.#nodeName = args.node.nodeName;
|
7586
7626
|
this.#parentVNode = args.parentVNode;
|
7587
7627
|
this.#bindings = args.bindings;
|
7628
|
+
this.#initDependentIdentifiers = args.dependentIdentifiers;
|
7588
7629
|
this.#parentVNode?.addChild(this);
|
7589
7630
|
// If the node is a text node, check for expressions and create a text evaluator
|
7590
7631
|
if (this.#nodeType === Node.TEXT_NODE) {
|
@@ -7736,6 +7777,8 @@ class VNode {
|
|
7736
7777
|
}
|
7737
7778
|
// Collect identifiers from text evaluator and directives
|
7738
7779
|
const ids = [];
|
7780
|
+
// Include initial dependent identifiers, if any
|
7781
|
+
ids.push(...this.#initDependentIdentifiers ?? []);
|
7739
7782
|
// If this is a text node with a text evaluator, include its identifiers
|
7740
7783
|
if (this.#textEvaluator) {
|
7741
7784
|
ids.push(...this.#textEvaluator.identifiers);
|
@@ -7767,6 +7810,29 @@ class VNode {
|
|
7767
7810
|
this.#preparableIdentifiers = preparableIdentifiers.length === 0 ? [] : [...new Set(preparableIdentifiers)];
|
7768
7811
|
return this.#preparableIdentifiers;
|
7769
7812
|
}
|
7813
|
+
/**
|
7814
|
+
* The DOM path of this virtual node.
|
7815
|
+
* This is a string representation of the path from the root to this node,
|
7816
|
+
* using the node names and their indices among siblings with the same name.
|
7817
|
+
* For example: "DIV[0]/SPAN[1]/#text[0]"
|
7818
|
+
* @return The DOM path as a string.
|
7819
|
+
*/
|
7820
|
+
get domPath() {
|
7821
|
+
const path = [];
|
7822
|
+
let node = this;
|
7823
|
+
while (node) {
|
7824
|
+
if (node.parentVNode && node.parentVNode.childVNodes) {
|
7825
|
+
const siblings = node.parentVNode.childVNodes.filter(v => v.nodeName === node?.nodeName);
|
7826
|
+
const index = siblings.indexOf(node);
|
7827
|
+
path.unshift(`${node.nodeName}[${index}]`);
|
7828
|
+
}
|
7829
|
+
else {
|
7830
|
+
path.unshift(node.nodeName);
|
7831
|
+
}
|
7832
|
+
node = node.parentVNode;
|
7833
|
+
}
|
7834
|
+
return path.join('/');
|
7835
|
+
}
|
7770
7836
|
/**
|
7771
7837
|
* Updates the virtual node and its children based on the current bindings.
|
7772
7838
|
* This method evaluates any expressions in text nodes and applies effectors from directives.
|
@@ -7863,24 +7929,46 @@ class VNode {
|
|
7863
7929
|
/**
|
7864
7930
|
* Adds a dependent virtual node that relies on this node's bindings.
|
7865
7931
|
* @param dependent The dependent virtual node to add.
|
7932
|
+
* @param dependentIdentifiers The identifiers that the dependent node relies on.
|
7933
|
+
* If not provided, the dependent node's own identifiers will be used.
|
7866
7934
|
* @returns A list of closers to unregister the dependency, or undefined if no dependency was added.
|
7867
7935
|
*/
|
7868
|
-
addDependent(dependent) {
|
7936
|
+
addDependent(dependent, dependentIdentifiers = undefined) {
|
7869
7937
|
// List of closers to unregister the dependency
|
7870
7938
|
const closers = [];
|
7871
|
-
//
|
7872
|
-
|
7873
|
-
|
7874
|
-
|
7939
|
+
// If dependent identifiers are not provided, use the dependent node's own identifiers
|
7940
|
+
if (!dependentIdentifiers) {
|
7941
|
+
dependentIdentifiers = [...dependent.dependentIdentifiers];
|
7942
|
+
}
|
7943
|
+
// Prepare alternative identifiers by stripping array indices (e.g., "items[0]" -> "items")
|
7944
|
+
const allDeps = new Set();
|
7945
|
+
dependentIdentifiers.forEach(id => {
|
7946
|
+
allDeps.add(id);
|
7947
|
+
const idx = id.indexOf('[');
|
7948
|
+
if (idx !== -1) {
|
7949
|
+
allDeps.add(id.substring(0, idx));
|
7950
|
+
}
|
7951
|
+
});
|
7952
|
+
// Get this node's identifiers
|
7953
|
+
const thisIds = [...this.preparableIdentifiers];
|
7954
|
+
if (this.#bindings) {
|
7955
|
+
thisIds.push(...this.#bindings?.raw ? Object.keys(this.#bindings.raw) : []);
|
7875
7956
|
}
|
7876
7957
|
// If the dependent node has an identifier in this node's identifiers, add it as a dependency
|
7877
|
-
if (
|
7958
|
+
if ([...allDeps].some(id => thisIds.includes(id))) {
|
7878
7959
|
// If the dependencies list is not initialized, create it
|
7879
7960
|
if (!this.#dependents) {
|
7880
7961
|
this.#dependents = [];
|
7881
7962
|
}
|
7882
7963
|
// Add the dependent node to the list
|
7883
7964
|
this.#dependents.push(dependent);
|
7965
|
+
// Remove the matched identifiers from the dependent node's identifiers to avoid duplicate dependencies
|
7966
|
+
thisIds.forEach(id => {
|
7967
|
+
const idx = dependentIdentifiers.indexOf(id);
|
7968
|
+
if (idx !== -1) {
|
7969
|
+
dependentIdentifiers.splice(idx, 1);
|
7970
|
+
}
|
7971
|
+
});
|
7884
7972
|
// Create a closer to unregister the dependency
|
7885
7973
|
closers.push({
|
7886
7974
|
close: () => {
|
@@ -7893,7 +7981,7 @@ class VNode {
|
|
7893
7981
|
});
|
7894
7982
|
}
|
7895
7983
|
// Recursively add the dependency to the parent node, if any
|
7896
|
-
this.#parentVNode?.addDependent(dependent)?.forEach(closer => closers.push(closer));
|
7984
|
+
this.#parentVNode?.addDependent(dependent, dependentIdentifiers)?.forEach(closer => closers.push(closer));
|
7897
7985
|
// Return a closer to unregister the dependency
|
7898
7986
|
return closers.length > 0 ? closers : undefined;
|
7899
7987
|
}
|
@@ -8565,7 +8653,8 @@ class VForDirective {
|
|
8565
8653
|
node: clone,
|
8566
8654
|
vApplication: this.#vNode.vApplication,
|
8567
8655
|
parentVNode: this.#vNode.parentVNode,
|
8568
|
-
bindings
|
8656
|
+
bindings,
|
8657
|
+
dependentIdentifiers: [`${this.#sourceName}[${context.index}]`]
|
8569
8658
|
});
|
8570
8659
|
return vNode;
|
8571
8660
|
}
|
@@ -9595,7 +9684,7 @@ class VApplication {
|
|
9595
9684
|
#initializeBindings() {
|
9596
9685
|
// Create bindings with change tracking
|
9597
9686
|
this.#bindings = new VBindings({
|
9598
|
-
onChange: (
|
9687
|
+
onChange: (identifier) => {
|
9599
9688
|
this.#scheduleUpdate();
|
9600
9689
|
}
|
9601
9690
|
});
|
@@ -9659,6 +9748,15 @@ class VApplication {
|
|
9659
9748
|
}
|
9660
9749
|
const computed = new Set();
|
9661
9750
|
const processing = new Set();
|
9751
|
+
// Gather all changed identifiers, including parent properties for array items
|
9752
|
+
const allChanges = new Set();
|
9753
|
+
this.#bindings?.changes.forEach(id => {
|
9754
|
+
allChanges.add(id);
|
9755
|
+
const idx = id.indexOf('[');
|
9756
|
+
if (idx !== -1) {
|
9757
|
+
allChanges.add(id.substring(0, idx));
|
9758
|
+
}
|
9759
|
+
});
|
9662
9760
|
// Helper function to recursively compute a property
|
9663
9761
|
const compute = (key) => {
|
9664
9762
|
// Skip if already computed in this update cycle
|
@@ -9674,7 +9772,7 @@ class VApplication {
|
|
9674
9772
|
// Get the dependencies for this computed property
|
9675
9773
|
const deps = this.#computedDependencies[key] || [];
|
9676
9774
|
// If none of the dependencies have changed, skip recomputation
|
9677
|
-
if (!deps.some(dep =>
|
9775
|
+
if (!deps.some(dep => allChanges.has(dep))) {
|
9678
9776
|
computed.add(key);
|
9679
9777
|
return;
|
9680
9778
|
}
|
@@ -9692,7 +9790,13 @@ class VApplication {
|
|
9692
9790
|
// Track if the computed value actually changed
|
9693
9791
|
if (oldValue !== newValue) {
|
9694
9792
|
this.#bindings?.set(key, newValue);
|
9695
|
-
this.#
|
9793
|
+
this.#bindings?.changes.forEach(id => {
|
9794
|
+
allChanges.add(id);
|
9795
|
+
const idx = id.indexOf('[');
|
9796
|
+
if (idx !== -1) {
|
9797
|
+
allChanges.add(id.substring(0, idx));
|
9798
|
+
}
|
9799
|
+
});
|
9696
9800
|
}
|
9697
9801
|
}
|
9698
9802
|
catch (error) {
|
@@ -9704,7 +9808,7 @@ class VApplication {
|
|
9704
9808
|
// Find all computed properties that need to be recomputed
|
9705
9809
|
for (const [key, deps] of Object.entries(this.#computedDependencies)) {
|
9706
9810
|
// Check if any dependency has changed
|
9707
|
-
if (deps.some(dep =>
|
9811
|
+
if (deps.some(dep => allChanges.has(dep))) {
|
9708
9812
|
compute(key);
|
9709
9813
|
}
|
9710
9814
|
}
|