@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.
@@ -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 (!hasChanged && Array.isArray(newValue)) {
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
- // If the node is an element, initialize directives and child nodes
7645
- if (this.#nodeType === Node.ELEMENT_NODE && this.#node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
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
- // For non-v-for elements, recursively create VNode instances for child nodes
7652
- if (!this.#directiveManager.directives?.some(d => d.templatize)) {
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
- // If there is a parent virtual node, add this node as a dependency
7663
- if (this.#parentVNode) {
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
- // Prepare new bindings using directive bindings preparers, if any
7863
- if (this.#directiveManager?.bindingsPreparers) {
7864
- // Ensure local bindings are initialized
7865
- if (!this.#bindings) {
7866
- this.#bindings = new VBindings({ parent: this.bindings });
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 for each preparer if relevant identifiers have changed
7869
- for (const preparer of this.#directiveManager.bindingsPreparers) {
7870
- const changed = preparer.dependentIdentifiers.some(id => changes.includes(id));
7871
- if (changed) {
7872
- preparer.prepareBindings();
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
- // Apply DOM updaters from directives, if any
7877
- if (this.#directiveManager?.domUpdaters) {
7878
- for (const updater of this.#directiveManager.domUpdaters) {
7879
- const changed = updater.dependentIdentifiers.some(id => changes.includes(id));
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
- updater.applyToDOM();
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
- // Prepare new bindings using directive bindings preparers, if any
7907
- if (this.#directiveManager?.bindingsPreparers) {
7908
- // Ensure local bindings are initialized
7909
- if (!this.#bindings) {
7910
- this.#bindings = new VBindings({ parent: this.bindings });
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
- // Prepare bindings for each preparer if relevant identifiers have changed
7913
- for (const preparer of this.#directiveManager.bindingsPreparers) {
7914
- preparer.prepareBindings();
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
- // Apply DOM updaters from directives, if any
7918
- if (this.#directiveManager?.domUpdaters) {
7919
- for (const updater of this.#directiveManager.domUpdaters) {
7920
- updater.applyToDOM();
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
- this.#renderedVNode.node.parentNode?.removeChild(this.#renderedVNode.node);
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
- if (vNode.node.parentNode) {
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
- const parsed = Number(result);
8951
- // Only convert if it's a valid number
8952
- if (!isNaN(parsed)) {
8953
- result = parsed;
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
- * This directive is essential for handling user interactions in your application.
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
- // Create and attach the event listener
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
- * Creates a wrapper function for the event handler, generated once and reused.
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
- #createHandlerWrapper(expression) {
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 (![LogLevel.DEBUG].includes(this.#logManager.logLevel)) {
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 (![LogLevel.DEBUG, LogLevel.INFO].includes(this.#logManager.logLevel)) {
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 (![LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN].includes(this.#logManager.logLevel)) {
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
- this.#bindings?.changes.forEach(id => {
9800
- allChanges.add(id);
9801
- const idx = id.indexOf('[');
9802
- if (idx !== -1) {
9803
- allChanges.add(id.substring(0, idx));
9804
- }
9805
- });
10228
+ allChanges.add(key);
9806
10229
  }
9807
10230
  }
9808
10231
  catch (error) {