@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.umd.js
CHANGED
@@ -6879,6 +6879,42 @@
|
|
6879
6879
|
get expression() {
|
6880
6880
|
return this.#expression;
|
6881
6881
|
}
|
6882
|
+
/**
|
6883
|
+
* @inheritdoc
|
6884
|
+
*/
|
6885
|
+
get onMount() {
|
6886
|
+
return undefined;
|
6887
|
+
}
|
6888
|
+
/**
|
6889
|
+
* @inheritdoc
|
6890
|
+
*/
|
6891
|
+
get onMounted() {
|
6892
|
+
return undefined;
|
6893
|
+
}
|
6894
|
+
/**
|
6895
|
+
* @inheritdoc
|
6896
|
+
*/
|
6897
|
+
get onUpdate() {
|
6898
|
+
return undefined;
|
6899
|
+
}
|
6900
|
+
/**
|
6901
|
+
* @inheritdoc
|
6902
|
+
*/
|
6903
|
+
get onUpdated() {
|
6904
|
+
return undefined;
|
6905
|
+
}
|
6906
|
+
/**
|
6907
|
+
* @inheritdoc
|
6908
|
+
*/
|
6909
|
+
get onUnmount() {
|
6910
|
+
return undefined;
|
6911
|
+
}
|
6912
|
+
/**
|
6913
|
+
* @inheritdoc
|
6914
|
+
*/
|
6915
|
+
get onUnmounted() {
|
6916
|
+
return undefined;
|
6917
|
+
}
|
6882
6918
|
/**
|
6883
6919
|
* @inheritdoc
|
6884
6920
|
*/
|
@@ -7169,6 +7205,10 @@
|
|
7169
7205
|
* The change tracker, if any.
|
7170
7206
|
*/
|
7171
7207
|
#onChange;
|
7208
|
+
/**
|
7209
|
+
* The logger instance.
|
7210
|
+
*/
|
7211
|
+
#logger;
|
7172
7212
|
/**
|
7173
7213
|
* The set of changed identifiers.
|
7174
7214
|
*/
|
@@ -7184,6 +7224,10 @@
|
|
7184
7224
|
constructor(args = {}) {
|
7185
7225
|
this.#parent = args.parent;
|
7186
7226
|
this.#onChange = args.onChange;
|
7227
|
+
this.#logger = args.vApplication?.logManager.getLogger('VBindings');
|
7228
|
+
if (this.#logger?.isDebugEnabled) {
|
7229
|
+
this.#logger.debug(`VBindings created. Parent: ${this.#parent ? 'yes' : 'no'}`);
|
7230
|
+
}
|
7187
7231
|
this.#local = new Proxy({}, {
|
7188
7232
|
get: (obj, key) => {
|
7189
7233
|
if (Reflect.has(obj, key)) {
|
@@ -7208,6 +7252,7 @@
|
|
7208
7252
|
let path = '';
|
7209
7253
|
for (const part of changedPath?.split('.') || []) {
|
7210
7254
|
path = path ? `${path}.${part}` : part;
|
7255
|
+
this.#logger?.debug(`Binding changed: ${path}`);
|
7211
7256
|
this.#changes.add(path);
|
7212
7257
|
}
|
7213
7258
|
this.#onChange?.(changedPath);
|
@@ -7218,15 +7263,22 @@
|
|
7218
7263
|
// Detect changes
|
7219
7264
|
let hasChanged = oldValue !== newValue;
|
7220
7265
|
// Special handling for arrays: check length changes even if same object reference
|
7221
|
-
if (
|
7266
|
+
if (Array.isArray(newValue)) {
|
7222
7267
|
const cachedLength = this.#lengthCache.get(key);
|
7223
7268
|
const currentLength = newValue.length;
|
7224
|
-
if (cachedLength !== undefined && cachedLength !== currentLength) {
|
7269
|
+
if (!hasChanged && cachedLength !== undefined && cachedLength !== currentLength) {
|
7225
7270
|
hasChanged = true;
|
7226
7271
|
}
|
7227
7272
|
this.#lengthCache.set(key, currentLength);
|
7228
7273
|
}
|
7229
7274
|
if (hasChanged) {
|
7275
|
+
if (this.#logger?.isDebugEnabled) {
|
7276
|
+
const oldValueString = typeof oldValue === 'string' ? `"${oldValue}"` : JSON.stringify(oldValue) || 'undefined';
|
7277
|
+
const newValueString = typeof newValue === 'string' ? `"${newValue}"` : JSON.stringify(newValue) || 'undefined';
|
7278
|
+
const oldValuePreview = oldValueString.length > 100 ? `${oldValueString.substring(0, 100)}...` : oldValueString;
|
7279
|
+
const newValuePreview = newValueString.length > 100 ? `${newValueString.substring(0, 100)}...` : newValueString;
|
7280
|
+
this.#logger.debug(`Binding set on ${target === obj ? 'local' : 'parent'}: ${key}: ${oldValuePreview} -> ${newValuePreview}`);
|
7281
|
+
}
|
7230
7282
|
this.#changes.add(key);
|
7231
7283
|
this.#onChange?.(key);
|
7232
7284
|
}
|
@@ -7234,6 +7286,7 @@
|
|
7234
7286
|
},
|
7235
7287
|
deleteProperty: (obj, key) => {
|
7236
7288
|
const result = Reflect.deleteProperty(obj, key);
|
7289
|
+
this.#logger?.debug(`Binding deleted: ${key}`);
|
7237
7290
|
this.#changes.add(key);
|
7238
7291
|
this.#onChange?.(key);
|
7239
7292
|
return result;
|
@@ -7620,6 +7673,11 @@
|
|
7620
7673
|
* This is optional and may be undefined if there are no dependencies.
|
7621
7674
|
*/
|
7622
7675
|
#closers;
|
7676
|
+
/**
|
7677
|
+
* Indicates whether this node has been templatized by a directive.
|
7678
|
+
* This is optional and may be undefined if the node has not been templatized.
|
7679
|
+
*/
|
7680
|
+
#templatized;
|
7623
7681
|
/**
|
7624
7682
|
* Creates an instance of the virtual node.
|
7625
7683
|
* @param args The initialization arguments for the virtual node.
|
@@ -7633,23 +7691,31 @@
|
|
7633
7691
|
this.#bindings = args.bindings;
|
7634
7692
|
this.#initDependentIdentifiers = args.dependentIdentifiers;
|
7635
7693
|
this.#parentVNode?.addChild(this);
|
7636
|
-
// If the node is a text node, check for expressions and create a text evaluator
|
7637
7694
|
if (this.#nodeType === Node.TEXT_NODE) {
|
7695
|
+
// If the node is a text node, check for expressions and create a text evaluator
|
7638
7696
|
const text = this.#node;
|
7639
7697
|
// Create a text evaluator if the text contains expressions
|
7640
7698
|
if (VTextEvaluator.containsExpression(text.data)) {
|
7641
7699
|
this.#textEvaluator = new VTextEvaluator(text.data, this.#vApplication.functionDependencies);
|
7642
7700
|
}
|
7643
7701
|
}
|
7644
|
-
|
7645
|
-
|
7702
|
+
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
7703
|
+
// If the node is an element, initialize directives and child nodes
|
7646
7704
|
this.#node;
|
7647
7705
|
// Initialize child virtual nodes
|
7648
7706
|
this.#childVNodes = [];
|
7649
7707
|
// Initialize directive manager
|
7650
7708
|
this.#directiveManager = new VDirectiveManager(this);
|
7651
|
-
//
|
7652
|
-
|
7709
|
+
// Determine if any directive requires template preservation
|
7710
|
+
this.#templatized = this.#directiveManager.directives?.some(d => d.templatize) ?? false;
|
7711
|
+
// If no directive requires template preservation, call onMount for directives that do not templatize
|
7712
|
+
if (!this.#templatized) {
|
7713
|
+
this.#directiveManager.directives?.forEach(d => {
|
7714
|
+
d.onMount?.();
|
7715
|
+
});
|
7716
|
+
}
|
7717
|
+
// Create child virtual nodes if template preservation is not required
|
7718
|
+
if (!this.#templatized) {
|
7653
7719
|
for (const childNode of Array.from(this.#node.childNodes)) {
|
7654
7720
|
new VNode({
|
7655
7721
|
node: childNode,
|
@@ -7658,11 +7724,18 @@
|
|
7658
7724
|
});
|
7659
7725
|
}
|
7660
7726
|
}
|
7727
|
+
// After creating child nodes, call onMounted for directives that do not templatize
|
7728
|
+
if (!this.#templatized) {
|
7729
|
+
// animation frame to ensure DOM is updated
|
7730
|
+
requestAnimationFrame(() => {
|
7731
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7732
|
+
d.onMounted?.();
|
7733
|
+
});
|
7734
|
+
});
|
7735
|
+
}
|
7661
7736
|
}
|
7662
|
-
//
|
7663
|
-
|
7664
|
-
this.#closers = this.#parentVNode.addDependent(this);
|
7665
|
-
}
|
7737
|
+
// Register this node as a dependent of the parent node, if any
|
7738
|
+
this.#closers = this.#parentVNode?.addDependent(this);
|
7666
7739
|
}
|
7667
7740
|
/**
|
7668
7741
|
* The application instance associated with this virtual node.
|
@@ -7848,8 +7921,8 @@
|
|
7848
7921
|
*/
|
7849
7922
|
update() {
|
7850
7923
|
const changes = this.bindings?.changes || [];
|
7851
|
-
// If this is a text node with a text evaluator, update its content if needed
|
7852
7924
|
if (this.#nodeType === Node.TEXT_NODE && this.#textEvaluator) {
|
7925
|
+
// If this is a text node with a text evaluator, update its content if needed
|
7853
7926
|
// Check if any of the identifiers are in the changed identifiers
|
7854
7927
|
const changed = this.#textEvaluator.identifiers.some(id => changes.includes(id));
|
7855
7928
|
// If the text node has changed, update its content
|
@@ -7857,38 +7930,52 @@
|
|
7857
7930
|
const text = this.#node;
|
7858
7931
|
text.data = this.#textEvaluator.evaluate(this.bindings);
|
7859
7932
|
}
|
7860
|
-
return;
|
7861
7933
|
}
|
7862
|
-
|
7863
|
-
|
7864
|
-
//
|
7865
|
-
if (!this.#
|
7866
|
-
this.#
|
7934
|
+
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
7935
|
+
// If this is an element node, update directives and child nodes
|
7936
|
+
// If no directive requires template preservation, call onUpdate for directives that do not templatize
|
7937
|
+
if (!this.#templatized) {
|
7938
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7939
|
+
d.onUpdate?.();
|
7940
|
+
});
|
7867
7941
|
}
|
7868
|
-
// Prepare bindings
|
7869
|
-
|
7870
|
-
|
7871
|
-
if (
|
7872
|
-
|
7942
|
+
// Prepare new bindings using directive bindings preparers, if any
|
7943
|
+
if (this.#directiveManager?.bindingsPreparers) {
|
7944
|
+
// Ensure local bindings are initialized
|
7945
|
+
if (!this.#bindings) {
|
7946
|
+
this.#bindings = new VBindings({ parent: this.bindings });
|
7947
|
+
}
|
7948
|
+
// Prepare bindings for each preparer if relevant identifiers have changed
|
7949
|
+
for (const preparer of this.#directiveManager.bindingsPreparers) {
|
7950
|
+
const changed = preparer.dependentIdentifiers.some(id => changes.includes(id));
|
7951
|
+
if (changed) {
|
7952
|
+
preparer.prepareBindings();
|
7953
|
+
}
|
7873
7954
|
}
|
7874
7955
|
}
|
7875
|
-
|
7876
|
-
|
7877
|
-
|
7878
|
-
|
7879
|
-
|
7956
|
+
// Apply DOM updaters from directives, if any
|
7957
|
+
if (this.#directiveManager?.domUpdaters) {
|
7958
|
+
for (const updater of this.#directiveManager.domUpdaters) {
|
7959
|
+
const changed = updater.dependentIdentifiers.some(id => changes.includes(id));
|
7960
|
+
if (changed) {
|
7961
|
+
updater.applyToDOM();
|
7962
|
+
}
|
7963
|
+
}
|
7964
|
+
}
|
7965
|
+
// Recursively update dependent virtual nodes
|
7966
|
+
this.#dependents?.forEach(dependentNode => {
|
7967
|
+
const changed = dependentNode.dependentIdentifiers.some(id => changes.includes(id));
|
7880
7968
|
if (changed) {
|
7881
|
-
|
7969
|
+
dependentNode.update();
|
7882
7970
|
}
|
7971
|
+
});
|
7972
|
+
// If no directive requires template preservation, call onUpdated for directives that do not templatize
|
7973
|
+
if (!this.#templatized) {
|
7974
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7975
|
+
d.onUpdated?.();
|
7976
|
+
});
|
7883
7977
|
}
|
7884
7978
|
}
|
7885
|
-
// Recursively update dependent virtual nodes
|
7886
|
-
this.#dependents?.forEach(dependentNode => {
|
7887
|
-
const changed = dependentNode.dependentIdentifiers.some(id => changes.includes(id));
|
7888
|
-
if (changed) {
|
7889
|
-
dependentNode.update();
|
7890
|
-
}
|
7891
|
-
});
|
7892
7979
|
}
|
7893
7980
|
/**
|
7894
7981
|
* Forces an update of the virtual node and its children, regardless of changed identifiers.
|
@@ -7897,33 +7984,47 @@
|
|
7897
7984
|
* This is useful when an immediate update is needed, bypassing the usual change detection.
|
7898
7985
|
*/
|
7899
7986
|
forceUpdate() {
|
7900
|
-
// If this is a text node with a text evaluator, update its content if needed
|
7901
7987
|
if (this.#nodeType === Node.TEXT_NODE && this.#textEvaluator) {
|
7988
|
+
// If this is a text node with a text evaluator, update its content if needed
|
7902
7989
|
const text = this.#node;
|
7903
7990
|
text.data = this.#textEvaluator.evaluate(this.bindings);
|
7904
|
-
return;
|
7905
7991
|
}
|
7906
|
-
|
7907
|
-
|
7908
|
-
//
|
7909
|
-
if (!this.#
|
7910
|
-
this.#
|
7992
|
+
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
7993
|
+
// If this is an element node, update directives and child nodes
|
7994
|
+
// If no directive requires template preservation, call onUpdate for directives that do not templatize
|
7995
|
+
if (!this.#templatized) {
|
7996
|
+
this.#directiveManager?.directives?.forEach(d => {
|
7997
|
+
d.onUpdate?.();
|
7998
|
+
});
|
7999
|
+
}
|
8000
|
+
// Prepare new bindings using directive bindings preparers, if any
|
8001
|
+
if (this.#directiveManager?.bindingsPreparers) {
|
8002
|
+
// Ensure local bindings are initialized
|
8003
|
+
if (!this.#bindings) {
|
8004
|
+
this.#bindings = new VBindings({ parent: this.bindings });
|
8005
|
+
}
|
8006
|
+
// Prepare bindings for each preparer if relevant identifiers have changed
|
8007
|
+
for (const preparer of this.#directiveManager.bindingsPreparers) {
|
8008
|
+
preparer.prepareBindings();
|
8009
|
+
}
|
7911
8010
|
}
|
7912
|
-
//
|
7913
|
-
|
7914
|
-
|
8011
|
+
// Apply DOM updaters from directives, if any
|
8012
|
+
if (this.#directiveManager?.domUpdaters) {
|
8013
|
+
for (const updater of this.#directiveManager.domUpdaters) {
|
8014
|
+
updater.applyToDOM();
|
8015
|
+
}
|
7915
8016
|
}
|
7916
|
-
|
7917
|
-
|
7918
|
-
|
7919
|
-
|
7920
|
-
|
8017
|
+
// Recursively update child virtual nodes
|
8018
|
+
this.#childVNodes?.forEach(childVNode => {
|
8019
|
+
childVNode.forceUpdate();
|
8020
|
+
});
|
8021
|
+
// If no directive requires template preservation, call onUpdated for directives that do not templatize
|
8022
|
+
if (!this.#templatized) {
|
8023
|
+
this.#directiveManager?.directives?.forEach(d => {
|
8024
|
+
d.onUpdated?.();
|
8025
|
+
});
|
7921
8026
|
}
|
7922
8027
|
}
|
7923
|
-
// Recursively update child virtual nodes
|
7924
|
-
this.#childVNodes?.forEach(childVNode => {
|
7925
|
-
childVNode.forceUpdate();
|
7926
|
-
});
|
7927
8028
|
}
|
7928
8029
|
/**
|
7929
8030
|
* Adds a child virtual node to this virtual node.
|
@@ -7996,6 +8097,12 @@
|
|
7996
8097
|
* This method is called when the virtual node is no longer needed.
|
7997
8098
|
*/
|
7998
8099
|
destroy() {
|
8100
|
+
// If no directive requires template preservation, call onUnmount for directives that do not templatize
|
8101
|
+
if (!this.#templatized) {
|
8102
|
+
this.#directiveManager?.directives?.forEach(d => {
|
8103
|
+
d.onUnmount?.();
|
8104
|
+
});
|
8105
|
+
}
|
7999
8106
|
// Recursively destroy child nodes
|
8000
8107
|
if (this.#childVNodes) {
|
8001
8108
|
for (const childVNode of this.#childVNodes) {
|
@@ -8020,6 +8127,12 @@
|
|
8020
8127
|
}
|
8021
8128
|
// Clean up directive handler
|
8022
8129
|
this.#directiveManager?.destroy();
|
8130
|
+
// If no directive requires template preservation, call onUnmounted for directives that do not templatize
|
8131
|
+
if (!this.#templatized) {
|
8132
|
+
this.#directiveManager?.directives?.forEach(d => {
|
8133
|
+
d.onUnmounted?.();
|
8134
|
+
});
|
8135
|
+
}
|
8023
8136
|
}
|
8024
8137
|
}
|
8025
8138
|
|
@@ -8187,6 +8300,42 @@
|
|
8187
8300
|
}
|
8188
8301
|
return this.#evaluate();
|
8189
8302
|
}
|
8303
|
+
/**
|
8304
|
+
* @inheritdoc
|
8305
|
+
*/
|
8306
|
+
get onMount() {
|
8307
|
+
return undefined;
|
8308
|
+
}
|
8309
|
+
/**
|
8310
|
+
* @inheritdoc
|
8311
|
+
*/
|
8312
|
+
get onMounted() {
|
8313
|
+
return undefined;
|
8314
|
+
}
|
8315
|
+
/**
|
8316
|
+
* @inheritdoc
|
8317
|
+
*/
|
8318
|
+
get onUpdate() {
|
8319
|
+
return undefined;
|
8320
|
+
}
|
8321
|
+
/**
|
8322
|
+
* @inheritdoc
|
8323
|
+
*/
|
8324
|
+
get onUpdated() {
|
8325
|
+
return undefined;
|
8326
|
+
}
|
8327
|
+
/**
|
8328
|
+
* @inheritdoc
|
8329
|
+
*/
|
8330
|
+
get onUnmount() {
|
8331
|
+
return undefined;
|
8332
|
+
}
|
8333
|
+
/**
|
8334
|
+
* @inheritdoc
|
8335
|
+
*/
|
8336
|
+
get onUnmounted() {
|
8337
|
+
return undefined;
|
8338
|
+
}
|
8190
8339
|
/**
|
8191
8340
|
* @inheritdoc
|
8192
8341
|
*/
|
@@ -8241,8 +8390,10 @@
|
|
8241
8390
|
// Not rendered, no action needed
|
8242
8391
|
return;
|
8243
8392
|
}
|
8244
|
-
|
8393
|
+
// Destroy VNode first (calls @unmount hooks while DOM is still accessible)
|
8245
8394
|
this.#renderedVNode.destroy();
|
8395
|
+
// Then remove from DOM
|
8396
|
+
this.#renderedVNode.node.parentNode?.removeChild(this.#renderedVNode.node);
|
8246
8397
|
this.#renderedVNode = undefined;
|
8247
8398
|
}
|
8248
8399
|
/**
|
@@ -8471,16 +8622,56 @@
|
|
8471
8622
|
get dependentIdentifiers() {
|
8472
8623
|
return this.#dependentIdentifiers ?? [];
|
8473
8624
|
}
|
8625
|
+
/**
|
8626
|
+
* @inheritdoc
|
8627
|
+
*/
|
8628
|
+
get onMount() {
|
8629
|
+
return undefined;
|
8630
|
+
}
|
8631
|
+
/**
|
8632
|
+
* @inheritdoc
|
8633
|
+
*/
|
8634
|
+
get onMounted() {
|
8635
|
+
return undefined;
|
8636
|
+
}
|
8637
|
+
/**
|
8638
|
+
* @inheritdoc
|
8639
|
+
*/
|
8640
|
+
get onUpdate() {
|
8641
|
+
return undefined;
|
8642
|
+
}
|
8643
|
+
/**
|
8644
|
+
* @inheritdoc
|
8645
|
+
*/
|
8646
|
+
get onUpdated() {
|
8647
|
+
return undefined;
|
8648
|
+
}
|
8649
|
+
/**
|
8650
|
+
* @inheritdoc
|
8651
|
+
*/
|
8652
|
+
get onUnmount() {
|
8653
|
+
return undefined;
|
8654
|
+
}
|
8655
|
+
/**
|
8656
|
+
* @inheritdoc
|
8657
|
+
*/
|
8658
|
+
get onUnmounted() {
|
8659
|
+
return undefined;
|
8660
|
+
}
|
8474
8661
|
/**
|
8475
8662
|
* @inheritdoc
|
8476
8663
|
*/
|
8477
8664
|
destroy() {
|
8478
8665
|
// Clean up all rendered items
|
8666
|
+
// First destroy all VNodes (calls @unmount hooks), then remove from DOM
|
8667
|
+
for (const vNode of this.#renderedItems.values()) {
|
8668
|
+
vNode.destroy();
|
8669
|
+
}
|
8670
|
+
// Then remove DOM nodes
|
8479
8671
|
for (const vNode of this.#renderedItems.values()) {
|
8480
8672
|
if (vNode.node.parentNode) {
|
8481
8673
|
vNode.node.parentNode.removeChild(vNode.node);
|
8482
8674
|
}
|
8483
|
-
vNode.destroy();
|
8484
8675
|
}
|
8485
8676
|
this.#renderedItems.clear();
|
8486
8677
|
this.#previousIterations = [];
|
@@ -8537,14 +8728,20 @@
|
|
8537
8728
|
// Track which keys are still needed
|
8538
8729
|
const neededKeys = new Set(newIterations.map(ctx => ctx.key));
|
8539
8730
|
// Remove items that are no longer needed
|
8731
|
+
// First destroy VNodes (calls @unmount hooks while DOM is still accessible)
|
8732
|
+
const nodesToRemove = [];
|
8540
8733
|
for (const [key, vNode] of this.#renderedItems) {
|
8541
8734
|
if (!neededKeys.has(key)) {
|
8542
|
-
|
8543
|
-
vNode.node.parentNode.removeChild(vNode.node);
|
8544
|
-
}
|
8735
|
+
nodesToRemove.push(vNode);
|
8545
8736
|
vNode.destroy();
|
8546
8737
|
}
|
8547
8738
|
}
|
8739
|
+
// Then remove from DOM
|
8740
|
+
for (const vNode of nodesToRemove) {
|
8741
|
+
if (vNode.node.parentNode) {
|
8742
|
+
vNode.node.parentNode.removeChild(vNode.node);
|
8743
|
+
}
|
8744
|
+
}
|
8548
8745
|
// Add or reorder items
|
8549
8746
|
let prevNode = anchor;
|
8550
8747
|
for (const context of newIterations) {
|
@@ -8857,6 +9054,42 @@
|
|
8857
9054
|
get dependentIdentifiers() {
|
8858
9055
|
return this.#dependentIdentifiers ?? [];
|
8859
9056
|
}
|
9057
|
+
/**
|
9058
|
+
* @inheritdoc
|
9059
|
+
*/
|
9060
|
+
get onMount() {
|
9061
|
+
return undefined;
|
9062
|
+
}
|
9063
|
+
/**
|
9064
|
+
* @inheritdoc
|
9065
|
+
*/
|
9066
|
+
get onMounted() {
|
9067
|
+
return undefined;
|
9068
|
+
}
|
9069
|
+
/**
|
9070
|
+
* @inheritdoc
|
9071
|
+
*/
|
9072
|
+
get onUpdate() {
|
9073
|
+
return undefined;
|
9074
|
+
}
|
9075
|
+
/**
|
9076
|
+
* @inheritdoc
|
9077
|
+
*/
|
9078
|
+
get onUpdated() {
|
9079
|
+
return undefined;
|
9080
|
+
}
|
9081
|
+
/**
|
9082
|
+
* @inheritdoc
|
9083
|
+
*/
|
9084
|
+
get onUnmount() {
|
9085
|
+
return undefined;
|
9086
|
+
}
|
9087
|
+
/**
|
9088
|
+
* @inheritdoc
|
9089
|
+
*/
|
9090
|
+
get onUnmounted() {
|
9091
|
+
return undefined;
|
9092
|
+
}
|
8860
9093
|
/**
|
8861
9094
|
* @inheritdoc
|
8862
9095
|
*/
|
@@ -8947,10 +9180,13 @@
|
|
8947
9180
|
}
|
8948
9181
|
// .number modifier: convert to number
|
8949
9182
|
if (this.#modifiers.has('number')) {
|
8950
|
-
|
8951
|
-
|
8952
|
-
|
8953
|
-
|
9183
|
+
// Skip conversion if the value is empty string
|
9184
|
+
if (result !== '') {
|
9185
|
+
const parsed = Number(result);
|
9186
|
+
// Only convert if it's a valid number
|
9187
|
+
if (!isNaN(parsed)) {
|
9188
|
+
result = parsed;
|
9189
|
+
}
|
8954
9190
|
}
|
8955
9191
|
}
|
8956
9192
|
return result;
|
@@ -9013,7 +9249,7 @@
|
|
9013
9249
|
|
9014
9250
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9015
9251
|
/**
|
9016
|
-
* Directive for binding event listeners to DOM elements.
|
9252
|
+
* Directive for binding event listeners to DOM elements and lifecycle hooks.
|
9017
9253
|
* The `v-on` directive allows you to listen to DOM events and execute specified methods when those events are triggered.
|
9018
9254
|
* 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.
|
9019
9255
|
* Example usage:
|
@@ -9022,7 +9258,16 @@
|
|
9022
9258
|
* You can also use the shorthand `@event` instead of `v-on:event`. For example, `@click="handleClick"` is equivalent to `v-on:click="handleClick"`.
|
9023
9259
|
* The `v-on` directive supports event modifiers such as `.stop`, `.prevent`, `.capture`, `.self`, and `.once` to modify the behavior of the event listener.
|
9024
9260
|
* For example, `v-on:click.stop="handleClick"` will stop the event from propagating up the DOM tree.
|
9025
|
-
*
|
9261
|
+
*
|
9262
|
+
* Additionally, this directive supports lifecycle hooks:
|
9263
|
+
* @mount="onMount" - Called before the element is inserted into the DOM
|
9264
|
+
* @mounted="onMounted" - Called after the element is inserted into the DOM
|
9265
|
+
* @update="onUpdate" - Called before the element is updated
|
9266
|
+
* @updated="onUpdated" - Called after the element is updated
|
9267
|
+
* @unmount="onUnmount" - Called before the element is removed from the DOM
|
9268
|
+
* @unmounted="onUnmounted" - Called after the element is removed from the DOM
|
9269
|
+
*
|
9270
|
+
* This directive is essential for handling user interactions and lifecycle events in your application.
|
9026
9271
|
* Note that the methods referenced in the directive should be defined in the component's methods object.
|
9027
9272
|
*/
|
9028
9273
|
class VOnDirective {
|
@@ -9036,10 +9281,12 @@
|
|
9036
9281
|
#dependentIdentifiers;
|
9037
9282
|
/**
|
9038
9283
|
* The event handler wrapper function, generated once and reused.
|
9284
|
+
* For lifecycle hooks, this is a no-argument function.
|
9285
|
+
* For DOM events, this accepts an Event parameter.
|
9039
9286
|
*/
|
9040
9287
|
#handlerWrapper;
|
9041
9288
|
/**
|
9042
|
-
* The event name (e.g., "click", "input", "keydown").
|
9289
|
+
* The event name (e.g., "click", "input", "keydown") or lifecycle hook name (e.g., "mount", "mounted").
|
9043
9290
|
*/
|
9044
9291
|
#eventName;
|
9045
9292
|
/**
|
@@ -9047,9 +9294,13 @@
|
|
9047
9294
|
*/
|
9048
9295
|
#modifiers = new Set();
|
9049
9296
|
/**
|
9050
|
-
* The event listener function.
|
9297
|
+
* The event listener function for DOM events.
|
9051
9298
|
*/
|
9052
9299
|
#listener;
|
9300
|
+
/**
|
9301
|
+
* Map of lifecycle hook names to their handler functions.
|
9302
|
+
*/
|
9303
|
+
#lifecycleHooks = new Map();
|
9053
9304
|
/**
|
9054
9305
|
* @param context The context for parsing the directive.
|
9055
9306
|
*/
|
@@ -9073,10 +9324,22 @@
|
|
9073
9324
|
const expression = context.attribute.value;
|
9074
9325
|
if (expression) {
|
9075
9326
|
this.#dependentIdentifiers = ExpressionUtils.extractIdentifiers(expression, context.vNode.vApplication.functionDependencies);
|
9076
|
-
this.#handlerWrapper = this.#createHandlerWrapper(expression);
|
9077
9327
|
}
|
9078
|
-
//
|
9079
|
-
if (this.#eventName) {
|
9328
|
+
// Check if this is a lifecycle hook or a regular event
|
9329
|
+
if (this.#eventName && this.#isLifecycleHook(this.#eventName)) {
|
9330
|
+
// Create handler wrapper for lifecycle hook (no event parameter)
|
9331
|
+
if (expression) {
|
9332
|
+
const handler = this.#createLifecycleHandlerWrapper(expression);
|
9333
|
+
this.#handlerWrapper = handler;
|
9334
|
+
this.#lifecycleHooks.set(this.#eventName, handler);
|
9335
|
+
}
|
9336
|
+
}
|
9337
|
+
else if (this.#eventName) {
|
9338
|
+
// Create handler wrapper for DOM event (with event parameter)
|
9339
|
+
if (expression) {
|
9340
|
+
this.#handlerWrapper = this.#createEventHandlerWrapper(expression);
|
9341
|
+
}
|
9342
|
+
// Create and attach DOM event listener
|
9080
9343
|
this.#attachEventListener();
|
9081
9344
|
}
|
9082
9345
|
// Remove the directive attribute from the element
|
@@ -9124,6 +9387,42 @@
|
|
9124
9387
|
get dependentIdentifiers() {
|
9125
9388
|
return this.#dependentIdentifiers ?? [];
|
9126
9389
|
}
|
9390
|
+
/**
|
9391
|
+
* @inheritdoc
|
9392
|
+
*/
|
9393
|
+
get onMount() {
|
9394
|
+
return this.#lifecycleHooks.get('mount');
|
9395
|
+
}
|
9396
|
+
/**
|
9397
|
+
* @inheritdoc
|
9398
|
+
*/
|
9399
|
+
get onMounted() {
|
9400
|
+
return this.#lifecycleHooks.get('mounted');
|
9401
|
+
}
|
9402
|
+
/**
|
9403
|
+
* @inheritdoc
|
9404
|
+
*/
|
9405
|
+
get onUpdate() {
|
9406
|
+
return this.#lifecycleHooks.get('update');
|
9407
|
+
}
|
9408
|
+
/**
|
9409
|
+
* @inheritdoc
|
9410
|
+
*/
|
9411
|
+
get onUpdated() {
|
9412
|
+
return this.#lifecycleHooks.get('updated');
|
9413
|
+
}
|
9414
|
+
/**
|
9415
|
+
* @inheritdoc
|
9416
|
+
*/
|
9417
|
+
get onUnmount() {
|
9418
|
+
return this.#lifecycleHooks.get('unmount');
|
9419
|
+
}
|
9420
|
+
/**
|
9421
|
+
* @inheritdoc
|
9422
|
+
*/
|
9423
|
+
get onUnmounted() {
|
9424
|
+
return this.#lifecycleHooks.get('unmounted');
|
9425
|
+
}
|
9127
9426
|
/**
|
9128
9427
|
* @inheritdoc
|
9129
9428
|
*/
|
@@ -9200,11 +9499,48 @@
|
|
9200
9499
|
element.addEventListener(eventName, this.#listener, useCapture);
|
9201
9500
|
}
|
9202
9501
|
/**
|
9203
|
-
*
|
9502
|
+
* Checks if the event name is a lifecycle hook.
|
9503
|
+
* @param eventName The event name to check.
|
9504
|
+
* @returns True if the event name is a lifecycle hook, false otherwise.
|
9505
|
+
*/
|
9506
|
+
#isLifecycleHook(eventName) {
|
9507
|
+
return ['mount', 'mounted', 'update', 'updated', 'unmount', 'unmounted'].includes(eventName);
|
9508
|
+
}
|
9509
|
+
/**
|
9510
|
+
* Creates a wrapper function for lifecycle hooks (with element parameter).
|
9511
|
+
* @param expression The expression string to evaluate.
|
9512
|
+
* @returns A function that handles the lifecycle hook.
|
9513
|
+
*/
|
9514
|
+
#createLifecycleHandlerWrapper(expression) {
|
9515
|
+
const identifiers = this.#dependentIdentifiers ?? [];
|
9516
|
+
const vNode = this.#vNode;
|
9517
|
+
// Return a function that handles the lifecycle hook with proper scope
|
9518
|
+
return () => {
|
9519
|
+
const bindings = vNode.bindings;
|
9520
|
+
const el = vNode.node;
|
9521
|
+
// If the expression is just a method name, call it with bindings as 'this'
|
9522
|
+
const trimmedExpr = expression.trim();
|
9523
|
+
if (identifiers.includes(trimmedExpr) && typeof bindings?.get(trimmedExpr) === 'function') {
|
9524
|
+
const methodName = trimmedExpr;
|
9525
|
+
const originalMethod = bindings?.get(methodName);
|
9526
|
+
// Call the method with bindings as 'this' context and element as parameter
|
9527
|
+
// This allows the method to access the DOM element and bindings
|
9528
|
+
return originalMethod(el);
|
9529
|
+
}
|
9530
|
+
// For inline expressions, evaluate normally with element parameter
|
9531
|
+
const values = identifiers.map(id => vNode.bindings?.get(id));
|
9532
|
+
const args = [...identifiers, 'el'].join(", ");
|
9533
|
+
const funcBody = `return (${expression});`;
|
9534
|
+
const func = new Function(args, funcBody);
|
9535
|
+
return func.call(bindings?.raw, ...values, el);
|
9536
|
+
};
|
9537
|
+
}
|
9538
|
+
/**
|
9539
|
+
* Creates a wrapper function for DOM event handlers (with event parameter).
|
9204
9540
|
* @param expression The expression string to evaluate.
|
9205
9541
|
* @returns A function that handles the event.
|
9206
9542
|
*/
|
9207
|
-
#
|
9543
|
+
#createEventHandlerWrapper(expression) {
|
9208
9544
|
const identifiers = this.#dependentIdentifiers ?? [];
|
9209
9545
|
const vNode = this.#vNode;
|
9210
9546
|
// Return a function that handles the event with proper scope
|
@@ -9363,6 +9699,42 @@
|
|
9363
9699
|
// Hide the element
|
9364
9700
|
element.style.display = "none";
|
9365
9701
|
}
|
9702
|
+
/**
|
9703
|
+
* @inheritdoc
|
9704
|
+
*/
|
9705
|
+
get onMount() {
|
9706
|
+
return undefined;
|
9707
|
+
}
|
9708
|
+
/**
|
9709
|
+
* @inheritdoc
|
9710
|
+
*/
|
9711
|
+
get onMounted() {
|
9712
|
+
return undefined;
|
9713
|
+
}
|
9714
|
+
/**
|
9715
|
+
* @inheritdoc
|
9716
|
+
*/
|
9717
|
+
get onUpdate() {
|
9718
|
+
return undefined;
|
9719
|
+
}
|
9720
|
+
/**
|
9721
|
+
* @inheritdoc
|
9722
|
+
*/
|
9723
|
+
get onUpdated() {
|
9724
|
+
return undefined;
|
9725
|
+
}
|
9726
|
+
/**
|
9727
|
+
* @inheritdoc
|
9728
|
+
*/
|
9729
|
+
get onUnmount() {
|
9730
|
+
return undefined;
|
9731
|
+
}
|
9732
|
+
/**
|
9733
|
+
* @inheritdoc
|
9734
|
+
*/
|
9735
|
+
get onUnmounted() {
|
9736
|
+
return undefined;
|
9737
|
+
}
|
9366
9738
|
/**
|
9367
9739
|
* @inheritdoc
|
9368
9740
|
*/
|
@@ -9478,49 +9850,105 @@
|
|
9478
9850
|
})(LogLevel || (LogLevel = {}));
|
9479
9851
|
|
9480
9852
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9853
|
+
/**
|
9854
|
+
* A simple logger class for virtual applications.
|
9855
|
+
*/
|
9481
9856
|
class VLogger {
|
9857
|
+
/** The name of the logger. */
|
9482
9858
|
#name;
|
9859
|
+
/** The log manager instance. */
|
9483
9860
|
#logManager;
|
9484
9861
|
constructor(name, logManager) {
|
9485
9862
|
this.#name = name;
|
9486
9863
|
this.#logManager = logManager;
|
9487
9864
|
}
|
9865
|
+
/**
|
9866
|
+
* Indicates whether the debug level is enabled.
|
9867
|
+
*/
|
9868
|
+
get isDebugEnabled() {
|
9869
|
+
return [LogLevel.DEBUG].includes(this.#logManager.logLevel);
|
9870
|
+
}
|
9871
|
+
/**
|
9872
|
+
* Indicates whether the info level is enabled.
|
9873
|
+
*/
|
9874
|
+
get isInfoEnabled() {
|
9875
|
+
return [LogLevel.DEBUG, LogLevel.INFO].includes(this.#logManager.logLevel);
|
9876
|
+
}
|
9877
|
+
/**
|
9878
|
+
* Indicates whether the warn level is enabled.
|
9879
|
+
*/
|
9880
|
+
get isWarnEnabled() {
|
9881
|
+
return [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN].includes(this.#logManager.logLevel);
|
9882
|
+
}
|
9883
|
+
/**
|
9884
|
+
* Logs a debug message.
|
9885
|
+
* @param message The message to log.
|
9886
|
+
*/
|
9488
9887
|
debug(message) {
|
9489
|
-
if (!
|
9888
|
+
if (!this.isDebugEnabled) {
|
9490
9889
|
return;
|
9491
9890
|
}
|
9492
9891
|
console.debug(`[${this.#name}] ${LogLevel.DEBUG}: ${message}`);
|
9493
9892
|
}
|
9893
|
+
/**
|
9894
|
+
* Logs an info message.
|
9895
|
+
* @param message The message to log.
|
9896
|
+
*/
|
9494
9897
|
info(message) {
|
9495
|
-
if (!
|
9898
|
+
if (!this.isInfoEnabled) {
|
9496
9899
|
return;
|
9497
9900
|
}
|
9498
9901
|
console.info(`[${this.#name}] ${LogLevel.INFO}: ${message}`);
|
9499
9902
|
}
|
9903
|
+
/**
|
9904
|
+
* Logs a warn message.
|
9905
|
+
* @param message The message to log.
|
9906
|
+
*/
|
9500
9907
|
warn(message) {
|
9501
|
-
if (!
|
9908
|
+
if (!this.isWarnEnabled) {
|
9502
9909
|
return;
|
9503
9910
|
}
|
9504
9911
|
console.warn(`[${this.#name}] ${LogLevel.WARN}: ${message}`);
|
9505
9912
|
}
|
9913
|
+
/**
|
9914
|
+
* Logs an error message.
|
9915
|
+
* @param message The message to log.
|
9916
|
+
*/
|
9506
9917
|
error(message) {
|
9507
9918
|
console.error(`[${this.#name}] ${LogLevel.ERROR}: ${message}`);
|
9508
9919
|
}
|
9509
9920
|
}
|
9510
9921
|
|
9511
9922
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9923
|
+
/**
|
9924
|
+
* Manages loggers and their log levels.
|
9925
|
+
*/
|
9512
9926
|
class VLogManager {
|
9927
|
+
/** The current log level. */
|
9513
9928
|
#logLevel;
|
9929
|
+
/** A map of logger instances by name. */
|
9514
9930
|
#loggers = new Map();
|
9515
9931
|
constructor(logLevel = LogLevel.INFO) {
|
9516
9932
|
this.#logLevel = logLevel;
|
9517
9933
|
}
|
9934
|
+
/**
|
9935
|
+
* Sets the log level for all loggers.
|
9936
|
+
* @param level The log level to set.
|
9937
|
+
*/
|
9518
9938
|
set logLevel(level) {
|
9519
9939
|
this.#logLevel = level;
|
9520
9940
|
}
|
9941
|
+
/**
|
9942
|
+
* Gets the current log level.
|
9943
|
+
*/
|
9521
9944
|
get logLevel() {
|
9522
9945
|
return this.#logLevel;
|
9523
9946
|
}
|
9947
|
+
/**
|
9948
|
+
* Gets a logger by name, creating it if it doesn't exist.
|
9949
|
+
* @param name The name of the logger.
|
9950
|
+
* @returns The logger instance.
|
9951
|
+
*/
|
9524
9952
|
getLogger(name) {
|
9525
9953
|
if (this.#loggers.has(name)) {
|
9526
9954
|
return this.#loggers.get(name);
|
@@ -9692,7 +10120,8 @@
|
|
9692
10120
|
this.#bindings = new VBindings({
|
9693
10121
|
onChange: (identifier) => {
|
9694
10122
|
this.#scheduleUpdate();
|
9695
|
-
}
|
10123
|
+
},
|
10124
|
+
vApplication: this
|
9696
10125
|
});
|
9697
10126
|
// Inject utility methods into bindings
|
9698
10127
|
this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
|
@@ -9796,13 +10225,7 @@
|
|
9796
10225
|
// Track if the computed value actually changed
|
9797
10226
|
if (oldValue !== newValue) {
|
9798
10227
|
this.#bindings?.set(key, newValue);
|
9799
|
-
|
9800
|
-
allChanges.add(id);
|
9801
|
-
const idx = id.indexOf('[');
|
9802
|
-
if (idx !== -1) {
|
9803
|
-
allChanges.add(id.substring(0, idx));
|
9804
|
-
}
|
9805
|
-
});
|
10228
|
+
allChanges.add(key);
|
9806
10229
|
}
|
9807
10230
|
}
|
9808
10231
|
catch (error) {
|