diff --git a/litmus/bugs/link-target.js b/litmus/bugs/link-target.js
new file mode 100644
index 000000000..00c1b233b
--- /dev/null
+++ b/litmus/bugs/link-target.js
@@ -0,0 +1,21 @@
+// Link and LinkButton with a local href and a target other than _self. The browser should follow
+// them: _blank opens a new tab, a named target opens (and then reuses) a tab with that name, and
+// the url shown below stays the same. A link without a target navigates inside the app.
+//
+// Run: import Demo from "./bugs/link-target"; in litmus/index.js
+import { Link, LinkButton } from "cx/widgets";
+
+export default (
+
+
+
+ Current url:
+
+
+
+
+
+
+
+
+);
diff --git a/litmus/index.js b/litmus/index.js
index eadadfe6e..c7a41ad9d 100644
--- a/litmus/index.js
+++ b/litmus/index.js
@@ -141,7 +141,8 @@ import "./index.scss";
// import Demo from "./features/charts/line-graph/LineGraph";
// import Demo from "./features/charts/line-graph/SmoothingOvershoot";
//import Demo from "./features/window/resize-cursor";
-import Demo from "./features/dropdown/lookup-inside-iframe-portal";
+// import Demo from "./features/dropdown/lookup-inside-iframe-portal";
+import Demo from "./bugs/link-target";
// import Demo from "./features/dropdown/lookup-inside-real-iframe";
// import Demo from "./features/dropdown/context-menu-iframe-focus";
// import Demo from "./bugs/GridDefaultSortFieldClearableSortIssue";
diff --git a/packages/cx/src/widgets/nav/Link.spec.tsx b/packages/cx/src/widgets/nav/Link.spec.tsx
index c013643a3..59e4badd1 100644
--- a/packages/cx/src/widgets/nav/Link.spec.tsx
+++ b/packages/cx/src/widgets/nav/Link.spec.tsx
@@ -1,5 +1,6 @@
import { Link } from "./Link";
import { Store } from "../../data/Store";
+import { History } from "../../ui/app/History";
import { createModel } from "../../data/createAccessorModelProxy";
import { createTestRenderer } from "../../util/test/createTestRenderer";
import assert from "assert";
@@ -110,6 +111,46 @@ describe("Link", () => {
});
});
+ describe("click on a local href", () => {
+ let pushState = History.pushState;
+ let pushed: string[];
+
+ beforeEach(() => {
+ pushed = [];
+ History.pushState = (state, title, url) => {
+ pushed.push(url);
+ return true;
+ };
+ });
+
+ afterEach(() => {
+ History.pushState = pushState;
+ });
+
+ async function click(target?: string) {
+ let store = new Store();
+ let component = await createTestRenderer(
+ store,
+
+
+ ,
+ );
+ let prevented = false;
+ findAnchor(component.toJSON()).props.onClick({ preventDefault: () => (prevented = true) });
+ return prevented;
+ }
+
+ it("navigates in the app without a target", async () => {
+ assert.equal(await click(), true);
+ assert.deepEqual(pushed, ["/reports"]);
+ });
+
+ it("leaves target=_blank to the browser", async () => {
+ assert.equal(await click("_blank"), false);
+ assert.deepEqual(pushed, []);
+ });
+ });
+
describe("active override", () => {
it("true wins over a non-matching url", async () => {
assert.equal(await isActive("~/other", { href: "~/items", active: true }), true);
diff --git a/packages/cx/src/widgets/nav/LinkButton.ts b/packages/cx/src/widgets/nav/LinkButton.ts
index 695511a77..d29573a2b 100644
--- a/packages/cx/src/widgets/nav/LinkButton.ts
+++ b/packages/cx/src/widgets/nav/LinkButton.ts
@@ -168,7 +168,14 @@ export class LinkButton extends Button {
if (this.onClick && instance.invoke("onClick", e, instance) === false) return;
- if (data.href && Url.isLocal(data.href) && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
+ if (
+ data.href &&
+ Url.isLocal(data.href) &&
+ (!data.target || data.target === "_self") &&
+ !e.ctrlKey &&
+ !e.shiftKey &&
+ !e.metaKey
+ ) {
e.preventDefault();
History.pushState({}, null, data.href);
}