fix(vue): update output target and properly emit events (#30227)

Issue number: resolves #30206 resolves #30178 resolves #30177 resolves
#30175 resolves #30170

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
There have been plenty of issues reported in regards to Vue components
failing to propagate events. It seems like when we updated the Vue
output target and started to use the provided runtime code from the
output target, we have changed the way how event names are computed.
Ionic has used a custom wrapper for handling events that would kebab
case event names. That is no longer needed and removing it fixes
observed issues.

Reproduction case working:
https://stackblitz.com/edit/vj18czas-wdhzxjom?file=package.json

## What is the new behavior?
We have received a fix for this in
https://github.com/stenciljs/output-targets/pull/617 which I hope will
resolve this issue by updating the dependency.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

Dev build: `8.4.4-dev.11741193800.14916f6f`
This commit is contained in:
Christian Bromann
2025-03-11 13:39:31 -07:00
committed by GitHub
parent ba8d8f4896
commit 11554a5d35
9 changed files with 43 additions and 102 deletions

15
core/package-lock.json generated
View File

@ -28,7 +28,7 @@
"@stencil/angular-output-target": "^0.10.0",
"@stencil/react-output-target": "0.5.3",
"@stencil/sass": "^3.0.9",
"@stencil/vue-output-target": "^0.9.0",
"@stencil/vue-output-target": "^0.9.6",
"@types/jest": "^29.5.6",
"@types/node": "^14.6.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",
@ -1850,10 +1850,11 @@
}
},
"node_modules/@stencil/vue-output-target": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.2.tgz",
"integrity": "sha512-AeBmfo8bQhtob4VKpYTNiCoqh50MeXUwRgYLyO/JxRgAAK9GSfenNrUxXDrK0DK65SWsx/GCOsRwWbfOveorOQ==",
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.6.tgz",
"integrity": "sha512-IxLknP+bZ2Di3EOEtd8ozh/9JHyoFyvfO+gapO7IpSjT1zFoEfIpFN0/IZPKN6VNI5lMrQ3BFIRs9C689g7VsQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0",
"vue": "^3.4.38"
@ -11834,9 +11835,9 @@
"requires": {}
},
"@stencil/vue-output-target": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.2.tgz",
"integrity": "sha512-AeBmfo8bQhtob4VKpYTNiCoqh50MeXUwRgYLyO/JxRgAAK9GSfenNrUxXDrK0DK65SWsx/GCOsRwWbfOveorOQ==",
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.6.tgz",
"integrity": "sha512-IxLknP+bZ2Di3EOEtd8ozh/9JHyoFyvfO+gapO7IpSjT1zFoEfIpFN0/IZPKN6VNI5lMrQ3BFIRs9C689g7VsQ==",
"dev": true,
"requires": {}
},

View File

@ -50,7 +50,7 @@
"@stencil/angular-output-target": "^0.10.0",
"@stencil/react-output-target": "0.5.3",
"@stencil/sass": "^3.0.9",
"@stencil/vue-output-target": "^0.9.0",
"@stencil/vue-output-target": "^0.9.6",
"@types/jest": "^29.5.6",
"@types/node": "^14.6.0",
"@typescript-eslint/eslint-plugin": "^6.7.2",

View File

@ -1,4 +1,4 @@
import { getMode, setMode, setPlatformHelpers } from '@stencil/core';
import { getMode, setMode } from '@stencil/core';
import type { IonicConfig, Mode } from '../interface';
import { isPlatform, setupPlatforms } from '../utils/platform';
@ -22,18 +22,6 @@ export const initialize = (userConfig: IonicConfig = {}) => {
const win = window;
const Ionic = ((win as any).Ionic = (win as any).Ionic || {});
const platformHelpers: any = {};
if (userConfig._ael) {
platformHelpers.ael = userConfig._ael;
}
if (userConfig._rel) {
platformHelpers.rel = userConfig._rel;
}
if (userConfig._ce) {
platformHelpers.ce = userConfig._ce;
}
setPlatformHelpers(platformHelpers);
// create the Ionic.config from raw config object (if it exists)
// and convert Ionic.config into a ConfigApi that has a get() fn
const configObj = {

View File

@ -234,9 +234,6 @@ export interface IonicConfig {
_forceStatusbarPadding?: boolean;
_testing?: boolean;
_zoneGate?: (h: () => any) => any;
_ael?: (el: any, name: string, cb: any, opts: any) => any;
_rel?: (el: any, name: string, cb: any, opts: any) => any;
_ce?: (eventName: string, opts: any) => any;
}
type FocusManagerPriority = 'content' | 'heading' | 'banner';

View File

@ -42,7 +42,7 @@ const transitionEnd = (el: HTMLElement | null, expectedDuration = 0, callback: (
if (el) {
el.addEventListener('webkitTransitionEnd', onTransitionEnd, opts);
el.addEventListener('transitionend', onTransitionEnd, opts);
animationTimeout = setTimeout(onTransitionEnd, expectedDuration + ANIMATION_FALLBACK_TIMEOUT);
animationTimeout = setTimeout(onTransitionEnd, expectedDuration + ANIMATION_FALLBACK_TIMEOUT) as unknown as number;
unRegTrans = () => {
if (animationTimeout !== undefined) {
@ -190,36 +190,10 @@ export const inheritAriaAttributes = (el: HTMLElement, ignoreList?: string[]) =>
};
export const addEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
if (typeof (window as any) !== 'undefined') {
const win = window as any;
const config = win?.Ionic?.config;
if (config) {
const ael = config.get('_ael');
if (ael) {
return ael(el, eventName, callback, opts);
} else if (config._ael) {
return config._ael(el, eventName, callback, opts);
}
}
}
return el.addEventListener(eventName, callback, opts);
};
export const removeEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
if (typeof (window as any) !== 'undefined') {
const win = window as any;
const config = win?.Ionic?.config;
if (config) {
const rel = config.get('_rel');
if (rel) {
return rel(el, eventName, callback, opts);
} else if (config._rel) {
return config._rel(el, eventName, callback, opts);
}
}
}
return el.removeEventListener(eventName, callback, opts);
};

View File

@ -18,7 +18,7 @@
"@ionic/prettier-config": "^2.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^11.1.5",
"@stencil/vue-output-target": "0.9.4",
"@stencil/vue-output-target": "0.9.6",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"change-case": "^4.1.1",
@ -691,9 +691,9 @@
}
},
"node_modules/@stencil/vue-output-target": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.4.tgz",
"integrity": "sha512-nXt1ZKjQ8n+ZaKbj1gcutqcgt7SCwVYzNxa1LfKpKz4L1DST33k1/goahvFeWO/lJzLm47spPtHfcjeaLUg/iQ==",
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.6.tgz",
"integrity": "sha512-IxLknP+bZ2Di3EOEtd8ozh/9JHyoFyvfO+gapO7IpSjT1zFoEfIpFN0/IZPKN6VNI5lMrQ3BFIRs9C689g7VsQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@ -4342,9 +4342,9 @@
"integrity": "sha512-WPrTHFngvN081RY+dJPneKQLwnOFD60OMCOQGmmSHfCW0f4ujPMzzhwWU1gcSwXPWXz5O+8cBiiCaxAbJU7kAg=="
},
"@stencil/vue-output-target": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.4.tgz",
"integrity": "sha512-nXt1ZKjQ8n+ZaKbj1gcutqcgt7SCwVYzNxa1LfKpKz4L1DST33k1/goahvFeWO/lJzLm47spPtHfcjeaLUg/iQ==",
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.9.6.tgz",
"integrity": "sha512-IxLknP+bZ2Di3EOEtd8ozh/9JHyoFyvfO+gapO7IpSjT1zFoEfIpFN0/IZPKN6VNI5lMrQ3BFIRs9C689g7VsQ==",
"dev": true,
"requires": {}
},

View File

@ -54,7 +54,7 @@
"@ionic/prettier-config": "^2.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^11.1.5",
"@stencil/vue-output-target": "0.9.4",
"@stencil/vue-output-target": "0.9.6",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"change-case": "^4.1.1",

View File

@ -2,25 +2,6 @@ import type { IonicConfig } from "@ionic/core/components";
import { initialize } from "@ionic/core/components";
import type { App, Plugin } from "vue";
// TODO(FW-2969): types
const toKebabCase = (eventName: string) => {
return eventName
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2")
.toLowerCase();
};
const getHelperFunctions = () => {
return {
ael: (el: any, eventName: string, cb: any, opts: any) =>
el.addEventListener(toKebabCase(eventName), cb, opts),
rel: (el: any, eventName: string, cb: any, opts: any) =>
el.removeEventListener(toKebabCase(eventName), cb, opts),
ce: (eventName: string, opts: any) =>
new CustomEvent(toKebabCase(eventName), opts),
};
};
export const IonicVue: Plugin<[IonicConfig?]> = {
async install(_: App, config: IonicConfig = {}) {
/**
@ -34,12 +15,6 @@ export const IonicVue: Plugin<[IonicConfig?]> = {
document.documentElement.classList.add("ion-ce");
}
const { ael, rel, ce } = getHelperFunctions();
initialize({
...config,
_ael: ael,
_rel: rel,
_ce: ce,
});
initialize(config);
},
};

View File

@ -20,10 +20,10 @@ export const defineOverlayContainer = <Props extends object>(
const createControllerComponent = (options: ComponentOptions) => {
return defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
const eventListeners = [
{ componentEv: `${name}-will-present`, frameworkEv: "willPresent" },
{ componentEv: `${name}-did-present`, frameworkEv: "didPresent" },
{ componentEv: `${name}-will-dismiss`, frameworkEv: "willDismiss" },
{ componentEv: `${name}-did-dismiss`, frameworkEv: "didDismiss" },
{ componentEv: `${name}WillPresent`, frameworkEv: "willPresent" },
{ componentEv: `${name}DidPresent`, frameworkEv: "didPresent" },
{ componentEv: `${name}WillDismiss`, frameworkEv: "willDismiss" },
{ componentEv: `${name}DidDismiss`, frameworkEv: "didDismiss" },
];
if (defineCustomElement !== undefined) {
@ -139,7 +139,7 @@ export const defineOverlayContainer = <Props extends object>(
}, options);
};
const createInlineComponent = (options: any) => {
return defineComponent((props, { slots }) => {
return defineComponent((props, { slots, emit }) => {
if (defineCustomElement !== undefined) {
defineCustomElement();
}
@ -147,18 +147,24 @@ export const defineOverlayContainer = <Props extends object>(
const elementRef = ref();
onMounted(() => {
elementRef.value.addEventListener(
"ion-mount",
() => (isOpen.value = true)
);
elementRef.value.addEventListener(
"will-present",
() => (isOpen.value = true)
);
elementRef.value.addEventListener(
"did-dismiss",
() => (isOpen.value = false)
);
elementRef.value.addEventListener("ionMount", (ev: Event) => {
emit("ionMount", ev);
isOpen.value = true;
});
elementRef.value.addEventListener("willPresent", (ev: Event) => {
emit("willPresent", ev);
isOpen.value = true;
});
elementRef.value.addEventListener("didDismiss", (ev: Event) => {
emit("didDismiss", ev);
isOpen.value = false;
});
elementRef.value.addEventListener("willDismiss", (ev: Event) => {
emit("willDismiss", ev);
});
elementRef.value.addEventListener("didPresent", (ev: Event) => {
emit("didPresent", ev);
});
});
return () => {