{"id":40336225,"url":"https://github.com/erhathaway/router-primitives","last_synced_at":"2026-01-20T08:35:08.390Z","repository":{"id":35001651,"uuid":"145478784","full_name":"erhathaway/router-primitives","owner":"erhathaway","description":"A framework agnostic application router. Declarative routing by way of layout primitives   :sunrise_over_mountains:","archived":false,"fork":false,"pushed_at":"2024-10-10T19:50:16.000Z","size":2354,"stargazers_count":7,"open_issues_count":14,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-12T16:14:48.010Z","etag":null,"topics":["custom-primitives","data","feature","layout","layout-primitives","link","router","router-actions","router-primitives","router-state","scene","stack"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erhathaway.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2018-08-20T22:59:34.000Z","updated_at":"2025-10-08T01:16:32.000Z","dependencies_parsed_at":"2023-01-15T11:48:48.221Z","dependency_job_id":null,"html_url":"https://github.com/erhathaway/router-primitives","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/erhathaway/router-primitives","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhathaway%2Frouter-primitives","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhathaway%2Frouter-primitives/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhathaway%2Frouter-primitives/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhathaway%2Frouter-primitives/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erhathaway","download_url":"https://codeload.github.com/erhathaway/router-primitives/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erhathaway%2Frouter-primitives/sbom","scorecard":{"id":379968,"data":{"date":"2025-08-11","repo":{"name":"github.com/erhathaway/router-primitives","commit":"23ee2a97eee6d93dbac0422a4cdec2440345fe4b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/continuous_integration.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:51: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:54: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:59: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:72: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:75: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:80: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:93: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:96: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:101: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:114: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:117: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:122: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/continuous_integration.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/erhathaway/router-primitives/continuous_integration.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/continuous_integration.yml:24","Info:   0 out of  18 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"45 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-7wpw-2hjm-89gp","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T15:10:20.956Z","repository_id":35001651,"created_at":"2025-08-18T15:10:20.956Z","updated_at":"2025-08-18T15:10:20.956Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28599094,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T02:08:49.799Z","status":"ssl_error","status_checked_at":"2026-01-20T02:08:44.148Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["custom-primitives","data","feature","layout","layout-primitives","link","router","router-actions","router-primitives","router-state","scene","stack"],"created_at":"2026-01-20T08:35:07.192Z","updated_at":"2026-01-20T08:35:08.379Z","avatar_url":"https://github.com/erhathaway.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Router Primitives\n\n[![npm](https://img.shields.io/npm/v/router-primitives.svg?label=\u0026color=0080FF)](https://github.com/erhathaway/router-primitives/releases/latest)\n\nRouter Primitives is a **layout primitives** paradigm for application routing. Instead of focusing on pattern matching path names and query params, you describe the layout of your application in terms of router primitives. Primitives are composable and provide a simple declarative API to control routing actions and for adding complex animations.\n\nWith the Router Primitives abstraction, you can write apps that look like this:\n\n```typescript\nimport {Animatable, createRouterComponents} from 'router-primitives-react';\nimport anime from 'animejs';\nimport {Manager, predicates} from 'router-primitives';\n\nconst layout = {\n    name: 'Root',\n    children: {\n        scene: [\n            { name: 'UserScene',\n              children: {\n                 data: [{name: 'UserId', isPathRouter: true}]\n              }\n            },\n            { name: 'HomeScene',\n              defaultAction: ['show']\n            },\n            { name: 'OptionsScene',\n              children: {\n                scene: [{name: 'AppOptions', defaultAction: ['show']}, {name: 'UserOptions'}]\n              }\n            }\n        ],\n        features: [{name: 'SideNav', routeKey: 'nav'}]\n    }\n};\n\nconst manager = new Manager(layout);\nconst Routers = createRouterComponents(manager.routers);\n```\n\nIn yaml, the `layout` object would look like this:\n\n```yaml\nname: Root\nchildren:\n  scene:\n    - name: UserScene\n      children:\n        data:\n          - name: UserId\n            isPathRouter: true\n    - name: HomeScene\n      defaultAction: \n        - show\n    - name: OptionsScene\n      children:\n        scene:\n          - name: AppOptions\n            defaultAction:\n              - show\n          - name: UserOptions\n  features:\n    - name: SideNav\n      routeKey: nav\n```\n\nOnce you define a layout, you can use the generated router in an app. This example uses the React bindings:\n\n```jsx\nconst app = () =\u003e (\n    \u003cRouter.Root\u003e\n        \u003cRouter.UserScene\u003e\n            \u003cRouter.UserIdData\u003e{({state: {data}}) =\u003e`The current user id is: ${data}`}\u003c/Router.UserIdData\u003e\n        \u003c/Router.UserScene\u003e\n        \u003cRouter.HomeScene\u003e{'Welcome to the app'}\u003c/Router.HomeScene\u003e\n        \u003cRouter.OptionsScene\u003e\n            \u003cRouter.AppOptions.Link action={'show'}\u003e\n                \u003cdiv\u003e{`Show App Options`}\u003c/div\u003e\n            \u003c/Router.AppOptions.Link\u003e\n            \u003cRouter.UserOptions.Link action={'show'}\u003e\n                \u003cdiv\u003e{`Show User Options`}\u003c/div\u003e\n            \u003c/Router.UserOptions.Link\u003e\n            \u003cRouter.AppOptions\u003e{'All your app option components'}\u003c/Router.AppOptions\u003e\n            \u003cRouter.UserOptions\u003e{'All your user option components'}\u003c/Router.UserOptions\u003e\n        \u003c/Router.OptionsScene\u003e\n        \u003cRouter.SideNav.Animate\n            unMountOnHide\n            when={[\n                [\n                    predicates.isJustShown,\n                    ({node}) =\u003e anime({targets: `#${node.id}`, translateX: [0, 200]})\n                ][\n                    (predicates.isJustHidden,\n                    ({node}) =\u003e anime({targets: `#${node.id}`, translateX: [200, 0]}))\n                ]\n            ]}\n        \u003e\n            \u003cAnimatable\u003e\n                \u003cRouter.UserScene.Link action={'show'}\u003e\n                    \u003cdiv\u003e{`Show Home Scene`}\u003c/div\u003e\n                \u003c/Router.UserScene.Link\u003e\n                \u003cRouter.HomeScene.Link action={'show'}\u003e\n                    \u003cdiv\u003e{`Show Home Scene`}\u003c/div\u003e\n                \u003c/Router.HomeScene.Link\u003e\n                \u003cRouter.OptionsScene.Link action={'show'}\u003e\n                    \u003cdiv\u003e{`Show Options`}\u003c/div\u003e\n                \u003c/Router.OptionsScene.Link\u003e\n            \u003c/Animatable\u003e\n        \u003c/Router.SideNav.Animate\u003e\n        \u003cRouter.SideNav.ToggleLink\u003e\n            \u003cdiv\u003e{`Toggle Side Nav Visibility`}\u003c/div\u003e\n        \u003c/Router.SideNav.ToggleLink\u003e\n    \u003c/Router.Root\u003e\n);\n```\n\n# About\n\n#### Documentation\n\n-   **[About](#about)** :point_left:\n-   [Usage](#usage)\n-   [API](#api)\n-   [Primitives](#primitives)\n-   [Router Actions](#router-actions)\n-   [Router Links](#router-links)\n-   [Router State Predicates](#router-state-predicates)\n-   [Custom Primitives](#custom-primitives)\n\n## TL;DR\n\nDescribe the routing of your app in terms of **layout primitives**.\n\nCurrent router primitives are `scene`, `stack`, `feature`, and `data`, but you can easily define custom ones using simple template objects.\n\n-   Scene: sibling routers take the place of one another.\n-   Stack: sibling routers have an order with respect to one another.\n-   Feature: don't affect other routers. They are either visible or not.\n-   Data: add data to the url.\n\n## Overview\n\nNormally, with application routers, you define how path names and query params map to various elements of your application via pattern matching. When a match is found, you execute logic to show, hide, or move the element.\n\nWith `Router Primitives`, you don't need to think about pattern matching at all! You simply describe how your app is layed out in terms of scenes, stacks, features, data, and other router primitives.\n\nThe hierarchial arrangement of these layout primitives, in a `router declaration` object, generates routers that automatically construct the URL based on triggered actions (`show`, `hide`, etc...). Routers handle all layout level routing logic without need for additional code. This means that you don't need to write code to show, hide, or move elements with respect to one another.\n\nFor instance, sibling Scene routers automatically hide all other scenes when one of them becomes visible. This is similar to React Routers `switch` component. Or, as another example, Stack routers keep track of a position. If one sibling stack router jumps to the first position, the other siblings increment their position accordingly. These are useful if you have multiple modals, toast notifications, or other components that you want to register in the URL and be ordered.\n\nRouter Primitives is written as a high level abstraction to free developers from having to write the same routing logic over and over again. It's designed to have a simple, small, and declarative API with sensible ways to do complex and deterministic animations based on current router state, historical router state, and sibling router state.\n\nIf you work on a platform where there is no concept of a URL, you can still use this library. The URL is simply managed serialized state - which is platform aware and configurable!\n\nAlso, if you find that you want a new type of primitive, you can easily define one using a [template](#custom-primitives). In essence, a template describes how you serialize data to the URL, and how serialized data from the URL (namespaced to a router instance) affects the state of a router.\n\nFinally, Router Primitives is platform agnostic. This means that you can use the same router code for various frameworks and platforms. Currently, bindings exist for **[Mobx](https://github.com/erhathaway/recursive-router-mobx)**, and **[React](https://github.com/erhathaway/router-primitives-react)**. \n\n## Key Features\n\n|     |                                                                                          |\n| --- | ---------------------------------------------------------------------------------------- |\n| 😎  | View library agnostic - with bindings for React and Mobx                                 |\n| ✨  | Router state as a direct function of location (URL)                                      |\n| ⏱   | Built in history - Previous router state is tracked                                      |\n| 🔀  | One way data flow. Location -\u003e Router State tree -\u003e App                                  |\n| 🔗  | Trivial linking - Use the URL to generate an identical router state tree on any platform |\n| 😱  | Best practice opinionated and automatic URL construction                                 |\n| 🚀  | Reactive - Subscribe to the state of any router in the router state tree                 |\n| 👌  | Simple - Declare the router tree using a small but expressive syntax set                  |\n| 💃  | State predicate functions for making **complex animations easy**                         |\n\n## Paradigm\n\nIn the context of this library, a router should be thought of as a feature of your application that responds to actions of other application features.\n\nFor example, a router can be 'visible' when other routers are 'hidden'. This type of logic is what a scene router uses. Or, as another example, a router can be 'in front of' or 'behind' other routers. This type of logic is what a stack router uses. By defining your application in terms of visual elements like `scene` or `stack` (along with `feature` and `data`) you can implement variations of complex application routing.\n\n## How it works\n\n1. Router Primitives treats the URL as a namespace for the storage of a state tree representing `all routable state`™.\n2. Writing to the URL is handled by the router and via direct user modification.\n3. Changes to the URL are reduced over the router state tree\n4. Various types of routers in the router state tree exist. The differences are used to control how their state will get updated when the URL changes.\n5. Once the router state tree has been updated, observers of only updated routers are notified.\n\n## Custom Router Primitives\n\nShould the existing router primitives not be enough, this library provides you with a way to create your own routers! See [Router templates](#custom-primitives)\n\n# Usage\n\n#### Documentation\n\n-   [About](#about)\n-   **[Usage](#usage)** :point_left:\n-   [API](#api)\n-   [Primitives](#primitives)\n-   [Router Actions](#router-actions)\n-   [Router Links](#router-links)\n-   [Router State Predicates](#router-state-predicates)\n-   [Custom Primitives](#custom-primitives)\n\n## 1. Declare the layout of your app in terms of router primitives\n\n```typescript\nimport {IRouterDeclaration, AllTemplates} from 'router-primitives';\n\nconst routerDeclaration: IRouterDeclaration\u003cAllTemplates\u003e = {\n    name: 'root',\n    children: {\n        scene: [\n            {\n                name: 'user',\n                children: {\n                    data: [{name: 'userId', isPathRouter: true}]\n                }\n            },\n            {name: 'home', defaultAction: ['show']},\n            {\n                name: 'options',\n                children: {\n                    scene: [{name: 'appOptions', defaultAction: ['show']}, {name: 'userOptions'}]\n                }\n            }\n        ],\n        features: [{name: 'sideNav', routeKey: 'nav'}]\n    }\n};\n```\n\nThe above router declaration would generate the following paths:\n\n```\n/user\n/user?nav=true\n\n/user/:userId\n/user/:userId?nav=true\n\n/home\n/home?nav=true\n\n/options\n/options?nav=true\n\n/options/appOptions\n/options/appOptions?nav=true\n\n/options/userOptions\n/options/userOptions?nav=true\n```\n\n## 2. Build routers using the declaration object\n\n```typescript\nimport {Manager} from 'router-primitives';\n\nconst manager = new Manager({routerDeclaration});\n\nconst {routers} = manager;\n```\n\n## 3. Use the routers to navigate\n\n```typescript\nrouters.sideNav.subscribe(({visible}) =\u003e {\n    console.log(`Side nav is changing state. Visible: ${visible}`);\n});\n\nrouters.userId.subscribe(({data}) =\u003e {\n    console.log(`The current userId is ${data}`);\n});\n\n// the URL starts off at /home because the 'home' router has a default action of 'show'\n\nrouters.appOptions.show(); // transitions URL to /options/appOptions\n\nrouters.sideNav.show(); // transitions URL to /options/appOptions?nav\n\nrouters.userId.show({data: 1}); // transitions URL to /user/1?nav\n\nrouters.sideNav.hide(); // transitions URL to /user/1\n\nrouters.userOptions.link('show'); // generates the URL string for a link to this location\n```\n\n## 4. Use in React\n\n[Using router-primitives-react](https://github.com/erhathaway/router-primitives-react)\n\n#### A. Instantiate the router manager and generate Router Primitive React components\n\n```typescript\nimport {createRouterComponents} from 'router-primitives-react';\n\nconst routers = manager.routers;\nconst routerComponents = createRouterComponents(routers);\n\nconst Root = routerComponents['root'];\nconst UserScene = routerComponents['user'];\nconst UserIdData = routerComponents['userId'];\nconst HomeScene = routerComponents['home'];\nconst OptionsScene = routerComponents['options'];\nconst AppOptionsScene = routerComponents['appOptions'];\nconst UserOptionsScene = routerComponents['userOptionsScene'];\nconst SideNavFeature = routerComponents['sideNav'];\n```\n\n#### B. Use the components in your app\n\n```typescript\nimport {Animatable} from 'animated-components-react';\nimport anime from 'animejs';\nimport {predicates} from 'router-primitives';\n\nconst app = () =\u003e (\n    \u003cRoot\u003e\n        \u003cUserScene\u003e\n            \u003cUserIdData\u003e{`The current user id is: ${routers.userId.state.data}`}\u003c/UserIdData\u003e\n        \u003c/UserScene\u003e\n        \u003cHomeScene\u003e{'Welcome to the app'}\u003c/HomeScene\u003e\n        \u003cOptionsScene\u003e\n            \u003cAppOptions.Link action={'show'}\u003e\n                \u003cdiv\u003e{`Show App Options`}\u003c/div\u003e\n            \u003c/AppOptions.Link\u003e\n            \u003cUserOptions.Link action={'show'}\u003e\n                \u003cdiv\u003e{`Show User Options`}\u003c/div\u003e\n            \u003c/UserOptions.Link\u003e\n            \u003cAppOptions\u003e{'All your app option components'}\u003c/AppOptions\u003e\n            \u003cUserOptions\u003e{'All your user option components'}\u003c/UserOptions\u003e\n        \u003c/OptionsScene\u003e\n        \u003cSideNav.Animate\n            unMountOnHide\n            when={[\n                [\n                    predicates.isJustShown,\n                    ({node}) =\u003e anime({targets: `#${node.id}`, translateX: [0, 200]})\n                ][\n                    (predicates.isJustHidden,\n                    ({node}) =\u003e anime({targets: `#${node.id}`, translateX: [200, 0]}))\n                ]\n            ]}\n        \u003e\n            \u003cAnimatable\u003e\n                \u003cUserScene.Link action={'show'}\u003e\n                    \u003cdiv\u003e{`Show Home Scene`}\u003c/div\u003e\n                \u003c/UserScene.Link\u003e\n                \u003cHomeScene.Link action={'show'}\u003e\n                    \u003cdiv\u003e{`Show Home Scene`}\u003c/div\u003e\n                \u003c/HomeScene.Link\u003e\n                \u003cOptionsScene.Link action={'show'}\u003e\n                    \u003cdiv\u003e{`Show Options`}\u003c/div\u003e\n                \u003c/OptionsScene.Link\u003e\n            \u003c/Animatable\u003e\n        \u003c/SideNav.Animate\u003e\n        \u003cSideNav.ToggleLink\u003e\n            \u003cdiv\u003e{`Toggle Side Nav Visibility`}\u003c/div\u003e\n        \u003c/SideNav.ToggleLink\u003e\n    \u003c/Root\u003e\n);\n```\n\n# API\n\n#### Documentation\n\n-   [About](#about)\n-   [Usage](#usage)\n-   **[API](#api)** :point_left:\n-   [Primitives](#primitives)\n-   [Router Actions](#router-actions)\n-   [Router Links](#router-links)\n-   [Router State Predicates](#router-state-predicates)\n-   [Custom Primitives](#custom-primitives)\n\n\n## API: Manager\n\n### Manager Methods\n\n| Method         | Signature                                                                    | Description                                                    |\n| -------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------- |\n| `addRouters`   | `(router: IRouterDeclaration, type: RouterType, parentName: string) =\u003e void` | Add one router or an entire tree of router declaration objects |\n| `removeRouter` | `(routerName: string) =\u003e void`                                               | Remove a router                                                |\n\n### Manager Attributes\n\n| Attribute | Type                                | Description                                      |\n| --------- | ----------------------------------- | ------------------------------------------------ |\n| `routers` | `{ [routerName: string]: IRouter }` | All of the routers the manager currently manages |\n\n## API: Router\n\n### Router Common Methods\n\nAll router instances have the following methods:\n\n| Method            | Signature                                                   | Description                                                                                                      |\n| ----------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n| `show`            | `(options: IRouterOptions) =\u003e void`                         | Makes the router visible. This will update the router state tree and add the router `key` to the location        |\n| `hide`            | `(options: IRouterOptions) =\u003e void`                         | Makes the router invisible. This will update the router state tree and remove the router `key` from the location |\n| `neighborsOfType` | `() =\u003e Array\u003cIRouter\u003e`                                      | Gets routers that have the same parent but are not of the same type                                              |\n| `subscribe`       | `(fn: (newState) =\u003e any) =\u003e void`                           | Subscribe to router state changes                                                                                |\n| `link`            | `(actionName: string, options: IRouterOptions) =\u003e location` | generates a link that mimics what this action will do when pasted into the URL                                   |\n\n### Router Common Attributes\n\nAll router instances have the following attributes:\n\n| Attribute      | Type                                          | Description                                                                       |\n| -------------- | --------------------------------------------- | --------------------------------------------------------------------------------- |\n| `name`         | `string`                                      | router name                                                                       |\n| `type`         | `string literal` - the name of a primitive    | primitive type                                                                    |\n| `manager`      | `Manager`                                     | the manager controlling the router                                                |\n| `parent`       | `Router` - union of all possible router types | the parent of the router, if any                                                  |\n| `children`     | `{ [routerType: string]: Array\u003cIRouter\u003e}`     | the children routers of this router children                                      |\n| `root`         | `Router`                                      | the root router of the entire router tree tree                                    |\n| `config`       | `IRouterConfig`                               | the config options set during initialization that customize the router's behavior |\n| `isPathRouter` | `boolean`                                     | whether the router will appear in the pathname or query part of the location      |\n| `siblings`     | `Router[]`                                    | routers of the same primitive type who share the same parent                      |\n| `state`        | `RouterState`                                 | the current state of the router                                                   |\n| `history`      | `RouterState[]`                               | previous states of the router                                                     |\n| `data`         | `any` - depending on the template             | the data the router has                                                           |\n\n### Primitive Specific Methods\n\nAdditional methods may exist depending on the particular router primitive. For example, `stack` routers also have the methods `forward`, `backward`, `toFront`, `toBack`. Likewise, `data` routers has the method `setData`.\n\n#### Data Router\n\n| Method    | Signature                | Description                       |\n| --------- | ------------------------ | --------------------------------- |\n| `setData` | `(data: string) =\u003e void` | sets the data for the data router |\n\n#### Stack Router\n\n| Method     | Signature                           | Description                                         |\n| ---------- | ----------------------------------- | --------------------------------------------------- |\n| `forward`  | `(options: IRouterOptions) =\u003e void` | decrement the router position forward by 1          |\n| `backward` | `(options: IRouterOptions) =\u003e void` | increments the router position forward by 1         |\n| `toFront`  | `(options: IRouterOptions) =\u003e void` | sets the router position to 0                       |\n| `toBack`   | `(options: IRouterOptions) =\u003e void` | sets the router position to largest position number |\n\n# Primitives\n\n#### Documentation\n\n-   [About](#about)\n-   [Usage](#usage)\n-   [API](#api)\n-   **[Primitives](#primitives)** :point_left:\n-   [Router Actions](#router-actions)\n-   [Router Links](#router-links)\n-   [Router State Predicates](#router-state-predicates)\n-   [Custom Primitives](#custom-primitives)\n\nFor the most part, you'll be able to express the route-able layout of your app in terms of the 4 predefined primitives: `Stack`, `Scene`, `Feature`, and `Data`.\n\n-   Scene: sibling routers take the place of one another.\n-   Stack: sibling routers have an order with respect to one another.\n-   Feature: don't affect other routers. They are either visible or not.\n-   Data: add data to the url.\n\nThe are 4 important configuration options each primitive has that you should take note of:\n\n-   **show**: How it affects it's sibling(s)\n-   **canBePathRouter**: Whether it can occupy the pathname part of the URL\n-   **isPathRouterByDefault**: Whether it will occupy the pathname part of the URL by default\n-   **isDependentOnExternalData**: Whether it is dependent on external (user) data\n\nLets walk through the different primitives and look at each consideration.\n\n## Scene\n\n**Scene primitives allow you to implement layout items that take the place of one another**\n\nThe scene router's purpose is to represent layouts where you only want 1 item in a certain view at a time. For example, you may want a `users` scene, a `info` scene, and a `product` scene, all with the same parent. Because these are all sibling scenes, only one of them will be visible at a time. Furthermore, because they are all `scene` primitives, they will occupy the same space in the location (URL) store. This allows you to have three URLs like: `mysite.com/users`, `mysite.com/info` and `mysite.com/product`.\n\n| Option                        | Configuration                                                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |\n| **show**                      | Hides all sibling routers and makes router visible.                                                                                 |\n| **canBePathRouter**           | Yes. As long as all parents are also path routers                                                                                   |\n| **isPathRouterByDefault**     | Yes. As long as all parents are also path routers. Can be turned off by setting `isPathRouter` to `false` in the router declaration |\n| **isDependentOnExternalData** | No.                                                                                                                                 |\n\n## Stack\n\n**Stack primitives allow you to implement layout items that have an ordering to them.**\n\n\u003e Note: Stack routers have the orders 1, 2, 3... . 0 index is not used.\n\nThe stack router's purpose is to represent layouts where have multiple items that are visible but they need to have some order about them. For example, you may have a bunch of modals that you want to display only on a certain page. You could make a bunch of stack routers such they they all have the page router as their parent. You could then control the ordering of the modals via their `order` state.\n\nThe stack router primitive will store its state in only the `query` part of the `serialized state store` (URL). The store keys are `router.routeKey` and the values are the ordering of sibling routers with respect to one another.\n\nAn example URL is:\n\n-   `http://\u003csomething\u003e?stack1=0\u0026stack2=1`\n\nNote the order of `stack1` is `1`, and the order of `stack2` is `2`\n\n| Option                        | Configuration                                                                                              |\n| ----------------------------- | ---------------------------------------------------------------------------------------------------------- |\n| **show**                      | Moves the router to the first position and makes it visible. All sibling router positions are incremented. |\n| **canBePathRouter**           | No.                                                                                                        |\n| **isPathRouterByDefault**     | No.                                                                                                        |\n| **isDependentOnExternalData** | No.                                                                                                        |\n\n## Feature\n\n**Feature primitives allow you to implement layout items that seamlessly coexist with one another**\n\nThe feature router's purpose is to coexist seamlessly with other routers of the same parent. Sibling feature routers (routers with the same parent) will not affect the presence of one another. For example, you could use a feature router to control whether a menu bar is opened or closed.\n\nThe feature router primitive will store its state in only the `query` part of the `serialized state store` (URL).\n\nAn example URL is:\n\n-   `http://\u003csomething\u003e?feature1\u0026feature2`\n\n| Option                        | Configuration                                               |\n| ----------------------------- | ----------------------------------------------------------- |\n| **show**                      | Makes the router visible. Has no affect on sibling routers. |\n| **canBePathRouter**           | No.                                                         |\n| **isPathRouterByDefault**     | No.                                                         |\n| **isDependentOnExternalData** | No.                                                         |\n\n## Data\n\n**Data primitives allow you to markup the layout with arbitrary data**\n\nThe data router's purpose is to allow you to store data in the URL. This makes it possible to implement `page numbers`, `item IDs`, `callback URLs` etc... For example, you could wrap a `userId` data router in a `user` scene router. This would allow you to construct the urls: `mysite.com/user` and `mysite.com/user/:userId` (where `:userId` is variable data).\n\n#### Serialized state (URL)\n\nThe data router primitive will store its state in both the `query` and `path` part of the `serialized state store` (URL).\n\nExample URLs are:\n\n-   `http://\u003csomething\u003e?data1\u0026data2`\n-   `http://\u003csomething\u003e/data3/?data1\u0026data2`\n\n| Option                        | Configuration                                                                                                                                                                                                                                                                                                                              |\n| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| **show**                      | Makes the router visible. Has no affect on sibling routers. Uses whatever data it has and sets it in the URL                                                                                                                                                                                                                               |\n| **canBePathRouter**           | Yes. As long as all parent routers are also path routers _AND there are no other neighboring routers that are also path routers_                                                                                                                                                                                                           |\n| **isPathRouterByDefault**     | No.                                                                                                                                                                                                                                                                                                                                        |\n| **isDependentOnExternalData** | Yes. Data needs to be added via A. the `defaultAction` during the router declaration, B. the `data` option when calling the action (ex: `myDataRouter.show({data: 'somedata'})`), or in the `pathData` option when calling another router with this router in the path (ex: `mySceneRouter.show({pathData: {myDataRouter: 'someData'}})`)) |\n\n# Router Actions\n\n#### Documentation\n\n-   [About](#about)\n-   [Usage](#usage)\n-   [API](#api)\n-   [Primitives](#primitives)\n-   **[Router Actions](#router-actions)** :point_left:\n-   [Router Links](#router-links)\n-   [Router State Predicates](#router-state-predicates)\n-   [Custom Primitives](#custom-primitives)\n\nRouter actions are methods that do work. They allow you to change route location and set data. All routers have the actions `show` and `hide`. Routers can also have custom actions. For example, Data primitives have the `setData` action, and Stack primitives have the actions `toFront`, `toBack`, `forward`, and `backwards`.\n\n## Calling a router action\n\n```typescript\n\u003cmyRouter\u003e.\u003caction\u003e(\u003coptions\u003e)\n```\n\nExample:\n\n```typescript\nmyRouter.show({replaceLocation: true});\n```\n\n## Router action options\n\nAll actions take the same option type.\n\n```typescript\nexport interface IRouterActionOptions\u003cCustomState\u003e {\n    data?: CustomState;\n    pathData?: Record\u003cstring, unknown\u003e;\n    disableCaching?: boolean; // the setting will only persist for the router\n    replaceLocation?: boolean; // used to replace location in history rather than append to history\n    dryRun?: boolean; // will prevent cache from being updated or the new location state from being stored\n    addCacheToLocation?: boolean; // serializes the current router cache into the location. Useful for rehydrating exact router state.\n}\n```\n\n| Option             | Explanation                                                                                                                                                                                                                                                                                                                                                               |\n| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| data               | If this router is dependent on external data, this will set that data. Data routers can have their data set via this option                                                                                                                                                                                                                                               |\n| pathData           | If this router isn't dependent on external data, but another router in the path that the action will render is, data can be set for that router with this option. For example: `myRouter.show({pathData:{myDataRouter: 'somedata'}})`                                                                                                                                     |\n| disableCaching     | Router state is cached by default. If you want to disable caching for this router during this action run you can set it to `true`                                                                                                                                                                                                                                         |\n| replaceLocation    | Every location change via an action is added to the platforms location history. However, sometimes you don't want to add to this history but rather replace the current location. You can do that by setting `replaceLocation: true`                                                                                                                                      |\n| dryRun             | This will run the action executor but never use the new location (it won't be set to the history or URL). Instead, the location is returned. Internally, this option is how links are created via the `myRouter.link` method.                                                                                                                                             |\n| addCacheToLocation | Internally router actions are cached. This gives the impression that you can navigate to where you left off rather than have the default actions run every time. However, this information is not existent in the URL. If you want to add it to the URL so that pasting it into another browser creates an identical router experience, this option will do that for you. |\n\n# Router Links\n\n#### Documentation\n\n-   [About](#about)\n-   [Usage](#usage)\n-   [API](#api)\n-   [Primitives](#primitives)\n-   [Router Actions](#router-actions)\n-   **[Router Links](#router-links)** :point_left:\n-   [Router State Predicates](#router-state-predicates)\n-   [Custom Primitives](#custom-primitives)\n\nOften times, you'll want a link rather than calling an location action directly. The `link` method allows you to create such a link. It is essentially calling the action with the `dryRun` option.\n\nExample usage:\n\n```typescript\nmyRouter.link('show', linkOptions);\n```\n\nThe options available to the link method are a subset of the `action options`. See [router action options](#router-action-options) for an overview.\n\n```typescript\nexport interface ILinkOptions\u003cCustomState\u003e {\n    data?: CustomState;\n    pathData?: Record\u003cstring, unknown\u003e;\n    addCacheToLocation?: boolean; // serializes the current router cache into the location. Useful for rehydrating exact router state.\n}\n```\n\n# Router State Predicates\n\n#### Documentation\n\n-   [About](#about)\n-   [Usage](#usage)\n-   [API](#api)\n-   [Primitives](#primitives)\n-   [Router Actions](#router-actions)\n-   [Router Links](#router-links)\n-   **[Router State Predicates](#router-state-predicates)** :point_left:\n-   [Custom Primitives](#custom-primitives)\n\nArguably one of the hardest problems with routing is adding in complex animations / transitions. Router Primitives aims to make this easy by providing state predicates that can tell you if a particular state transition has occurred.\n\nState predicates derive information off of the router passed into them. If you'd like to make your own check out [src/state_predicates.ts](/src/state_predicates.ts) for inspiration.\n\n### Existing predicates\n\n| Predicate                                  | Explanation                                                                |\n| ------------------------------------------ | -------------------------------------------------------------------------- |\n| isVisible                                  | Whether the router is visible                                              |\n| isHidden                                   | Whether the router is hidden                                               |\n| isJustHidden                               | Whether the scene was just hidden in the last action                       |\n| isJustShown                                | Whether the scene was just shown in the last action                        |\n| isFirstTimeBeingShown                      | Whether the router is being shown for the first time                       |\n| hasBeenShownBefore                         | Whether the router was ever shown in its past                              |\n| scene.isVisibleSiblingsFirstTimeBeingShown | Whether the now visible sibling router is the first time being shown       |\n| scene.hasVisibleSiblingBeenShownBefore     | Whether the now visible sibling has been visible before                    |\n| stack.isMovingForward                      | Whether the order position is getting smaller (to the front)               |\n| stack.isMovingBackward                     | Whether the order position is getting larger (to the back)                 |\n| stack.isAtFront                            | Whether the order position is = 1                                          |\n|                                            |\n| stack.isAtBack                             | Whether the order position is the largest out of all sibling stack routers |\n| stack.isPositionSameAsLastShown            | Whether the order position is the same as the last time it was shown       |\n\n### Example state predicate usage\n\nLets look at how these can be used with a popular animation library like [Anime.js](https://github.com/juliangarnier/anime)\n\n```typescript\nimport {statePredicates} from 'router-primitives';\n\nconst myRouterOfInterest = manager.routers.myRouterOfInterest;\nconst {isMovingForward} = statePredicates.stack;\n\nif (isMovingForward(myRouterOfInterest)) {\n    anime({\n        targets: 'MyComponentId',\n        translateX: 250\n    });\n}\n```\n\nBindings to various view frameworks, like React, explicitly implement an API to make this even cleaner. For example:\n\n```jsx\nimport anime from 'animejs';\nimport {statePredicates} from 'router-primitives';\nimport {when, and, createRouterComponents} from 'router-primitives-react';\n\nconst {\n    isJustHidden,\n    isJustShown,\n    stack: {isMovingForward}\n} = statePredicates;\n\nconst StackRouter = createRouterComponents(manager.routers).myRouterOfInterest;\n\n\u003cStackRouter\n    onChange={[\n        when(and(isMovingForward, isJustShown), uuid =\u003e anime({target: uuid, translateX: 250})),\n        when(isJustHidden, uuid =\u003e anime({target: uuid, translateX: -250}))\n    ]}\n\u003e\n    {({uuid}) =\u003e \u003cMyComponent id={uuid} /\u003e}\n\u003c/StackRouter\u003e;\n```\n\n# Custom Primitives\n\n#### Documentation\n\n-   [About](#about)\n-   [Usage](#usage)\n-   [API](#api)\n-   [Primitives](#primitives)\n-   [Router Actions](#router-actions)\n-   [Router Links](#router-links)\n-   [Router State Predicates](#router-state-predicates)\n-   **[Custom Primitives](#custom-primitives)** :point_left:\n\nMaking custom primitives allows you to define new types of routing for your layout!\n\n\u003e It might be helpful to look at the [templates for `scene`, `stack`, `feature`, `data` primitives as a guide](/src/router_templates)\n\n## Router Primitive Type Signature\n\nA layout primitive is defined by a template which has the type signature:\n\n```typescript\ntype RouterTemplate = {\n    actions: ActionFunction[];\n    reducer: (newLocation: Location) =\u003e NewState;\n    options: {\n        canBePathRouter?: boolean;\n        isPathRouter?: boolean;\n        shouldInverselyActivate?: boolean;\n        disableCaching?: boolean;\n        shouldParentTryToActivateSiblings?: boolean;\n        isDependentOnExternalData?: boolean;\n    };\n};\n\ntype ActionFunction = (\n    options?: ActionOptions // Same options object that is talked about in the Router Actions section. These are set by the user to get specific action functionality.\n    existingLocation?: Location, // The existing URL\n\n    routerInstance?: Router, // The router that this function is a method on.\n    ctx?: ActionContext // A context object that is passed to every action function in the chain of action functions that is kicked off by the users action call.\n) =\u003e Location;\n\ntype Location = {\n  path: string[], // the components that form the path part of the URL\n  search: {}, // the components that form the query params pat of the URL\n}\n\ntype NewState = {\n  visible: boolean,\n  data?: unknown // this type varies on a router by router basis. You define the type with a generic when making the template.\n  actionCount: number // the action number that this state is associated with. Each action call increments the actionCount by 1.\n}\n\n```\n\n### Overview of writing a template\n\n#### Template Actions\n\nWhen you write a template you need to define, at a minimum, the `show` and `hide` actions. You can add as many actions as you like as long as the name doesn't conflict with a method name defined in [router_base.ts](/src/router_base.ts).\n\nThe goal of an action is to take the existing `location` and return a `new location`. The template should only modify the location state of the router in the `routerInstance` param of the action function type. Additionally, in an action you may call sibling router actions.\n\n#### Template Reducer\n\nThe goal of a reducer is to take the `final location` from the action call chain that a user initiated and reduce it down to a state specific to this router. The final reduction may modify the `visible` and `data` keys of the state object, but it should not touch the `actionCount` part.\n\n#### Template Options\n\nThe template options do the following:\n\n| Option                            | Purpose                                                                                                                                                                                                                                                                        | Default |\n| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |\n| canBePathRouter                   | Whether the primitive can occupy the pathname part of the URL                                                                                                                                                                                                                  | false   |\n| isPathRouter                      | Whether it will occupy the pathname part of the URL by default                                                                                                                                                                                                                 | false   |\n| isDependentOnExternalData         | Whether it is dependent on external (user) data                                                                                                                                                                                                                                | false   |\n| shouldParentTryToActivateSiblings | Whether the parent of the primitive should check if it should be shown from the cache when a sibling primitive was activated                                                                                                                                                   | true    |\n| disableCaching                    | Whether caching should occur for the primitive. Caching allows you to navigate to a different route and then come back to find the same cache state. For example, if you opened a menu, navigated away, and came back you could find the same menu open when cache is enabled. | false   |\n| shouldInverselyActivate           | Whether activation of a child router should activate the router    | true    |\n\n### Using templates\n\n```typescript\ntype CustomTemplates = {\n    myTemplateName: myTemplate;\n};\n\nconst manager = new Manager\u003cCustomTemplates\u003e({customTemplates, routerDeclaration});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferhathaway%2Frouter-primitives","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferhathaway%2Frouter-primitives","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferhathaway%2Frouter-primitives/lists"}