# Lightning Web Runtime :: Navigation and Routing

## Table of contents

-   [Introduction](#introduction)
-   [Background](#background)
    -   [Definitions](#definitions)
    -   [Prerequisites](#prerequisites)
-   [Webruntime Routing APIs](#webruntime-routing-apis)
    -   [`createRouter()`](#createrouter)
        -   [Lifecycle hooks](#lifecycle-hooks)
    -   [`NavigationContext`](#navigationcontext)
    -   [`navigate()`](#navigate)
    -   [`generateUrl()`](#generateurl)
    -   [`subscribe()`](#subscribe)
    -   [`webruntime-link`](#webruntime-link)
    -   [Usage examples](#usage-examples)
        -   [Creating a root router with hooks](#creating-a-root-router-with-hooks)
        -   [Using the `webruntime/navigation` APIs](#using-the-webruntimenavigation-apis)
-   [Customize a router](#customize-a-router)
    -   [Translation layer plug-points](#translation-layer-plug-points)
    -   [Navigation event plug-points](#navigation-event-plug-points)
    -   [Usage examples](#usage-examples-1)
        -   [Customizing a root router](#customizing-a-root-router)
-   [Nesting routers](#nesting-routers)
    -   [`webruntime-router`](#webruntime-router)
    -   [Usage examples](#usage-examples-2)
        -   [Create and customize a `webruntime-router`](#create-and-customize-a-webruntime-router)
        -   [Root router and `webruntime-router` relationship](#root-router-and-webruntime-router-relationship)
-   [Using Router Views](#using-router-views)
    -   [Setup route definitions](#setup-route-definitions)
    -   [Generate a router](#generate-a-router)
    -   [Add a router outlet](#add-a-router-outlet)
-   [Lightning Navigation](#lightning-navigation)
    -   [`CurrentPageReference`](#currentpagereference)
    -   [`NavigationMixin`](#navigationmixin)
    -   [Usage examples](#usage-examples-3)
        -   [`NavigationMixin` and `CurrentPageReference`](#navigationmixin-and-currentpagereference)
-   [Advanced topics](#advanced-topics)
    -   [`provideContext()`](#providecontext)
    -   [Hardcoding context](#hardcoding-context)
-   [Setup](#setup)
    -   [Install](#install)
    -   [Build](#build)
    -   [Tests](#tests)
-   [Architecture](#architecture)
    -   [Static analysis](#static-analysis)
    -   [Route definition matching](#route-definition-matching)
    -   [Diagrams](#diagrams)

## Introduction

This is a client-side routing package for off-core [LWC](https://developer.salesforce.com/docs/component-library/documentation/lwc)-based applications, from Lightning Web Runtime (Webruntime).

It supplies an API with the ability to create a router, navigate, generate URLs and subscribe to navigation events.

Routers can be customized at various plug points, or used as-is. Routers can also be nested, to create a hierarchy on the page.

Built on top of the Webruntime routing APIs is a **Lightning Navigation** layer. It provides implementations for the `NavigationMixin` and `CurrentPageReference` wire adapter from [lightning/navigation](http://component-library-dev.herokuapp.com/docs/component-library/bundle/lightning-navigation/documentation). This allows a component to be written once, and plugged in anywhere that uses the `lightning/navigation` contracts.

A consumer of this package can write an application that uses the Webruntime routing APIs, the Lightning Navigation layer, or both. Customer components or applications will most likely use the Lightning Navigation API, as our public-facing contract.

## Background

### Definitions

These concepts are used throughout this documentation. All examples are using this URL:

```
https://www.somewhere.com/case/10?param1=one&param2=two&param3
```

-   **URL**: A browser URL, either absolute or relative, containing both the path and query parameters (e.g.: `https://www.somewhere.com/case/10?param1=one&param2=two&param3` or `/case/10?param1=one&param2=two&param3`)
-   **path**: The `pathname` of a URL (e.g.: `/case/10`)
-   **absolute path**: A path that contains the entire `pathname` of a URL (e.g.: `/case/10` NOT `/10`)
-   **query string**: The query part of a URL (e.g.: `?param1=one&param2=two&param3`)
-   **query object**: An object representing a parsed query string, e.g.:

```json
{
    "param1": "one",
    "param2": "two",
    "param3": ""
}
```

-   **route definition**: An object used to describe a URL category or pattern. A shape containing a **unique** `id` and an optionally parameterized `path` is used. [Path-to-regexp](https://github.com/pillarjs/path-to-regexp) is used to parse the `path`. To support `lightning/navigation` APIs, add the `page` property. Additional custom data may also be included, e.g.:

```js
{
    /* required data */
    "path": "/case/:recordId/:optional?", // Uses path-to-regexp for parsing parameters.
    "exact": true/false, // When true, will only match if the path matches location.pathname exactly. The default is `true`.

    /* Basic routing */
    "id": "case-detail",

    /* lightning/navigation */
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    },

    /* custom data */
    "view": "caseDetail"
    "label": "Case Detail"
}
```

_Note_: More information about route definition matching can be found [here](#route-definition-matching).

-   **route**: An object representing the current URL. The `id` comes from the matching route definition. The `attributes` property contains the parameters parsed from the URL using the route definition `path`. The `state` property contains the query object, e.g.:

```json
{
    "id": "case-detail",
    "attributes": {
        "objectApiName": "Case",
        "recordId": "place"
    },
    "state": {
        "param1": "one",
        "param2": "two",
        "param3": ""
    }
}
```

-   **[page reference](https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/components_navigation_page_definitions.htm)**: A special type of route object used for `lightning/navigation`. The `type` property is used instead of the `id`, e.g.:

```json
{
    "type": "standard__recordPage",
    "attributes": {
        "objectApiName": "Case",
        "recordId": "place"
    },
    "state": {
        "param1": "one",
        "param2": "two",
        "param3": ""
    }
}
```

-   **simple route**: When a URL does not match any route definitions, a default route containing the absolute path and query object is produced by the router, e.g.:

```json
{
    "type": "standard__simpleRoute",
    "attributes": {
        "path": "/case/10"
    },
    "state": {
        "param1": "one",
        "param2": "two",
        "param3": ""
    }
}
```

-   **navigation event**: The navigation state is changing, either by a browser URL update, or programmatically.
-   **navigation context**: When a component provides navigation context, it implements the functions necessary to support the Webruntime routing APIs for its descendent components.
-   **router**: A piece of code that manages navigation changes. A router can have up to 1 direct child router. A router provides navigation context.
-   **root router**: The router instance that sits highest up in the DOM hierarchy. Updates flow from the root router to its child router, then grandchild router, etc. Some applications may only have a root router.
-   **translation layer**: The functions a router uses to translate a URL to a route, and a route to a URL.

### Prerequisites

The navigation package has a dependency upon the [LWC wire-service](https://developer.salesforce.com/docs/component-library/documentation/lwc/data_wire_service_about). To include the `wire-service`:

1. Preload the `wire-service` in `webruntime-app.config.js`:

```js
preloadModules: ['wire-service'],
```

2. Register the `wire-service` in the root component of the application:

```js
import { LightningElement, register } from 'lwc';
import { registerWireService } from 'wire-service';

registerWireService(register);

export default class RootApp extends LightningElement {
    // ...
}
```

## Webruntime routing APIs

The Webruntime routing APIs supply functions to create a root router, navigate (programmatically and declaratively), generate URLs, and subscribe to navigation events.

```js
// app.js
import { createRouter, navigate, generateUrl, subscribe, NavigationContext } from 'webruntime/navigation';

<!-- component.html -->
<webruntime-link path="/some/where">CLICK ME</webruntime-link>`
```

### `createRouter()`

`const router = createRouter(config);`

Create a new root router for an application with arguments:

-   `config`: Configure the router with an object containing these properties:
    -   `basePath`: Set a base path for this router. The default is an empty string.
    -   `routes`: Set an array of route definitions for URL parsing. The default is an empty array.
    -   `noHistory`: By default, the router will manage the [browser history](https://developer.mozilla.org/en-US/docs/Web/API/Window/history). To turn this behavior off, set `noHistory: true`.
    -   `caseSensitive`: When `true`, the route definition path matching will be case sensitive. The default is `false`.

A router object is returned:

-   `connect()`: Call this to start the router.
-   `disconnect()`: Call this to stop the router from interacting with the application.
-   `addPreNavigate(function | function[]) -> this router`: Add a listener function, or array of functions, to the pre navigate event hook.
-   `addPostNavigate(function | function[]) -> this router`: Add a listener function, or array of functions, to the post navigate event hook.
-   `addErrorNavigate(function | function[]) -> this router`: Add a listener function, or array of functions, to the navigation error hook.
-   `id`: The navigation context ID for this router.

#### Lifecycle hooks

A router can have 0 or more listeners attached to each of its lifecycle hooks. The listeners are fired synchronously in the order in which they were attached:

-   `preNavigate(transaction)`: This runs during a navigation event, before the state is changed. Listeners can stop the transaction at this point. If stopped, the `errorNavigate` listeners will be fired. The `preNavigate` functions should return one of these values:
    -   `true`: The route should be processed.
    -   `false`: The navigation event should be stopped, and no more listeners run on _any_ router.
    -   `Promise`: Resolves to one of the values above; rejected `Promise`s are treated as `false`.
-   `postNavigate(transaction)`: This runs after a navigation event completes. Subscribers will not be notified until all post navigation listeners have finished executing. If a `postNavigate` listener returns `false` or a rejected `Promise`, the remaining listeners for that router are skipped, but the `postNavigate` listeners on any descendent routers will still run.
-   `errorNavigate(error)`: This runs if the router encounters an error trying to navigate.

The pre and post navigation listens are passed a read-only transaction object:

-   `current`: The navigation information `{ route, data }` for the current state, route may be `null` during `preNavigate`.
-   `next` (`preNavigate` only): The navigation information that will load next (if not stopped).
-   `previous` (`postNavigate` only): The navigation information that just unloaded, route may be `null`.

The `postNavigate` listeners are passed an `error` object:

```
{
    code: an integer error code,
    message: a string error message,
    level: 1,
    url: an optional URL where more information on the error can be found
}
```

See an example [here](#creating-a-root-router-with-hooks).

### `NavigationContext`

```js
@wire(NavigationContext)
navContext;
```

A [wire adapter](https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.data_wire_service_about) that gives a component access to the its navigation context. The navigation context is used to call the Webruntime routing APIs below.

### `navigate()`

`navigate(navigationContext, location, shouldReplace);`

Navigate to a new page programmatically with arguments:

-   `navigationContext`: The navigation context in which to navigate.
-   `location`: A route, or absolute path, to navigate to.
-   `shouldReplace`: Send `true` if the new location should replace the current one in the browser history. The default is `false`.

### `generateUrl()`

`generateUrl(navigationContext, route).then((url) => this.path = url);`

Given a route, returns a `Promise` to a string URL.

### `subscribe()`

```js
const callback = (route, data) => console.log(route, data);
this.subscription = subscribe(navigationContext, callback);
// .....
if (this.subscription) {
    this.subscription.unsubscribe();
}
```

Pass in a callback function that will get executed every time the navigation state changes. Receive a `Promise` to an observer with an `unsubscribe()` function in return.

The callback function takes these arguments:

-   `route`: The new current route.
-   `data`: Extra data associated with the current route. In the default implementation, this is the route definition which matches the current route. It will be null in the case of Simple Route (i.e.: no match).

### `webruntime-link`

A LWC used for declarative navigation. It can take either a string URL or a route as input.

```html
<webruntime-link path="/a/path?filter=all">
    <span>CLICK HERE</span>
</webruntime-link>
```

```html
<webruntime-link route="{route}">
    <img src="/my/link.gif" alt="click me" />
</webruntime-link>
```

The following CSS variables are used to style the internal `a` tag. Overwrite the values to update the look of the `link`s.

```css
a {
    color: var(--webruntime-link-color);
    font-size: var(--webruntime-link-font-size);
    text-decoration: var(--webruntime-link-text-decoration);
}
a:hover,
a:active {
    color: var(--webruntime-link-color-active, var(--webruntime-link-color));
    font-size: var(--webruntime-link-font-size-active, var(--webruntime-link-font-size));
    text-decoration: var(--webruntime-link-text-decoration-active);
}
```

### Usage examples

#### Creating a root router with hooks

```js
import { createRouter } from 'webruntime/navigation';
const ROUTE_DEFINITIONS = [
    {
        id: 'recordPage',
        path: '/r/:objectApiName/:recordId/:actionName',
    },
    {
        id: 'objectPage',
        path: '/o/:objectApiName/:actionName',
    },
    {
        id: 'homePage',
        path: '/', // This is the default route, and it must be last.
    },
];

const router = createRouter({ basePath: '/demo', routes: ROUTE_DEFINITIONS });

// Add logger pre and post navigation hook listeners. Then connect.
router
    .addPreNavigate((t) => console.log(`pre: Current: %o. Next: %o`, t.current.route, t.next.route))
    .addPostNavigate((t) =>
        console.log(`post: Current: %o. Previous: %o`, t.current.route, t.previous.route)
    )
    .addErrorNavigate((e) =>
        console.error(`There was a problem during navigation: ${e.code} :: ${e.message}`)
    )
    .connect();
```

#### Using the `webruntime/navigation` APIs

```js
import { track, wire, LightningElement } from 'lwc';
import { navigate, generateUrl, subscribe, NavigationContext } from 'webruntime/navigation';

const aRoute = {
    id: 'page',
    attributes: {
        name: 'sample',
    },
};

export default class Example extends LightningElement {
    @track name = '';
    @track path = null;
    subscription = null;

    // Get a reference to the navigation context for this component.
    @wire(NavigationContext)
    navContext;

    // Subscribe to updates on the current state.
    connectedCallback() {
        this.subscription = subscribe(this.navContext, (route, data) => {
            this.name = route.attributes.name || '';
            this.path = data ? data.path : null;
        });
    }

    // Navigate programmatically by URL.
    navUrl() {
        navigate(this.navContext, '/some/path');
    }

    // Navigate programmatically by route.
    navRoute() {
        navigate(this.navContext, aRoute);
    }

    // Generate a URL for a route.
    getUrl() {
        generateUrl(this.navContext, aRoute).then((url) => console.log(url));
    }

    // Disconnect from the navigation event subscription.
    disconnectedCallback() {
        if (this.subscription) {
            this.subscription.unsubscribe();
        }
    }
}
```

## Customize a router

A router has plug-points that accept logic to override the default behavior. The translation layer (route <-> URL) and navigation event handling can be customized. Pass the plug-points in as properties on a router's [config](#createrouter) object.

### Translation layer plug-points

-   **getRouteFromUrl**: `f(url, defaultImpl) -> { route, data }`: Given a URL (absolute or relative), return the associated route and data. If a route could not be found to match the given URL then `route: null`.
-   **getUrlFromRoute**: `f(route, defaultImpl) -> { url, data }`: Given a route, return the associated relative URL string with an absolute path and data. If a URL could not be created for the given route then `url: null`.

Things to note:

-   Returning `null` for a `url` or `route` will result in the `errorNavigation` hook listeners being call on all routers.
-   If a plug-point is not provided, a default implementation is used. The default implementation function is provided as a second argument to the plug-point functions for convenience.
-   The default implementation of `getRouteFromUrl` will never return `route: null`. A Simple Route is returned for URLs that do not match any route definition. See this [example](#customizing-a-root-router) for turning off Simple Routes.

### Navigation event plug-points

-   **handleNavigation**: `f(url | route, shouldReplace) -> boolean`: This is called whenever a navigation event bubbles up through a router. Return false to stop propagation and cancel the event. This function is a no-op by default. The arguments are:
    -   `url` | `route`: The string URL or route object passed in from [`navigate()`](#navigate).
    -   `shouldReplace`: `true` if the URL should replace the current one in the browser history.

### Usage examples

#### Customizing a root router

```js
import { LightningElement } from 'lwc';
import { createRouter } from 'webruntime/navigation';
import ROUTE_DEFINITIONS from './routeDefinitions';

export default class Example extends LightningElement {
    constructor() {
        super();
        this.router = createRouter({
            basePath: '/demo',
            routes: ROUTE_DEFINITIONS,
            handleNavigation: this.handleNavigation.bind(this),
            getRouteFromUrl: this.getRouteFromUrl.bind(this),
            getUrlFromRoute: this.getUrlFromRoute.bind(this),
        });
        this.router.connect();
    }

    disconnectedCallback() {
        this.router.disconnect();
    }

    // Process a navigation event bubbling up from descendent components.
    // The getRouteFromUrl function is provided for convenience.
    // This example stops propagation if the route id is 'no__good'.
    handleNavigation(input, options, getRouteFromUrl) {
        const route = typeof input === 'string' ? getRouteFromUrl(input).route : input;
        return route.id !== 'no__good';
    }

    // Return null as the route instead of Simple Routes.
    // This turns off the Simple Route feature.
    // The errorNavigate hook listeners are run when { route: null }
    getRouteFromUrl(url, defaultImpl) {
        const defaultInfo = defaultImpl(url);
        return defaultInfo.route.type === 'standard__simpleRoute'
            ? { route: null, data: null }
            : defaultInfo;
    }

    // Set a sticky query parameter on every URL.
    getUrlFromRoute(route, defaultImpl) {
        const { url, routeDef } = defaultImpl(route);
        return {
            url: url ? `${url}?new=param` : null,
            data: routeDef,
        };
    }
}
```

## Nesting routers

Multiple routers can be nested, with each router having up to 1 child. Each router is responsible for parsing part of the URL path, and providing the associated route to its descendent subscribers.

### `webruntime-router`

```html
<webruntime-router
    routes="{routes}"
    base-path="/base"
    no-history
    case-sensitive
    onprenavigate="{preNavigate}"
    onpostnavigate="{postNavigate}"
    onerrornavigate="{errorNavigate}"
    onhandlenavigation="{handleNavigation}"
></webruntime-router>
```

Create a child router declaratively with this LWC. Pass the [config](#createrouter) options to the router via [@api properties](https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.js_props_public):

-   `routes`
-   `base-path`
-   `no-history`
-   `case-sensitive`

Attach hooks with event listeners:

-   `onprenavigate` is cancelable
-   `onpostnavigate`
-   `onerrornavigate`
-   `onhandlenavigation` is cancelable

### Usage examples

#### Create and customize a `webruntime-router`

```html
<template>
    <webruntime-router
        routes="{routes}"
        base-path="/demo"
        onprenavigate="{preNavigate}"
        onpostnavigate="{postNavigate}"
        onerrornavigate="{errorNavigate}"
        onhandlenavigation="{handleNavigation}"
    >
        <template if:true="{message}"><error>{message}</error></template>
        <webruntime-link path="/a/place">I'm inside the child navigation context</webruntime-link>
        <view></view>
    </webruntime-router>
</template>
```

```js
import { track, LightningElement } from 'lwc';
import ROUTE_DEFINITIONS from './routeDefinitions';
import { user } from '../services/user';
import { getData } from '../services/xhr';

export default class Example extends LightningElement {
    @track errorMessage = null;
    routes = ROUTE_DEFINITIONS;

    // Add lifecycle hook events.
    preNavigate(e) {
        e.stopPropagation();
        // If any pre-navigate hook returns false, cancel the event.
        if (!this.authorization(e.detail) || !this.unmountCheck(e.detail)) {
            e.preventDefault();
        }
    }
    postNavigate(e) {
        e.stopPropagation();
        this.logNav(e.detail);
        this.getData(e.detail);
    }
    errorNavigate(e) {
        e.stopPropagation();
        this.showError(e.detail);
        this.getData(e.detail);
    }
    handleNavigation(e) {
        e.stopPropagation();
        const { input } = e.detail;
        console.log('Bubbling navigation event: ', input);
    }

    // Do not allow navigation to private pages for guest users.
    authorization(transaction) {
        if (user.isGuest() && transaction.next.route.id === 'private-page') {
            return false;
        }
        return true;
    }

    // Warn users before leaving the current page.
    // !transaction.current.route indicates a first time page load.
    unmountCheck(transaction) {
        return !transaction.current.route
            ? true
            : new Promise((resolve) => {
                  resolve(confirm('Are you sure you want to leave?'));
              });
    }

    // Log navigation events that just completed.
    // Clear the error message.
    logNav(transaction) {
        console.log(
            `postNav -> Current: %o. Previous: %o`,
            transaction.current.route,
            transaction.previous.route
        );
        this.errorMessage = null;
    }

    // Get some XHR data before the subscribers are notified of this navigation event.
    getData(transaction) {
        return getData(transaction.current.route.id).then((data) => doSomething(data));
    }

    // Show navigation errors to the user.
    showError(e) {
        this.errorMessage = `There was a problem during navigation: ${e.message}`;
    }
}
```

#### Root router and `webruntime-router` relationship

When a child router is added, there are two navigation context providers present in the application. Consider a root router with these properties:

```js
basePath: '/lightning';
routes: [
    {
        id: 'app',
        path: '/app/:appName',
        exact: false, // This allows the child router at this route definition
    },
];
```

a child router with:

```js
basePath: '';
routes: [
    {
        id: 'page',
        path: '/page/:name',
    },
];
```

and a URL like this:
`/lightning/app/cat+app/page/siamese?cute=yes`

A successful navigation event looks like this:

1. A navigation event is detected, and the `preNavigate` hooks are fired from root -> leaf router node.
2. The root router matches the first 3 segments of the path and produces this route:

```json
{
    "id": "app",
    "attributes": {
        "appName": "cat app"
    },
    "state": {
        "cute": "yes"
    }
}
```

3. The route is sent to the subscribers of the root router navigation context.
4. The root router delegates to the child router by sending it the absolute path of the URL.
5. The child router matches the last 2 segments of the path and produces this route:

```json
{
    "id": "page",
    "attributes": {
        "name": "siamese"
    },
    "state": {
        "cute": "yes"
    }
}
```

6. The `postNavigate` hooks are fired on the **root** and its subscribers are notified.
7. The `postNavigate` hooks are fired on the **child** and its subscribers are notified.

_Note_: Subscribers receive different data depending on their navigation context, provided by the closest ancestor router.

## Using Router Views

The LWR Routing Service allows a lwc to be specified for each route definition. LWR also provides a component which automatically displays the lwc associated with each route. To use the Routing Service, add it to the `services` array in _webruntime-app.config.js_:

```js
const { RoutingService } = require('@webruntime/navigation');
module.exports = {
    services: [RoutingService],
};
```

### Setup route definitions

Create one or more JSON files in a LWR project to hold route defintion data:

```
projectRoot
├── routes/
│   └── cooking.json    // route set id = "cooking"
│   └── child.json      // route set id = "child"
```

_Note_: Multiple files are created to support [nested routers](#nesting-routers).

Each file holds a "route set" with an ID matching the filename. The route set JSON looks like this:

```json
{
    "home": {
        "path": "/",
        "component": "x/home"
    },
    "recipe": {
        "path": "/recipes/:title",
        "component": "x/recipeItem"
    }
}
```

_Note_: The route definition IDs are keys in this object. This automatically supports ID uniqueness across the route set.

### Generate a router

The LWR Routing Service generates a router component, given a route set ID:

```html
<!-- x/app template -->
<template>
    <!-- Add a router to the application template
         This router receives the data from cooking.json above -->
    <webruntime-router-cooking></webruntime-router-cooking>
</template>
```

The `webruntime-router-{setID}` components support the same properties and events as the [`webruntime-router`](#webruntime-router) component, besides `routes` which is passed automatically given the route set ID.

### Add a router outlet

The [route sets](#setup-route-definitions) contain a component for each route definition. Add a `webruntime-outlet` component under a router to automatically display the component on route change:

```html
<!-- x/app template -->
<template>
    <!-- Add a router to the application template
         This router receives the data from cooking.json above -->
    <webruntime-router-cooking>
        <!-- Add an outlet as a child to the router
             This renders the current component view -->
        <webruntime-outlet>
            <span slot="error">
                <x-error></x-error>
            </span>
            <span slot="404">
                <x-404></x-404>
            </span>
        </webruntime-outlet>
    </webruntime-router-cooking>
</template>
```

The `webruntime-outlet` component contains:

-   slots:
    -   "error": Shows when there is an error loading the component for the route.
    -   "404": Shows when the component could not be resolved.
-   properties:
    -   `refocus-off`: The outlet automatically puts [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus) on the component when it is loaded, for accessibility. To turn this feature off, add the `refocus-off` property to `webruntime-outlet`

The attributes for the current route are automatically passed into the corresponding component as public properties (`@api`). Given this route:

```json
{
    "id": "recipe",
    "attributes": {
        "title": "bread"
    }
}
```

and the "x/recipeItem" component:

```js
import { LightningElement, api } from 'lwc';
export default class XRecipeItem extends LightningElement {
    @api title;
}
```

the router outlet would pass in "bread" for the title property:

```html
<x-recipe-item title="bread"></x-recipe-item>
```

## Lightning Navigation

The `webruntime/lightningNavigation` package provides implementations for the `NavigationMixin` and `CurrentPageReference` wire adapter from [lightning/navigation](http://component-library-dev.herokuapp.com/docs/component-library/bundle/lightning-navigation/documentation).

```js
import { NavigationMixin, CurrentPageReference } from 'webruntime/lightningNavigation';
```

### `CurrentPageReference`

A [wire adapter](https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.data_wire_service_about) that gives a component access to the current page reference (relative to its navigation context).

### `NavigationMixin`

A JavaScript class mixin that provides functions to navigate or generate a URL:

-   `this[NavigationMixin.Navigate](url | pageRef)`: Programmatically navigate to a string URL or page reference.
-   `this[NavigationMixin.GenerateUrl](pageRef) => Promise<string>`: Translate a page reference into an absolute path.

### Usage examples

#### `NavigationMixin` and `CurrentPageReference`

This example is analogous to the [`webruntime/navigation` API example](#using-the-webruntimenavigation-apis).

```js
import { wire, LightningElement } from 'lwc';
import { NavigationMixin, CurrentPageReference } from 'webruntime/lightningNavigation';

const aPageRef = {
    type: 'page',
    attributes: {
        name: 'sample',
    },
};

export default class Example extends NavigationMixin(LightningElement) {
    // Subscribe to updates on the current state.
    @wire(CurrentPageReference)
    currentPageRef;

    // Use the current page reference.
    @track
    get name() {
        return this.currentPageRef ? this.currentPageRef.attributes.name : '';
    }

    // Navigate by URL.
    navUrl() {
        this[NavigationMixin.Navigate]('/some/path');
    }

    // Navigate by page reference.
    navPageRef() {
        this[NavigationMixin.Navigate](aPageRef);
    }

    // Generate a URL.
    getUrl() {
        this[NavigationMixin.GenerateUrl](aPageRef).then((url) => console.log(url));
    }
}
```

## Advanced topics

### `provideContext()`

`provideContext(context, node)`

Using the [`createRouter()`](#createrouter) API will create a `Router` with navigation context automatically. To create custom navigation context, call the `provideContext()` API with the following arguments:

-   `context`: The provided context must have exactly these properties:
    -   `navigate`: An implementation of the [`navigate()`](#navigate) function.
    -   `generateUrl`: An implementation of the [`generateUrl()`](#generateurl) function.
    -   `subscribe`: An implementation of the [`subscribe()`](#subscribe) function.

An object is returned containing these properties:

-   `id`: An identifier for the navigation context. This is the value returned over the [`NavigationContext`](#navigationcontext) wire adapter.
-   `update(value)`: Update the `context` object to a new value. Subscribers will be updated with the new API implementations.
-   `disconnect()`: Stop the navigation context from being detected by descendant components.

### Hardcoding context

In a case where an application only has one router, the application developers can consider providing their users with Navigation APIs which are locked to the single navigation context. Then users do not need to pull in the navigation context themselves.

```js
import {
    navigate as webruntimeNavigate,
    generateUrl as webruntimeGenerateUrl,
    subscribe as webruntimeSubscribe,
    createRouter
} from 'webruntime/navigation';

// Create and start the router.
const router = createRouter({...});

// Navigate programmatically.
export function navigate(loc, options) {
    webruntimeNavigate(router.id, loc, options);
}

// Generate a URL for the given route.
export function generateUrl(route) {
    return webruntimeGenerateUrl(router.id, route);
}

// Subscribe to navigation state changes.
export function subscribe(callback) {
    webruntimeSubscribe(router.id, callback);
}
```

## Setup

### Install

```bash
yarn add @webruntime/navigation
```

### Build

Build tasks can be run in this repository from the **/packages/@webruntime/navigation** directory with:

```bash
yarn clean
yarn build
```

### Tests

Test tasks can be run in this repository from the **/packages/@webruntime/navigation** directory with:

```bash
yarn test     // run jest tests
yarn coverage // test with coverage output
yarn lint     // lint the code
```

## Architecture

### Static analysis

A routing goal is to be able to statically analyze by URL path. Meaning, the code needed to display a component/view/page for a given path can be known ahead of runtime. Route definitions should contain the metadata needed for a builder plugin to do the analysis. A different builder plugin is written to analyze different styles of route definition metadata. Example route definitions:

-   Basic id-based routing:

```json
{
    "id": "home",
    "path": "/home",
    "component": "our-home"
}
```

-   Lightning Navigation page-based:

```json
{
    "path": "/case/:recordId",
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}
```

-   Webruntime view-based, with Lightning Navigation support:

```json
{
    "id": "path-detail",
    "path": "/case/:recordId",
    "view": "caseDetail",
    "label": "Case Detail",
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}
```

### Route definition matching

Matching a route to a route definition is relatively straight forward, since the `id`s are unique. Here is the criteria:

-   `route.id === routeDef.id`
-   each non-optional parameterized path segment (e.g.: `/:recordId`) in the route definition `path`, must match a key in the route `attributes`

Here is an example:

```json
// route definition
{
    "id": "home",
    "path": "/app/:appName"
}
// matching route
{
    "id": "home",
    "attributes": {
        "appName": "awesome"
    }
}
// NOT matching route
{
    "id": "home"
}
```

Matching a page reference is more complex. Here is the criteria:

-   `pageRef.type === routeDef.page.type`
-   each non-optional parameterized path segment (e.g.: `/:recordId`) in the route definition `path`, must match a key in the page reference `attributes`
-   each key/value pair in the route definition `page.attributes`, must match a key/value pair in the page reference `attributes`
-   if there is more than 1 route definition match for a page reference, pick the first one

Here are some examples:

-   simplest case, no `attributes`:

```json
// page reference
{
    "type": "home"
}
// matching route definition
{
    "path": "/home",
    "page": {
        "type": "home"
    }
}
// NOT matching route definition
{
    "path": "/old/home",
    "page": {
        "type": "home"
    }
}
```

-   route definition with parameterized `path`:

```json
// page reference
{
    "type": "standard__recordPage",
    "attributes": {
        "recordId": "0D50M00004NgNxtSAF"
    }
}
// matching route definition
{
    "path": "/record/:recordId",
    "page": {
        "type": "standard__recordPage"
    }
}
```

-   route definition with parameterized `path` containing optional parameters:

```json
// page reference
{
    "type": "standard__recordPage",
    "attributes": {
        "recordId": "0D50M00004NgNxtSAF"
    }
}
// matching route definition
{
    "path": "/record/:recordId/:recordName?",
    "page": {
        "type": "standard__recordPage"
    }
}
```

-   route definition has `page.attributes`:

```json
// page reference
{
    "type": "standard__objectPage",
    "attributes": {
        "objectApiName": "Case"
    }
}
// matching route definition
{
    "path": "/cases",
    "page": {
        "type": "standard__objectPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}
```

-   route definition has both a parameterized `path` and `page.attributes`:

```json
// page reference
{
    "type": "standard__recordPage",
    "attributes": {
        "objectApiName": "Case",
        "recordId": "0D50M00004NgNxtSAF"
    }
}
// matching route definition
{
    "path": "/case/:recordId",
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}
```

### Diagrams

Programmatic navigation events flow up from their source node through each navigation context. If not stopped, they eventually reach the root router, which processes the event.

The URL is then parsed starting at the root router and flowing down through any child routers. First the `preNavigate` hook listeners are run, from root to leaf. If all return true, the `postNavigate` hook listeners are run, then each router hydrates their subscribers with a new route/page reference.

![navigation event flow diagram](./doc/nav_flow.png?raw=true)
