@lwc/engine-core 8.18.1 → 8.18.2

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.
@@ -0,0 +1,5 @@
1
+ import type { VM } from '../vm';
2
+ import type { VBaseElement } from '../vnodes';
3
+ import type { RendererAPI } from '../renderer';
4
+ export declare function patchDynamicEventListeners(oldVnode: VBaseElement | null, vnode: VBaseElement, renderer: RendererAPI, owner: VM): void;
5
+ //# sourceMappingURL=dynamic-events.d.ts.map
@@ -72,6 +72,8 @@ export interface VM<N = HostNode, E = HostElement> {
72
72
  readonly owner: VM<N, E> | null;
73
73
  /** References to elements rendered using lwc:ref (template refs) */
74
74
  refVNodes: RefVNodes | null;
75
+ /** event listeners added to elements corresponding to functions provided by lwc:on */
76
+ attachedEventListeners: WeakMap<Element, Record<string, EventListener | undefined>>;
75
77
  /** Whether or not the VM was hydrated */
76
78
  readonly hydrated: boolean;
77
79
  /** Rendering operations associated with the VM */
@@ -109,6 +109,8 @@ export interface VNodeData {
109
109
  readonly styleDecls?: ReadonlyArray<[string, string, boolean]>;
110
110
  readonly context?: Readonly<Record<string, Readonly<Record<string, any>>>>;
111
111
  readonly on?: Readonly<Record<string, (event: Event) => any>>;
112
+ readonly dynamicOn?: Readonly<Record<string, (event: Event) => any>>;
113
+ readonly dynamicOnRaw?: Readonly<Record<string, (event: Event) => any>>;
112
114
  readonly svg?: boolean;
113
115
  readonly renderer?: RendererAPI;
114
116
  }
package/dist/index.cjs.js CHANGED
@@ -251,16 +251,6 @@ function shouldBeFormAssociated(Ctor) {
251
251
  }
252
252
  return ctorFormAssociated && apiFeatureEnabled;
253
253
  }
254
- // check if a property is in an object, and if the object throws an error merely because we are
255
- // checking if the property exists, return false
256
- function safeHasProp(obj, prop) {
257
- try {
258
- return prop in obj;
259
- }
260
- catch (_err) {
261
- return false;
262
- }
263
- }
264
254
 
265
255
  /*
266
256
  * Copyright (c) 2024, Salesforce, Inc.
@@ -620,9 +610,6 @@ function componentValueObserved(vm, key, target = {}) {
620
610
  if (lwcRuntimeFlags.ENABLE_EXPERIMENTAL_SIGNALS &&
621
611
  shared.isObject(target) &&
622
612
  !shared.isNull(target) &&
623
- safeHasProp(target, 'value') &&
624
- safeHasProp(target, 'subscribe') &&
625
- shared.isFunction(target.subscribe) &&
626
613
  shared.isTrustedSignal(target) &&
627
614
  // Only subscribe if a template is being rendered by the engine
628
615
  tro.isObserving()) {
@@ -4243,6 +4230,73 @@ function applyEventListeners(vnode, renderer) {
4243
4230
  }
4244
4231
  }
4245
4232
 
4233
+ /*
4234
+ * Copyright (c) 2025, salesforce.com, inc.
4235
+ * All rights reserved.
4236
+ * SPDX-License-Identifier: MIT
4237
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
4238
+ */
4239
+ function patchDynamicEventListeners(oldVnode, vnode, renderer, owner) {
4240
+ const { elm, data: { dynamicOn, dynamicOnRaw }, sel, } = vnode;
4241
+ // dynamicOn : A cloned version of the object passed to lwc:on, with null prototype and only its own enumerable properties.
4242
+ const oldDynamicOn = oldVnode?.data?.dynamicOn ?? EmptyObject;
4243
+ const newDynamicOn = dynamicOn ?? EmptyObject;
4244
+ // dynamicOnRaw : object passed to lwc:on
4245
+ // Compare dynamicOnRaw to check if same object is passed to lwc:on
4246
+ const isObjectSame = oldVnode?.data?.dynamicOnRaw === dynamicOnRaw;
4247
+ const { addEventListener, removeEventListener } = renderer;
4248
+ const attachedEventListeners = getAttachedEventListeners(owner, elm);
4249
+ // Properties that are present in 'oldDynamicOn' but not in 'newDynamicOn'
4250
+ for (const eventType in oldDynamicOn) {
4251
+ if (!(eventType in newDynamicOn)) {
4252
+ // log error if same object is passed
4253
+ if (isObjectSame && process.env.NODE_ENV !== 'production') {
4254
+ logError(`Detected mutation of property '${eventType}' in the object passed to lwc:on for <${sel}>. Reusing the same object with modified properties is prohibited. Please pass a new object instead.`, owner);
4255
+ }
4256
+ // Remove listeners that were attached previously but don't have a corresponding property in `newDynamicOn`
4257
+ const attachedEventListener = attachedEventListeners[eventType];
4258
+ removeEventListener(elm, eventType, attachedEventListener);
4259
+ attachedEventListeners[eventType] = undefined;
4260
+ }
4261
+ }
4262
+ // Ensure that the event listeners that are attached match what is present in `newDynamicOn`
4263
+ for (const eventType in newDynamicOn) {
4264
+ const typeExistsInOld = eventType in oldDynamicOn;
4265
+ const newCallback = newDynamicOn[eventType];
4266
+ // Skip if callback hasn't changed
4267
+ if (typeExistsInOld && oldDynamicOn[eventType] === newCallback) {
4268
+ continue;
4269
+ }
4270
+ // log error if same object is passed
4271
+ if (isObjectSame && process.env.NODE_ENV !== 'production') {
4272
+ logError(`Detected mutation of property '${eventType}' in the object passed to lwc:on for <${sel}>. Reusing the same object with modified properties is prohibited. Please pass a new object instead.`, owner);
4273
+ }
4274
+ // Remove listener that was attached previously
4275
+ if (typeExistsInOld) {
4276
+ const attachedEventListener = attachedEventListeners[eventType];
4277
+ removeEventListener(elm, eventType, attachedEventListener);
4278
+ }
4279
+ // Bind new callback to owner component and add it as listener to element
4280
+ const newBoundEventListener = bindEventListener(owner, newCallback);
4281
+ addEventListener(elm, eventType, newBoundEventListener);
4282
+ // Store the newly added eventListener
4283
+ attachedEventListeners[eventType] = newBoundEventListener;
4284
+ }
4285
+ }
4286
+ function getAttachedEventListeners(vm, elm) {
4287
+ let attachedEventListeners = vm.attachedEventListeners.get(elm);
4288
+ if (shared.isUndefined(attachedEventListeners)) {
4289
+ attachedEventListeners = {};
4290
+ vm.attachedEventListeners.set(elm, attachedEventListeners);
4291
+ }
4292
+ return attachedEventListeners;
4293
+ }
4294
+ function bindEventListener(vm, fn) {
4295
+ return function (event) {
4296
+ invokeEventListener(vm, fn, vm.component, event);
4297
+ };
4298
+ }
4299
+
4246
4300
  /*
4247
4301
  * Copyright (c) 2018, salesforce.com, inc.
4248
4302
  * All rights reserved.
@@ -4895,6 +4949,7 @@ function patchElementPropsAndAttrsAndRefs$1(oldVnode, vnode, renderer) {
4895
4949
  applyStaticStyleAttribute(vnode, renderer);
4896
4950
  }
4897
4951
  const { owner } = vnode;
4952
+ patchDynamicEventListeners(oldVnode, vnode, renderer, owner);
4898
4953
  // Attrs need to be applied to element before props IE11 will wipe out value on radio inputs if
4899
4954
  // value is set before type=radio.
4900
4955
  patchClassAttribute(oldVnode, vnode, renderer);
@@ -6776,6 +6831,7 @@ function createVM(elm, ctor, renderer, options) {
6776
6831
  mode,
6777
6832
  owner,
6778
6833
  refVNodes: null,
6834
+ attachedEventListeners: new WeakMap(),
6779
6835
  children: EmptyArray,
6780
6836
  aChildren: EmptyArray,
6781
6837
  velements: EmptyArray,
@@ -7982,6 +8038,7 @@ function handleMismatch(node, vnode, renderer) {
7982
8038
  }
7983
8039
  function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7984
8040
  applyEventListeners(vnode, renderer);
8041
+ patchDynamicEventListeners(null, vnode, renderer, vnode.owner);
7985
8042
  patchProps(null, vnode, renderer);
7986
8043
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7987
8044
  applyRefs(vnode, vnode.owner);
@@ -8529,5 +8586,5 @@ exports.swapTemplate = swapTemplate;
8529
8586
  exports.track = track;
8530
8587
  exports.unwrap = unwrap;
8531
8588
  exports.wire = wire;
8532
- /** version: 8.18.1 */
8589
+ /** version: 8.18.2 */
8533
8590
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.js CHANGED
@@ -248,16 +248,6 @@ function shouldBeFormAssociated(Ctor) {
248
248
  }
249
249
  return ctorFormAssociated && apiFeatureEnabled;
250
250
  }
251
- // check if a property is in an object, and if the object throws an error merely because we are
252
- // checking if the property exists, return false
253
- function safeHasProp(obj, prop) {
254
- try {
255
- return prop in obj;
256
- }
257
- catch (_err) {
258
- return false;
259
- }
260
- }
261
251
 
262
252
  /*
263
253
  * Copyright (c) 2024, Salesforce, Inc.
@@ -617,9 +607,6 @@ function componentValueObserved(vm, key, target = {}) {
617
607
  if (lwcRuntimeFlags.ENABLE_EXPERIMENTAL_SIGNALS &&
618
608
  isObject(target) &&
619
609
  !isNull(target) &&
620
- safeHasProp(target, 'value') &&
621
- safeHasProp(target, 'subscribe') &&
622
- isFunction$1(target.subscribe) &&
623
610
  isTrustedSignal(target) &&
624
611
  // Only subscribe if a template is being rendered by the engine
625
612
  tro.isObserving()) {
@@ -4240,6 +4227,73 @@ function applyEventListeners(vnode, renderer) {
4240
4227
  }
4241
4228
  }
4242
4229
 
4230
+ /*
4231
+ * Copyright (c) 2025, salesforce.com, inc.
4232
+ * All rights reserved.
4233
+ * SPDX-License-Identifier: MIT
4234
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
4235
+ */
4236
+ function patchDynamicEventListeners(oldVnode, vnode, renderer, owner) {
4237
+ const { elm, data: { dynamicOn, dynamicOnRaw }, sel, } = vnode;
4238
+ // dynamicOn : A cloned version of the object passed to lwc:on, with null prototype and only its own enumerable properties.
4239
+ const oldDynamicOn = oldVnode?.data?.dynamicOn ?? EmptyObject;
4240
+ const newDynamicOn = dynamicOn ?? EmptyObject;
4241
+ // dynamicOnRaw : object passed to lwc:on
4242
+ // Compare dynamicOnRaw to check if same object is passed to lwc:on
4243
+ const isObjectSame = oldVnode?.data?.dynamicOnRaw === dynamicOnRaw;
4244
+ const { addEventListener, removeEventListener } = renderer;
4245
+ const attachedEventListeners = getAttachedEventListeners(owner, elm);
4246
+ // Properties that are present in 'oldDynamicOn' but not in 'newDynamicOn'
4247
+ for (const eventType in oldDynamicOn) {
4248
+ if (!(eventType in newDynamicOn)) {
4249
+ // log error if same object is passed
4250
+ if (isObjectSame && process.env.NODE_ENV !== 'production') {
4251
+ logError(`Detected mutation of property '${eventType}' in the object passed to lwc:on for <${sel}>. Reusing the same object with modified properties is prohibited. Please pass a new object instead.`, owner);
4252
+ }
4253
+ // Remove listeners that were attached previously but don't have a corresponding property in `newDynamicOn`
4254
+ const attachedEventListener = attachedEventListeners[eventType];
4255
+ removeEventListener(elm, eventType, attachedEventListener);
4256
+ attachedEventListeners[eventType] = undefined;
4257
+ }
4258
+ }
4259
+ // Ensure that the event listeners that are attached match what is present in `newDynamicOn`
4260
+ for (const eventType in newDynamicOn) {
4261
+ const typeExistsInOld = eventType in oldDynamicOn;
4262
+ const newCallback = newDynamicOn[eventType];
4263
+ // Skip if callback hasn't changed
4264
+ if (typeExistsInOld && oldDynamicOn[eventType] === newCallback) {
4265
+ continue;
4266
+ }
4267
+ // log error if same object is passed
4268
+ if (isObjectSame && process.env.NODE_ENV !== 'production') {
4269
+ logError(`Detected mutation of property '${eventType}' in the object passed to lwc:on for <${sel}>. Reusing the same object with modified properties is prohibited. Please pass a new object instead.`, owner);
4270
+ }
4271
+ // Remove listener that was attached previously
4272
+ if (typeExistsInOld) {
4273
+ const attachedEventListener = attachedEventListeners[eventType];
4274
+ removeEventListener(elm, eventType, attachedEventListener);
4275
+ }
4276
+ // Bind new callback to owner component and add it as listener to element
4277
+ const newBoundEventListener = bindEventListener(owner, newCallback);
4278
+ addEventListener(elm, eventType, newBoundEventListener);
4279
+ // Store the newly added eventListener
4280
+ attachedEventListeners[eventType] = newBoundEventListener;
4281
+ }
4282
+ }
4283
+ function getAttachedEventListeners(vm, elm) {
4284
+ let attachedEventListeners = vm.attachedEventListeners.get(elm);
4285
+ if (isUndefined$1(attachedEventListeners)) {
4286
+ attachedEventListeners = {};
4287
+ vm.attachedEventListeners.set(elm, attachedEventListeners);
4288
+ }
4289
+ return attachedEventListeners;
4290
+ }
4291
+ function bindEventListener(vm, fn) {
4292
+ return function (event) {
4293
+ invokeEventListener(vm, fn, vm.component, event);
4294
+ };
4295
+ }
4296
+
4243
4297
  /*
4244
4298
  * Copyright (c) 2018, salesforce.com, inc.
4245
4299
  * All rights reserved.
@@ -4892,6 +4946,7 @@ function patchElementPropsAndAttrsAndRefs$1(oldVnode, vnode, renderer) {
4892
4946
  applyStaticStyleAttribute(vnode, renderer);
4893
4947
  }
4894
4948
  const { owner } = vnode;
4949
+ patchDynamicEventListeners(oldVnode, vnode, renderer, owner);
4895
4950
  // Attrs need to be applied to element before props IE11 will wipe out value on radio inputs if
4896
4951
  // value is set before type=radio.
4897
4952
  patchClassAttribute(oldVnode, vnode, renderer);
@@ -6773,6 +6828,7 @@ function createVM(elm, ctor, renderer, options) {
6773
6828
  mode,
6774
6829
  owner,
6775
6830
  refVNodes: null,
6831
+ attachedEventListeners: new WeakMap(),
6776
6832
  children: EmptyArray,
6777
6833
  aChildren: EmptyArray,
6778
6834
  velements: EmptyArray,
@@ -7979,6 +8035,7 @@ function handleMismatch(node, vnode, renderer) {
7979
8035
  }
7980
8036
  function patchElementPropsAndAttrsAndRefs(vnode, renderer) {
7981
8037
  applyEventListeners(vnode, renderer);
8038
+ patchDynamicEventListeners(null, vnode, renderer, vnode.owner);
7982
8039
  patchProps(null, vnode, renderer);
7983
8040
  // The `refs` object is blown away in every re-render, so we always need to re-apply them
7984
8041
  applyRefs(vnode, vnode.owner);
@@ -8475,5 +8532,5 @@ function readonly(obj) {
8475
8532
  }
8476
8533
 
8477
8534
  export { BaseBridgeElement, LightningElement, profilerControl as __unstable__ProfilerControl, reportingControl as __unstable__ReportingControl, api$1 as api, computeShadowAndRenderMode, connectRootElement, createContextProviderWithRegister, createVM, disconnectRootElement, freezeTemplate, getAssociatedVMIfPresent, getComponentAPIVersion, getComponentConstructor, getComponentDef, getComponentHtmlPrototype, hydrateRoot, isComponentConstructor, parseFragment, parseSVGFragment, readonly, registerComponent, registerDecorators, registerTemplate, runFormAssociatedCallback, runFormDisabledCallback, runFormResetCallback, runFormStateRestoreCallback, sanitizeAttribute, shouldBeFormAssociated, swapComponent, swapStyle, swapTemplate, track, unwrap, wire };
8478
- /** version: 8.18.1 */
8535
+ /** version: 8.18.2 */
8479
8536
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/engine-core",
7
- "version": "8.18.1",
7
+ "version": "8.18.2",
8
8
  "description": "Core LWC engine APIs.",
9
9
  "keywords": [
10
10
  "lwc"
@@ -46,9 +46,9 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@lwc/features": "8.18.1",
50
- "@lwc/shared": "8.18.1",
51
- "@lwc/signals": "8.18.1"
49
+ "@lwc/features": "8.18.2",
50
+ "@lwc/shared": "8.18.2",
51
+ "@lwc/signals": "8.18.2"
52
52
  },
53
53
  "devDependencies": {
54
54
  "observable-membrane": "2.0.0"