@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.umd.js
CHANGED
@@ -7050,34 +7050,46 @@
|
|
7050
7050
|
*/
|
7051
7051
|
class ReactiveProxy {
|
7052
7052
|
/**
|
7053
|
-
* A WeakMap to store the
|
7054
|
-
* This
|
7053
|
+
* A WeakMap to store the proxy for each target object and path combination.
|
7054
|
+
* This prevents creating multiple proxies for the same object accessed from different paths.
|
7055
7055
|
*/
|
7056
|
-
static
|
7056
|
+
static proxyCache = new WeakMap();
|
7057
7057
|
/**
|
7058
7058
|
* Creates a reactive proxy for the given object.
|
7059
7059
|
* The proxy will call the onChange callback whenever a property is modified.
|
7060
7060
|
*
|
7061
7061
|
* @param target The object to make reactive.
|
7062
|
-
* @param onChange Callback function to call when the object changes. Receives the changed
|
7062
|
+
* @param onChange Callback function to call when the object changes. Receives the full path of the changed property.
|
7063
|
+
* @param path The current path in the object tree (used internally for nested objects).
|
7063
7064
|
* @returns A reactive proxy of the target object.
|
7064
7065
|
*/
|
7065
|
-
static create(target, onChange) {
|
7066
|
+
static create(target, onChange, path = '') {
|
7066
7067
|
// If the target is not an object or is null, return it as-is
|
7067
7068
|
if (typeof target !== 'object' || target === null) {
|
7068
7069
|
return target;
|
7069
7070
|
}
|
7070
|
-
//
|
7071
|
-
|
7072
|
-
|
7071
|
+
// Check if we already have a proxy for this target with this path
|
7072
|
+
let pathMap = this.proxyCache.get(target);
|
7073
|
+
if (pathMap) {
|
7074
|
+
const existingProxy = pathMap.get(path);
|
7075
|
+
if (existingProxy) {
|
7076
|
+
return existingProxy;
|
7077
|
+
}
|
7078
|
+
}
|
7079
|
+
else {
|
7080
|
+
pathMap = new Map();
|
7081
|
+
this.proxyCache.set(target, pathMap);
|
7073
7082
|
}
|
7074
|
-
// Create the proxy
|
7083
|
+
// Create the proxy with path captured in closure
|
7075
7084
|
const proxy = new Proxy(target, {
|
7076
7085
|
get(obj, key) {
|
7077
7086
|
const value = Reflect.get(obj, key);
|
7078
7087
|
// If the value is an object or array, make it reactive too
|
7079
7088
|
if (typeof value === 'object' && value !== null) {
|
7080
|
-
|
7089
|
+
// Build the nested path
|
7090
|
+
const keyStr = String(key);
|
7091
|
+
const nestedPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7092
|
+
return ReactiveProxy.create(value, onChange, nestedPath);
|
7081
7093
|
}
|
7082
7094
|
// For arrays, intercept mutation methods
|
7083
7095
|
if (Array.isArray(obj) && typeof value === 'function') {
|
@@ -7085,7 +7097,7 @@
|
|
7085
7097
|
if (arrayMutationMethods.includes(key)) {
|
7086
7098
|
return function (...args) {
|
7087
7099
|
const result = value.apply(this, args);
|
7088
|
-
onChange();
|
7100
|
+
onChange(path || undefined);
|
7089
7101
|
return result;
|
7090
7102
|
};
|
7091
7103
|
}
|
@@ -7097,18 +7109,22 @@
|
|
7097
7109
|
const result = Reflect.set(obj, key, value);
|
7098
7110
|
// Only trigger onChange if the value actually changed
|
7099
7111
|
if (oldValue !== value) {
|
7100
|
-
|
7112
|
+
const keyStr = String(key);
|
7113
|
+
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7114
|
+
onChange(fullPath);
|
7101
7115
|
}
|
7102
7116
|
return result;
|
7103
7117
|
},
|
7104
7118
|
deleteProperty(obj, key) {
|
7105
7119
|
const result = Reflect.deleteProperty(obj, key);
|
7106
|
-
|
7120
|
+
const keyStr = String(key);
|
7121
|
+
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7122
|
+
onChange(fullPath);
|
7107
7123
|
return result;
|
7108
7124
|
}
|
7109
7125
|
});
|
7110
|
-
//
|
7111
|
-
|
7126
|
+
// Cache the proxy for this path
|
7127
|
+
pathMap.set(path, proxy);
|
7112
7128
|
return proxy;
|
7113
7129
|
}
|
7114
7130
|
/**
|
@@ -7118,7 +7134,7 @@
|
|
7118
7134
|
* @returns True if the object is a reactive proxy, false otherwise.
|
7119
7135
|
*/
|
7120
7136
|
static isReactive(obj) {
|
7121
|
-
return this.
|
7137
|
+
return this.proxyCache.has(obj);
|
7122
7138
|
}
|
7123
7139
|
/**
|
7124
7140
|
* Unwraps a reactive proxy to get the original object.
|
@@ -7157,6 +7173,10 @@
|
|
7157
7173
|
* The set of changed identifiers.
|
7158
7174
|
*/
|
7159
7175
|
#changes = new Set();
|
7176
|
+
/**
|
7177
|
+
* Cache for array lengths to detect length changes when the same object reference is used.
|
7178
|
+
*/
|
7179
|
+
#lengthCache = new Map();
|
7160
7180
|
/**
|
7161
7181
|
* Creates a new instance of VBindings.
|
7162
7182
|
* @param parent The parent bindings, if any.
|
@@ -7184,14 +7204,29 @@
|
|
7184
7204
|
let newValue = value;
|
7185
7205
|
if (typeof value === 'object' && value !== null) {
|
7186
7206
|
// Wrap objects/arrays with reactive proxy, tracking the root key
|
7187
|
-
newValue = ReactiveProxy.create(value, () => {
|
7188
|
-
|
7189
|
-
|
7190
|
-
|
7207
|
+
newValue = ReactiveProxy.create(value, (changedPath) => {
|
7208
|
+
let path = '';
|
7209
|
+
for (const part of changedPath?.split('.') || []) {
|
7210
|
+
path = path ? `${path}.${part}` : part;
|
7211
|
+
this.#changes.add(path);
|
7212
|
+
}
|
7213
|
+
this.#onChange?.(changedPath);
|
7214
|
+
}, key);
|
7191
7215
|
}
|
7192
7216
|
const oldValue = Reflect.get(target, key);
|
7193
7217
|
const result = Reflect.set(target, key, newValue);
|
7194
|
-
|
7218
|
+
// Detect changes
|
7219
|
+
let hasChanged = oldValue !== newValue;
|
7220
|
+
// Special handling for arrays: check length changes even if same object reference
|
7221
|
+
if (!hasChanged && Array.isArray(newValue)) {
|
7222
|
+
const cachedLength = this.#lengthCache.get(key);
|
7223
|
+
const currentLength = newValue.length;
|
7224
|
+
if (cachedLength !== undefined && cachedLength !== currentLength) {
|
7225
|
+
hasChanged = true;
|
7226
|
+
}
|
7227
|
+
this.#lengthCache.set(key, currentLength);
|
7228
|
+
}
|
7229
|
+
if (hasChanged) {
|
7195
7230
|
this.#changes.add(key);
|
7196
7231
|
this.#onChange?.(key);
|
7197
7232
|
}
|
@@ -7547,6 +7582,11 @@
|
|
7547
7582
|
* The data bindings associated with this virtual node, if any.
|
7548
7583
|
*/
|
7549
7584
|
#bindings;
|
7585
|
+
/**
|
7586
|
+
* The initial set of identifiers that this node depends on.
|
7587
|
+
* This is optional and may be undefined if there are no dependent identifiers.
|
7588
|
+
*/
|
7589
|
+
#initDependentIdentifiers;
|
7550
7590
|
/**
|
7551
7591
|
* An evaluator for text nodes that contain expressions in {{...}}.
|
7552
7592
|
* This is used to dynamically update the text content based on data bindings.
|
@@ -7591,6 +7631,7 @@
|
|
7591
7631
|
this.#nodeName = args.node.nodeName;
|
7592
7632
|
this.#parentVNode = args.parentVNode;
|
7593
7633
|
this.#bindings = args.bindings;
|
7634
|
+
this.#initDependentIdentifiers = args.dependentIdentifiers;
|
7594
7635
|
this.#parentVNode?.addChild(this);
|
7595
7636
|
// If the node is a text node, check for expressions and create a text evaluator
|
7596
7637
|
if (this.#nodeType === Node.TEXT_NODE) {
|
@@ -7742,6 +7783,8 @@
|
|
7742
7783
|
}
|
7743
7784
|
// Collect identifiers from text evaluator and directives
|
7744
7785
|
const ids = [];
|
7786
|
+
// Include initial dependent identifiers, if any
|
7787
|
+
ids.push(...this.#initDependentIdentifiers ?? []);
|
7745
7788
|
// If this is a text node with a text evaluator, include its identifiers
|
7746
7789
|
if (this.#textEvaluator) {
|
7747
7790
|
ids.push(...this.#textEvaluator.identifiers);
|
@@ -7773,6 +7816,29 @@
|
|
7773
7816
|
this.#preparableIdentifiers = preparableIdentifiers.length === 0 ? [] : [...new Set(preparableIdentifiers)];
|
7774
7817
|
return this.#preparableIdentifiers;
|
7775
7818
|
}
|
7819
|
+
/**
|
7820
|
+
* The DOM path of this virtual node.
|
7821
|
+
* This is a string representation of the path from the root to this node,
|
7822
|
+
* using the node names and their indices among siblings with the same name.
|
7823
|
+
* For example: "DIV[0]/SPAN[1]/#text[0]"
|
7824
|
+
* @return The DOM path as a string.
|
7825
|
+
*/
|
7826
|
+
get domPath() {
|
7827
|
+
const path = [];
|
7828
|
+
let node = this;
|
7829
|
+
while (node) {
|
7830
|
+
if (node.parentVNode && node.parentVNode.childVNodes) {
|
7831
|
+
const siblings = node.parentVNode.childVNodes.filter(v => v.nodeName === node?.nodeName);
|
7832
|
+
const index = siblings.indexOf(node);
|
7833
|
+
path.unshift(`${node.nodeName}[${index}]`);
|
7834
|
+
}
|
7835
|
+
else {
|
7836
|
+
path.unshift(node.nodeName);
|
7837
|
+
}
|
7838
|
+
node = node.parentVNode;
|
7839
|
+
}
|
7840
|
+
return path.join('/');
|
7841
|
+
}
|
7776
7842
|
/**
|
7777
7843
|
* Updates the virtual node and its children based on the current bindings.
|
7778
7844
|
* This method evaluates any expressions in text nodes and applies effectors from directives.
|
@@ -7869,24 +7935,46 @@
|
|
7869
7935
|
/**
|
7870
7936
|
* Adds a dependent virtual node that relies on this node's bindings.
|
7871
7937
|
* @param dependent The dependent virtual node to add.
|
7938
|
+
* @param dependentIdentifiers The identifiers that the dependent node relies on.
|
7939
|
+
* If not provided, the dependent node's own identifiers will be used.
|
7872
7940
|
* @returns A list of closers to unregister the dependency, or undefined if no dependency was added.
|
7873
7941
|
*/
|
7874
|
-
addDependent(dependent) {
|
7942
|
+
addDependent(dependent, dependentIdentifiers = undefined) {
|
7875
7943
|
// List of closers to unregister the dependency
|
7876
7944
|
const closers = [];
|
7877
|
-
//
|
7878
|
-
|
7879
|
-
|
7880
|
-
|
7945
|
+
// If dependent identifiers are not provided, use the dependent node's own identifiers
|
7946
|
+
if (!dependentIdentifiers) {
|
7947
|
+
dependentIdentifiers = [...dependent.dependentIdentifiers];
|
7948
|
+
}
|
7949
|
+
// Prepare alternative identifiers by stripping array indices (e.g., "items[0]" -> "items")
|
7950
|
+
const allDeps = new Set();
|
7951
|
+
dependentIdentifiers.forEach(id => {
|
7952
|
+
allDeps.add(id);
|
7953
|
+
const idx = id.indexOf('[');
|
7954
|
+
if (idx !== -1) {
|
7955
|
+
allDeps.add(id.substring(0, idx));
|
7956
|
+
}
|
7957
|
+
});
|
7958
|
+
// Get this node's identifiers
|
7959
|
+
const thisIds = [...this.preparableIdentifiers];
|
7960
|
+
if (this.#bindings) {
|
7961
|
+
thisIds.push(...this.#bindings?.raw ? Object.keys(this.#bindings.raw) : []);
|
7881
7962
|
}
|
7882
7963
|
// If the dependent node has an identifier in this node's identifiers, add it as a dependency
|
7883
|
-
if (
|
7964
|
+
if ([...allDeps].some(id => thisIds.includes(id))) {
|
7884
7965
|
// If the dependencies list is not initialized, create it
|
7885
7966
|
if (!this.#dependents) {
|
7886
7967
|
this.#dependents = [];
|
7887
7968
|
}
|
7888
7969
|
// Add the dependent node to the list
|
7889
7970
|
this.#dependents.push(dependent);
|
7971
|
+
// Remove the matched identifiers from the dependent node's identifiers to avoid duplicate dependencies
|
7972
|
+
thisIds.forEach(id => {
|
7973
|
+
const idx = dependentIdentifiers.indexOf(id);
|
7974
|
+
if (idx !== -1) {
|
7975
|
+
dependentIdentifiers.splice(idx, 1);
|
7976
|
+
}
|
7977
|
+
});
|
7890
7978
|
// Create a closer to unregister the dependency
|
7891
7979
|
closers.push({
|
7892
7980
|
close: () => {
|
@@ -7899,7 +7987,7 @@
|
|
7899
7987
|
});
|
7900
7988
|
}
|
7901
7989
|
// Recursively add the dependency to the parent node, if any
|
7902
|
-
this.#parentVNode?.addDependent(dependent)?.forEach(closer => closers.push(closer));
|
7990
|
+
this.#parentVNode?.addDependent(dependent, dependentIdentifiers)?.forEach(closer => closers.push(closer));
|
7903
7991
|
// Return a closer to unregister the dependency
|
7904
7992
|
return closers.length > 0 ? closers : undefined;
|
7905
7993
|
}
|
@@ -8571,7 +8659,8 @@
|
|
8571
8659
|
node: clone,
|
8572
8660
|
vApplication: this.#vNode.vApplication,
|
8573
8661
|
parentVNode: this.#vNode.parentVNode,
|
8574
|
-
bindings
|
8662
|
+
bindings,
|
8663
|
+
dependentIdentifiers: [`${this.#sourceName}[${context.index}]`]
|
8575
8664
|
});
|
8576
8665
|
return vNode;
|
8577
8666
|
}
|
@@ -9601,7 +9690,7 @@
|
|
9601
9690
|
#initializeBindings() {
|
9602
9691
|
// Create bindings with change tracking
|
9603
9692
|
this.#bindings = new VBindings({
|
9604
|
-
onChange: (
|
9693
|
+
onChange: (identifier) => {
|
9605
9694
|
this.#scheduleUpdate();
|
9606
9695
|
}
|
9607
9696
|
});
|
@@ -9665,6 +9754,15 @@
|
|
9665
9754
|
}
|
9666
9755
|
const computed = new Set();
|
9667
9756
|
const processing = new Set();
|
9757
|
+
// Gather all changed identifiers, including parent properties for array items
|
9758
|
+
const allChanges = new Set();
|
9759
|
+
this.#bindings?.changes.forEach(id => {
|
9760
|
+
allChanges.add(id);
|
9761
|
+
const idx = id.indexOf('[');
|
9762
|
+
if (idx !== -1) {
|
9763
|
+
allChanges.add(id.substring(0, idx));
|
9764
|
+
}
|
9765
|
+
});
|
9668
9766
|
// Helper function to recursively compute a property
|
9669
9767
|
const compute = (key) => {
|
9670
9768
|
// Skip if already computed in this update cycle
|
@@ -9680,7 +9778,7 @@
|
|
9680
9778
|
// Get the dependencies for this computed property
|
9681
9779
|
const deps = this.#computedDependencies[key] || [];
|
9682
9780
|
// If none of the dependencies have changed, skip recomputation
|
9683
|
-
if (!deps.some(dep =>
|
9781
|
+
if (!deps.some(dep => allChanges.has(dep))) {
|
9684
9782
|
computed.add(key);
|
9685
9783
|
return;
|
9686
9784
|
}
|
@@ -9698,7 +9796,13 @@
|
|
9698
9796
|
// Track if the computed value actually changed
|
9699
9797
|
if (oldValue !== newValue) {
|
9700
9798
|
this.#bindings?.set(key, newValue);
|
9701
|
-
this.#
|
9799
|
+
this.#bindings?.changes.forEach(id => {
|
9800
|
+
allChanges.add(id);
|
9801
|
+
const idx = id.indexOf('[');
|
9802
|
+
if (idx !== -1) {
|
9803
|
+
allChanges.add(id.substring(0, idx));
|
9804
|
+
}
|
9805
|
+
});
|
9702
9806
|
}
|
9703
9807
|
}
|
9704
9808
|
catch (error) {
|
@@ -9710,7 +9814,7 @@
|
|
9710
9814
|
// Find all computed properties that need to be recomputed
|
9711
9815
|
for (const [key, deps] of Object.entries(this.#computedDependencies)) {
|
9712
9816
|
// Check if any dependency has changed
|
9713
|
-
if (deps.some(dep =>
|
9817
|
+
if (deps.some(dep => allChanges.has(dep))) {
|
9714
9818
|
compute(key);
|
9715
9819
|
}
|
9716
9820
|
}
|