Code Monkey home page Code Monkey logo

svelte-navigator's Introduction

Svelte Navigator

npm package npm bundle size NPM GitHub last commit Code Style Prettier Build Status

Simple, accessible routing for Svelte.

Svelte Navigator is an accessible and relatively lightweight Single Page App Router inspired by react-router and @reach/router.

This started as a fork of svelte-routing, with added configuration options and access to parts of the Routers context through React-esque hooks.

Features

  • Accessible routing: The Router manages focus in your app automatically and makes announcements to screen reader users
  • Relative routing: Paths and links are relative to the parent Route and Router
  • Nestable Routes for easy, flexible and reusable component composition
  • Automatic route ranking: The Router chooses the best match automatically, so you don't need to worry about the order of your Routes
  • Route parameters user/:id and (namable) wildcards blog/*, blog/*wildcardName
  • React-esque hooks api for accessing parts of the Router context
  • Nestable Routers for seamless merging of many smaller apps
  • HTML5 history mode by default (Memory mode as fallback, or for testing)
  • SSR (Server Side Rendering) support
  • TypeScript ready 🎉

Table of Contents

Getting started

Look at the example folder for a few example project setups, or checkout the examples in the Svelte REPL:

Installation

With yarn:

yarn add svelte-navigator

With npm:

npm install --save svelte-navigator

Usage

Basic Setup for a client-side SPA:

<!-- App.svelte -->
<script>
	import { Router, Link, Route } from "svelte-navigator";
	import Home from "./routes/Home.svelte";
	import About from "./routes/About.svelte";
	import Blog from "./routes/Blog.svelte";
	import Search from "./routes/Search.svelte";
</script>

<Router>
	<nav>
		<Link to="/">Home</Link>
		<Link to="about">About</Link>
		<Link to="blog">Blog</Link>
	</nav>
	<div>
		<Route path="/">
			<Home />
		</Route>
		<Route path="about" component={About} />
		<Route path="blog/*">
			<Route path="/">
				<Blog />
			</Route>
			<Route path=":id" component={BlogPost} />
		</Route>
		<Route path="search/:query" let:params>
			<Search query={params.query} />
		</Route>
	</div>
</Router>

Svelte Navigator uses the HTML5 History API by default. For it to work properly, you need to setup your server correctly. If you're using sirv, as is common with a lot of Svelte projects, you need to pass it the --single option.

You can read more about the History API here:

SSR Caveat

In the browser we wait until all child Route components have registered with their ancestor Router component before we let the Router pick the best match. This approach is not possible on the server, because when all Route components have registered and it is time to pick a match the SSR has already completed, and a document with no matching Route will be returned.

We therefore resort to picking the first matching Route that is registered on the server, so it is of utmost importance that you sort your Route components from the most specific to the least specific if you are using SSR.

FAQ

I'm using Vite. Why am I getting errors with svelte-navigator?

Vite tries to optimize the dependencies of your app. Unfortunately, this process can break svelte-navigator, because it creates two versions of a variable, svelte-navigator uses internally. To fix this update your vite.config.js (or vite.config.ts) file:

import { defineConfig } from "vite";
import svelte from "@sveltejs/vite-plugin-svelte";

// https://vitejs.dev/config/
export default defineConfig({
	// ... your config ...
	plugins: [svelte() /* ... your plugins ... */],
	// Add this line:
	optimizeDeps: { exclude: ["svelte-navigator"] },
});

I'm coming from svelte-routing. How can I switch to svelte-navigator?

svelte-navigator started as a fork of svelte-routing. Its API is largely identical. Svelte Navigator mainly adds functionality through hooks. Things that work in Svelte Routing should just work in Svelte Navigator as well. Switching libraries is as easy as updating your imports:

// Change your imports from
import { Router, Route /* , ... */ } from "svelte-routing";
// to
import { Router, Route /* , ... */ } from "svelte-navigator";

Enjoy added functionality, like access to the current location or params through hooks, scoped paths in navigate with useNavigate, nested Routes, improved accessibility and more.

Why am I getting a warning about unused props for my route components?

To be precise, this warning: <Svelte component> was created with unknown prop 'location' & 'navigate'.

This happens, because Svelte Navigator injects the current location and a scoped navigate function to components rendered via the Route's component prop. To avoid the warning, you can instead render your components as Route children:

<!-- No unknown props will be injected -->
<Route path="my/path">
	<MyComponent />
</Route>

<!-- `location` and `navigate` props will be injected -->
<Route path="my/path" component="{MyComponent}" />

Read more in the Route section of the API docs.

Why don't CSS classes work with Link?

<style>
	/*
		Svelte will mark this class as unused and will remove it from
		the CSS output.
	*/
	.my-link { /* ... */ }
</style>

<Link class="my-link" to="my-path">...</Link>

Having a class attribute on Svelte components does not work with Svelte's CSS scoping. Svelte does not treat class props to components as special props and does not recognize them as classnames either. Theoretically Link could use the class prop for something entirely different than styling, so Svelte can't make any assumptions about it. As far as Svelte is concerned the class="my-link" attribute and the .my-link are totally unrelated.

To work around this, you can often make use of the scoping of a wrapping or an inner html element:

<style>
	/*
		`.wrapper` is a standard html element, so Svelte will recognize its
		`class` attribute and scope any styles accordingly.
		Since part of the selector is scoped you don't need to worry about the
		global part leaking styles.
	*/
	.wrapper :global(.my-link) { /* ... */ }

	/*
		Again, scoping works just fine with `.link-content`, so it can be styled
		as expected. This way you don't have direct access to the `<a />` tag of
		the `Link` however.
	*/
	.link-content { /* ... */ }
</style>

<div class="wrapper">
	<Link class="my-link" to="my-path">...</Link>
</div>

<Link to="my-path">
	<span class="link-content">...</span>
</Link>

If that does not work for you, you can use the use:link action instead (which has its limitations though, see link section in the API docs).

<script>
	import { link } from "svelte-navigator";
</script>

<style>
	/* This works as expected */
	.my-link {
		/* ... */
	}
</style>

<a class="my-link" href="my-path" use:link>...</a>

What are the weird rectangles around the headings in my app?

Focus outline around heading

These outlines appear, because Svelte Navigator focuses a heading inside the Route that was rendered after a navigation. This helps people relying on assistive technology, such as screen reader users, orientate on your website. If the router didn't take focus, and you were to click a link, it would remain focused after the navigation. Screenreader users would just not know that something changed on the page. (This is a common problem with spa routers). The idea of focusing a heading is, that it gives the user a starting point from where they can tab to the changed content. Since it is just the starting point, you can disable the focus ring for just the headers, which aren't focusable by default anyway. Or you could style them to better suit your design (see this article about styling focus indicators). But please, don't disable focus rings alltogether!

Testing

When testing your app's components it is sometimes necessary to have them rendered inside an instance of Router or Route. A component could for example use the useNavigate hook to redirect after some user interaction. This will however fail if the component is not somewhere inside a Router. Similarly using the useFocus hook will only work when the component is somewhere inside a Route.

If you're testing your app with @testing-library/svelte a custom render function and a WrapRouter component can do the trick:

// renderWithRouter.js
import { render } from "@testing-library/svelte";
import WrapRouter from "./WrapRouter.svelte";

/**
 * Test-render a component, that relies on some of svelte-navigator's
 * features, inside a Router.
 *
 * @param component The component you want to wrap in a Router
 * @param componentProps The props you want to pass to it
 * @param routerOptions Futher configuration (`onNavigate`,
 * `withRoute`, `initialPathname`)
 * @param options Options for testing library's `render` function
 */
const renderWithRouter = (component, componentProps, routerOptions, options) =>
	render(WrapRouter, { component, componentProps, ...routerOptions }, options);

export default renderWithRouter;
<!-- WrapRouter.svelte -->
<script>
	import { onDestroy } from "svelte";
	import {
		Router,
		Route,
		createMemorySource,
		createHistory,
	} from "svelte-navigator";

	/** The component you want to wrap in a Router */
	export let component;
	/** The props you want to pass to it */
	export let componentProps;
	/**
	 * A callback you can use to check if a navigation has occurred.
	 * It will be called with the new location and the action that lead
	 * to the navigation.
	 */
	export let onNavigate = () => {};
	/**
	 * If true, the component will be wrapped in a Route component as well.
	 * Some features of svelte-navigator can only be used inside a Route,
	 * for example `useParams`.
	 */
	export let withRoute = false;
	/** Supply an initial location to the Router */
	export let initialPathname = "/";

	const history = createHistory(createMemorySource(initialPathname));

	const unlisten = history.listen(onNavigate);

	onDestroy(unlisten);
</script>

<Router {history}>
	{#if withRoute}
	<Route path="*">
		<svelte:component this="{component}" {...componentProps} />
	</Route>
	{:else}
	<svelte:component this="{component}" {...componentProps} />
	{/if}
</Router>

Then import it in your test script:

import MyComponent from "./MyComponent.svelte";
import renderWithRouter from "../test/renderWithRouter";

it("works", () => {
	const { getByTestId } = renderWithRouter(MyComponent);
	expect(getByTestId("my-input")).toHaveValue("my-value");
});

API

Components

The main elements to configure and use routing in your Svelte app.

Router

The Router component supplies the Link and Route descendant components with routing information through context, so you need at least one Router at the top of your application. It assigns a score to all its Route descendants and picks the best match to render.

<Router>
	<Link to="profile">Go to /profile</Link>
	<Link to="blog">Go to /blog</Link>
	<Route path="blog" component={Blog} />
	<Route path="profile" component={Profile} />
</Router>

The Router will automatically manage focus in your app. When you change Routes, it will focus the first heading in the matched Route.

If you have multiple Routers, for example one for a navigation bar and one for the main content, make sure to pass primary={false} to all Routers, you don't want to manage focus (in this case the nav Router).

<Router primary={false}>
	<nav>
		<Link to="profile">Go to /profile</Link>
		<Link to="blog">Go to /blog</Link>
	</nav>
</Router>
<!-- ... -->
<Router>
	<main>
		<Route path="blog" component={Blog} />
		<Route path="profile" component={Profile} />
	</main>
</Router>

If you want to focus a different element, like a skip-navigtion-link or an info text, you can use the useFocus hook, to specify a custom focus element.

Svelte navigator also announces navigations to a screen reader. You can customize its message (i.e. for localization) via the a11y.createAnnouncement prop.

If you're interested in accessibility concerns in SPA routers you can check out this article, which provided much of the information, regarding focus management, used for implementing Svelte Navigators focus management.

<!-- App.svelte -->
<script>
	import { Router, Route, Link } from "svelte-navigator";

	// Provide a custom message when navigating using
	// a routes `meta` information
	function createAnnouncement(route, location) {
		const viewName = route.meta.name;
		const { pathname } = location;
		return `Navigated to the ${viewName} view at ${pathname}`;
	}
</script>

<Router a11y="{{ createAnnouncement }}">
	<Link to="profile">Go to /profile</Link>
	<Route
		path="profile"
		component="{Profile}"
		meta="{{ name: 'user profile' }}"
	/>
	<Route path="blog/*" meta="{{ name: 'blog' }}">
		<Blog />
	</Route>
</Router>

<!-- Blog.svelte -->
<script>
	import { Route, Link, useFocus } from "svelte-navigator";

	// Provide a custom element to focus when this Route is navigated to
	const registerFocus = useFocus();

	function skipNavigation() { /* ... */ }
</script>

<button use:registerFocus on:click={skipNavigation}>
	Skip navigation
</button>
<Link to="svelte">Go to /blog/svelte</Link>
<Link to="navigator">Go to /blog/navigator</Link>
<Route path="svelte">Yeah, Svelte!</Route>
<Route path="navigator">Yeah, Routing!</Route>

Router components can also be nested to allow for seamless merging of many smaller apps. Just make sure not to forget the wildcard (*) in the parent Routes path.

It's probably easier to nest Routes though.

<!-- App.svelte -->
<Router>
	<Link to="profile">Go to /profile</Link>
	<Route path="profile" component="{Profile}" />
	<Route path="blog/*">
		<Blog />
	</Route>
</Router>

<!-- Blog.svelte -->
<Router>
	<Link to="svelte">Go to /blog/svelte</Link>
	<Link to="navigator">Go to /blog/navigator</Link>
	<!-- Break out of the scope of the current Router -->
	<Link to="../profile">Go to /profile</Link>
	<Route path="svelte">Yeah, Svelte!</Route>
	<Route path="navigator">Yeah, Routing!</Route>
	<Route path=":id" let:params>
		<BlogPost id={params.id} />
	</Route>
</Router>

When you are serving your app from a subdirectory on your server, you can add a basepath prop to the router. It will be prepended to all routes and to all resolved navigations (i.e. using Link, useNavigate or useResolve). A properly formatted basepath should have a leading, but no trailing slash.

<Router basepath="/base">
	<Link to="profile">Go to /base/profile</Link>
	<Link to="blog">Go to /base/blog</Link>
	<Route path="blog" component={Blog} />
	<Route path="profile" component={Profile} />
</Router>

By default Routers use the HTML5 history API for navigation. You can provide a different history through the history prop. Svelte Navigator ships with a memory based history, which is used, when the application does not seem to run in a browser (i.e. in a test environment) or in an embedded page, like the Svelte REPL. You can explicitly set the memory history or you can provide your own implementation (for example a Hash based history).

<script>
	import { createHistory, createMemorySource } from "svelte-navigator";

	const memoryHistory = createHistory(createMemorySource());
</script>

<Router history="{memoryHistory}">
	<!-- ... -->
</Router>

If you have a strict Content Security Policy, inline styles might be forbidden. Svelte-Navigator makes some use of inline styles though for internal marker elements and for screen reader announcements. If that is the case, you can disable inline styles, though you need to import those styles manually. If you have a bundler set up, that will be as easy as adding one import statement to your applications entry point. Otherwise, you might need to copy the contents of svelte-navigator.css into your applications css.

// In your applications entrypoint, such as `index.js` or `main.js`
import "svelte-navigator/svelte-navigator.css";
Properties
Property Type Default Value Description
basepath string '/' The basepath property will be added to all path properties of Route descendants and to every navigation, that has access to the Routers context (from a Link with a to property or via useNavigate). This property can be ignored in most cases, but if you host your application on e.g. https://example.com/my-site, the basepath should be set to /my-site. Note that navigate and the link and links actions don't have access to the context. You may resolve the link manually using the useResolve hook.
url string '' The url property is used in SSR to force the current URL of the application and will be used by all Link and Route descendants. A falsy value will be ignored by the Router, so it's enough to declare export let url = ''; for your topmost component and only give it a value in SSR.
history HistorySource <HTML5 History> The history property can be used to use a navigation method other than the browsers History API (See custom Hash based history).
primary boolean true If set to false, the Router will not manage focus for its children. Analougus to the Routes primary prop.
a11y object Configuration object for Svelte Navigators accessibility features
a11y.createAnnouncement CreateAnnouncement route => 'Navigated to ${route.uri}' Function to create an announcement message, that is read by screen readers on navigation. It takes the matched Route and the current location as arguments and returns a string or a Promise, that resolves to a string.
a11y.announcements boolean true Set it to false, to disable screen reader announcements
disableInlineStyles boolean false Disable the inline styles, that are used internally by svelte-navigator. This might be necessary when your Content Security Policy disallows inline styles. To still remain functional, be sure to include the svelte-navigator.css in your application.

Where:

interface Route {
	uri: string;
	path: string;
	meta: object;
	params: object;
}

interface Location {
	pathname: string;
	search: string;
	hash: string;
	state: object;
}

type CreateAnnouncement = (
	route: Route,
	location: Location,
) => string | Promise<string>;

interface HistorySource {
	readonly location: Location;
	addEventListener(event: "popstate", handler: () => void): void;
	removeEventListener(event: "popstate", handler: () => void): void;
	history: {
		readonly state: object;
		pushState(state: object, title: string, uri: string): void;
		replaceState(state: object, title: string, uri: string): void;
		go(to: number): void;
	};
}

Link

A component used to navigate around the application. It will automatically resolve the to path relative to the current Route and to the Routers basepath.

<Router>
	<Route path="blog/*">
		<Link to="svelte">Go to /blog/svelte</Link>
		<Link to="../profile">Go to /profile</Link>
	</Route>
</Router>
<Router basepath="/base">
	<Route path="blog/*">
		<Link to="svelte">Go to /base/blog/svelte</Link>
		<Link to="../profile">Go to /base/profile</Link>
	</Route>
</Router>
Properties
Property Type Default Value Description
to string URL the component should link to. It will be resolved relative to the current Route.
replace boolean false When true, clicking the Link will replace the current entry in the history stack instead of adding a new one.
state object {} An object that will be pushed to the history stack when the Link is clicked. A state is arbitrary data, that you don't want to communicate through the url, much like the body of a HTTP POST request.
getProps GetProps null A function that returns an object that will be spread on the underlying anchor element's attributes. The first argument given to the function is an object with the properties location, href, isPartiallyCurrent, isCurrent. Look at the NavLink component in the example project setup to see how you can build your own link components with this.

Where:

interface Location {
	pathname: string;
	search: string;
	hash: string;
	state: object;
}

type GetProps = ({
	location: Location;
	href: string;
	isPartiallyCurrent: boolean;
	isCurrent: boolean;
}) => object;

All other props will be passed to the underlying <a /> element. If the passed props and the props returned from getProps contain clashing keys, the values returned from getProps will be used.

Route

A component that will render its component property or children when its ancestor Router component decides it is the best match.

All properties other than path, component, meta and primary given to the Route will be passed to the rendered component.

A Route path can match parameters with "path/:parameterName" and wildcards with "path/*" or "path/*wildcardName". All parameters and wildcard values will be provided to the component as props. They can also be accessed inside a Route slot via let:params.

The Route component will also receive the current location, as well as the navigate function, that is scoped to the current Route as props. They can be accessed inside the Route slot, via let:location and let:navigate.

<!-- Both variants will do the same -->
<Route path="blog/:id" component="{BlogPost}" />
<Route path="blog/:id" let:params>
	<BlogPost id="{params.id}" />
</Route>

<!-- Access the current location inside the slot -->
<Route path="search" let:location>
	<BlogPost queryString="{location.search}" />
</Route>

<!--
  Navigate programatically using relative links
  (See also `navigate` and `useNavigate`)
-->
<Route path="search" let:navigate>
	<BlogPost {navigate} />
</Route>

<!--
  Routes without a path are default routes.
  They will match if no other Route could be matched
-->
<Route component="{Home}"></Route>

You can nest Routes, to easily define a routing structure for your app. Just remember to add a splat (*) to the end of the parent Routes path.

<!-- Don't forget the '*' -->
<Route path="blog/*">
	<!-- Render specific post with id "123" at /blog/post/123 -->
	<Route path="post/:id" component="{BlogPost}" />
	<!-- Index Route for /blog -->
	<Route path="/" component="{Favourites}" />
</Route>
<Route component="{Home}"></Route>

You can also provide a meta prop to a Route, that you can use to identify the Route for example, when providing a custom a11y.createAnnouncement function the the parent Router.

<script>
	import { Router, Route, Link } from "svelte-navigator";

	// Provide a custom message when navigating using
	// a routes `meta` information
	function createAnnouncement(route, location) {
		const viewName = route.meta.name;
		const { pathname } = location;
		return `Navigated to the ${viewName} view at ${pathname}`;
	}
</script>

<Router a11y="{{ createAnnouncement }}">
	<Link to="profile">Go to /profile</Link>
	<Route
		path="profile"
		component="{Profile}"
		meta="{{ name: 'user profile' }}"
	/>
	<Route path="blog/*" meta="{{ name: 'blog' }}">
		<Blog />
	</Route>
</Router>
Properties
Property Type Default Value Description
path string '' The path for when this component should be rendered. If no path is given the Route will act as the default that matches if no other Route in the Router matches.
component SvelteComponent null The component constructor that will be used for rendering when the Route matches. If component is not set, the children of Route will be rendered instead.
meta object {} An arbitrary object you can pass the Route, to later access it (for example in a11y.createAnnouncement).
primary boolean true If set to false, the parent Router will not manage focus for this Route or any child Routes.

Hooks

Svelte Navigator exposes a few React-esque hooks to access parts of the Routers context. These hooks must always be called during component initialization, because thats when Sveltes getContext must be called.

All navigator hooks return either a readable store you can subscibe to, or a function, that internally interacts with the Routers context.

useNavigate

A hook, that returns a context-aware version of navigate. It will automatically resolve the given link relative to the current Route.

<!-- App.svelte -->
<script>
	import { Router, Route } from "svelte-navigator";
	import RouteComponent from "./RouteComponent.svelte";
</script>

<Router>
	<Route path="routePath">
		<RouteComponent />
	</Route>
	<!-- ... -->
</Router>

<!-- RouteComponent.svelte -->
<script>
	import { useNavigate } from "svelte-navigator";

	const navigate = useNavigate();
</script>

<button on:click="{() => navigate('relativePath')}">
	go to /routePath/relativePath
</button>
<button on:click="{() => navigate('/absolutePath')}">
	go to /absolutePath
</button>

It will also resolve a link against the basepath of the Router

<!-- App.svelte -->
<Router basepath="/base">
	<Route path="routePath">
		<RouteComponent />
	</Route>
	<!-- ... -->
</Router>

<!-- RouteComponent.svelte -->
<script>
	import { useNavigate } from "svelte-navigator";

	const navigate = useNavigate();
</script>

<button on:click="{() => navigate('relativePath')}">
	go to /base/routePath/relativePath
</button>
<button on:click="{() => navigate('/absolutePath')}">
	go to /base/absolutePath
</button>

The returned navigate function is identical to the navigate prop, that is passed to a Routes component. useNavigates advantage is, that you can use it easily in deeply nested components.

<!-- App.svelte -->
<Router>
	<Route path="routeA" component="{RouteA}" />
	<Route path="routeB" let:navigate>
		<RouteB {navigate} />
	</Route>
	<Route path="routeC">
		<RouteC />
	</Route>
	<!-- ... -->
</Router>

<!-- All three components can use the navigate function in the same way -->
<!-- RouteA.svelte -->
<script>
	export let navigate;
</script>

<!-- RouteB.svelte -->
<script>
	export let navigate;
</script>

<!-- RouteC.svelte -->
<script>
	import { useNavigate } from "svelte-navigator";
	const navigate = useNavigate();
</script>

The returned navigate function accepts the same parameters as the global navigate function.

Parameters
Parameter Type Default Value Description
to string | number The path you want to navigate to. If to is a number, it is used to navigate in through the existing history stack, to the entry with the index currentStackIndex + to (navigate(-1) is equivalent to hitting the back button in your browser)
options object The navigation options
options.state object {} An arbitrary object, that will be pushed to the history state stack
options.replace boolean false If true, the current entry in the history stack will be replaced with the next navigation, instead of pushing the next navigation onto the stack

useLocation

Access the current location via a readable store and react to changes in location.

<!-- RouteComponent.svelte -->
<script>
	import { useLocation } from "svelte-navigator";

	const location = useLocation();

	$: console.log($location);
	/*
	  {
	    pathname: "/blog",
	    search: "?id=123",
	    hash: "#comments",
	    state: {}
	  }
	*/
</script>

useResolve

Resolve a given link relative to the current Route and the Routers basepath. It is used under the hood in Link and useNavigate. You can use it to manually resolve links, when using the link or links actions. (See link)

<script>
	import { link, useResolve } from "svelte-navigator";

	export let path;

	const resolve = useResolve();
	// `resolvedLink` will be resolved relative to its parent Route
	// and the Router `basepath`
	$: resolvedLink = resolve(path);
</script>

<a href="{resolvedLink}" use:link>Relative link</a>

Note, that you might need to re-resolve the link, to avoid stale links on location changes. You can achive this by deriving a store from the $location store, or by forcing Svelte to recompute the reactive resolvedLink variable, by passing $location as a second argument to resolve:

<script>
	import { link, useResolve, useLocation } from "svelte-navigator";

	export let path;

	const resolve = useResolve();
	const location = useLocation();
	// Force Svelte to re-run this assignement, when location changes
	$: resolvedLink = resolve(path, $location);
</script>

<a href="{resolvedLink}" use:link>Relative link</a>

useResolvable

Resolve a given link relative to the current Route and the Routers basepath. It works very similar to useResolve, but returns a store of the resolved path, that updates, when location changes. You will prabably want to use useResolvable, when the path you want to resolve does not change, and useResolve, when you're path is changing, for example, when you get it from a prop.

You can use useResolvable to manually resolve links, when using the link or links actions. (See link)

<script>
	import { link, useResolvable } from "svelte-navigator";

	// `resolvedLink` will be resolved relative to its parent Route
	// and the Router `basepath`
	const resolvedLink = useResolvable("relativePath");
</script>

<a href="{$resolvedLink}" use:link>Relative link</a>

useMatch

Use Svelte Navigators matching without needing to use a Route. Returns a readable store with the potential match, that changes, when the location changes.

<script>
	import { useMatch } from "svelte-navigator";

	const relativeMatch = useMatch("relative/path/:to/*somewhere");
	const absoluteMatch = useMatch("/absolute/path/:to/*somewhere");

	$: console.log($relativeMatch.params.to);
	$: console.log($absoluteMatch.params.somewhere);
</script>

useParams

Access the parent Routes matched params and wildcards via a readable store.

<!--
	Somewhere inside <Route path="user/:id/*splat" />
	with a current url of "/myApp/user/123/pauls-profile"
-->
<script>
	import { useParams } from "svelte-navigator";

	const params = useParams();

	$: console.log($params); // -> { id: "123", splat: "pauls-profile" }
</script>

<h3>Welcome user {$params.id}! bleep bloop...</h3>

useFocus

Provide a custom element to focus, when the parent route is visited. It returns the registerFocus action you can apply to an element via the use directive:

<!-- Somewhere inside a Route -->
<script>
	import { useFocus } from "svelte-navigator";

	const registerFocus = useFocus();
</script>

<h1>Don't worry about me...</h1>
<p use:registerFocus>Here, look at me!</p>

You can also use registerFocus asynchronously:

<!-- Somewhere inside a Route -->
<script>
	import { onMount } from "svelte";
	import { useFocus } from "svelte-navigator";

	const registerFocus = useFocus();

	const lazyImport = import("./MyComponent.svelte").then(
		module => module.default,
	);
</script>

{#await lazyImport then MyComponent}
<MyComponent {registerFocus} />
{/await}

<!-- MyComponent.svelte -->
<script>
	export let registerFocus;
</script>

<h1 use:registerFocus>Hi there!</h1>

You should however only use it asynchronously, if you know, that the focus element will register soon. Otherwise, focus will remain at the clicked link, and randomly change a few seconds later without explanation, which is a very bad experience for screen reader users.

When you need to wait for data, before you can render a component, you should consider providing a hidden heading, that informs a screen reader user about the current loading process.

<!-- Somewhere inside a Route -->
<script>
	import { onMount } from "svelte";
	import { useFocus } from "svelte-navigator";
	import BlogPost from "./BlogPost.svelte";

	const registerFocus = useFocus();

	const blogPostRequest = fetch("some/blog/post");
</script>

<style>
	.visuallyHidden {
		position: absolute;
		width: 1px;
		height: 1px;
		padding: 0;
		overflow: hidden;
		clip: rect(0, 0, 0, 0);
		white-space: nowrap;
		border: 0;
	}
</style>

{#await blogPostRequest}
<h1 class="visuallyHidden" use:registerFocus>
	The blog post is being loaded...
</h1>
{:then data}
<BlogPost {data} />
{/await}

Programmatic Navigation

Svelte Navigator exports a global navigate function, you can use to programmatically navigate around your application.

It will however not be able to perform relative navigation. Use the useNavigate hook instead.

If your using a custom history (for example with createMemorySource), the created history will have its own navigate function. Calling the globally exported function, will not work as intended.

If you're serving your app from a subdirectory or if you're using a custom history, it is not advised to use navigate. Use useNavigate instead.

navigate

A function that allows you to imperatively navigate around the application for those use cases where a Link component is not suitable, e.g. after submitting a form.

The first argument is a string denoting where to navigate to, and the second argument is an object with a replace and state property equivalent to those in the Link component.

Note that navigate does not have access to the Routers context, so it cannot automatically resolve relative links. You might prefer useNavigate instead.

<script>
	import { navigate } from "svelte-navigator";

	function onSubmit() {
		login().then(() => {
			navigate("/success", { replace: true });
		});
	}
</script>

If the first parameter to navigate is a number, it is used to navigate the history stack (for example for a browser like "go back/go forward" functionality).

<script>
	import { navigate } from "svelte-navigator";
</script>

<button on:click="{() => navigate(-1)}">Back</button>
<button on:click="{() => navigate(1)}">Forward</button>
Parameters
Parameter Type Default Value Description
to string | number The path you want to navigate to. If to is a number, it is used to navigate in through the existing history stack, to the entry with the index currentStackIndex + to (navigate(-1) is equivalent to hitting the back button in your browser)
options object The navigation options
options.state object {} An arbitrary object, that will be pushed to the history state stack
options.replace boolean false If true, the current entry in the history stack will be replaced with the next navigation, instead of pushing the next navigation onto the stack

Actions

You can use the link and links actions, to use standard <a href="..." /> elements for navigation.

link

An action used on anchor tags to navigate around the application. You can add an attribute replace to replace the current entry in the history stack instead of adding a new one.

<!-- App.svelte -->
<script>
	import { link, Route, Router } from "svelte-navigator";
	import RouteComponent from "./RouteComponent.svelte";
</script>

<Router>
	<a href="/" use:link>Home</a>
	<a href="/replace" use:link replace>Replace this URL</a>
	<!-- ... -->
</Router>

You should note that an action has no access to sveltes context, so links will not automatically be resolved on navigation. This will be a problem when you want to take advantage of Svelte Navigators relative navigation or when your app is served from a subdirectory. You can use the useResolve hook to resolve the link manually.

<script>
	import { link, useResolve } from "svelte-navigator";

	const resolve = useResolve();
	// `resolvedLink` will be "/route1/relativePath"
	const resolvedLink = resolve("relativePath");
</script>

<a href="{resolvedLink}" use:link>Relative link</a>

link uses the global navigate function by default, so if you're not using the default history mode (for example, memory mode or a custom history), navigating with it will not work as intended. To fix this, you could either use a Link component, or you can pass a custom navigate function to the action.

<!-- App.svelte -->
<script>
	import {
		link,
		Route,
		Router,
		createHistory,
		createMemorySource,
	} from "svelte-navigator";

	const memoryHistory = createHistory(createMemorySource());
	const { navigate } = memoryHistory;
</script>

<Router history="{memoryHistory}">
	<a href="/" use:link="{navigate}">Home</a>
	<!-- ... -->
</Router>

Because of the issues with link resolution and the dependency on the global navigation function, it is generally advised, not to use the link and links actions if you're not using a standard app, with all the default configuration.

links

An action used on a root element to make all relative anchor elements navigate around the application. You can add an attribute replace on any anchor to replace the current entry in the history stack instead of adding a new one. You can add an attribute noroute for this action to skip over the anchor and allow it to use the native browser action.

<!-- App.svelte -->
<script>
	import { links, Router } from "svelte-navigator";
</script>

<div use:links>
	<Router>
		<a href="/">Home</a>
		<a href="/replace" replace>Replace this URL</a>
		<a href="/native" noroute>Use the native action</a>
		<!-- ... -->
	</Router>
</div>

As with the link action, the href attribute of the used <a /> elements will not be resolved automatically.

If you're using a custom history, you need to pass its navigate function to the links function, just like you have to do with link.

Custom History

If you don't want to use the HTML5 History API for Navigation, you can use a custom history.

Svelte Navigator comes with a memory based history, you can use. It is practical for testing.

To create a custom history, pass a history source to the createHistory function.

You could use multiple Routers, that don't interfere with each other, by using a different history for each one.

<script>
	import { Router, createHistory, createMemorySource } from "svelte-navigator";

	const html5History = createHistory(window);
	const memoryHistory = createHistory(createMemorySource());
</script>

<Router history="{html5History}">
	<!-- I will function like the standard Router -->
</Router>

<Router history="{memoryHistory}">
	<!-- I store the history stack in memory -->
</Router>

For a more advanced example, checkout the Custom Hash History example.

Deploying to Vercel

To make sure your deeplinks work when you deploy to vercel remember to add this rule to your vercel.json file

{
	"rewrites": [{ "source": "/(.*)", "destination": "/" }]
}

License

MIT

svelte-navigator's People

Contributors

chunxei avatar cibernox avatar cristovao-trevisan avatar dependabot[bot] avatar emiltholin avatar hidde avatar jacwright avatar jlecordier avatar jordienr avatar kaisermann avatar mefechoel avatar oldtinroof avatar scottbedard avatar seeekr avatar tiberiuc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

svelte-navigator's Issues

In-page jumps to fragment links don't work with programmatic navigation

Describe the bug

See #3:

when navigating to a Route, that is not already rendered, the browser will check for a jump mark immediately, but of course rendering the Route takes some time, so the browser won't find the element and won't scroll the page.

I think there are two possible ways to solve this. Either, when calling navigate, e.g. by clicking a Link, we need to update the Routers internal location first, wait for the Routes to render, and then call history.pushState. But there is probably a lot of tricky work to be done. The second possibility is to assign window.location.href back to itself, after all Routes have rendered. This however triggers a popstate event in some browsers, which the Router will interpret as a new navigation, so we need to make sure, not to cause infinite loops by accident.

To Reproduce

... some content ...
<Route path="myRoute">
  <main id="main">
    Lorem ipsum dolor sit amet consectetur adipisicing elit.
  </main>
</Route>

When opening /myRoute#main, the page does not scroll to the main Element.
The same happens, when navigating to a Route via a Link or navigate.

Expected behavior
The page should scroll to the element, referenced by the fragment.

Can't render a route and its parent (Outlet concept)

Describe the bug

I can't render a route and its parent, comparison to the outlet concept of react-router

To Reproduce Steps to reproduce the behavior:

<Router>
  <Route path="/*" component={Layout}>
    <Route path="pvp" component={Pvp} />
  </Route>
</Router>

Navigating to /pvp will render Layout correctly but won't render Pvp at all

Desktop (please complete the following information):

  • OS: Windows 10
  • Browser: Chrome
  • Version 3.1.5

Additional context

Maybe I just didn't understand if it's possible and how to do it

Add example illustrating the use of skip links

Svelte Navigator gives you a lot of accessibility improvements out of the box, but for an even better experience the user needs to tailor focus management manually.

A common and apparently very useful technique to improve a11y is the usage of skip links.
This is described further in this blog post by Marcy Sutton on the results of user testing different a11y approaches.

It might not be obvious how to implement the technique with Svelte Navigator, so it should be broken down in an example.

Page refresh is not working

I am trying to build a simple client-side svelte app with firebase (firestore and firebase auth) as a backend. I am getting a page with (This page cannot be found message) every time I try to do browser refresh/reload.

May be something incorrect in the private route example

Describe the bug
I was looking at the private-routes example. Once you start the serve and navigate to http://localhost:<port>/profile, it does not render the login page, event though the url changes to http://localhost:<port>/login.

I moved code in PrivateRouteGaurd to onMount that appeared to work. But I am not sure if that is correct.

    onMount(checkUser);
    function checkUser() {
        if (!$user) {
            navigate("/login", {
                state: { from: $location.pathname },
                replace: true,
            });
        }
    }

To Reproduce
Steps to reproduce the behavior:

  1. Go to http://localhost:<port>/profile once you start private-routes example

route guard

Is your feature request related to a problem? Please describe.

To have guarded some routes(admin, user, ...) something like angular router, so function which determines if the route can be activeted

Describe the solution you'd like

<script lang="ts">
function canActivate():boolean {
}
</script>
<Router {basepath}>
  <main>
    <Navbar />

    <Route path="queries" component={Queries}  />
    <Route path="statistics" component={Statistics} canActivate={canActivate}/>
    <Route path="status" component={Status} />
  </main>
</Router>

Describe alternatives you've considered

<Router {basepath}>
  <main>
    <Navbar />

    <Route path="queries" component={Queries}  />
     {#if canActivate}
        <Route path="statistics" component={Statistics} />
     {/if}   
    <Route path="status" component={Status} />
  </main>
</Router>

<Svelte component> was created with unknown prop 'location' & 'navigate'

I am getting these messages in the browser for a bunch of my Svelte components:

MainSidebar.svelte:18 was created with unknown prop 'location'
MainSidebar.svelte:18 was created with unknown prop 'navigate'

To avoid these messages, I have to define and export 2 variables/properties:

export let location // object with pathname: "/main/dashboard"
export let navigate

Do I have to do that for every single of my components? I didn't see any of this in the examples.

Prompt for unsaved forms

Thank you for this library!

I'm looking for something like React routers Prompt.

When there is unsaved work there are three ways the user should be warned:

  1. window.onbeforeunload: The user presses refresh in the browser or navigates to another website
  2. navigate: The user presses on an internal link that will change the page in the router
  3. popstate: The user presses back in the browser

1 and 2 are solvable relatively easily. createHistory could allow registering callbacks for handling prompts. If any of these listeners return an unsuccessful value (as in there is unsaved work) a prompt would be displayed.

3 is a bit more tricky since the browser doesn't offer a way to block the URL change. Thus on cancel we'd have to navigate back to the page the user just left. This would have to be done before the router removes anything from the page so that no state is lost. This is what react-router does, I think.

Has any work in this area been done? I'll create an implementation if you don't see anything wrong with this approach.

lazy loading & chunk bundles

Is your feature request related to a problem? Please describe.

  • Some routes are accessible later so the component could be loaded as needed(something like angular)
  • So in that regards is it possible to have chunk bundles (like angular)

Describe the solution you'd like

<Router {basepath}>
  <main>
    <Navbar />

    <Route path="" component={Queries} />
    <Route path="queries" component={Queries} />
    <Route path="statistics" component={Statistics} lazy="true"/>
    <Route path="status" component={Status}  lazy="true"/>
  </main>
</Router>

Allow preprocessing (as a Promise) to run before any navigation

Is your feature request related to a problem? Please describe.
No.

Describe the solution you'd like

Something like:

import * as navigator from 'svelte-navigator';
navigator.beforeNavigation((route, location) => new Promise);

This will alow something like a progress bar. I also don't want the current route content to be empty while progressing.

Describe alternatives you've considered

  • Announcement: Promise is not waited before navigation.
  • The lazy loading example isn't working on this regard and it replaces the current route content immediately.

how do you handle 404

How do you do a 404 page for routes that don't exist?

I"ve tried a Route with no path (just as svelte-routing suggest) with no success

<Router>
  <Route path="blog">
    <Blog />
  </Route>
  <!-- ... the rest of your routes -->
  <Route>
    <!-- Your 404 component -->
    <NotFound />
  </Route>
</Router>

any idea?

Meta prop not working

Hi again, thank you for your support.
Hope you're doing well!
Today I was trying to implement a kind of accessibility features to my website.
I'm trying to do a a11y.createAnnouncement function based on the example you give in the README.md
When I add a meta object to a Route, this is not added to the component and when I print the information it is undefined.
Here you can see:

To Reproduce Steps to reproduce the behavior:

  1. Make a createAnnouncement function like this:
const createAnnouncement = (route, location) => {
    const viewName = route.meta.name
    const { pathname } = location
    console.log(`Navegado a la vista de ${viewName} en ${pathname}`)
    return `Navegado a la vista de ${viewName} en ${pathname}`
  }
  1. Before returning the value, I print the information.
  2. Go to the console of the browser and load a page.
  3. See error

Expected behavior
route.meta.name is filled with the meta I pass to the Route component.

Screenshots
My code:
Screenshot from 2020-12-15 20-33-15

The console in my browser:
Screenshot from 2020-12-15 20-21-41

Desktop (please complete the following information):

  • OS: Pop!_OS 20.10/Linux
  • Browser: Brave Browser
  • Version: 1.15.76 Chromium: 86.0.4240.111 (Official Build) (64-bit)

Additional context
And a more question 😅: What are the types of route and location in the parameters of the function? I'd love to know them to type them in my code since I'm working with TypeScript.

two updates from useLocation() on page reload

I have the following app:

//App.svelte

<script>
  import { Route, Router } from 'svelte-navigator'
  import Temp from './Temp.svelte'
</script>

<Router>
  <Route path="/temp" component={Temp} />
</Router>
//Temp.svelte
<script>
  import { useLocation } from 'svelte-navigator'
  const location$ = useLocation()
  $: console.log(JSON.stringify($location$))
</script>

After going to /temp path (or refreshing) there are two identical messages in console:

{"pathname":"/temp","hash":"","search":"","state":null}
{"pathname":"/temp","hash":"","search":"","state":null}

Why is the reactive statement called twice if the location didn't change?

Add some example CSS to illustrate some best practices on accessible styling

Some CSS should be added to the examples, to show some basic techniques on how to style an app in an accessible way.

Focus outlines should be explained, as they are very relevant to the project (see #10).

Maybe Custom inputs, e.g. checkboxes, since they are very easy to make inaccessible.
Maybe add some more information and linked articles to the readme.

Hiding the focus outline - accessibility considerations?

👋 I have a question regarding the focus handling. I found it to be a bit distracting in my app. Instead of disabling it altogether through <Route primary={false}>, I instead hid the focus outline for all elements like so:

<style>
  /* App.svelte */
  :global(:focus) { 
    outline: none; 
  }
</style>

I tried out the result in the Screen Reader Chrome extension and it seems to work as intended, ie. the element still pulls focus but without visual indication. I understand that losing a visual cue can be disorienting for some users, but are there any accessibility concerns in terms of screen readers when hiding the outline as above?

Redirect from click handler

Hi there! How can I redirect a user after he clicks the if button? The Navigate method only works when replace is enabled, otherwise only the URL changes and the page remains the same

Some outline on title on navigation

Describe the bug
On navigation title is highlighted. Looks worse than the attached screenshot when used with bootstrap. I could not figure out what is that outline.

Screen Shot 2020-08-15 at 5 16 28 pm

To Reproduce
Steps to reproduce the behavior:

  1. Go to private-routes example and click on profile page

Expected behavior
The outline should not appear

Screenshots
attached, see the outline on "Login" title

A route with a path parameter is not matched if the path parameter value contains a dot

When you have a route with a path parameter you won't be able to navigate to the route if your path parameter value contains a dot.

Steps to reproduce the behavior:

  1. Create a route which expects a path parameter, for instance "/foo/:bar"
  2. Attempt to access the route in your browser by going to for instance "/foo/bar.baz"
  3. Experience a 404 error

Expected behavior

As the dot character is not a reserved sign it does not need to be URL encoded. I expect to be able to navigate to a route with a path parameter value containing a dot.

  • OS: Arch Linux 64-bit
  • Browser: Chromium
  • Version: 97.0.4692.71

Navigation between components - is possible?

I have a need to refresh a section of the page which is a separate component with a dynamically generated list (paging). But it is not the main part of navigation and main menu only a subpage.
I've been sitting on this for 3 days (sic!) and can't figure it out.

I prepared some example image and code to present my problem:

routing

Below is the simplified code, the real one is more complicated. Of course, this example not working

https://codesandbox.io/s/stupefied-firefly-mdw5n

Login component not rendered when navigating directly to a PrivateRoute in provided example

Describe the bug
When pointing my browser to http://localhost:6062/profile, the URL is properly redirected to http://localhost:6062/login but the login form component is not displayed.

To Reproduce
Steps to reproduce the behavior:

  1. Clone the repo
  2. cd svelte-navigator/example/private-routes
  3. Run npm install and npm start
  4. Navigate to http://localhost:6062/profile in web browser
  5. The URL is properly redirected to http://localhost:6062/login, but the login form component is not rendered as shown in the screen shot. If you click the "Profile" link then the login form will be properly displayed.

Expected behavior
When you land on a private route the login form should be displayed.

Screenshots
Screen Shot 2020-09-08 at 12 09 56 PM

Desktop (please complete the following information):

  • OS: macOS 10.14.8
  • Browsers: Chrome 85.0.4183.83, Firefox 80.0.1, Safari 13.1.2

Add TypeScript support

With TypeScript now being a first class citizen in Svelte, Svelte Navigator should provide typings.

I don't think it's possible at the moment to write everything in TS, because that would require any project using Svelte Navigator to have the TS pre-processor set up as well.

There would need to be a way to strip type annotations from Svelte components and produce regular Svelte components and declaration files from it.

So to get things started, declaration files should be enough.

(Vite) useLocation hook throws `Function called outside...

Describe the bug A clear and concise description of what the bug is.

useLocation() hook throws error Function called outside component initialization.

To Reproduce Steps to reproduce the behavior:

  1. npm init @vitejs/app (choose svelte > javascript)
  2. cd vite-project && npm install
  3. npm install svelte-navigator --save
  4. add Router with Link to Route having useLocation()
  5. click the link and see error in console

Please add a link to an example in the Svelte REPL or in a Codesandbox, if you
can.

https://github.com/mataslib/uselocation-bug-repl

Expected behavior A clear and concise description of what you expected to
happen.

Show route content.

Screenshots If applicable, add screenshots to help explain your problem.

errorprtscr

Additional context Add any other context about the problem here.

Seems to not work only with Vite dev. It works with vite build, when u visit builded dist html. I used useLocation() before with rollup and it was working. Then I switched to vite for its speed and it also did work. Today, I was removing and updating npm deps and it stopped working with this error. Unfortunately I wasn't able to revert to working state and detect the culprit, so I made this clean repl.

beforeNavigate & afterNavigate hooks on Router

I have a Svelte app where I can set errors into a store, and they will show up on the page. However, the errors persist after changing the page. It would be handy to have navigate hooks to clear the errors before the page navigates (either via Link click or navigate api). The ability to perform some actions after navigate could also be potentially useful, e.g. scrolling to a specific part of the page if needed.

Suggested improvement:

<Router hook:beforeNavigate={clearErrors} hook:afterNavigate={scrollPage}>
  ...
</Router>

It could also be used for things like stopping navigate if a form is unsaved. If a beforeNavigate hook returns false, then the navigation should not proceed.

How do i get the current location globally in my App.svelte?

I wan't to store the current location e.g. ("/path") globally in my App.svelte to check on which path i am. How do i accomplish this is there something like a reactive "currentLocation" i can use? I didn't fnd anything in the documentation unfortunately.

visuallyHiddenStyle should include initial position to avoid visual bug

Describe the bug A clear and concise description of what the bug is.

visuallyHiddenStyle in a11y.js should include an initial position of bottom: 0. Without it, it renders under (or sometimes besides) elements in the Router causing scrollbars to appear.

To Reproduce Steps to reproduce the behavior:

  1. Wrap a div with style of height:100vh with Router.
  2. See scrollbar

Please add a link to an example in the Svelte REPL or in a Codesandbox, if you
can.

https://svelte.dev/repl/b4d98fdee025474291747a40a4ed32a7?version=3.31.2

Expected behavior A clear and concise description of what you expected to
happen.

No scrollbar or abnormal sizing issues.

$parentBase with null value causes error in segmentize function

Describe the bug
The screenshot below shows that join() is being passed pathFragments of null and "" respectively in paths.js. This ultimately leads to a TypeError: str is null in the stripSlashes() function. I can see from the stack trace that $parentBase is the null value in question in Route.svelte.

To Reproduce
Difficult, my codebase is a bit involved at this point. I have two <a> elements on a navbar with use:link attributes. Normally, they switch between pages/views perfectly.
One of my pages renders child component forms conditionally, based on some on:click logic to mount/destroy the child components. These children in no way interact with routing. This error only occurs if any of the child components forms are loaded and I select the other <a> on my navbar.

Screenshots
image
Full stack trace
image

Desktop (please complete the following information):

  • OS: Linux
  • Browser: Firefox
  • Version: 91.0.2

Please let me know if you require any additional info, I'll try to put together a minimal example and reproduce in a REPL
Thanks for your work, I really enjoy using this library and the examples in the docs helped a lot.

Variable assignment redeclares global property

In the docs, this line of code:

const location = useLocation(); 

seems to redeclare the global location (window.location). Maybe this should use a different name that doesn't already exist in the browser?

Navigate with <a> generated with vanillajs

I have links that are generated in svg outside svelte but would like to use the routing instead of a page refresh

image

Its generated like so

    const anchor = document.createElementNS("http://www.w3.org/2000/svg", "a")
            anchor.setAttributeNS(null, "href", `${person.username}`)

I can't use use:link for the <a> as in svelte files.

Focusing `h1` on route breaks in page / fragment links

Describe the bug

Upon route, focus is placed on the h1. This is good. But when my route contains a hash in its path, that breaks browser default behavior, which would be to scroll to relevant element and move the sequential focus starting point.

To Reproduce

Steps to reproduce the behavior:

  1. Follow a link with a fragment identifier

Expected behavior

Focus of heading behavior is skipped, because the hash specifies a more specific location.

Actual behavior

Heading is focused.

Screenshots

Desktop (please complete the following information):

n/a

Smartphone (please complete the following information):

n/a

Additional context

Route to fragment does not work

Describe the bug

It looks like a Link that routes to something on the same page does not actually work.

To Reproduce

  1. Add a Link component to all pages in your app, with in its to something that refers to an anchor on a specific page, say /about#digital, for instance <Link to="/about#digital"/>
  2. Open in browser and click the Link

REPL

Expected behavior

Page jumps to #digital on /about

Extra info

In a tool I work on, we go around this by manually focusing the fragment and scrolling to it (running a function called honourFragmentIdLinks, onMount of a page).

This does the job, although it breaks if no navigation to a different page is happening (as that requires no mounting).

Roadmap v4

V4 should make the router usable for more use cases. I've been planing on adding a hash based history for a while now. But there are a few things standing in the way to do this properly. Although the hash history would be optional, you'd still need the default browser history, as well as the fallback memory history module, even though you'd never use them. I've setup a new repo, where the history modules will be developed (svelte-navigator-history).
As a result the Router API will need to change slightly. There will need to be different Routers, like a HashRouter and BrowserRouter, similar to react-routers API. That's the only way to not bundle all history modules for every project.

In more detail, for v4 I'm planning to finish these tasks:

  • Add more tests to ensure everything is still working as intended
  • Add Router modules that are bound to a specific history
    • BrowserRouter using the HTML5 History API
    • HashRouter using the hash fragment
    • MemoryRouter for testing and embedded widgets
    • A Router, that automatically falls back to a memory history when the browser history doesn't work. Maybe AutoRouter
  • Add a way to block navigation, so the loss of unsaved data can be prevented
  • Make it possible to force a nested Router to act as a top-level Router, so it can be used to control a single widget on the page
  • Write a migration guide (also for people coming from svelte-routing #13)

Possible missing null check

Describe the bug

actions.js:55 Uncaught TypeError: Cannot read property 'hasAttribute' of null
    at actions.js:55
    at HTMLDivElement.handleClick (actions.js:12)

if (!anchor.hasAttribute("noroute")) {

To Reproduce
Steps to reproduce the behaviour:

  1. Use use:links on the top level element
  2. Click on anywhere on the page that is not a child of an anchor <a> element

Any way to disable the a11y?

Is there any way to disable the a11y functionality?

I went from svelte-routing to this one, because it felt like it was much more mature - but for some reason a focus rectangle popped up.

It took me a while to figure out that it was the a11y that set the tab index for the element to -1.

I don't need/want to have a11y for the page I'm trying to build and just wonder if there's any way to disable the functionality?

Can't stylize Link component

Hi. I'm using Svelte with Typescript. I'm using the Link component to navigate through my SPA.

I'm trying to write a class attribute to the Link component to stylize it but Typescript says it can't be.
Does exist any solution for it? I've read the whole documentation but I didn't found anything.
Thank you for your time.

Samuel,

Manage Focus on navigation

Currently, when navigating by clicking a Link, the link will remain focused after the transition is complete. This is a bad experience when using screen readers and other assistive technology.

The Router should move focus when navigating. It should:

  • Reset the focus from the active Link
  • Focus an appropriate heading element
  • Allow the user to set a custom focus element (possibly via a hook or action)
  • Announce the navigation

Resources:

navigation not working when deployed on server

Describe the bug

  • I'm new to this svelte-navigator, so maybe I'm doing something wrong
  • when run on my localhost dev Server(webpack) it's working but when deploy to the server(Apache) navigation is not working(no errors in chrome dev console)
  • my index.html has because of api
  • my Apache

To Reproduce

  1. App.svelte
<Router>
  <main>
    <Navbar />

    <Route path="/" component={Queries} />
    <Route path="queries" component={Queries} />
    <Route path="statistics" component={Statistics} />
    <Route path="status" component={Status} />
  </main>
</Router>
  1. Navbar.svelte
<nav class="navbar navbar-expand-lg navbar-light bg-dark">
  <a href="./" class="navbar-brand" use:link>
    <h3>Analizator</h3>
  </a>
  <a class="nav-link" href="./queries" use:link> Poizvedbe </a>
   <a class="nav-link" href="./statistics" use:link> Telefonija </a>
   <a class="nav-link" href="./status" use:link> Stanje Arnes klicnih centrov</a>
</nav>
  1. links on localhost
http://localhost:3000/   -> OK
http://localhost:3000/queries   -> OK
http://localhost:3000/statistics   -> OK
http://localhost:3000/status   -> OK

localhostStatus
localhostRoot

  1. links on server
https://sfinga2.arnes.si/analizator/   -> NOT OK
https://sfinga2.arnes.si/analizator/queries   -> NOT OK
https://sfinga2.arnes.si/analizator/statistics   -> NOT OK
https://sfinga2.arnes.si/analizator/status   -> NOT OK

sfingaRoot
sfingaQuery
sfingaStatus

Expected behavior
working as in dev server on localhost

Desktop (please complete the following information):

  • OS: windows 10
  • Browser chrome Version 90.0.4430.93

Additional context Add any other context about the problem here.

  • my SPA is served as war on payara
  • on server I have apache virtual host and proxy to maj java web app(serve index.html + bundle.js)
  • I have the same concept for this SPA build with angular in it works

Regards, Tomaž

Example of async private route

Firstly, thanks for creating this module - it's been a great help with creating my first couple of Svelte apps :)

In my latest app I've implemented the private route guard as in the example, and it works great when checking a simple bool or object on the store.

My auth requires an async call (to establish a JWT refresh on page reload), but I can't quite work out how to fit it in.

I thought I could use onMount, but that seems to be called after the reactive declaration. I also tried another 'checking' route that the private route could forward to on first load/browser refresh - which I would aim to handle the async check, but couldn't get that to work either.

Do you have an example how I might do this? Or if you can point me in the right direction, that would be much appreciated.

My current code looks like this:

    import { onMount } from 'svelte';
    import { useNavigate, useLocation } from 'svelte-navigator';
    import { checkAuthStatus, isAuthenticated } from '../stores/auth';

    const navigate = useNavigate();
    const location = useLocation();

    const navigateToLogin = () => {
        navigate('/login', {
            state: { from: $location.pathname },
            replace: true,
        });
    };

    onMount(async () => {
        try {
            await checkAuthStatus();
        } catch {
            navigateToLogin();
        }
    });

    $: if (!$isAuthenticated) {
        navigateToLogin();
    }
</script>

{#if $isAuthenticated}
    <slot />
{/if}

Conditional routes

Is this something evil or can it be supported?

            {#if $userDataStore}
                <Route path="/" component="{Home}" />
            {:else}
                <Route path="/" component="{Feed}" />
            {/if}

useLocation hook throws

Describe the bug A clear and concise description of what the bug is.

useLocation() hook throws error Function called oustide component initialization.

To Reproduce Steps to reproduce the behavior:

  1. npm init @vitejs/app (choose svelte > javascript)
  2. cd vite-project && npm install
  3. npm install svelte-navigator --save
  4. add router with link to route having useLocation()
  5. click the link and see error in console

Please add a link to an example in the Svelte REPL or in a Codesandbox, if you
can.

https://github.com/mataslib/uselocation-bug-repl

Expected behavior A clear and concise description of what you expected to
happen.

Show route content

Screenshots If applicable, add screenshots to help explain your problem.

errorprtscr

Additional context Add any other context about the problem here.

Seems to not work only with Vite dev. It works with vite build, when u visit builded dist html. I used useLocation() before with rollup and it was working. Then I switched to vite for its speed and it also did work. Today, I was removing and updating npm deps and it stopped working with this error. Unfortunately I wasn't able to revert to working state and detect the culprit, so I made this clean repl.

Can we get a live demo to assist in testing?

Is your feature request related to a problem? Please describe.
I need to test libs in edge 44
Describe the solution you'd like
a link to avoid having to set up my own example
Describe alternatives you've considered
my minimal example fails on edge 44 but im not sure if its due to svelte navigator

TypeError: append_styles is not a function

Describe the bug

When using renderWithRouter with jest + TypeScript the following error occurs:

TypeError: append_styles is not a function

  at Object.init (node_modules/svelte/internal/index.js:1791:22)
  at new Router (node_modules/svelte-navigator/dist/svelte-navigator.umd.js:1243:16)
  at create_fragment (src/testHelp/WrapRouter.svelte:335:11)
  at init (node_modules/svelte/internal/index.js:1809:37)
  at new WrapRouter (src/testHelp/WrapRouter.svelte:452:3)
  at render (node_modules/@testing-library/svelte/dist/pure.js:81:21)
  at renderWithRouter (src/testHelp/renderWithRouter.ts:22:9)
  at Object.<anonymous> (src/components/Layout.test.ts:5:19)
  at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
  at runJest (node_modules/@jest/core/build/runJest.js:387:19)
  at _run10000 (node_modules/@jest/core/build/cli/index.js:408:7)
  at runCLI (node_modules/@jest/core/build/cli/index.js:261:3)

To Reproduce

Here's a repo to reproduce the error.
Here's the error reproduced in GitHub Actions

Expected behavior

No error and succesfully rendering the component with the wrapped router.

how to send variable to Link

i want send params from my variable

localhost:3000/details/myparams

example

<script>

let id = "myparams"
</script
			<Link to="details/" **+ id** >details</Link> // id is my params  ->  localhost:3000/details/myparams

<Route path="details/id" component={Details}></Route>

my Details.Vue

<script>
	import {useParams} from 'svelte-navigator'
	let params = useParams()
</script>

detaiks aja {params.id}

Add Router modules, bound to specific history type

Add Router modules that are bound to a specific history:

  • BrowserRouter using the HTML5 History API
  • HashRouter using the hash fragment
  • MemoryRouter for testing and embedded widgets
  • A Router, that automatically falls back to a memory history when the browser history doesn't work. Maybe AutoRouter

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.