@mintjamsinc/ichigojs 0.1.66 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ichigo.cjs CHANGED
@@ -11537,6 +11537,15 @@
11537
11537
  * Mouse button modifiers (MouseEvent): `.left`, `.middle`, `.right`.
11538
11538
  * System modifiers (KeyboardEvent and MouseEvent): `.shift`, `.ctrl`, `.alt`, `.meta`, plus `.exact` to require that no other system modifiers are held.
11539
11539
  *
11540
+ * Listen target and filter modifiers (two orthogonal axes):
11541
+ * - Listen target (where the listener is attached): `.window`, `.document`. When omitted the listener
11542
+ * is attached to the bound element. This is useful for global / cross-component events, e.g.
11543
+ * `@webtop-message.document="onMessage"`, and the listener is removed automatically on unmount.
11544
+ * - Filter (whether the handler runs): `.self` fires only when `event.target` is the bound element;
11545
+ * `.outside` fires only when `event.target` is outside the bound element (e.g. click-outside to
11546
+ * close a popup). `.outside` implies listening on `document` (capture phase) even without `.document`,
11547
+ * and `.self` / `.outside` are mutually exclusive.
11548
+ *
11540
11549
  * Additionally, this directive supports lifecycle hooks:
11541
11550
  * @mount="onMount" - Called before the VNode is mounted to the DOM element
11542
11551
  * @mounted="onMounted" - Called after the VNode is mounted to the DOM element
@@ -11575,6 +11584,26 @@
11575
11584
  * The event listener function for DOM events.
11576
11585
  */
11577
11586
  #listener;
11587
+ /**
11588
+ * The resolved target the listener is attached to (element, document, or window).
11589
+ * Stored so destroy() removes the listener from the same target it was added to.
11590
+ */
11591
+ #resolvedTarget;
11592
+ /**
11593
+ * The resolved capture flag. Shared by attach and destroy so they stay in sync,
11594
+ * since `.outside` forces capture phase regardless of the `.capture` modifier.
11595
+ */
11596
+ #useCapture = false;
11597
+ /**
11598
+ * Whether the listener has actually been attached. `.outside` defers attachment by a
11599
+ * microtask, so destroy() must not attempt removal before it is attached.
11600
+ */
11601
+ #attached = false;
11602
+ /**
11603
+ * Whether the directive has been destroyed. Guards the deferred `.outside` attachment
11604
+ * from attaching after the node was already unmounted.
11605
+ */
11606
+ #destroyed = false;
11578
11607
  /**
11579
11608
  * Map of lifecycle hook names to their handler functions.
11580
11609
  */
@@ -11598,6 +11627,12 @@
11598
11627
  this.#eventName = parts[0];
11599
11628
  parts.slice(1).forEach(mod => this.#modifiers.add(mod));
11600
11629
  }
11630
+ // `.self` and `.outside` are mutually exclusive filters; together they can never fire.
11631
+ if (this.#modifiers.has('self') && this.#modifiers.has('outside')) {
11632
+ context.vNode.vApplication.logManager
11633
+ .getLogger('VOnDirective')
11634
+ .warn(`The '.self' and '.outside' modifiers on '${attrName}' are mutually exclusive; the handler will never fire.`);
11635
+ }
11601
11636
  // Parse the expression to extract identifiers and create the handler wrapper.
11602
11637
  // Event handlers are parsed in script mode so that users can write multi-statement bodies
11603
11638
  // (e.g. "a=1; b=2"), declarations, and control-flow constructs — matching Vue semantics.
@@ -11707,11 +11742,10 @@
11707
11742
  * @inheritdoc
11708
11743
  */
11709
11744
  destroy() {
11710
- // Remove the event listener when the directive is destroyed
11711
- if (this.#eventName && this.#listener) {
11712
- const element = this.#vNode.node;
11713
- const useCapture = this.#modifiers.has('capture');
11714
- element.removeEventListener(this.#eventName, this.#listener, useCapture);
11745
+ this.#destroyed = true;
11746
+ // Remove the event listener from the same target/phase it was attached to.
11747
+ if (this.#eventName && this.#listener && this.#resolvedTarget && this.#attached) {
11748
+ this.#resolvedTarget.removeEventListener(this.#eventName, this.#listener, this.#useCapture);
11715
11749
  }
11716
11750
  }
11717
11751
  /**
@@ -11727,8 +11761,21 @@
11727
11761
  }
11728
11762
  const element = this.#vNode.node;
11729
11763
  const eventName = this.#eventName;
11730
- const useCapture = this.#modifiers.has('capture');
11731
11764
  const isOnce = this.#modifiers.has('once');
11765
+ const isOutside = this.#modifiers.has('outside');
11766
+ // Resolve the listen target (orthogonal to filters): `.window` / `.document` attach
11767
+ // the listener globally; `.outside` also requires a global listener to detect events
11768
+ // originating outside the element, so it implies `document`.
11769
+ this.#resolvedTarget = this.#modifiers.has('window')
11770
+ ? window
11771
+ : (this.#modifiers.has('document') || isOutside)
11772
+ ? document
11773
+ : element;
11774
+ // `.outside` listens in capture phase so it is not suppressed by a descendant's
11775
+ // stopPropagation(); otherwise the capture flag follows the `.capture` modifier.
11776
+ this.#useCapture = this.#modifiers.has('capture') || isOutside;
11777
+ const useCapture = this.#useCapture;
11778
+ const target = this.#resolvedTarget;
11732
11779
  // System modifier keys (held during the event) shared by KeyboardEvent and MouseEvent.
11733
11780
  const systemModifiers = ['shift', 'ctrl', 'alt', 'meta'];
11734
11781
  // Create the event listener function
@@ -11814,6 +11861,14 @@
11814
11861
  if (this.#modifiers.has('self') && event.target !== element) {
11815
11862
  return;
11816
11863
  }
11864
+ // `.outside`: only fire when the event originates outside the bound element.
11865
+ // A non-Node target (e.g. window) is treated as outside.
11866
+ if (isOutside) {
11867
+ const eventTarget = event.target;
11868
+ if (eventTarget instanceof Node && element.contains(eventTarget)) {
11869
+ return;
11870
+ }
11871
+ }
11817
11872
  // Call the pre-generated handler wrapper (if exists)
11818
11873
  if (this.#handlerWrapper) {
11819
11874
  this.#handlerWrapper(event);
@@ -11822,11 +11877,26 @@
11822
11877
  // No need to manually call scheduleUpdate() here
11823
11878
  // If 'once' modifier is used, remove the listener after first execution
11824
11879
  if (isOnce && this.#listener) {
11825
- element.removeEventListener(eventName, this.#listener, useCapture);
11880
+ target.removeEventListener(eventName, this.#listener, useCapture);
11881
+ this.#attached = false;
11826
11882
  }
11827
11883
  };
11828
- // Attach the event listener
11829
- element.addEventListener(eventName, this.#listener, useCapture);
11884
+ if (isOutside) {
11885
+ // Defer attachment by one microtask so the listener does not catch the same
11886
+ // interaction that mounted this element (e.g. the click that opened a popup,
11887
+ // which would otherwise immediately close it). Skip if already destroyed.
11888
+ queueMicrotask(() => {
11889
+ if (this.#destroyed || !this.#listener) {
11890
+ return;
11891
+ }
11892
+ target.addEventListener(eventName, this.#listener, useCapture);
11893
+ this.#attached = true;
11894
+ });
11895
+ }
11896
+ else {
11897
+ target.addEventListener(eventName, this.#listener, useCapture);
11898
+ this.#attached = true;
11899
+ }
11830
11900
  }
11831
11901
  /**
11832
11902
  * Checks if the event name is a lifecycle hook.
@@ -13610,6 +13680,7 @@
13610
13680
  // Inject utility methods into bindings
13611
13681
  this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
13612
13682
  this.#bindings.set('$markRaw', (obj) => ReactiveProxy.markRaw(obj));
13683
+ this.#bindings.set('$emit', (name, detail, options) => this.#emit(name, detail, options));
13613
13684
  // Add methods
13614
13685
  if (this.#options.methods) {
13615
13686
  for (const [key, method] of Object.entries(this.#options.methods)) {
@@ -13855,6 +13926,38 @@
13855
13926
  compute(key);
13856
13927
  }
13857
13928
  }
13929
+ /**
13930
+ * Dispatches a CustomEvent, providing the framework-level `$emit` available in expressions
13931
+ * and methods. By default the event is dispatched on the application root element with
13932
+ * `bubbles: true`, so a parent component can listen for it via `v-on` / `@` on the component
13933
+ * tag (the root is rendered inside the host custom element, so the event bubbles out of it).
13934
+ *
13935
+ * The dispatch target can be overridden via `options.target` (e.g. `document` / `window`) to
13936
+ * use a global event bus, interoperating with native `addEventListener` listeners.
13937
+ *
13938
+ * @param name The event name (e.g. "selected"). Listened to as `@selected` on the parent side.
13939
+ * @param detail The payload exposed as `event.detail`.
13940
+ * @param options Dispatch options (bubbles, cancelable, composed, target).
13941
+ * @returns The result of dispatchEvent: false if a listener called preventDefault(), otherwise true.
13942
+ */
13943
+ #emit(name, detail, options) {
13944
+ // Documentation/validation only: warn when emitting an event not declared in `emits`.
13945
+ if (this.#options.emits && !this.#options.emits.includes(name)) {
13946
+ this.#logger.warn(`Event '${name}' is emitted but not declared in the 'emits' option.`);
13947
+ }
13948
+ const target = options?.target ?? this.#vNode?.node;
13949
+ if (!target) {
13950
+ this.#logger.warn(`$emit('${name}') was called before the application was mounted; the event was not dispatched.`);
13951
+ return false;
13952
+ }
13953
+ const event = new CustomEvent(name, {
13954
+ detail,
13955
+ bubbles: options?.bubbles ?? true,
13956
+ cancelable: options?.cancelable ?? true,
13957
+ composed: options?.composed ?? false,
13958
+ });
13959
+ return target.dispatchEvent(event);
13960
+ }
13858
13961
  /**
13859
13962
  * Executes a callback after the next DOM update.
13860
13963
  * @param callback The callback to execute.
@@ -14147,7 +14250,7 @@
14147
14250
  * @param options Component options including template selector and optional props.
14148
14251
  */
14149
14252
  function defineComponent(tagName, options) {
14150
- const { props = [], template, data, computed, methods, watch, logLevel } = options;
14253
+ const { props = [], template, data, computed, methods, watch, emits, logLevel } = options;
14151
14254
  // Build a subclass of IchigoElement specific to this component
14152
14255
  class ComponentElement extends IchigoElement {
14153
14256
  static _template = template;
@@ -14169,6 +14272,7 @@
14169
14272
  computed,
14170
14273
  methods,
14171
14274
  watch,
14275
+ emits,
14172
14276
  logLevel,
14173
14277
  };
14174
14278
  }