@mintjamsinc/ichigojs 0.1.2 → 0.1.4
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 +75 -1
- package/dist/ichigo.esm.js +507 -84
- 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 +507 -84
- 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/VBindingsInit.d.ts +5 -0
- package/dist/types/ichigo/directives/VBindDirective.d.ts +24 -0
- package/dist/types/ichigo/directives/VConditionalDirective.d.ts +24 -0
- package/dist/types/ichigo/directives/VDirective.d.ts +30 -0
- package/dist/types/ichigo/directives/VForDirective.d.ts +24 -0
- package/dist/types/ichigo/directives/VModelDirective.d.ts +24 -0
- package/dist/types/ichigo/directives/VOnDirective.d.ts +35 -2
- package/dist/types/ichigo/directives/VShowDirective.d.ts +24 -0
- package/dist/types/ichigo/util/VLogManager.d.ts +15 -0
- package/dist/types/ichigo/util/VLogger.d.ts +31 -0
- package/package.json +1 -1
package/dist/ichigo.esm.js
CHANGED
@@ -6873,6 +6873,42 @@ class VBindDirective {
|
|
6873
6873
|
get expression() {
|
6874
6874
|
return this.#expression;
|
6875
6875
|
}
|
6876
|
+
/**
|
6877
|
+
* @inheritdoc
|
6878
|
+
*/
|
6879
|
+
get onMount() {
|
6880
|
+
return undefined;
|
6881
|
+
}
|
6882
|
+
/**
|
6883
|
+
* @inheritdoc
|
6884
|
+
*/
|
6885
|
+
get onMounted() {
|
6886
|
+
return undefined;
|
6887
|
+
}
|
6888
|
+
/**
|
6889
|
+
* @inheritdoc
|
6890
|
+
*/
|
6891
|
+
get onUpdate() {
|
6892
|
+
return undefined;
|
6893
|
+
}
|
6894
|
+
/**
|
6895
|
+
* @inheritdoc
|
6896
|
+
*/
|
6897
|
+
get onUpdated() {
|
6898
|
+
return undefined;
|
6899
|
+
}
|
6900
|
+
/**
|
6901
|
+
* @inheritdoc
|
6902
|
+
*/
|
6903
|
+
get onUnmount() {
|
6904
|
+
return undefined;
|
6905
|
+
}
|
6906
|
+
/**
|
6907
|
+
* @inheritdoc
|
6908
|
+
*/
|
6909
|
+
get onUnmounted() {
|
6910
|
+
return undefined;
|
6911
|
+
}
|
6876
6912
|
/**
|
6877
6913
|
* @inheritdoc
|
6878
6914
|
*/
|
@@ -7163,6 +7199,10 @@ class VBindings {
|
|
7163
7199
|
* The change tracker, if any.
|
7164
7200
|
*/
|
7165
7201
|
#onChange;
|
7202
|
+
/**
|
7203
|
+
* The logger instance.
|
7204
|
+
*/
|
7205
|
+
#logger;
|
7166
7206
|
/**
|
7167
7207
|
* The set of changed identifiers.
|
7168
7208
|
*/
|
@@ -7178,6 +7218,10 @@ class VBindings {
|
|
7178
7218
|
constructor(args = {}) {
|
7179
7219
|
this.#parent = args.parent;
|
7180
7220
|
this.#onChange = args.onChange;
|
7221
|
+
this.#logger = args.vApplication?.logManager.getLogger('VBindings');
|
7222
|
+
if (this.#logger?.isDebugEnabled) {
|
7223
|
+
this.#logger.debug(`VBindings created. Parent: ${this.#parent ? 'yes' : 'no'}`);
|
7224
|
+
}
|
7181
7225
|
this.#local = new Proxy({}, {
|
7182
7226
|
get: (obj, key) => {
|
7183
7227
|
if (Reflect.has(obj, key)) {
|
@@ -7202,6 +7246,7 @@ class VBindings {
|
|
7202
7246
|
let path = '';
|
7203
7247
|
for (const part of changedPath?.split('.') || []) {
|
7204
7248
|
path = path ? `${path}.${part}` : part;
|
7249
|
+
this.#logger?.debug(`Binding changed: ${path}`);
|
7205
7250
|
this.#changes.add(path);
|
7206
7251
|
}
|
7207
7252
|
this.#onChange?.(changedPath);
|
@@ -7212,15 +7257,22 @@ class VBindings {
|
|
7212
7257
|
// Detect changes
|
7213
7258
|
let hasChanged = oldValue !== newValue;
|
7214
7259
|
// Special handling for arrays: check length changes even if same object reference
|
7215
|
-
if (
|
7260
|
+
if (Array.isArray(newValue)) {
|
7216
7261
|
const cachedLength = this.#lengthCache.get(key);
|
7217
7262
|
const currentLength = newValue.length;
|
7218
|
-
if (cachedLength !== undefined && cachedLength !== currentLength) {
|
7263
|
+
if (!hasChanged && cachedLength !== undefined && cachedLength !== currentLength) {
|
7219
7264
|
hasChanged = true;
|
7220
7265
|
}
|
7221
7266
|
this.#lengthCache.set(key, currentLength);
|
7222
7267
|
}
|
7223
7268
|
if (hasChanged) {
|
7269
|
+
if (this.#logger?.isDebugEnabled) {
|
7270
|
+
const oldValueString = typeof oldValue === 'string' ? `"${oldValue}"` : JSON.stringify(oldValue) || 'undefined';
|
7271
|
+
const newValueString = typeof newValue === 'string' ? `"${newValue}"` : JSON.stringify(newValue) || 'undefined';
|
7272
|
+
const oldValuePreview = oldValueString.length > 100 ? `${oldValueString.substring(0, 100)}...` : oldValueString;
|
7273
|
+
const newValuePreview = newValueString.length > 100 ? `${newValueString.substring(0, 100)}...` : newValueString;
|
7274
|
+
this.#logger.debug(`Binding set on ${target === obj ? 'local' : 'parent'}: ${key}: ${oldValuePreview} -> ${newValuePreview}`);
|
7275
|
+
}
|
7224
7276
|
this.#changes.add(key);
|
7225
7277
|
this.#onChange?.(key);
|
7226
7278
|
}
|
@@ -7228,6 +7280,7 @@ class VBindings {
|
|
7228
7280
|
},
|
7229
7281
|
deleteProperty: (obj, key) => {
|
7230
7282
|
const result = Reflect.deleteProperty(obj, key);
|
7283
|
+
this.#logger?.debug(`Binding deleted: ${key}`);
|
7231
7284
|
this.#changes.add(key);
|
7232
7285
|
this.#onChange?.(key);
|
7233
7286
|
return result;
|
@@ -7614,6 +7667,11 @@ class VNode {
|
|
7614
7667
|
* This is optional and may be undefined if there are no dependencies.
|
7615
7668
|
*/
|
7616
7669
|
#closers;
|
7670
|
+
/**
|
7671
|
+
* Indicates whether this node has been templatized by a directive.
|
7672
|
+
* This is optional and may be undefined if the node has not been templatized.
|
7673
|
+
*/
|
7674
|
+
#templatized;
|
7617
7675
|
/**
|
7618
7676
|
* Creates an instance of the virtual node.
|
7619
7677
|
* @param args The initialization arguments for the virtual node.
|
@@ -7627,23 +7685,31 @@ class VNode {
|
|
7627
7685
|
this.#bindings = args.bindings;
|
7628
7686
|
this.#initDependentIdentifiers = args.dependentIdentifiers;
|
7629
7687
|
this.#parentVNode?.addChild(this);
|
7630
|
-
// If the node is a text node, check for expressions and create a text evaluator
|
7631
7688
|
if (this.#nodeType === Node.TEXT_NODE) {
|
7689
|
+
// If the node is a text node, check for expressions and create a text evaluator
|
7632
7690
|
const text = this.#node;
|
7633
7691
|
// Create a text evaluator if the text contains expressions
|
7634
7692
|
if (VTextEvaluator.containsExpression(text.data)) {
|
7635
7693
|
this.#textEvaluator = new VTextEvaluator(text.data, this.#vApplication.functionDependencies);
|
7636
7694
|
}
|
7637
7695
|
}
|
7638
|
-
|
7639
|
-
|
7696
|
+
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
7697
|
+
// If the node is an element, initialize directives and child nodes
|
7640
7698
|
this.#node;
|
7641
7699
|
// Initialize child virtual nodes
|
7642
7700
|
this.#childVNodes = [];
|
7643
7701
|
// Initialize directive manager
|
7644
7702
|
this.#directiveManager = new VDirectiveManager(this);
|
7645
|
-
//
|
7646
|
-
|
7703
|
+
// Determine if any directive requires template preservation
|
7704
|
+
this.#templatized = this.#directiveManager.directives?.some(d => d.templatize) ?? false;
|
7705
|
+
// If no directive requires template preservation, call onMount for directives that do not templatize
|
7706
|
+
if (!this.#templatized) {
|
7707
|
+
this.#directiveManager.directives?.forEach(d => {
|
7708
|
+
d.onMount?.();
|
7709
|
+
});
|
7710
|
+
}
|
7711
|
+
// Create child virtual nodes if template preservation is not required
|
7712
|
+
if (!this.#templatized) {
|
7647
7713
|
for (const childNode of Array.from(this.#node.childNodes)) {
|
7648
7714
|
new VNode({
|
7649
7715
|
node: childNode,
|
@@ -7652,11 +7718,18 @@ class VNode {
|
|
7652
7718
|
});
|
7653
7719
|
}
|
7654
7720
|
}
|
7721
|
+
// After creating child nodes, call onMounted for directives that do not templatize
|
7722
|
+
if (!this.#templatized) {
|
7723
|
+
// animation frame to ensure DOM is updated
|
7724
|
+
requestAnimationFrame(() => {
|
7725
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7726
|
+
d.onMounted?.();
|
7727
|
+
});
|
7728
|
+
});
|
7729
|
+
}
|
7655
7730
|
}
|
7656
|
-
//
|
7657
|
-
|
7658
|
-
this.#closers = this.#parentVNode.addDependent(this);
|
7659
|
-
}
|
7731
|
+
// Register this node as a dependent of the parent node, if any
|
7732
|
+
this.#closers = this.#parentVNode?.addDependent(this);
|
7660
7733
|
}
|
7661
7734
|
/**
|
7662
7735
|
* The application instance associated with this virtual node.
|
@@ -7842,8 +7915,8 @@ class VNode {
|
|
7842
7915
|
*/
|
7843
7916
|
update() {
|
7844
7917
|
const changes = this.bindings?.changes || [];
|
7845
|
-
// If this is a text node with a text evaluator, update its content if needed
|
7846
7918
|
if (this.#nodeType === Node.TEXT_NODE && this.#textEvaluator) {
|
7919
|
+
// If this is a text node with a text evaluator, update its content if needed
|
7847
7920
|
// Check if any of the identifiers are in the changed identifiers
|
7848
7921
|
const changed = this.#textEvaluator.identifiers.some(id => changes.includes(id));
|
7849
7922
|
// If the text node has changed, update its content
|
@@ -7851,38 +7924,52 @@ class VNode {
|
|
7851
7924
|
const text = this.#node;
|
7852
7925
|
text.data = this.#textEvaluator.evaluate(this.bindings);
|
7853
7926
|
}
|
7854
|
-
return;
|
7855
7927
|
}
|
7856
|
-
|
7857
|
-
|
7858
|
-
//
|
7859
|
-
if (!this.#
|
7860
|
-
this.#
|
7928
|
+
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
7929
|
+
// If this is an element node, update directives and child nodes
|
7930
|
+
// If no directive requires template preservation, call onUpdate for directives that do not templatize
|
7931
|
+
if (!this.#templatized) {
|
7932
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7933
|
+
d.onUpdate?.();
|
7934
|
+
});
|
7861
7935
|
}
|
7862
|
-
// Prepare bindings
|
7863
|
-
|
7864
|
-
|
7865
|
-
if (
|
7866
|
-
|
7936
|
+
// Prepare new bindings using directive bindings preparers, if any
|
7937
|
+
if (this.#directiveManager?.bindingsPreparers) {
|
7938
|
+
// Ensure local bindings are initialized
|
7939
|
+
if (!this.#bindings) {
|
7940
|
+
this.#bindings = new VBindings({ parent: this.bindings });
|
7941
|
+
}
|
7942
|
+
// Prepare bindings for each preparer if relevant identifiers have changed
|
7943
|
+
for (const preparer of this.#directiveManager.bindingsPreparers) {
|
7944
|
+
const changed = preparer.dependentIdentifiers.some(id => changes.includes(id));
|
7945
|
+
if (changed) {
|
7946
|
+
preparer.prepareBindings();
|
7947
|
+
}
|
7867
7948
|
}
|
7868
7949
|
}
|
7869
|
-
|
7870
|
-
|
7871
|
-
|
7872
|
-
|
7873
|
-
|
7950
|
+
// Apply DOM updaters from directives, if any
|
7951
|
+
if (this.#directiveManager?.domUpdaters) {
|
7952
|
+
for (const updater of this.#directiveManager.domUpdaters) {
|
7953
|
+
const changed = updater.dependentIdentifiers.some(id => changes.includes(id));
|
7954
|
+
if (changed) {
|
7955
|
+
updater.applyToDOM();
|
7956
|
+
}
|
7957
|
+
}
|
7958
|
+
}
|
7959
|
+
// Recursively update dependent virtual nodes
|
7960
|
+
this.#dependents?.forEach(dependentNode => {
|
7961
|
+
const changed = dependentNode.dependentIdentifiers.some(id => changes.includes(id));
|
7874
7962
|
if (changed) {
|
7875
|
-
|
7963
|
+
dependentNode.update();
|
7876
7964
|
}
|
7965
|
+
});
|
7966
|
+
// If no directive requires template preservation, call onUpdated for directives that do not templatize
|
7967
|
+
if (!this.#templatized) {
|
7968
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7969
|
+
d.onUpdated?.();
|
7970
|
+
});
|
7877
7971
|
}
|
7878
7972
|
}
|
7879
|
-
// Recursively update dependent virtual nodes
|
7880
|
-
this.#dependents?.forEach(dependentNode => {
|
7881
|
-
const changed = dependentNode.dependentIdentifiers.some(id => changes.includes(id));
|
7882
|
-
if (changed) {
|
7883
|
-
dependentNode.update();
|
7884
|
-
}
|
7885
|
-
});
|
7886
7973
|
}
|
7887
7974
|
/**
|
7888
7975
|
* Forces an update of the virtual node and its children, regardless of changed identifiers.
|
@@ -7891,33 +7978,47 @@ class VNode {
|
|
7891
7978
|
* This is useful when an immediate update is needed, bypassing the usual change detection.
|
7892
7979
|
*/
|
7893
7980
|
forceUpdate() {
|
7894
|
-
// If this is a text node with a text evaluator, update its content if needed
|
7895
7981
|
if (this.#nodeType === Node.TEXT_NODE && this.#textEvaluator) {
|
7982
|
+
// If this is a text node with a text evaluator, update its content if needed
|
7896
7983
|
const text = this.#node;
|
7897
7984
|
text.data = this.#textEvaluator.evaluate(this.bindings);
|
7898
|
-
return;
|
7899
7985
|
}
|
7900
|
-
|
7901
|
-
|
7902
|
-
//
|
7903
|
-
if (!this.#
|
7904
|
-
this.#
|
7986
|
+
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
7987
|
+
// If this is an element node, update directives and child nodes
|
7988
|
+
// If no directive requires template preservation, call onUpdate for directives that do not templatize
|
7989
|
+
if (!this.#templatized) {
|
7990
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7991
|
+
d.onUpdate?.();
|
7992
|
+
});
|
7993
|
+
}
|
7994
|
+
// Prepare new bindings using directive bindings preparers, if any
|
7995
|
+
if (this.#directiveManager?.bindingsPreparers) {
|
7996
|
+
// Ensure local bindings are initialized
|
7997
|
+
if (!this.#bindings) {
|
7998
|
+
this.#bindings = new VBindings({ parent: this.bindings });
|
7999
|
+
}
|
8000
|
+
// Prepare bindings for each preparer if relevant identifiers have changed
|
8001
|
+
for (const preparer of this.#directiveManager.bindingsPreparers) {
|
8002
|
+
preparer.prepareBindings();
|
8003
|
+
}
|
7905
8004
|
}
|
7906
|
-
//
|
7907
|
-
|
7908
|
-
|
8005
|
+
// Apply DOM updaters from directives, if any
|
8006
|
+
if (this.#directiveManager?.domUpdaters) {
|
8007
|
+
for (const updater of this.#directiveManager.domUpdaters) {
|
8008
|
+
updater.applyToDOM();
|
8009
|
+
}
|
7909
8010
|
}
|
7910
|
-
|
7911
|
-
|
7912
|
-
|
7913
|
-
|
7914
|
-
|
8011
|
+
// Recursively update child virtual nodes
|
8012
|
+
this.#childVNodes?.forEach(childVNode => {
|
8013
|
+
childVNode.forceUpdate();
|
8014
|
+
});
|
8015
|
+
// If no directive requires template preservation, call onUpdated for directives that do not templatize
|
8016
|
+
if (!this.#templatized) {
|
8017
|
+
this.#directiveManager?.directives?.forEach(d => {
|
8018
|
+
d.onUpdated?.();
|
8019
|
+
});
|
7915
8020
|
}
|
7916
8021
|
}
|
7917
|
-
// Recursively update child virtual nodes
|
7918
|
-
this.#childVNodes?.forEach(childVNode => {
|
7919
|
-
childVNode.forceUpdate();
|
7920
|
-
});
|
7921
8022
|
}
|
7922
8023
|
/**
|
7923
8024
|
* Adds a child virtual node to this virtual node.
|
@@ -7990,6 +8091,12 @@ class VNode {
|
|
7990
8091
|
* This method is called when the virtual node is no longer needed.
|
7991
8092
|
*/
|
7992
8093
|
destroy() {
|
8094
|
+
// If no directive requires template preservation, call onUnmount for directives that do not templatize
|
8095
|
+
if (!this.#templatized) {
|
8096
|
+
this.#directiveManager?.directives?.forEach(d => {
|
8097
|
+
d.onUnmount?.();
|
8098
|
+
});
|
8099
|
+
}
|
7993
8100
|
// Recursively destroy child nodes
|
7994
8101
|
if (this.#childVNodes) {
|
7995
8102
|
for (const childVNode of this.#childVNodes) {
|
@@ -8014,6 +8121,12 @@ class VNode {
|
|
8014
8121
|
}
|
8015
8122
|
// Clean up directive handler
|
8016
8123
|
this.#directiveManager?.destroy();
|
8124
|
+
// If no directive requires template preservation, call onUnmounted for directives that do not templatize
|
8125
|
+
if (!this.#templatized) {
|
8126
|
+
this.#directiveManager?.directives?.forEach(d => {
|
8127
|
+
d.onUnmounted?.();
|
8128
|
+
});
|
8129
|
+
}
|
8017
8130
|
}
|
8018
8131
|
}
|
8019
8132
|
|
@@ -8181,6 +8294,42 @@ class VConditionalDirective {
|
|
8181
8294
|
}
|
8182
8295
|
return this.#evaluate();
|
8183
8296
|
}
|
8297
|
+
/**
|
8298
|
+
* @inheritdoc
|
8299
|
+
*/
|
8300
|
+
get onMount() {
|
8301
|
+
return undefined;
|
8302
|
+
}
|
8303
|
+
/**
|
8304
|
+
* @inheritdoc
|
8305
|
+
*/
|
8306
|
+
get onMounted() {
|
8307
|
+
return undefined;
|
8308
|
+
}
|
8309
|
+
/**
|
8310
|
+
* @inheritdoc
|
8311
|
+
*/
|
8312
|
+
get onUpdate() {
|
8313
|
+
return undefined;
|
8314
|
+
}
|
8315
|
+
/**
|
8316
|
+
* @inheritdoc
|
8317
|
+
*/
|
8318
|
+
get onUpdated() {
|
8319
|
+
return undefined;
|
8320
|
+
}
|
8321
|
+
/**
|
8322
|
+
* @inheritdoc
|
8323
|
+
*/
|
8324
|
+
get onUnmount() {
|
8325
|
+
return undefined;
|
8326
|
+
}
|
8327
|
+
/**
|
8328
|
+
* @inheritdoc
|
8329
|
+
*/
|
8330
|
+
get onUnmounted() {
|
8331
|
+
return undefined;
|
8332
|
+
}
|
8184
8333
|
/**
|
8185
8334
|
* @inheritdoc
|
8186
8335
|
*/
|
@@ -8235,8 +8384,10 @@ class VConditionalDirective {
|
|
8235
8384
|
// Not rendered, no action needed
|
8236
8385
|
return;
|
8237
8386
|
}
|
8238
|
-
|
8387
|
+
// Destroy VNode first (calls @unmount hooks while DOM is still accessible)
|
8239
8388
|
this.#renderedVNode.destroy();
|
8389
|
+
// Then remove from DOM
|
8390
|
+
this.#renderedVNode.node.parentNode?.removeChild(this.#renderedVNode.node);
|
8240
8391
|
this.#renderedVNode = undefined;
|
8241
8392
|
}
|
8242
8393
|
/**
|
@@ -8465,16 +8616,56 @@ class VForDirective {
|
|
8465
8616
|
get dependentIdentifiers() {
|
8466
8617
|
return this.#dependentIdentifiers ?? [];
|
8467
8618
|
}
|
8619
|
+
/**
|
8620
|
+
* @inheritdoc
|
8621
|
+
*/
|
8622
|
+
get onMount() {
|
8623
|
+
return undefined;
|
8624
|
+
}
|
8625
|
+
/**
|
8626
|
+
* @inheritdoc
|
8627
|
+
*/
|
8628
|
+
get onMounted() {
|
8629
|
+
return undefined;
|
8630
|
+
}
|
8631
|
+
/**
|
8632
|
+
* @inheritdoc
|
8633
|
+
*/
|
8634
|
+
get onUpdate() {
|
8635
|
+
return undefined;
|
8636
|
+
}
|
8637
|
+
/**
|
8638
|
+
* @inheritdoc
|
8639
|
+
*/
|
8640
|
+
get onUpdated() {
|
8641
|
+
return undefined;
|
8642
|
+
}
|
8643
|
+
/**
|
8644
|
+
* @inheritdoc
|
8645
|
+
*/
|
8646
|
+
get onUnmount() {
|
8647
|
+
return undefined;
|
8648
|
+
}
|
8649
|
+
/**
|
8650
|
+
* @inheritdoc
|
8651
|
+
*/
|
8652
|
+
get onUnmounted() {
|
8653
|
+
return undefined;
|
8654
|
+
}
|
8468
8655
|
/**
|
8469
8656
|
* @inheritdoc
|
8470
8657
|
*/
|
8471
8658
|
destroy() {
|
8472
8659
|
// Clean up all rendered items
|
8660
|
+
// First destroy all VNodes (calls @unmount hooks), then remove from DOM
|
8661
|
+
for (const vNode of this.#renderedItems.values()) {
|
8662
|
+
vNode.destroy();
|
8663
|
+
}
|
8664
|
+
// Then remove DOM nodes
|
8473
8665
|
for (const vNode of this.#renderedItems.values()) {
|
8474
8666
|
if (vNode.node.parentNode) {
|
8475
8667
|
vNode.node.parentNode.removeChild(vNode.node);
|
8476
8668
|
}
|
8477
|
-
vNode.destroy();
|
8478
8669
|
}
|
8479
8670
|
this.#renderedItems.clear();
|
8480
8671
|
this.#previousIterations = [];
|
@@ -8531,14 +8722,20 @@ class VForDirective {
|
|
8531
8722
|
// Track which keys are still needed
|
8532
8723
|
const neededKeys = new Set(newIterations.map(ctx => ctx.key));
|
8533
8724
|
// Remove items that are no longer needed
|
8725
|
+
// First destroy VNodes (calls @unmount hooks while DOM is still accessible)
|
8726
|
+
const nodesToRemove = [];
|
8534
8727
|
for (const [key, vNode] of this.#renderedItems) {
|
8535
8728
|
if (!neededKeys.has(key)) {
|
8536
|
-
|
8537
|
-
vNode.node.parentNode.removeChild(vNode.node);
|
8538
|
-
}
|
8729
|
+
nodesToRemove.push(vNode);
|
8539
8730
|
vNode.destroy();
|
8540
8731
|
}
|
8541
8732
|
}
|
8733
|
+
// Then remove from DOM
|
8734
|
+
for (const vNode of nodesToRemove) {
|
8735
|
+
if (vNode.node.parentNode) {
|
8736
|
+
vNode.node.parentNode.removeChild(vNode.node);
|
8737
|
+
}
|
8738
|
+
}
|
8542
8739
|
// Add or reorder items
|
8543
8740
|
let prevNode = anchor;
|
8544
8741
|
for (const context of newIterations) {
|
@@ -8851,6 +9048,42 @@ class VModelDirective {
|
|
8851
9048
|
get dependentIdentifiers() {
|
8852
9049
|
return this.#dependentIdentifiers ?? [];
|
8853
9050
|
}
|
9051
|
+
/**
|
9052
|
+
* @inheritdoc
|
9053
|
+
*/
|
9054
|
+
get onMount() {
|
9055
|
+
return undefined;
|
9056
|
+
}
|
9057
|
+
/**
|
9058
|
+
* @inheritdoc
|
9059
|
+
*/
|
9060
|
+
get onMounted() {
|
9061
|
+
return undefined;
|
9062
|
+
}
|
9063
|
+
/**
|
9064
|
+
* @inheritdoc
|
9065
|
+
*/
|
9066
|
+
get onUpdate() {
|
9067
|
+
return undefined;
|
9068
|
+
}
|
9069
|
+
/**
|
9070
|
+
* @inheritdoc
|
9071
|
+
*/
|
9072
|
+
get onUpdated() {
|
9073
|
+
return undefined;
|
9074
|
+
}
|
9075
|
+
/**
|
9076
|
+
* @inheritdoc
|
9077
|
+
*/
|
9078
|
+
get onUnmount() {
|
9079
|
+
return undefined;
|
9080
|
+
}
|
9081
|
+
/**
|
9082
|
+
* @inheritdoc
|
9083
|
+
*/
|
9084
|
+
get onUnmounted() {
|
9085
|
+
return undefined;
|
9086
|
+
}
|
8854
9087
|
/**
|
8855
9088
|
* @inheritdoc
|
8856
9089
|
*/
|
@@ -8941,10 +9174,13 @@ class VModelDirective {
|
|
8941
9174
|
}
|
8942
9175
|
// .number modifier: convert to number
|
8943
9176
|
if (this.#modifiers.has('number')) {
|
8944
|
-
|
8945
|
-
|
8946
|
-
|
8947
|
-
|
9177
|
+
// Skip conversion if the value is empty string
|
9178
|
+
if (result !== '') {
|
9179
|
+
const parsed = Number(result);
|
9180
|
+
// Only convert if it's a valid number
|
9181
|
+
if (!isNaN(parsed)) {
|
9182
|
+
result = parsed;
|
9183
|
+
}
|
8948
9184
|
}
|
8949
9185
|
}
|
8950
9186
|
return result;
|
@@ -9007,7 +9243,7 @@ class VModelDirective {
|
|
9007
9243
|
|
9008
9244
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9009
9245
|
/**
|
9010
|
-
* Directive for binding event listeners to DOM elements.
|
9246
|
+
* Directive for binding event listeners to DOM elements and lifecycle hooks.
|
9011
9247
|
* The `v-on` directive allows you to listen to DOM events and execute specified methods when those events are triggered.
|
9012
9248
|
* The syntax for using the `v-on` directive is `v-on:event="methodName"`, where `event` is the name of the event to listen for (e.g., `click`, `mouseover`, etc.), and `methodName` is the name of the method to be called when the event occurs.
|
9013
9249
|
* Example usage:
|
@@ -9016,7 +9252,16 @@ class VModelDirective {
|
|
9016
9252
|
* You can also use the shorthand `@event` instead of `v-on:event`. For example, `@click="handleClick"` is equivalent to `v-on:click="handleClick"`.
|
9017
9253
|
* The `v-on` directive supports event modifiers such as `.stop`, `.prevent`, `.capture`, `.self`, and `.once` to modify the behavior of the event listener.
|
9018
9254
|
* For example, `v-on:click.stop="handleClick"` will stop the event from propagating up the DOM tree.
|
9019
|
-
*
|
9255
|
+
*
|
9256
|
+
* Additionally, this directive supports lifecycle hooks:
|
9257
|
+
* @mount="onMount" - Called before the element is inserted into the DOM
|
9258
|
+
* @mounted="onMounted" - Called after the element is inserted into the DOM
|
9259
|
+
* @update="onUpdate" - Called before the element is updated
|
9260
|
+
* @updated="onUpdated" - Called after the element is updated
|
9261
|
+
* @unmount="onUnmount" - Called before the element is removed from the DOM
|
9262
|
+
* @unmounted="onUnmounted" - Called after the element is removed from the DOM
|
9263
|
+
*
|
9264
|
+
* This directive is essential for handling user interactions and lifecycle events in your application.
|
9020
9265
|
* Note that the methods referenced in the directive should be defined in the component's methods object.
|
9021
9266
|
*/
|
9022
9267
|
class VOnDirective {
|
@@ -9030,10 +9275,12 @@ class VOnDirective {
|
|
9030
9275
|
#dependentIdentifiers;
|
9031
9276
|
/**
|
9032
9277
|
* The event handler wrapper function, generated once and reused.
|
9278
|
+
* For lifecycle hooks, this is a no-argument function.
|
9279
|
+
* For DOM events, this accepts an Event parameter.
|
9033
9280
|
*/
|
9034
9281
|
#handlerWrapper;
|
9035
9282
|
/**
|
9036
|
-
* The event name (e.g., "click", "input", "keydown").
|
9283
|
+
* The event name (e.g., "click", "input", "keydown") or lifecycle hook name (e.g., "mount", "mounted").
|
9037
9284
|
*/
|
9038
9285
|
#eventName;
|
9039
9286
|
/**
|
@@ -9041,9 +9288,13 @@ class VOnDirective {
|
|
9041
9288
|
*/
|
9042
9289
|
#modifiers = new Set();
|
9043
9290
|
/**
|
9044
|
-
* The event listener function.
|
9291
|
+
* The event listener function for DOM events.
|
9045
9292
|
*/
|
9046
9293
|
#listener;
|
9294
|
+
/**
|
9295
|
+
* Map of lifecycle hook names to their handler functions.
|
9296
|
+
*/
|
9297
|
+
#lifecycleHooks = new Map();
|
9047
9298
|
/**
|
9048
9299
|
* @param context The context for parsing the directive.
|
9049
9300
|
*/
|
@@ -9067,10 +9318,22 @@ class VOnDirective {
|
|
9067
9318
|
const expression = context.attribute.value;
|
9068
9319
|
if (expression) {
|
9069
9320
|
this.#dependentIdentifiers = ExpressionUtils.extractIdentifiers(expression, context.vNode.vApplication.functionDependencies);
|
9070
|
-
this.#handlerWrapper = this.#createHandlerWrapper(expression);
|
9071
9321
|
}
|
9072
|
-
//
|
9073
|
-
if (this.#eventName) {
|
9322
|
+
// Check if this is a lifecycle hook or a regular event
|
9323
|
+
if (this.#eventName && this.#isLifecycleHook(this.#eventName)) {
|
9324
|
+
// Create handler wrapper for lifecycle hook (no event parameter)
|
9325
|
+
if (expression) {
|
9326
|
+
const handler = this.#createLifecycleHandlerWrapper(expression);
|
9327
|
+
this.#handlerWrapper = handler;
|
9328
|
+
this.#lifecycleHooks.set(this.#eventName, handler);
|
9329
|
+
}
|
9330
|
+
}
|
9331
|
+
else if (this.#eventName) {
|
9332
|
+
// Create handler wrapper for DOM event (with event parameter)
|
9333
|
+
if (expression) {
|
9334
|
+
this.#handlerWrapper = this.#createEventHandlerWrapper(expression);
|
9335
|
+
}
|
9336
|
+
// Create and attach DOM event listener
|
9074
9337
|
this.#attachEventListener();
|
9075
9338
|
}
|
9076
9339
|
// Remove the directive attribute from the element
|
@@ -9118,6 +9381,42 @@ class VOnDirective {
|
|
9118
9381
|
get dependentIdentifiers() {
|
9119
9382
|
return this.#dependentIdentifiers ?? [];
|
9120
9383
|
}
|
9384
|
+
/**
|
9385
|
+
* @inheritdoc
|
9386
|
+
*/
|
9387
|
+
get onMount() {
|
9388
|
+
return this.#lifecycleHooks.get('mount');
|
9389
|
+
}
|
9390
|
+
/**
|
9391
|
+
* @inheritdoc
|
9392
|
+
*/
|
9393
|
+
get onMounted() {
|
9394
|
+
return this.#lifecycleHooks.get('mounted');
|
9395
|
+
}
|
9396
|
+
/**
|
9397
|
+
* @inheritdoc
|
9398
|
+
*/
|
9399
|
+
get onUpdate() {
|
9400
|
+
return this.#lifecycleHooks.get('update');
|
9401
|
+
}
|
9402
|
+
/**
|
9403
|
+
* @inheritdoc
|
9404
|
+
*/
|
9405
|
+
get onUpdated() {
|
9406
|
+
return this.#lifecycleHooks.get('updated');
|
9407
|
+
}
|
9408
|
+
/**
|
9409
|
+
* @inheritdoc
|
9410
|
+
*/
|
9411
|
+
get onUnmount() {
|
9412
|
+
return this.#lifecycleHooks.get('unmount');
|
9413
|
+
}
|
9414
|
+
/**
|
9415
|
+
* @inheritdoc
|
9416
|
+
*/
|
9417
|
+
get onUnmounted() {
|
9418
|
+
return this.#lifecycleHooks.get('unmounted');
|
9419
|
+
}
|
9121
9420
|
/**
|
9122
9421
|
* @inheritdoc
|
9123
9422
|
*/
|
@@ -9194,11 +9493,48 @@ class VOnDirective {
|
|
9194
9493
|
element.addEventListener(eventName, this.#listener, useCapture);
|
9195
9494
|
}
|
9196
9495
|
/**
|
9197
|
-
*
|
9496
|
+
* Checks if the event name is a lifecycle hook.
|
9497
|
+
* @param eventName The event name to check.
|
9498
|
+
* @returns True if the event name is a lifecycle hook, false otherwise.
|
9499
|
+
*/
|
9500
|
+
#isLifecycleHook(eventName) {
|
9501
|
+
return ['mount', 'mounted', 'update', 'updated', 'unmount', 'unmounted'].includes(eventName);
|
9502
|
+
}
|
9503
|
+
/**
|
9504
|
+
* Creates a wrapper function for lifecycle hooks (with element parameter).
|
9505
|
+
* @param expression The expression string to evaluate.
|
9506
|
+
* @returns A function that handles the lifecycle hook.
|
9507
|
+
*/
|
9508
|
+
#createLifecycleHandlerWrapper(expression) {
|
9509
|
+
const identifiers = this.#dependentIdentifiers ?? [];
|
9510
|
+
const vNode = this.#vNode;
|
9511
|
+
// Return a function that handles the lifecycle hook with proper scope
|
9512
|
+
return () => {
|
9513
|
+
const bindings = vNode.bindings;
|
9514
|
+
const el = vNode.node;
|
9515
|
+
// If the expression is just a method name, call it with bindings as 'this'
|
9516
|
+
const trimmedExpr = expression.trim();
|
9517
|
+
if (identifiers.includes(trimmedExpr) && typeof bindings?.get(trimmedExpr) === 'function') {
|
9518
|
+
const methodName = trimmedExpr;
|
9519
|
+
const originalMethod = bindings?.get(methodName);
|
9520
|
+
// Call the method with bindings as 'this' context and element as parameter
|
9521
|
+
// This allows the method to access the DOM element and bindings
|
9522
|
+
return originalMethod(el);
|
9523
|
+
}
|
9524
|
+
// For inline expressions, evaluate normally with element parameter
|
9525
|
+
const values = identifiers.map(id => vNode.bindings?.get(id));
|
9526
|
+
const args = [...identifiers, 'el'].join(", ");
|
9527
|
+
const funcBody = `return (${expression});`;
|
9528
|
+
const func = new Function(args, funcBody);
|
9529
|
+
return func.call(bindings?.raw, ...values, el);
|
9530
|
+
};
|
9531
|
+
}
|
9532
|
+
/**
|
9533
|
+
* Creates a wrapper function for DOM event handlers (with event parameter).
|
9198
9534
|
* @param expression The expression string to evaluate.
|
9199
9535
|
* @returns A function that handles the event.
|
9200
9536
|
*/
|
9201
|
-
#
|
9537
|
+
#createEventHandlerWrapper(expression) {
|
9202
9538
|
const identifiers = this.#dependentIdentifiers ?? [];
|
9203
9539
|
const vNode = this.#vNode;
|
9204
9540
|
// Return a function that handles the event with proper scope
|
@@ -9357,6 +9693,42 @@ class VShowDirective {
|
|
9357
9693
|
// Hide the element
|
9358
9694
|
element.style.display = "none";
|
9359
9695
|
}
|
9696
|
+
/**
|
9697
|
+
* @inheritdoc
|
9698
|
+
*/
|
9699
|
+
get onMount() {
|
9700
|
+
return undefined;
|
9701
|
+
}
|
9702
|
+
/**
|
9703
|
+
* @inheritdoc
|
9704
|
+
*/
|
9705
|
+
get onMounted() {
|
9706
|
+
return undefined;
|
9707
|
+
}
|
9708
|
+
/**
|
9709
|
+
* @inheritdoc
|
9710
|
+
*/
|
9711
|
+
get onUpdate() {
|
9712
|
+
return undefined;
|
9713
|
+
}
|
9714
|
+
/**
|
9715
|
+
* @inheritdoc
|
9716
|
+
*/
|
9717
|
+
get onUpdated() {
|
9718
|
+
return undefined;
|
9719
|
+
}
|
9720
|
+
/**
|
9721
|
+
* @inheritdoc
|
9722
|
+
*/
|
9723
|
+
get onUnmount() {
|
9724
|
+
return undefined;
|
9725
|
+
}
|
9726
|
+
/**
|
9727
|
+
* @inheritdoc
|
9728
|
+
*/
|
9729
|
+
get onUnmounted() {
|
9730
|
+
return undefined;
|
9731
|
+
}
|
9360
9732
|
/**
|
9361
9733
|
* @inheritdoc
|
9362
9734
|
*/
|
@@ -9472,49 +9844,105 @@ var LogLevel;
|
|
9472
9844
|
})(LogLevel || (LogLevel = {}));
|
9473
9845
|
|
9474
9846
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9847
|
+
/**
|
9848
|
+
* A simple logger class for virtual applications.
|
9849
|
+
*/
|
9475
9850
|
class VLogger {
|
9851
|
+
/** The name of the logger. */
|
9476
9852
|
#name;
|
9853
|
+
/** The log manager instance. */
|
9477
9854
|
#logManager;
|
9478
9855
|
constructor(name, logManager) {
|
9479
9856
|
this.#name = name;
|
9480
9857
|
this.#logManager = logManager;
|
9481
9858
|
}
|
9859
|
+
/**
|
9860
|
+
* Indicates whether the debug level is enabled.
|
9861
|
+
*/
|
9862
|
+
get isDebugEnabled() {
|
9863
|
+
return [LogLevel.DEBUG].includes(this.#logManager.logLevel);
|
9864
|
+
}
|
9865
|
+
/**
|
9866
|
+
* Indicates whether the info level is enabled.
|
9867
|
+
*/
|
9868
|
+
get isInfoEnabled() {
|
9869
|
+
return [LogLevel.DEBUG, LogLevel.INFO].includes(this.#logManager.logLevel);
|
9870
|
+
}
|
9871
|
+
/**
|
9872
|
+
* Indicates whether the warn level is enabled.
|
9873
|
+
*/
|
9874
|
+
get isWarnEnabled() {
|
9875
|
+
return [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN].includes(this.#logManager.logLevel);
|
9876
|
+
}
|
9877
|
+
/**
|
9878
|
+
* Logs a debug message.
|
9879
|
+
* @param message The message to log.
|
9880
|
+
*/
|
9482
9881
|
debug(message) {
|
9483
|
-
if (!
|
9882
|
+
if (!this.isDebugEnabled) {
|
9484
9883
|
return;
|
9485
9884
|
}
|
9486
9885
|
console.debug(`[${this.#name}] ${LogLevel.DEBUG}: ${message}`);
|
9487
9886
|
}
|
9887
|
+
/**
|
9888
|
+
* Logs an info message.
|
9889
|
+
* @param message The message to log.
|
9890
|
+
*/
|
9488
9891
|
info(message) {
|
9489
|
-
if (!
|
9892
|
+
if (!this.isInfoEnabled) {
|
9490
9893
|
return;
|
9491
9894
|
}
|
9492
9895
|
console.info(`[${this.#name}] ${LogLevel.INFO}: ${message}`);
|
9493
9896
|
}
|
9897
|
+
/**
|
9898
|
+
* Logs a warn message.
|
9899
|
+
* @param message The message to log.
|
9900
|
+
*/
|
9494
9901
|
warn(message) {
|
9495
|
-
if (!
|
9902
|
+
if (!this.isWarnEnabled) {
|
9496
9903
|
return;
|
9497
9904
|
}
|
9498
9905
|
console.warn(`[${this.#name}] ${LogLevel.WARN}: ${message}`);
|
9499
9906
|
}
|
9907
|
+
/**
|
9908
|
+
* Logs an error message.
|
9909
|
+
* @param message The message to log.
|
9910
|
+
*/
|
9500
9911
|
error(message) {
|
9501
9912
|
console.error(`[${this.#name}] ${LogLevel.ERROR}: ${message}`);
|
9502
9913
|
}
|
9503
9914
|
}
|
9504
9915
|
|
9505
9916
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9917
|
+
/**
|
9918
|
+
* Manages loggers and their log levels.
|
9919
|
+
*/
|
9506
9920
|
class VLogManager {
|
9921
|
+
/** The current log level. */
|
9507
9922
|
#logLevel;
|
9923
|
+
/** A map of logger instances by name. */
|
9508
9924
|
#loggers = new Map();
|
9509
9925
|
constructor(logLevel = LogLevel.INFO) {
|
9510
9926
|
this.#logLevel = logLevel;
|
9511
9927
|
}
|
9928
|
+
/**
|
9929
|
+
* Sets the log level for all loggers.
|
9930
|
+
* @param level The log level to set.
|
9931
|
+
*/
|
9512
9932
|
set logLevel(level) {
|
9513
9933
|
this.#logLevel = level;
|
9514
9934
|
}
|
9935
|
+
/**
|
9936
|
+
* Gets the current log level.
|
9937
|
+
*/
|
9515
9938
|
get logLevel() {
|
9516
9939
|
return this.#logLevel;
|
9517
9940
|
}
|
9941
|
+
/**
|
9942
|
+
* Gets a logger by name, creating it if it doesn't exist.
|
9943
|
+
* @param name The name of the logger.
|
9944
|
+
* @returns The logger instance.
|
9945
|
+
*/
|
9518
9946
|
getLogger(name) {
|
9519
9947
|
if (this.#loggers.has(name)) {
|
9520
9948
|
return this.#loggers.get(name);
|
@@ -9686,7 +10114,8 @@ class VApplication {
|
|
9686
10114
|
this.#bindings = new VBindings({
|
9687
10115
|
onChange: (identifier) => {
|
9688
10116
|
this.#scheduleUpdate();
|
9689
|
-
}
|
10117
|
+
},
|
10118
|
+
vApplication: this
|
9690
10119
|
});
|
9691
10120
|
// Inject utility methods into bindings
|
9692
10121
|
this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
|
@@ -9790,13 +10219,7 @@ class VApplication {
|
|
9790
10219
|
// Track if the computed value actually changed
|
9791
10220
|
if (oldValue !== newValue) {
|
9792
10221
|
this.#bindings?.set(key, newValue);
|
9793
|
-
|
9794
|
-
allChanges.add(id);
|
9795
|
-
const idx = id.indexOf('[');
|
9796
|
-
if (idx !== -1) {
|
9797
|
-
allChanges.add(id.substring(0, idx));
|
9798
|
-
}
|
9799
|
-
});
|
10222
|
+
allChanges.add(key);
|
9800
10223
|
}
|
9801
10224
|
}
|
9802
10225
|
catch (error) {
|