mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 01:03:03 +08:00
fix(vue): pass router-link value to href to properly render clickable elements (#29745)
Issue number: N/A --------- ## What is the current behavior? Ionic Framework Vue components using `router-link` do not apply an `href` property which causes components to render `div` or `button` elements when they should render an `a`. This is inconsistent with the way Angular and Vue handle router link. ## What is the new behavior? Updates `@stencil/vue-output-target` to latest which adds the code from the following PR: https://github.com/ionic-team/stencil-ds-output-targets/pull/446 The update in vue output target checks if `router-link` and `navManager` are defined so this fix only applies to Ionic Framework components. If both are defined then it adds the `href` property to the element with the value of `router-link`. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Dev build: `8.2.7-dev.11722629362.1ac136c4`
This commit is contained in:
14
core/package-lock.json
generated
14
core/package-lock.json
generated
@ -28,7 +28,7 @@
|
|||||||
"@stencil/angular-output-target": "^0.8.4",
|
"@stencil/angular-output-target": "^0.8.4",
|
||||||
"@stencil/react-output-target": "^0.5.3",
|
"@stencil/react-output-target": "^0.5.3",
|
||||||
"@stencil/sass": "^3.0.9",
|
"@stencil/sass": "^3.0.9",
|
||||||
"@stencil/vue-output-target": "^0.8.7",
|
"@stencil/vue-output-target": "^0.8.9",
|
||||||
"@types/jest": "^29.5.6",
|
"@types/jest": "^29.5.6",
|
||||||
"@types/node": "^14.6.0",
|
"@types/node": "^14.6.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||||
@ -1858,9 +1858,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@stencil/vue-output-target": {
|
"node_modules/@stencil/vue-output-target": {
|
||||||
"version": "0.8.8",
|
"version": "0.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.8.9.tgz",
|
||||||
"integrity": "sha512-xrpz92lmTuLgwY9CLgl2Q14+zBXfBuXiRS6uXFPfeaFo0pe+do8cZitOOQ8i8tcoCa/tAqgD9B9CD+yQehSIGg==",
|
"integrity": "sha512-1yuapCWYViLlxGlEaeta2wryq4M5zZxxBa+4rEBp54VwW2W/trlzPv0IJyw6I3Il51rHYm2WmWlBLOGmoMyW9Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
|
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
|
||||||
@ -11602,9 +11602,9 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@stencil/vue-output-target": {
|
"@stencil/vue-output-target": {
|
||||||
"version": "0.8.8",
|
"version": "0.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.8.9.tgz",
|
||||||
"integrity": "sha512-xrpz92lmTuLgwY9CLgl2Q14+zBXfBuXiRS6uXFPfeaFo0pe+do8cZitOOQ8i8tcoCa/tAqgD9B9CD+yQehSIGg==",
|
"integrity": "sha512-1yuapCWYViLlxGlEaeta2wryq4M5zZxxBa+4rEBp54VwW2W/trlzPv0IJyw6I3Il51rHYm2WmWlBLOGmoMyW9Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
"@stencil/angular-output-target": "^0.8.4",
|
"@stencil/angular-output-target": "^0.8.4",
|
||||||
"@stencil/react-output-target": "^0.5.3",
|
"@stencil/react-output-target": "^0.5.3",
|
||||||
"@stencil/sass": "^3.0.9",
|
"@stencil/sass": "^3.0.9",
|
||||||
"@stencil/vue-output-target": "^0.8.7",
|
"@stencil/vue-output-target": "^0.8.9",
|
||||||
"@types/jest": "^29.5.6",
|
"@types/jest": "^29.5.6",
|
||||||
"@types/node": "^14.6.0",
|
"@types/node": "^14.6.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||||
|
@ -5,6 +5,9 @@ set -e
|
|||||||
# Delete old packages
|
# Delete old packages
|
||||||
rm -f *.tgz
|
rm -f *.tgz
|
||||||
|
|
||||||
|
# Delete vite cache
|
||||||
|
rm -rf node_modules/.vite
|
||||||
|
|
||||||
# Pack @ionic/core
|
# Pack @ionic/core
|
||||||
npm pack ../../core
|
npm pack ../../core
|
||||||
|
|
||||||
|
@ -91,8 +91,17 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
|
|||||||
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
|
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
|
||||||
eventsNames.forEach((eventName: string) => {
|
eventsNames.forEach((eventName: string) => {
|
||||||
el.addEventListener(eventName.toLowerCase(), (e: Event) => {
|
el.addEventListener(eventName.toLowerCase(), (e: Event) => {
|
||||||
|
/**
|
||||||
|
* Only update the v-model binding if the event's target is the element we are
|
||||||
|
* listening on. For example, Component A could emit ionChange, but it could also
|
||||||
|
* have a descendant Component B that also emits ionChange. We only want to update
|
||||||
|
* the v-model for Component A when ionChange originates from that element and not
|
||||||
|
* when ionChange bubbles up from Component B.
|
||||||
|
*/
|
||||||
|
if (e.target.tagName === el.tagName) {
|
||||||
modelPropValue = (e?.target as any)[modelProp];
|
modelPropValue = (e?.target as any)[modelProp];
|
||||||
emit(UPDATE_VALUE_EVENT, modelPropValue);
|
emit(UPDATE_VALUE_EVENT, modelPropValue);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -106,6 +115,16 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
|
|||||||
if (routerLink === EMPTY_PROP) return;
|
if (routerLink === EMPTY_PROP) return;
|
||||||
|
|
||||||
if (navManager !== undefined) {
|
if (navManager !== undefined) {
|
||||||
|
/**
|
||||||
|
* This prevents the browser from
|
||||||
|
* performing a page reload when pressing
|
||||||
|
* an Ionic component with routerLink.
|
||||||
|
* The page reload interferes with routing
|
||||||
|
* and causes ion-back-button to disappear
|
||||||
|
* since the local history is wiped on reload.
|
||||||
|
*/
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
let navigationPayload: any = { event: ev };
|
let navigationPayload: any = { event: ev };
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
const value = props[key];
|
const value = props[key];
|
||||||
@ -176,6 +195,17 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If router link is defined, add href to props
|
||||||
|
// in order to properly render an anchor tag inside
|
||||||
|
// of components that should become activatable and
|
||||||
|
// focusable with router link.
|
||||||
|
if (props[ROUTER_LINK_VALUE] !== EMPTY_PROP) {
|
||||||
|
propsToAdd = {
|
||||||
|
...propsToAdd,
|
||||||
|
href: props[ROUTER_LINK_VALUE],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vModelDirective is only needed on components that support v-model.
|
* vModelDirective is only needed on components that support v-model.
|
||||||
* As a result, we conditionally call withDirectives with v-model components.
|
* As a result, we conditionally call withDirectives with v-model components.
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item button router-link="/components/breadcrumbs">
|
<ion-item router-link="/components/breadcrumbs">
|
||||||
<ion-label>Breadcrumbs</ion-label>
|
<ion-label>Breadcrumbs</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/components/select">
|
<ion-item router-link="/components/select">
|
||||||
<ion-label>Select</ion-label>
|
<ion-label>Select</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/components/range">
|
<ion-item router-link="/components/range">
|
||||||
<ion-label>Range</ion-label>
|
<ion-label>Range</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
@ -17,43 +17,43 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item button router-link="/overlays">
|
<ion-item router-link="/overlays">
|
||||||
<ion-label>Overlays</ion-label>
|
<ion-label>Overlays</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/icons">
|
<ion-item router-link="/icons">
|
||||||
<ion-label>Icons</ion-label>
|
<ion-label>Icons</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/inputs">
|
<ion-item router-link="/inputs">
|
||||||
<ion-label>Inputs</ion-label>
|
<ion-label>Inputs</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/navigation" id="navigation">
|
<ion-item router-link="/navigation" id="navigation">
|
||||||
<ion-label>Navigation</ion-label>
|
<ion-label>Navigation</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/routing" id="routing">
|
<ion-item router-link="/routing" id="routing">
|
||||||
<ion-label>Routing</ion-label>
|
<ion-label>Routing</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/default-href" id="default-href">
|
<ion-item router-link="/default-href" id="default-href">
|
||||||
<ion-label>Default Href</ion-label>
|
<ion-label>Default Href</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/nested" id="nested">
|
<ion-item router-link="/nested" id="nested">
|
||||||
<ion-label>Nested Router Outlet</ion-label>
|
<ion-label>Nested Router Outlet</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/tabs" id="tabs">
|
<ion-item router-link="/tabs" id="tabs">
|
||||||
<ion-label>Tabs</ion-label>
|
<ion-label>Tabs</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/tabs-secondary" id="tab-secondary">
|
<ion-item router-link="/tabs-secondary" id="tab-secondary">
|
||||||
<ion-label>Tabs Secondary</ion-label>
|
<ion-label>Tabs Secondary</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/lifecycle" id="lifecycle">
|
<ion-item router-link="/lifecycle" id="lifecycle">
|
||||||
<ion-label>Lifecycle</ion-label>
|
<ion-label>Lifecycle</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/lifecycle-setup" id="lifecycle-setup">
|
<ion-item router-link="/lifecycle-setup" id="lifecycle-setup">
|
||||||
<ion-label>Lifecycle (Setup)</ion-label>
|
<ion-label>Lifecycle (Setup)</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/delayed-redirect" id="delayed-redirect">
|
<ion-item router-link="/delayed-redirect" id="delayed-redirect">
|
||||||
<ion-label>Delayed Redirect</ion-label>
|
<ion-label>Delayed Redirect</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/components">
|
<ion-item router-link="/components">
|
||||||
<ion-label>Components</ion-label>
|
<ion-label>Components</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
@ -16,31 +16,31 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-item button @click="setRouteParams" id="route-params">
|
<ion-item @click="setRouteParams" id="route-params">
|
||||||
<ion-label>Set Route Parameters</ion-label>
|
<ion-label>Set Route Parameters</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/routing/child" id="child">
|
<ion-item router-link="/routing/child" id="child">
|
||||||
<ion-label>Go to Child Page</ion-label>
|
<ion-label>Go to Child Page</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/routing/abc" id="parameter-abc">
|
<ion-item router-link="/routing/abc" id="parameter-abc">
|
||||||
<ion-label>Go to Parameter Page ABC</ion-label>
|
<ion-label>Go to Parameter Page ABC</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/routing/xyz" id="parameter-xyz">
|
<ion-item router-link="/routing/xyz" id="parameter-xyz">
|
||||||
<ion-label>Go to Parameter Page XYZ</ion-label>
|
<ion-label>Go to Parameter Page XYZ</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/routing/123/view" id="parameter-view-item">
|
<ion-item router-link="/routing/123/view" id="parameter-view-item">
|
||||||
<ion-label>Go to Parameterized Page View</ion-label>
|
<ion-label>Go to Parameterized Page View</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button @click="replace" id="replace">
|
<ion-item @click="replace" id="replace">
|
||||||
<ion-label>Replace to Navigation page</ion-label>
|
<ion-label>Replace to Navigation page</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/tabs/tab1" id="tab1">
|
<ion-item router-link="/tabs/tab1" id="tab1">
|
||||||
<ion-label>Go to /tabs/tab1</ion-label>
|
<ion-label>Go to /tabs/tab1</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
@ -17,18 +17,18 @@
|
|||||||
|
|
||||||
<ExploreContainer name="Tab 1 page" />
|
<ExploreContainer name="Tab 1 page" />
|
||||||
|
|
||||||
<ion-item button router-link="/tabs/tab1/childone" id="child-one">
|
<ion-item router-link="/tabs/tab1/childone" id="child-one">
|
||||||
<ion-label>Go to Tab 1 Child 1</ion-label>
|
<ion-label>Go to Tab 1 Child 1</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button router-link="/nested" id="nested">
|
<ion-item router-link="/nested" id="nested">
|
||||||
<ion-label>Go to Nested Outlet</ion-label>
|
<ion-label>Go to Nested Outlet</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/tabs-secondary" id="tabs-secondary">
|
<ion-item router-link="/tabs-secondary" id="tabs-secondary">
|
||||||
<ion-label>Go to Secondary Tabs</ion-label>
|
<ion-label>Go to Secondary Tabs</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/tabs" id="tabs-primary">
|
<ion-item router-link="/tabs" id="tabs-primary">
|
||||||
<ion-label>Go to Primary Tabs</ion-label>
|
<ion-label>Go to Primary Tabs</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-item button router-link="/routing" id="routing">
|
<ion-item router-link="/routing" id="routing">
|
||||||
<ion-label>Go to /routing</ion-label>
|
<ion-label>Go to /routing</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
@ -584,4 +584,10 @@ describe('Routing - Swipe to Go Back', () => {
|
|||||||
cy.ionPageDoesNotExist('routingparameter-abc');
|
cy.ionPageDoesNotExist('routingparameter-abc');
|
||||||
cy.ionPageVisible('routing');
|
cy.ionPageVisible('routing');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should be activatable when router-link is used on an item without the button property', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.get('ion-item[routerlink="/overlays"]').should('have.class', 'ion-activatable');
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user