@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.
@@ -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 (!hasChanged && Array.isArray(newValue)) {
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
- // If the node is an element, initialize directives and child nodes
7639
- if (this.#nodeType === Node.ELEMENT_NODE && this.#node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
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
- // For non-v-for elements, recursively create VNode instances for child nodes
7646
- if (!this.#directiveManager.directives?.some(d => d.templatize)) {
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
- // If there is a parent virtual node, add this node as a dependency
7657
- if (this.#parentVNode) {
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
- // Prepare new bindings using directive bindings preparers, if any
7857
- if (this.#directiveManager?.bindingsPreparers) {
7858
- // Ensure local bindings are initialized
7859
- if (!this.#bindings) {
7860
- this.#bindings = new VBindings({ parent: this.bindings });
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 for each preparer if relevant identifiers have changed
7863
- for (const preparer of this.#directiveManager.bindingsPreparers) {
7864
- const changed = preparer.dependentIdentifiers.some(id => changes.includes(id));
7865
- if (changed) {
7866
- preparer.prepareBindings();
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
- // Apply DOM updaters from directives, if any
7871
- if (this.#directiveManager?.domUpdaters) {
7872
- for (const updater of this.#directiveManager.domUpdaters) {
7873
- const changed = updater.dependentIdentifiers.some(id => changes.includes(id));
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
- updater.applyToDOM();
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
- // Prepare new bindings using directive bindings preparers, if any
7901
- if (this.#directiveManager?.bindingsPreparers) {
7902
- // Ensure local bindings are initialized
7903
- if (!this.#bindings) {
7904
- this.#bindings = new VBindings({ parent: this.bindings });
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
- // Prepare bindings for each preparer if relevant identifiers have changed
7907
- for (const preparer of this.#directiveManager.bindingsPreparers) {
7908
- preparer.prepareBindings();
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
- // Apply DOM updaters from directives, if any
7912
- if (this.#directiveManager?.domUpdaters) {
7913
- for (const updater of this.#directiveManager.domUpdaters) {
7914
- updater.applyToDOM();
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
- this.#renderedVNode.node.parentNode?.removeChild(this.#renderedVNode.node);
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
- if (vNode.node.parentNode) {
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
- const parsed = Number(result);
8945
- // Only convert if it's a valid number
8946
- if (!isNaN(parsed)) {
8947
- result = parsed;
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
- * This directive is essential for handling user interactions in your application.
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
- // Create and attach the event listener
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
- * Creates a wrapper function for the event handler, generated once and reused.
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
- #createHandlerWrapper(expression) {
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 (![LogLevel.DEBUG].includes(this.#logManager.logLevel)) {
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 (![LogLevel.DEBUG, LogLevel.INFO].includes(this.#logManager.logLevel)) {
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 (![LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN].includes(this.#logManager.logLevel)) {
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
- 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
- });
10222
+ allChanges.add(key);
9800
10223
  }
9801
10224
  }
9802
10225
  catch (error) {