├── .github
└── workflows
│ ├── ci.yml
│ ├── github-pages.yml
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bun.lockb
├── package.json
├── rollup.config.ts
├── src
├── Link.svelte
├── Link.svelte.d.ts
├── index.d.ts
└── index.js
├── tests
├── Link.test.svelte
├── Link.test.ts
└── __snapshots__
│ ├── Link.test.js.snap
│ └── Link.test.ts.snap
├── tsconfig.json
└── vite.config.ts
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request:
3 | push:
4 | branches: [master]
5 |
6 | jobs:
7 | test:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | - uses: oven-sh/setup-bun@v2
12 |
13 | - name: Install dependencies
14 | run: bun install
15 |
16 | - name: Run unit tests
17 | run: bun run test
18 |
--------------------------------------------------------------------------------
/.github/workflows/github-pages.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches: [master]
4 |
5 | jobs:
6 | deploy:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | - uses: oven-sh/setup-bun@v2
11 |
12 | - name: Install dependencies
13 | run: bun install
14 |
15 | - name: Build app
16 | run: bun --bun rollup -c
17 |
18 | - name: Deploy to GitHub Pages
19 | uses: peaceiris/actions-gh-pages@v4
20 | with:
21 | github_token: ${{ secrets.GITHUB_TOKEN }}
22 | publish_dir: dist
23 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | tags:
4 | - "v*"
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | contents: read
11 | id-token: write
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: oven-sh/setup-bun@v2
15 |
16 | - uses: actions/setup-node@v4
17 | with:
18 | node-version: "20.x"
19 | registry-url: "https://registry.npmjs.org"
20 |
21 | - name: Install dependencies
22 | run: bun install
23 |
24 | - name: Build package
25 | run: bun package
26 |
27 | - name: Publish package
28 | env:
29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
30 | run: |
31 | cd package
32 | npm publish --provenance --access public
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | dist
3 | package
4 | node_modules
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [2.0.0](https://github.com/metonym/svelte-link/releases/tag/v2.0.0) - 2024-08-13
9 |
10 | **Breaking Changes**
11 |
12 | - Set `type: "module"` in `package.json`
13 | - Add `exports` map to resolve Vite warning
14 | - Drop bundled ESM/UMD support
15 |
16 | **Fixes**
17 |
18 | - Support `data-*` attributes in TypeScript definitions
19 | - Drop legacy `sveltekit:*` attributes from TypeScript definitions
20 | - Ignore a11y warning
21 |
22 | ## [1.4.0](https://github.com/metonym/svelte-link/releases/tag/v1.4.0) - 2022-04-15
23 |
24 | - Add `sveltekit:reload` prop to TypeScript definitions
25 |
26 | ## [1.3.0](https://github.com/metonym/svelte-link/releases/tag/v1.3.0) - 2022-04-10
27 |
28 | - Add `sveltekit:prefetch`, `sveltekit:noscroll` props to TypeScript definitions
29 |
30 | ## [1.2.0](https://github.com/metonym/svelte-link/releases/tag/v1.2.0) - 2022-02-24
31 |
32 | - Add `active` prop; if `true`, it sets the class to "active" and `aria-current="page"`
33 |
34 | ## [1.1.0](https://github.com/metonym/svelte-link/releases/tag/v1.1.0) - 2022-01-15
35 |
36 | - Update JSDoc/TypeScript prop descriptions
37 | - Type `target` prop as `"_self" | "_blank" | "_parent" | "_top"` instead of a `string`
38 |
39 | ## [1.0.3](https://github.com/metonym/svelte-link/releases/tag/v1.0.3) - 2021-09-12
40 |
41 | - Use `.svelte.d.ts` extension for component TypeScript definition
42 |
43 | ## [1.0.2](https://github.com/metonym/svelte-link/releases/tag/v1.0.2) - 2020-12-08
44 |
45 | - Use `SvelteComponentTyped` in TypeScript definitions from Svelte version >=v3.31
46 |
47 | ## [1.0.1](https://github.com/metonym/svelte-link/releases/tag/v1.0.1) - 2020-11-29
48 |
49 | - Fix window check for SSR environments
50 |
51 | ## [1.0.0](https://github.com/metonym/svelte-link/releases/tag/v1.0.0) - 2020-11-29
52 |
53 | - Automatically set `outbound` to `true` if href is external ([PR #3](https://github.com/metonym/svelte-link/pull/3), [issue #2](https://github.com/metonym/svelte-link/issues/2) – thanks [@seaneking](https://github.com/seaneking))
54 |
55 | **Breaking Changes**
56 |
57 | - Set `outbound` to `false` to disable the external URL auto-detection
58 |
59 | ## [0.3.0](https://github.com/metonym/svelte-link/releases/tag/v0.3.0) - 2020-11-28
60 |
61 | - Add TypeScript definitions
62 |
63 | ## [0.2.0](https://github.com/metonym/svelte-link/releases/tag/v0.2.0) - 2020-04-19
64 |
65 | - Prefetch link if non-standard `rel="prefetch"` is set (inspired by Sapper)
66 |
67 | ## [0.1.0](https://github.com/metonym/svelte-link/releases/tag/v0.1.0) - 2020-04-16
68 |
69 | - Initial release
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-present Eric Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # svelte-link
2 |
3 | [![NPM][npm]][npm-url]
4 |
5 |
6 |
7 | > Anchor link component for Svelte.
8 |
9 |
10 |
11 | ## Installation
12 |
13 | ```bash
14 | # npm
15 | npm i -D svelte-link
16 |
17 | # pnpm
18 | pnpm i -D svelte-link
19 |
20 | # Bun
21 | bun i -D svelte-link
22 |
23 | # Yarn
24 | yarn add -D svelte-link
25 | ```
26 |
27 | ## Usage
28 |
29 | ### Basic
30 |
31 | ```svelte
32 |
35 |
36 | GitHub
37 | ```
38 |
39 | ### Preventing the default behavior
40 |
41 | Because event modifiers cannot be used on Svelte components, use the mouse event in the forwarded `on:click` event to prevent the default behavior.
42 |
43 | ```svelte
44 | {
47 | e.preventDefault();
48 | }}
49 | >
50 | GitHub
51 |
52 | ```
53 |
54 | ### Outbound links
55 |
56 | `outbound` is an alias for setting `target="_blank"`. If `rel` is not specified for outbound links, [`rel="noopener noreferrer"` is set](https://developers.google.com/web/tools/lighthouse/audits/noopener).
57 |
58 | `outbound` defaults to `true` if `href` points to an external URL. You can override this behaviour by explicitly setting `outbound` to `false`.
59 |
60 | ```svelte
61 | GitHub
62 |
63 |
64 |
65 |
66 | GitHub
67 |
68 | ```
69 |
70 | ### Prefetch
71 |
72 | Inspired by [Sapper](https://sapper.svelte.dev/docs#prefetch_href), if the non-standard `rel="prefetch"` is present, this component will make a GET request to the `href` value when the user hovers over the link.
73 |
74 | ```svelte
75 | About
76 | ```
77 |
78 | ### Disabled state
79 |
80 | Setting `disabled` to `true` will render a `span` element instead of an anchor tag.
81 |
82 | ```svelte
83 | GitHub
84 |
85 |
86 | ```
87 |
88 | ### Active state
89 |
90 | Set `active` to `true` to signal an active state.
91 |
92 | If `true`, the anchor link is given an "active" class with the `aria-current` attribute set to "page."
93 |
94 | ```svelte no-eval
95 |
98 |
99 | GitHub
100 |
101 |
102 | ```
103 |
104 | ## API
105 |
106 | ### Props
107 |
108 | | Prop | Type | Default value |
109 | | :------- | :-------- | :---------------------- |
110 | | href | `string` | `"javascript:void(0);"` |
111 | | disabled | `boolean` | `false` |
112 | | outbound | `boolean` | `undefined` |
113 | | target | `string` | `undefined` |
114 | | rel | `string` | `undefined` |
115 | | active | `boolean` | `false` |
116 |
117 | ### Forwarded events
118 |
119 | - on:click
120 | - on:mouseover
121 | - on:mouseenter
122 | - on:mouseout
123 | - on:focus
124 | - on:blur
125 | - on:keydown
126 |
127 | ## Changelog
128 |
129 | [Changelog](CHANGELOG.md)
130 |
131 | ## License
132 |
133 | [MIT](LICENSE)
134 |
135 | [npm]: https://img.shields.io/npm/v/svelte-link?style=for-the-badge&color=%23ff3e00
136 | [npm-url]: https://npmjs.com/package/svelte-link
137 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metonym/svelte-link/7b27fc6a66050bfc75e070ef1f02920d34790281/bun.lockb
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-link",
3 | "version": "2.0.0",
4 | "license": "MIT",
5 | "description": "Anchor link component for Svelte",
6 | "author": "Eric Liu (https://github.com/metonym)",
7 | "svelte": "./src/index.js",
8 | "type": "module",
9 | "scripts": {
10 | "dev": "bun --bun rollup -cw",
11 | "test": "vitest",
12 | "package": "bun --run dlz"
13 | },
14 | "devDependencies": {
15 | "@sveltejs/vite-plugin-svelte": "^3.1.1",
16 | "@testing-library/svelte": "^5.2.1",
17 | "dlz": "^0.1.3",
18 | "jsdom": "^20.0.3",
19 | "svelte": "^4.2.18",
20 | "svelte-readme": "^3.6.3",
21 | "typescript": "^5.5.4",
22 | "vitest": "^2.0.5"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/metonym/svelte-link.git"
27 | },
28 | "homepage": "https://github.com/metonym/svelte-link",
29 | "bugs": "https://github.com/metonym/svelte-link/issues",
30 | "keywords": [
31 | "svelte",
32 | "link",
33 | "prefetch",
34 | "noopener noreferrer"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/rollup.config.ts:
--------------------------------------------------------------------------------
1 | export { default } from "svelte-readme";
2 |
--------------------------------------------------------------------------------
/src/Link.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
63 |
64 | {#if disabled}
65 |
66 |
76 |
77 |
78 | {:else}
79 | {
90 | if (rel === "prefetch") prefetch();
91 | }}
92 | on:mouseout
93 | on:focus
94 | on:blur
95 | on:keydown
96 | >
97 |
98 |
99 | {/if}
100 |
--------------------------------------------------------------------------------
/src/Link.svelte.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import type { SvelteComponentTyped } from "svelte";
3 | import type { SvelteHTMLElements } from "svelte/elements";
4 |
5 | type RestProps = SvelteHTMLElements["span"] & SvelteHTMLElements["a"];
6 |
7 | export interface LinkProps extends RestProps {
8 | /**
9 | * Specify the `href` attribute.
10 | * @default "javascript:void(0);"
11 | */
12 | href?: string;
13 |
14 | /**
15 | * Set to `true` to disable the link.
16 | * A `span` tag will be rendered instead of `a`.
17 | * @default false
18 | */
19 | disabled?: boolean;
20 |
21 | /**
22 | * Set to `true` to set `target="_blank"`
23 | * and `rel="noopener noreferrer"`.
24 | * @default undefined
25 | */
26 | outbound?: boolean;
27 |
28 | /**
29 | * Specify the `target` attribute.
30 | * @default undefined
31 | */
32 | target?: "_self" | "_blank" | "_parent" | "_top";
33 |
34 | /**
35 | * Specify the `rel` attribute.
36 | * Set to "prefetch" to fetch the `href` value.
37 | * @default undefined
38 | */
39 | rel?: string;
40 |
41 | /**
42 | * Set to `true` for the link to be active:
43 | * - link is given an "active" class
44 | * - `aria-current` is set to "page"
45 | * @default false
46 | */
47 | active?: boolean;
48 |
49 | [key: `data-${string}`]: any;
50 | }
51 |
52 | export default class Link extends SvelteComponentTyped<
53 | LinkProps,
54 | {
55 | click: WindowEventMap["click"];
56 | mouseover: WindowEventMap["mouseover"];
57 | mouseenter: WindowEventMap["mouseenter"];
58 | mouseout: WindowEventMap["mouseout"];
59 | focus: WindowEventMap["focus"];
60 | blur: WindowEventMap["blur"];
61 | keydown: WindowEventMap["keydown"];
62 | },
63 | { default: {} }
64 | > {}
65 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Link.svelte";
2 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default } from "./Link.svelte";
2 |
--------------------------------------------------------------------------------
/tests/Link.test.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 | Text
7 |
8 | Text
9 |
10 | Text
11 |
12 | Text
13 |
14 | {
22 | e.preventDefault();
23 | console.log(e); // MouseEvent
24 | }}
25 | on:mouseover={(e) => {
26 | console.log(e); // MouseEvent
27 | }}
28 | on:mouseenter={(e) => {
29 | console.log(e); // MouseEvent
30 | }}
31 | on:mouseout={(e) => {
32 | console.log(e); // MouseEvent
33 | }}
34 | on:focus={(e) => {
35 | console.log(e); // FocusEvent
36 | }}
37 | on:blur={(e) => {
38 | console.log(e); // FocusEvent
39 | }}
40 | on:keydown={(e) => {
41 | console.log(e); // KeyboardEvent
42 | }}
43 | >
44 | GitHub
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/tests/Link.test.ts:
--------------------------------------------------------------------------------
1 | import type { SvelteComponent } from "svelte";
2 | import Link from "./Link.test.svelte";
3 |
4 | describe("Link", () => {
5 | let instance: null | SvelteComponent = null;
6 |
7 | afterEach(() => {
8 | instance?.$destroy();
9 | instance = null;
10 | document.body.innerHTML = "";
11 | });
12 |
13 | test("Link", () => {
14 | instance = new Link({
15 | target: document.body,
16 | });
17 |
18 | expect(
19 | document.body.querySelector("[data-basic]")!.outerHTML,
20 | ).toMatchSnapshot();
21 | expect(
22 | document.body.querySelector("[data-outbound]")!.outerHTML,
23 | ).toMatchSnapshot();
24 | expect(
25 | document.body.querySelector("[data-disabled]")!.outerHTML,
26 | ).toMatchSnapshot();
27 | expect(
28 | document.body.querySelector("[data-active]")!.outerHTML,
29 | ).toMatchSnapshot();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/tests/__snapshots__/Link.test.js.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Link > Link 1`] = `"Text"`;
4 |
5 | exports[`Link > Link 2`] = `"Text"`;
6 |
7 | exports[`Link > Link 3`] = `"Text"`;
8 |
9 | exports[`Link > Link 4`] = `"Text"`;
10 |
--------------------------------------------------------------------------------
/tests/__snapshots__/Link.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Link > Link 1`] = `"Text"`;
4 |
5 | exports[`Link > Link 2`] = `"Text"`;
6 |
7 | exports[`Link > Link 3`] = `"Text"`;
8 |
9 | exports[`Link > Link 4`] = `"Text"`;
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noEmit": true,
4 | "esModuleInterop": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "ignoreDeprecations": "5.0",
7 | "importsNotUsedAsValues": "error",
8 | "isolatedModules": true,
9 | "target": "ESNext",
10 | "module": "ESNext",
11 | "moduleResolution": "node",
12 | "strict": true,
13 | "types": ["svelte", "vitest/globals"],
14 | "paths": {
15 | "svelte-link": ["./src"],
16 | "svelte-link/*": ["./src/*"]
17 | }
18 | },
19 | "include": ["src", "tests"]
20 | }
21 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { defineConfig } from "vite";
3 | import { svelte } from "@sveltejs/vite-plugin-svelte";
4 | import path from "path";
5 | import pkg from "./package.json";
6 |
7 | export default defineConfig({
8 | plugins: [svelte({ hot: false })],
9 | resolve: {
10 | alias: {
11 | [pkg.name]: path.resolve("./src"),
12 | },
13 | },
14 | test: {
15 | globals: true,
16 | environment: "jsdom",
17 | },
18 | });
19 |
--------------------------------------------------------------------------------