@mintjamsinc/ichigojs 0.1.66 → 0.1.68
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 +260 -17
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +260 -17
- package/dist/ichigo.esm.js.map +1 -1
- package/dist/ichigo.esm.min.js +1 -1
- package/dist/ichigo.min.cjs +1 -1
- package/dist/ichigo.umd.js +260 -17
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/VApplicationOptions.d.ts +7 -0
- package/dist/types/ichigo/VBindings.d.ts +7 -0
- package/dist/types/ichigo/VEmitOptions.d.ts +27 -0
- package/dist/types/ichigo/directives/VOnDirective.d.ts +9 -0
- package/dist/types/ichigo/util/ReactiveProxy.d.ts +47 -1
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/ichigo.cjs
CHANGED
|
@@ -7981,6 +7981,13 @@
|
|
|
7981
7981
|
* This allows retrieving the source path of an object for computed property mapping.
|
|
7982
7982
|
*/
|
|
7983
7983
|
static proxyPaths = new WeakMap();
|
|
7984
|
+
/**
|
|
7985
|
+
* Dispatchers per (target, path). Every target reached while walking a proxy
|
|
7986
|
+
* subtree registers an entry pointing at the same dispatcher as the outermost
|
|
7987
|
+
* proxy of that subtree, so callers can look up the dispatcher from any
|
|
7988
|
+
* intermediate proxy when subscribing.
|
|
7989
|
+
*/
|
|
7990
|
+
static dispatchers = new WeakMap();
|
|
7984
7991
|
/**
|
|
7985
7992
|
* A Map to store path aliases.
|
|
7986
7993
|
* Key: alias path (e.g., "editingNestedStep.steps")
|
|
@@ -7995,9 +8002,10 @@
|
|
|
7995
8002
|
* @param target The object to make reactive.
|
|
7996
8003
|
* @param onChange Callback function to call when the object changes. Receives the full path of the changed property.
|
|
7997
8004
|
* @param path The current path in the object tree (used internally for nested objects).
|
|
8005
|
+
* @param inheritedDispatcher Internal: the dispatcher inherited from an enclosing create() call when wrapping a nested target. External callers must omit this.
|
|
7998
8006
|
* @returns A reactive proxy of the target object.
|
|
7999
8007
|
*/
|
|
8000
|
-
static create(target, onChange, path = '') {
|
|
8008
|
+
static create(target, onChange, path = '', inheritedDispatcher) {
|
|
8001
8009
|
// If the target is not an object or is null, return it as-is
|
|
8002
8010
|
if (typeof target !== 'object' || target === null) {
|
|
8003
8011
|
return target;
|
|
@@ -8017,6 +8025,13 @@
|
|
|
8017
8025
|
if (this.proxyToTarget.has(target)) {
|
|
8018
8026
|
return target;
|
|
8019
8027
|
}
|
|
8028
|
+
// Resolve (and register) the dispatcher for this (target, path).
|
|
8029
|
+
// Nested create() calls inherit the dispatcher from the enclosing call so
|
|
8030
|
+
// that a single dispatcher fans out changes at any depth of the subtree.
|
|
8031
|
+
const dispatcher = this.resolveDispatcher(target, path, inheritedDispatcher);
|
|
8032
|
+
if (onChange) {
|
|
8033
|
+
dispatcher.listeners.add(onChange);
|
|
8034
|
+
}
|
|
8020
8035
|
// Check if we already have a proxy for this target with this path
|
|
8021
8036
|
let pathMap = this.proxyCache.get(target);
|
|
8022
8037
|
if (pathMap) {
|
|
@@ -8048,7 +8063,7 @@
|
|
|
8048
8063
|
// Build the nested path
|
|
8049
8064
|
const keyStr = String(key);
|
|
8050
8065
|
const nestedPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
|
8051
|
-
return ReactiveProxy.create(value,
|
|
8066
|
+
return ReactiveProxy.create(value, undefined, nestedPath, dispatcher);
|
|
8052
8067
|
}
|
|
8053
8068
|
// If the value is a function, we need to wrap it to ensure that any mutations it performs also trigger onChange
|
|
8054
8069
|
if (typeof value === 'function') {
|
|
@@ -8060,7 +8075,7 @@
|
|
|
8060
8075
|
}
|
|
8061
8076
|
return function (...args) {
|
|
8062
8077
|
const result = value.apply(this === receiver ? obj : this, args);
|
|
8063
|
-
|
|
8078
|
+
ReactiveProxy.dispatch(dispatcher, path || undefined);
|
|
8064
8079
|
return result;
|
|
8065
8080
|
};
|
|
8066
8081
|
}
|
|
@@ -8070,7 +8085,7 @@
|
|
|
8070
8085
|
return function (...args) {
|
|
8071
8086
|
const result = value.apply(this === receiver ? obj : this, args);
|
|
8072
8087
|
if (mapMutationMethods.includes(key)) {
|
|
8073
|
-
|
|
8088
|
+
ReactiveProxy.dispatch(dispatcher, path || undefined);
|
|
8074
8089
|
}
|
|
8075
8090
|
return result;
|
|
8076
8091
|
};
|
|
@@ -8081,7 +8096,7 @@
|
|
|
8081
8096
|
return function (...args) {
|
|
8082
8097
|
const result = value.apply(this === receiver ? obj : this, args);
|
|
8083
8098
|
if (setMutationMethods.includes(key)) {
|
|
8084
|
-
|
|
8099
|
+
ReactiveProxy.dispatch(dispatcher, path || undefined);
|
|
8085
8100
|
}
|
|
8086
8101
|
return result;
|
|
8087
8102
|
};
|
|
@@ -8096,7 +8111,7 @@
|
|
|
8096
8111
|
if (oldValue !== value) {
|
|
8097
8112
|
const keyStr = String(key);
|
|
8098
8113
|
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
|
8099
|
-
|
|
8114
|
+
ReactiveProxy.dispatch(dispatcher, fullPath);
|
|
8100
8115
|
}
|
|
8101
8116
|
return result;
|
|
8102
8117
|
},
|
|
@@ -8104,7 +8119,7 @@
|
|
|
8104
8119
|
const result = Reflect.deleteProperty(obj, key);
|
|
8105
8120
|
const keyStr = String(key);
|
|
8106
8121
|
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
|
8107
|
-
|
|
8122
|
+
ReactiveProxy.dispatch(dispatcher, fullPath);
|
|
8108
8123
|
return result;
|
|
8109
8124
|
}
|
|
8110
8125
|
});
|
|
@@ -8118,6 +8133,74 @@
|
|
|
8118
8133
|
}
|
|
8119
8134
|
return proxy;
|
|
8120
8135
|
}
|
|
8136
|
+
/**
|
|
8137
|
+
* Looks up the dispatcher associated with (target, path), or installs a new one.
|
|
8138
|
+
* When called for a nested target during proxy walking, the enclosing dispatcher
|
|
8139
|
+
* is reused so a single subtree fans out changes through one notification path.
|
|
8140
|
+
*/
|
|
8141
|
+
static resolveDispatcher(target, path, inheritedDispatcher) {
|
|
8142
|
+
let pathMap = this.dispatchers.get(target);
|
|
8143
|
+
if (!pathMap) {
|
|
8144
|
+
pathMap = new Map();
|
|
8145
|
+
this.dispatchers.set(target, pathMap);
|
|
8146
|
+
}
|
|
8147
|
+
const existing = pathMap.get(path);
|
|
8148
|
+
if (existing) {
|
|
8149
|
+
return existing;
|
|
8150
|
+
}
|
|
8151
|
+
const dispatcher = inheritedDispatcher ?? { listeners: new Set() };
|
|
8152
|
+
pathMap.set(path, dispatcher);
|
|
8153
|
+
return dispatcher;
|
|
8154
|
+
}
|
|
8155
|
+
/**
|
|
8156
|
+
* Invokes every listener attached to the dispatcher.
|
|
8157
|
+
* Iterates a snapshot of the listener set so unsubscribing during dispatch is safe.
|
|
8158
|
+
*/
|
|
8159
|
+
static dispatch(dispatcher, changedPath) {
|
|
8160
|
+
if (dispatcher.listeners.size === 0) {
|
|
8161
|
+
return;
|
|
8162
|
+
}
|
|
8163
|
+
const snapshot = Array.from(dispatcher.listeners);
|
|
8164
|
+
for (const listener of snapshot) {
|
|
8165
|
+
listener(changedPath);
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
/**
|
|
8169
|
+
* Subscribes a listener to changes inside the subtree of an existing reactive proxy.
|
|
8170
|
+
*
|
|
8171
|
+
* The listener is scoped by the proxy's source path: only changes at or below that
|
|
8172
|
+
* path are delivered, which lets a child component receive notifications when the
|
|
8173
|
+
* nested contents of a prop change even though the prop reference itself is unchanged.
|
|
8174
|
+
*
|
|
8175
|
+
* @param proxyOrTarget A proxy returned from create(), or the underlying target object.
|
|
8176
|
+
* @param listener Called with the full source path of every relevant change.
|
|
8177
|
+
* @returns A function that removes the subscription.
|
|
8178
|
+
*/
|
|
8179
|
+
static subscribe(proxyOrTarget, listener) {
|
|
8180
|
+
if (typeof proxyOrTarget !== 'object' || proxyOrTarget === null) {
|
|
8181
|
+
return () => { };
|
|
8182
|
+
}
|
|
8183
|
+
const target = (this.proxyToTarget.get(proxyOrTarget) ?? proxyOrTarget);
|
|
8184
|
+
const scopePath = this.proxyPaths.get(proxyOrTarget) ?? '';
|
|
8185
|
+
const pathMap = this.dispatchers.get(target);
|
|
8186
|
+
const dispatcher = pathMap?.get(scopePath);
|
|
8187
|
+
if (!dispatcher) {
|
|
8188
|
+
return () => { };
|
|
8189
|
+
}
|
|
8190
|
+
const wrapper = scopePath
|
|
8191
|
+
? (changedPath) => {
|
|
8192
|
+
if (changedPath === scopePath ||
|
|
8193
|
+
(typeof changedPath === 'string' && (changedPath.startsWith(scopePath + '.') ||
|
|
8194
|
+
changedPath.startsWith(scopePath + '[')))) {
|
|
8195
|
+
listener(changedPath);
|
|
8196
|
+
}
|
|
8197
|
+
}
|
|
8198
|
+
: listener;
|
|
8199
|
+
dispatcher.listeners.add(wrapper);
|
|
8200
|
+
return () => {
|
|
8201
|
+
dispatcher.listeners.delete(wrapper);
|
|
8202
|
+
};
|
|
8203
|
+
}
|
|
8121
8204
|
/**
|
|
8122
8205
|
* Checks if the given object is a reactive proxy.
|
|
8123
8206
|
*
|
|
@@ -8308,6 +8391,14 @@
|
|
|
8308
8391
|
* is updated by the recompute cycle through `setSilent`, which bypasses this routing.
|
|
8309
8392
|
*/
|
|
8310
8393
|
#writableComputeds = new Map();
|
|
8394
|
+
/**
|
|
8395
|
+
* Unsubscribe handles for external reactive proxies received as binding values
|
|
8396
|
+
* (typically component props). Each entry keeps the child bindings notified when
|
|
8397
|
+
* nested properties of a shared object change even though the prop reference itself
|
|
8398
|
+
* stays the same. The previous subscription is released whenever the binding value
|
|
8399
|
+
* is replaced, the binding is set to null/undefined, or the bindings are destroyed.
|
|
8400
|
+
*/
|
|
8401
|
+
#externalSubscriptions = new Map();
|
|
8311
8402
|
/**
|
|
8312
8403
|
* Creates a new instance of VBindings.
|
|
8313
8404
|
* @param parent The parent bindings, if any.
|
|
@@ -8345,6 +8436,7 @@
|
|
|
8345
8436
|
}
|
|
8346
8437
|
}
|
|
8347
8438
|
let newValue = value;
|
|
8439
|
+
let receivedExternalProxy = false;
|
|
8348
8440
|
if (typeof value === 'object' && value !== null) {
|
|
8349
8441
|
// Check if the value already has a path (it's an existing reactive proxy reference)
|
|
8350
8442
|
const existingPath = ReactiveProxy.getPath(value);
|
|
@@ -8354,6 +8446,7 @@
|
|
|
8354
8446
|
this.#logger?.debug(`Path alias registered: ${key} -> ${existingPath}`);
|
|
8355
8447
|
// Keep the existing proxy as-is to preserve reactivity chain
|
|
8356
8448
|
newValue = value;
|
|
8449
|
+
receivedExternalProxy = true;
|
|
8357
8450
|
}
|
|
8358
8451
|
else {
|
|
8359
8452
|
// Before wrapping, check if any properties are existing ReactiveProxies
|
|
@@ -8397,6 +8490,34 @@
|
|
|
8397
8490
|
}
|
|
8398
8491
|
const oldValue = Reflect.get(target, key);
|
|
8399
8492
|
const result = Reflect.set(target, key, newValue);
|
|
8493
|
+
// Manage external subscription to a reactive proxy received as the value.
|
|
8494
|
+
// When the reference changes, drop the previous subscription. When a new
|
|
8495
|
+
// reactive proxy is installed, subscribe so nested changes propagate to
|
|
8496
|
+
// this bindings instance even though the proxy reference itself is stable.
|
|
8497
|
+
if (oldValue !== newValue) {
|
|
8498
|
+
const prevUnsubscribe = this.#externalSubscriptions.get(key);
|
|
8499
|
+
if (prevUnsubscribe) {
|
|
8500
|
+
prevUnsubscribe();
|
|
8501
|
+
this.#externalSubscriptions.delete(key);
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
if (receivedExternalProxy && !this.#externalSubscriptions.has(key)) {
|
|
8505
|
+
const unsubscribe = ReactiveProxy.subscribe(newValue, (changedPath) => {
|
|
8506
|
+
if (!changedPath) {
|
|
8507
|
+
return;
|
|
8508
|
+
}
|
|
8509
|
+
let path = '';
|
|
8510
|
+
for (const part of changedPath.split('.')) {
|
|
8511
|
+
path = path ? `${path}.${part}` : part;
|
|
8512
|
+
this.#logger?.debug(`Binding changed (external): ${path}`);
|
|
8513
|
+
this.#changes.add(path);
|
|
8514
|
+
}
|
|
8515
|
+
if (!this.#suppressOnChange) {
|
|
8516
|
+
this.#onChange?.(changedPath);
|
|
8517
|
+
}
|
|
8518
|
+
});
|
|
8519
|
+
this.#externalSubscriptions.set(key, unsubscribe);
|
|
8520
|
+
}
|
|
8400
8521
|
// Detect changes
|
|
8401
8522
|
let hasChanged = oldValue !== newValue;
|
|
8402
8523
|
// Special handling for arrays: check length changes even if same object reference
|
|
@@ -8425,6 +8546,11 @@
|
|
|
8425
8546
|
},
|
|
8426
8547
|
deleteProperty: (obj, key) => {
|
|
8427
8548
|
const result = Reflect.deleteProperty(obj, key);
|
|
8549
|
+
const prevUnsubscribe = this.#externalSubscriptions.get(key);
|
|
8550
|
+
if (prevUnsubscribe) {
|
|
8551
|
+
prevUnsubscribe();
|
|
8552
|
+
this.#externalSubscriptions.delete(key);
|
|
8553
|
+
}
|
|
8428
8554
|
this.#logger?.debug(`Binding deleted: ${key}`);
|
|
8429
8555
|
this.#changes.add(key);
|
|
8430
8556
|
this.#onChange?.(key);
|
|
@@ -8506,6 +8632,18 @@
|
|
|
8506
8632
|
remove(key) {
|
|
8507
8633
|
delete this.#local[key];
|
|
8508
8634
|
}
|
|
8635
|
+
/**
|
|
8636
|
+
* Releases all external proxy subscriptions held by these bindings.
|
|
8637
|
+
* Should be called when the owning application is unmounted so the parent
|
|
8638
|
+
* application's reactive objects do not keep references to listener closures
|
|
8639
|
+
* (and through them, this bindings instance) alive.
|
|
8640
|
+
*/
|
|
8641
|
+
destroy() {
|
|
8642
|
+
for (const unsubscribe of this.#externalSubscriptions.values()) {
|
|
8643
|
+
unsubscribe();
|
|
8644
|
+
}
|
|
8645
|
+
this.#externalSubscriptions.clear();
|
|
8646
|
+
}
|
|
8509
8647
|
/**
|
|
8510
8648
|
* Sets a binding value without triggering onChange callback.
|
|
8511
8649
|
* This is useful for internal updates that shouldn't trigger reactivity.
|
|
@@ -11537,6 +11675,15 @@
|
|
|
11537
11675
|
* Mouse button modifiers (MouseEvent): `.left`, `.middle`, `.right`.
|
|
11538
11676
|
* System modifiers (KeyboardEvent and MouseEvent): `.shift`, `.ctrl`, `.alt`, `.meta`, plus `.exact` to require that no other system modifiers are held.
|
|
11539
11677
|
*
|
|
11678
|
+
* Listen target and filter modifiers (two orthogonal axes):
|
|
11679
|
+
* - Listen target (where the listener is attached): `.window`, `.document`. When omitted the listener
|
|
11680
|
+
* is attached to the bound element. This is useful for global / cross-component events, e.g.
|
|
11681
|
+
* `@webtop-message.document="onMessage"`, and the listener is removed automatically on unmount.
|
|
11682
|
+
* - Filter (whether the handler runs): `.self` fires only when `event.target` is the bound element;
|
|
11683
|
+
* `.outside` fires only when `event.target` is outside the bound element (e.g. click-outside to
|
|
11684
|
+
* close a popup). `.outside` implies listening on `document` (capture phase) even without `.document`,
|
|
11685
|
+
* and `.self` / `.outside` are mutually exclusive.
|
|
11686
|
+
*
|
|
11540
11687
|
* Additionally, this directive supports lifecycle hooks:
|
|
11541
11688
|
* @mount="onMount" - Called before the VNode is mounted to the DOM element
|
|
11542
11689
|
* @mounted="onMounted" - Called after the VNode is mounted to the DOM element
|
|
@@ -11575,6 +11722,26 @@
|
|
|
11575
11722
|
* The event listener function for DOM events.
|
|
11576
11723
|
*/
|
|
11577
11724
|
#listener;
|
|
11725
|
+
/**
|
|
11726
|
+
* The resolved target the listener is attached to (element, document, or window).
|
|
11727
|
+
* Stored so destroy() removes the listener from the same target it was added to.
|
|
11728
|
+
*/
|
|
11729
|
+
#resolvedTarget;
|
|
11730
|
+
/**
|
|
11731
|
+
* The resolved capture flag. Shared by attach and destroy so they stay in sync,
|
|
11732
|
+
* since `.outside` forces capture phase regardless of the `.capture` modifier.
|
|
11733
|
+
*/
|
|
11734
|
+
#useCapture = false;
|
|
11735
|
+
/**
|
|
11736
|
+
* Whether the listener has actually been attached. `.outside` defers attachment by a
|
|
11737
|
+
* microtask, so destroy() must not attempt removal before it is attached.
|
|
11738
|
+
*/
|
|
11739
|
+
#attached = false;
|
|
11740
|
+
/**
|
|
11741
|
+
* Whether the directive has been destroyed. Guards the deferred `.outside` attachment
|
|
11742
|
+
* from attaching after the node was already unmounted.
|
|
11743
|
+
*/
|
|
11744
|
+
#destroyed = false;
|
|
11578
11745
|
/**
|
|
11579
11746
|
* Map of lifecycle hook names to their handler functions.
|
|
11580
11747
|
*/
|
|
@@ -11598,6 +11765,12 @@
|
|
|
11598
11765
|
this.#eventName = parts[0];
|
|
11599
11766
|
parts.slice(1).forEach(mod => this.#modifiers.add(mod));
|
|
11600
11767
|
}
|
|
11768
|
+
// `.self` and `.outside` are mutually exclusive filters; together they can never fire.
|
|
11769
|
+
if (this.#modifiers.has('self') && this.#modifiers.has('outside')) {
|
|
11770
|
+
context.vNode.vApplication.logManager
|
|
11771
|
+
.getLogger('VOnDirective')
|
|
11772
|
+
.warn(`The '.self' and '.outside' modifiers on '${attrName}' are mutually exclusive; the handler will never fire.`);
|
|
11773
|
+
}
|
|
11601
11774
|
// Parse the expression to extract identifiers and create the handler wrapper.
|
|
11602
11775
|
// Event handlers are parsed in script mode so that users can write multi-statement bodies
|
|
11603
11776
|
// (e.g. "a=1; b=2"), declarations, and control-flow constructs — matching Vue semantics.
|
|
@@ -11707,11 +11880,10 @@
|
|
|
11707
11880
|
* @inheritdoc
|
|
11708
11881
|
*/
|
|
11709
11882
|
destroy() {
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
element.removeEventListener(this.#eventName, this.#listener, useCapture);
|
|
11883
|
+
this.#destroyed = true;
|
|
11884
|
+
// Remove the event listener from the same target/phase it was attached to.
|
|
11885
|
+
if (this.#eventName && this.#listener && this.#resolvedTarget && this.#attached) {
|
|
11886
|
+
this.#resolvedTarget.removeEventListener(this.#eventName, this.#listener, this.#useCapture);
|
|
11715
11887
|
}
|
|
11716
11888
|
}
|
|
11717
11889
|
/**
|
|
@@ -11727,8 +11899,21 @@
|
|
|
11727
11899
|
}
|
|
11728
11900
|
const element = this.#vNode.node;
|
|
11729
11901
|
const eventName = this.#eventName;
|
|
11730
|
-
const useCapture = this.#modifiers.has('capture');
|
|
11731
11902
|
const isOnce = this.#modifiers.has('once');
|
|
11903
|
+
const isOutside = this.#modifiers.has('outside');
|
|
11904
|
+
// Resolve the listen target (orthogonal to filters): `.window` / `.document` attach
|
|
11905
|
+
// the listener globally; `.outside` also requires a global listener to detect events
|
|
11906
|
+
// originating outside the element, so it implies `document`.
|
|
11907
|
+
this.#resolvedTarget = this.#modifiers.has('window')
|
|
11908
|
+
? window
|
|
11909
|
+
: (this.#modifiers.has('document') || isOutside)
|
|
11910
|
+
? document
|
|
11911
|
+
: element;
|
|
11912
|
+
// `.outside` listens in capture phase so it is not suppressed by a descendant's
|
|
11913
|
+
// stopPropagation(); otherwise the capture flag follows the `.capture` modifier.
|
|
11914
|
+
this.#useCapture = this.#modifiers.has('capture') || isOutside;
|
|
11915
|
+
const useCapture = this.#useCapture;
|
|
11916
|
+
const target = this.#resolvedTarget;
|
|
11732
11917
|
// System modifier keys (held during the event) shared by KeyboardEvent and MouseEvent.
|
|
11733
11918
|
const systemModifiers = ['shift', 'ctrl', 'alt', 'meta'];
|
|
11734
11919
|
// Create the event listener function
|
|
@@ -11814,6 +11999,14 @@
|
|
|
11814
11999
|
if (this.#modifiers.has('self') && event.target !== element) {
|
|
11815
12000
|
return;
|
|
11816
12001
|
}
|
|
12002
|
+
// `.outside`: only fire when the event originates outside the bound element.
|
|
12003
|
+
// A non-Node target (e.g. window) is treated as outside.
|
|
12004
|
+
if (isOutside) {
|
|
12005
|
+
const eventTarget = event.target;
|
|
12006
|
+
if (eventTarget instanceof Node && element.contains(eventTarget)) {
|
|
12007
|
+
return;
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
11817
12010
|
// Call the pre-generated handler wrapper (if exists)
|
|
11818
12011
|
if (this.#handlerWrapper) {
|
|
11819
12012
|
this.#handlerWrapper(event);
|
|
@@ -11822,11 +12015,26 @@
|
|
|
11822
12015
|
// No need to manually call scheduleUpdate() here
|
|
11823
12016
|
// If 'once' modifier is used, remove the listener after first execution
|
|
11824
12017
|
if (isOnce && this.#listener) {
|
|
11825
|
-
|
|
12018
|
+
target.removeEventListener(eventName, this.#listener, useCapture);
|
|
12019
|
+
this.#attached = false;
|
|
11826
12020
|
}
|
|
11827
12021
|
};
|
|
11828
|
-
|
|
11829
|
-
|
|
12022
|
+
if (isOutside) {
|
|
12023
|
+
// Defer attachment by one microtask so the listener does not catch the same
|
|
12024
|
+
// interaction that mounted this element (e.g. the click that opened a popup,
|
|
12025
|
+
// which would otherwise immediately close it). Skip if already destroyed.
|
|
12026
|
+
queueMicrotask(() => {
|
|
12027
|
+
if (this.#destroyed || !this.#listener) {
|
|
12028
|
+
return;
|
|
12029
|
+
}
|
|
12030
|
+
target.addEventListener(eventName, this.#listener, useCapture);
|
|
12031
|
+
this.#attached = true;
|
|
12032
|
+
});
|
|
12033
|
+
}
|
|
12034
|
+
else {
|
|
12035
|
+
target.addEventListener(eventName, this.#listener, useCapture);
|
|
12036
|
+
this.#attached = true;
|
|
12037
|
+
}
|
|
11830
12038
|
}
|
|
11831
12039
|
/**
|
|
11832
12040
|
* Checks if the event name is a lifecycle hook.
|
|
@@ -13510,6 +13718,7 @@
|
|
|
13510
13718
|
this.#vNode.destroy();
|
|
13511
13719
|
this.#vNode = undefined;
|
|
13512
13720
|
}
|
|
13721
|
+
this.#bindings?.destroy();
|
|
13513
13722
|
this.#logger.info('Application unmounted.');
|
|
13514
13723
|
}
|
|
13515
13724
|
/**
|
|
@@ -13610,6 +13819,7 @@
|
|
|
13610
13819
|
// Inject utility methods into bindings
|
|
13611
13820
|
this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
|
|
13612
13821
|
this.#bindings.set('$markRaw', (obj) => ReactiveProxy.markRaw(obj));
|
|
13822
|
+
this.#bindings.set('$emit', (name, detail, options) => this.#emit(name, detail, options));
|
|
13613
13823
|
// Add methods
|
|
13614
13824
|
if (this.#options.methods) {
|
|
13615
13825
|
for (const [key, method] of Object.entries(this.#options.methods)) {
|
|
@@ -13855,6 +14065,38 @@
|
|
|
13855
14065
|
compute(key);
|
|
13856
14066
|
}
|
|
13857
14067
|
}
|
|
14068
|
+
/**
|
|
14069
|
+
* Dispatches a CustomEvent, providing the framework-level `$emit` available in expressions
|
|
14070
|
+
* and methods. By default the event is dispatched on the application root element with
|
|
14071
|
+
* `bubbles: true`, so a parent component can listen for it via `v-on` / `@` on the component
|
|
14072
|
+
* tag (the root is rendered inside the host custom element, so the event bubbles out of it).
|
|
14073
|
+
*
|
|
14074
|
+
* The dispatch target can be overridden via `options.target` (e.g. `document` / `window`) to
|
|
14075
|
+
* use a global event bus, interoperating with native `addEventListener` listeners.
|
|
14076
|
+
*
|
|
14077
|
+
* @param name The event name (e.g. "selected"). Listened to as `@selected` on the parent side.
|
|
14078
|
+
* @param detail The payload exposed as `event.detail`.
|
|
14079
|
+
* @param options Dispatch options (bubbles, cancelable, composed, target).
|
|
14080
|
+
* @returns The result of dispatchEvent: false if a listener called preventDefault(), otherwise true.
|
|
14081
|
+
*/
|
|
14082
|
+
#emit(name, detail, options) {
|
|
14083
|
+
// Documentation/validation only: warn when emitting an event not declared in `emits`.
|
|
14084
|
+
if (this.#options.emits && !this.#options.emits.includes(name)) {
|
|
14085
|
+
this.#logger.warn(`Event '${name}' is emitted but not declared in the 'emits' option.`);
|
|
14086
|
+
}
|
|
14087
|
+
const target = options?.target ?? this.#vNode?.node;
|
|
14088
|
+
if (!target) {
|
|
14089
|
+
this.#logger.warn(`$emit('${name}') was called before the application was mounted; the event was not dispatched.`);
|
|
14090
|
+
return false;
|
|
14091
|
+
}
|
|
14092
|
+
const event = new CustomEvent(name, {
|
|
14093
|
+
detail,
|
|
14094
|
+
bubbles: options?.bubbles ?? true,
|
|
14095
|
+
cancelable: options?.cancelable ?? true,
|
|
14096
|
+
composed: options?.composed ?? false,
|
|
14097
|
+
});
|
|
14098
|
+
return target.dispatchEvent(event);
|
|
14099
|
+
}
|
|
13858
14100
|
/**
|
|
13859
14101
|
* Executes a callback after the next DOM update.
|
|
13860
14102
|
* @param callback The callback to execute.
|
|
@@ -14147,7 +14389,7 @@
|
|
|
14147
14389
|
* @param options Component options including template selector and optional props.
|
|
14148
14390
|
*/
|
|
14149
14391
|
function defineComponent(tagName, options) {
|
|
14150
|
-
const { props = [], template, data, computed, methods, watch, logLevel } = options;
|
|
14392
|
+
const { props = [], template, data, computed, methods, watch, emits, logLevel } = options;
|
|
14151
14393
|
// Build a subclass of IchigoElement specific to this component
|
|
14152
14394
|
class ComponentElement extends IchigoElement {
|
|
14153
14395
|
static _template = template;
|
|
@@ -14169,6 +14411,7 @@
|
|
|
14169
14411
|
computed,
|
|
14170
14412
|
methods,
|
|
14171
14413
|
watch,
|
|
14414
|
+
emits,
|
|
14172
14415
|
logLevel,
|
|
14173
14416
|
};
|
|
14174
14417
|
}
|