Skip to content

Commit ab4f279

Browse files
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: stenciljs/output-targets#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`
1 parent a9f278a commit ab4f279

File tree

10 files changed

+77
-38
lines changed

10 files changed

+77
-38
lines changed

core/package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@stencil/angular-output-target": "^0.8.4",
5151
"@stencil/react-output-target": "^0.5.3",
5252
"@stencil/sass": "^3.0.9",
53-
"@stencil/vue-output-target": "^0.8.7",
53+
"@stencil/vue-output-target": "^0.8.9",
5454
"@types/jest": "^29.5.6",
5555
"@types/node": "^14.6.0",
5656
"@typescript-eslint/eslint-plugin": "^6.7.2",

packages/vue/scripts/sync.sh

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ set -e
55
# Delete old packages
66
rm -f *.tgz
77

8+
# Delete vite cache
9+
rm -rf node_modules/.vite
10+
811
# Pack @ionic/core
912
npm pack ../../core
1013

packages/vue/src/vue-component-lib/utils.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,17 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
9191
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
9292
eventsNames.forEach((eventName: string) => {
9393
el.addEventListener(eventName.toLowerCase(), (e: Event) => {
94-
modelPropValue = (e?.target as any)[modelProp];
95-
emit(UPDATE_VALUE_EVENT, modelPropValue);
94+
/**
95+
* Only update the v-model binding if the event's target is the element we are
96+
* listening on. For example, Component A could emit ionChange, but it could also
97+
* have a descendant Component B that also emits ionChange. We only want to update
98+
* the v-model for Component A when ionChange originates from that element and not
99+
* when ionChange bubbles up from Component B.
100+
*/
101+
if (e.target.tagName === el.tagName) {
102+
modelPropValue = (e?.target as any)[modelProp];
103+
emit(UPDATE_VALUE_EVENT, modelPropValue);
104+
}
96105
});
97106
});
98107
},
@@ -106,6 +115,16 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
106115
if (routerLink === EMPTY_PROP) return;
107116

108117
if (navManager !== undefined) {
118+
/**
119+
* This prevents the browser from
120+
* performing a page reload when pressing
121+
* an Ionic component with routerLink.
122+
* The page reload interferes with routing
123+
* and causes ion-back-button to disappear
124+
* since the local history is wiped on reload.
125+
*/
126+
ev.preventDefault();
127+
109128
let navigationPayload: any = { event: ev };
110129
for (const key in props) {
111130
const value = props[key];
@@ -176,6 +195,17 @@ export const defineContainer = <Props, VModelType = string | number | boolean>(
176195
}
177196
}
178197

198+
// If router link is defined, add href to props
199+
// in order to properly render an anchor tag inside
200+
// of components that should become activatable and
201+
// focusable with router link.
202+
if (props[ROUTER_LINK_VALUE] !== EMPTY_PROP) {
203+
propsToAdd = {
204+
...propsToAdd,
205+
href: props[ROUTER_LINK_VALUE],
206+
};
207+
}
208+
179209
/**
180210
* vModelDirective is only needed on components that support v-model.
181211
* As a result, we conditionally call withDirectives with v-model components.

packages/vue/test/base/src/views/Components.vue

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
<ion-page>
33
<ion-content>
44
<ion-list>
5-
<ion-item button router-link="/components/breadcrumbs">
5+
<ion-item router-link="/components/breadcrumbs">
66
<ion-label>Breadcrumbs</ion-label>
77
</ion-item>
8-
<ion-item button router-link="/components/select">
8+
<ion-item router-link="/components/select">
99
<ion-label>Select</ion-label>
1010
</ion-item>
11-
<ion-item button router-link="/components/range">
11+
<ion-item router-link="/components/range">
1212
<ion-label>Range</ion-label>
1313
</ion-item>
1414
</ion-list>

packages/vue/test/base/src/views/Home.vue

+13-13
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,43 @@
1717
</ion-header>
1818

1919
<ion-list>
20-
<ion-item button router-link="/overlays">
20+
<ion-item router-link="/overlays">
2121
<ion-label>Overlays</ion-label>
2222
</ion-item>
23-
<ion-item button router-link="/icons">
23+
<ion-item router-link="/icons">
2424
<ion-label>Icons</ion-label>
2525
</ion-item>
26-
<ion-item button router-link="/inputs">
26+
<ion-item router-link="/inputs">
2727
<ion-label>Inputs</ion-label>
2828
</ion-item>
29-
<ion-item button router-link="/navigation" id="navigation">
29+
<ion-item router-link="/navigation" id="navigation">
3030
<ion-label>Navigation</ion-label>
3131
</ion-item>
32-
<ion-item button router-link="/routing" id="routing">
32+
<ion-item router-link="/routing" id="routing">
3333
<ion-label>Routing</ion-label>
3434
</ion-item>
35-
<ion-item button router-link="/default-href" id="default-href">
35+
<ion-item router-link="/default-href" id="default-href">
3636
<ion-label>Default Href</ion-label>
3737
</ion-item>
38-
<ion-item button router-link="/nested" id="nested">
38+
<ion-item router-link="/nested" id="nested">
3939
<ion-label>Nested Router Outlet</ion-label>
4040
</ion-item>
41-
<ion-item button router-link="/tabs" id="tabs">
41+
<ion-item router-link="/tabs" id="tabs">
4242
<ion-label>Tabs</ion-label>
4343
</ion-item>
44-
<ion-item button router-link="/tabs-secondary" id="tab-secondary">
44+
<ion-item router-link="/tabs-secondary" id="tab-secondary">
4545
<ion-label>Tabs Secondary</ion-label>
4646
</ion-item>
47-
<ion-item button router-link="/lifecycle" id="lifecycle">
47+
<ion-item router-link="/lifecycle" id="lifecycle">
4848
<ion-label>Lifecycle</ion-label>
4949
</ion-item>
50-
<ion-item button router-link="/lifecycle-setup" id="lifecycle-setup">
50+
<ion-item router-link="/lifecycle-setup" id="lifecycle-setup">
5151
<ion-label>Lifecycle (Setup)</ion-label>
5252
</ion-item>
53-
<ion-item button router-link="/delayed-redirect" id="delayed-redirect">
53+
<ion-item router-link="/delayed-redirect" id="delayed-redirect">
5454
<ion-label>Delayed Redirect</ion-label>
5555
</ion-item>
56-
<ion-item button router-link="/components">
56+
<ion-item router-link="/components">
5757
<ion-label>Components</ion-label>
5858
</ion-item>
5959
</ion-list>

packages/vue/test/base/src/views/Routing.vue

+7-7
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,31 @@
1616
</ion-toolbar>
1717
</ion-header>
1818

19-
<ion-item button @click="setRouteParams" id="route-params">
19+
<ion-item @click="setRouteParams" id="route-params">
2020
<ion-label>Set Route Parameters</ion-label>
2121
</ion-item>
2222

23-
<ion-item button router-link="/routing/child" id="child">
23+
<ion-item router-link="/routing/child" id="child">
2424
<ion-label>Go to Child Page</ion-label>
2525
</ion-item>
2626

27-
<ion-item button router-link="/routing/abc" id="parameter-abc">
27+
<ion-item router-link="/routing/abc" id="parameter-abc">
2828
<ion-label>Go to Parameter Page ABC</ion-label>
2929
</ion-item>
3030

31-
<ion-item button router-link="/routing/xyz" id="parameter-xyz">
31+
<ion-item router-link="/routing/xyz" id="parameter-xyz">
3232
<ion-label>Go to Parameter Page XYZ</ion-label>
3333
</ion-item>
3434

35-
<ion-item button router-link="/routing/123/view" id="parameter-view-item">
35+
<ion-item router-link="/routing/123/view" id="parameter-view-item">
3636
<ion-label>Go to Parameterized Page View</ion-label>
3737
</ion-item>
3838

39-
<ion-item button @click="replace" id="replace">
39+
<ion-item @click="replace" id="replace">
4040
<ion-label>Replace to Navigation page</ion-label>
4141
</ion-item>
4242

43-
<ion-item button router-link="/tabs/tab1" id="tab1">
43+
<ion-item router-link="/tabs/tab1" id="tab1">
4444
<ion-label>Go to /tabs/tab1</ion-label>
4545
</ion-item>
4646
</ion-content>

packages/vue/test/base/src/views/Tab1.vue

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@
1717

1818
<ExploreContainer name="Tab 1 page" />
1919

20-
<ion-item button router-link="/tabs/tab1/childone" id="child-one">
20+
<ion-item router-link="/tabs/tab1/childone" id="child-one">
2121
<ion-label>Go to Tab 1 Child 1</ion-label>
2222
</ion-item>
23-
<ion-item button router-link="/nested" id="nested">
23+
<ion-item router-link="/nested" id="nested">
2424
<ion-label>Go to Nested Outlet</ion-label>
2525
</ion-item>
2626

27-
<ion-item button router-link="/tabs-secondary" id="tabs-secondary">
27+
<ion-item router-link="/tabs-secondary" id="tabs-secondary">
2828
<ion-label>Go to Secondary Tabs</ion-label>
2929
</ion-item>
3030

31-
<ion-item button router-link="/tabs" id="tabs-primary">
31+
<ion-item router-link="/tabs" id="tabs-primary">
3232
<ion-label>Go to Primary Tabs</ion-label>
3333
</ion-item>
3434

packages/vue/test/base/src/views/Tab2.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</ion-toolbar>
1616
</ion-header>
1717

18-
<ion-item button router-link="/routing" id="routing">
18+
<ion-item router-link="/routing" id="routing">
1919
<ion-label>Go to /routing</ion-label>
2020
</ion-item>
2121
</ion-content>

packages/vue/test/base/tests/e2e/specs/routing.cy.js

+6
Original file line numberDiff line numberDiff line change
@@ -584,4 +584,10 @@ describe('Routing - Swipe to Go Back', () => {
584584
cy.ionPageDoesNotExist('routingparameter-abc');
585585
cy.ionPageVisible('routing');
586586
})
587+
588+
it('should be activatable when router-link is used on an item without the button property', () => {
589+
cy.visit('/');
590+
591+
cy.get('ion-item[routerlink="/overlays"]').should('have.class', 'ion-activatable');
592+
});
587593
})

0 commit comments

Comments
 (0)