diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d7a3b2c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,130 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.12.1] - 2026-04-01 + +### Fixed +- `href` getter now includes the hash fragment when `NavOpts` is constructed from a path array (previously the hash was silently dropped, breaking `history.pushState` for hash-based navigation) +- Hash is now preserved through redirects in `resolveCurrent()` + +### Changed +- Added `sideEffects: false` to both packages' `package.json` — bundlers can now tree-shake unused exports (e.g. drop `restoreHandling` when only `createRouter` is imported) +- Minor bundle size reduction: removed dead code in `scroll-restoration`, replaced `new Array()` with `[]` + +## [0.12.0] - 2026-04-01 + +### Added +- Type-safe routing: `createRouter` now infers a `RoutePaths` type from the routes configuration, and `router.go()` enforces typed paths at compile time +- Typed state: `NavOpts` and `createRouter` are now generic over the state type + +### Fixed +- `NavOpts.state` is now always `null` instead of `undefined` when no state is provided +- Route resolution now correctly returns not-found when an intermediate path segment is unmatched (previously could match a route deeper in the tree) + +## [0.11.1] - 2026-03-31 + +### Fixed +- Source maps are now included in the published npm package + +## [0.11.0] - 2025-10-21 + +### Added +- `router.render(defer?)` — pass a defer function to batch multiple successive navigations without intermediate renders. The router resolves only the final navigation once the deferred work completes. + +## [0.10.0] - 2025-04-17 + +### Fixed +- Search params passed via `NavMeta` are no longer overridden by the search string embedded in an href +- State defaults to `null` (previously could be `undefined`) + +## [0.9.0] - 2024-04-16 + +### Added +- `router.go()` now accepts a function `(prev: NavOpts) => NavMeta` to patch the current navigation state in place (uses `replaceState` by default) + +### Fixed +- Corrected TypeScript overload types for `router.go()` + +## [0.8.3] - 2024-04-16 + +### Added +- `router.current` exposes the current `NavOpts` after the last completed resolution + +## [0.8.1] - 2024-01-17 + +### Fixed +- Router no longer becomes permanently unresponsive when a route resolution promise rejects + +## [0.8.0] - 2023-04-12 + +### Added +- Scroll restoration via `restoreHandling()` — saves and restores scroll position on navigation, supports hash-based anchor scrolling and a configurable container/offset + +### Changed +- `NavOpts` now exposes `hash` and `pop` fields + +## [0.6.0] - 2022-10-18 + +### Added +- Guards: add a `"?"` key to any route object to register a guard function. If the guard returns a `NavOpts`, the navigation is redirected. +- `routes` is now optional in `createRouter()` (defaults to `{}`) + +## [0.5.4] - 2022-09-21 + +### Added +- `router.routes` is now exposed on the `Router` interface, allowing routes to be modified after creation + +### Fixed +- Fixed virtual route (`""` key) resolution ordering + +## [0.5.0] - 2022-09-18 + +### Changed +- **Breaking**: `createRouter` is now a factory function instead of a class — use `createRouter({ routes, ... })` instead of `new Router(...)`) +- Reduced bundle size by removing unused abstractions + +### Removed +- `NavOpts.equals()` method + +## [0.4.2] - 2022-07-01 + +### Fixed +- Guard was not being called on index routes (`""` key) +- Fixed ESM import paths in `@esroute/lit` + +## [0.4.1] - 2022-04-18 + +### Added +- `@esroute/lit` package: `renderRoutes` Lit directive for declarative route rendering in Lit components + +## [0.3.0] - 2022-04-13 + +### Changed +- Converted the resolver to a plain function for better tree-shaking +- Route spec compilation is now opt-in (not compiled by default) + +## [0.2.0] - 2022-04-08 + +### Added +- Deep link support in the route spec (nested path configuration) + +## [0.1.1] - 2022-01-01 + +### Fixed +- Minor fixes and dependency updates + +## [0.1.0] - 2022-01-01 + +### Added +- Initial release of `esroute` +- Framework-agnostic client-side router with `createRouter()` +- Path-based route matching with wildcard (`*`) support +- Virtual routes (`""` key) for middleware/layout patterns +- Anchor click interception +- Promise-based serialized navigation diff --git a/packages/esroute-lit/package.json b/packages/esroute-lit/package.json index e8d0535..8088d4a 100644 --- a/packages/esroute-lit/package.json +++ b/packages/esroute-lit/package.json @@ -1,8 +1,9 @@ { "name": "@esroute/lit", - "version": "0.12.0", + "version": "0.12.1", "description": "A small efficient client-side routing library for lit, written in TypeScript.", "main": "dist/index.js", + "sideEffects": false, "license": "MIT", "author": "Sven Rogge ", "repository": "github:sv2dev/esroute", @@ -21,7 +22,7 @@ "typescript": "^5.4.5" }, "dependencies": { - "esroute": "^0.12.0", + "esroute": "^0.12.1", "lit": "^3.1.1" } } diff --git a/packages/esroute/package.json b/packages/esroute/package.json index 2a64131..42f450e 100644 --- a/packages/esroute/package.json +++ b/packages/esroute/package.json @@ -1,9 +1,10 @@ { "name": "esroute", - "version": "0.12.0", + "version": "0.12.1", "description": "A small efficient framework-agnostic client-side routing library, written in TypeScript.", "types": "dist/index.d.ts", "main": "dist/index.js", + "sideEffects": false, "license": "MIT", "author": "Sven Rogge ", "repository": "github:sv2dev/esroute", diff --git a/packages/esroute/src/nav-opts.spec.ts b/packages/esroute/src/nav-opts.spec.ts index d0813b4..c77d187 100644 --- a/packages/esroute/src/nav-opts.spec.ts +++ b/packages/esroute/src/nav-opts.spec.ts @@ -97,6 +97,23 @@ describe("NavOpts", () => { }); }); + describe("href with hash", () => { + it("should include hash in href when constructed from path array", () => { + const opts = new NavOpts(["foo"], { hash: "section" }); + expect(opts.href).toBe("/foo#section"); + }); + + it("should include both search and hash in href", () => { + const opts = new NavOpts(["foo"], { search: { a: "b" }, hash: "top" }); + expect(opts.href).toBe("/foo?a=b#top"); + }); + + it("should include hash in href when constructed from StrictNavMeta", () => { + const opts = new NavOpts({ path: ["foo"], hash: "bar" }); + expect(opts.href).toBe("/foo#bar"); + }); + }); + describe("go", () => { it("should create a new NavigateOpts instance with a new path", () => { const opts1 = new NavOpts(["a", "b"]); diff --git a/packages/esroute/src/nav-opts.ts b/packages/esroute/src/nav-opts.ts index baf2f9b..59ffe83 100644 --- a/packages/esroute/src/nav-opts.ts +++ b/packages/esroute/src/nav-opts.ts @@ -73,7 +73,7 @@ export class NavOpts implements NavMeta { if (!this._h) { const s = new URLSearchParams(this.search).toString(); const p = `/${this.path!.join("/")}`; - this._h = `${p}${s ? `?${s}` : ""}`; + this._h = `${p}${s ? `?${s}` : ""}${this.hash ? `#${this.hash}` : ""}`; } return this._h; } diff --git a/packages/esroute/src/route-resolver.ts b/packages/esroute/src/route-resolver.ts index cde9c31..b69ec2f 100644 --- a/packages/esroute/src/route-resolver.ts +++ b/packages/esroute/src/route-resolver.ts @@ -22,7 +22,7 @@ export const resolve = async ( notFound: Resolve ): Promise> => { let value: NavOpts | T = opts; - const navPath = new Array>(); + const navPath: NavOpts[] = []; while (value instanceof NavOpts && navPath.length <= MAX_REDIRECTS) { opts = value; navPath.push(opts); diff --git a/packages/esroute/src/router.ts b/packages/esroute/src/router.ts index a287e7f..f9c68a2 100644 --- a/packages/esroute/src/router.ts +++ b/packages/esroute/src/router.ts @@ -221,6 +221,7 @@ export const createRouter = ( replace: true, search: opts.search, state: opts.state, + hash: opts.hash, }) ); } diff --git a/packages/esroute/src/scroll-restoration.ts b/packages/esroute/src/scroll-restoration.ts index ec4a39f..0970167 100644 --- a/packages/esroute/src/scroll-restoration.ts +++ b/packages/esroute/src/scroll-restoration.ts @@ -32,14 +32,11 @@ export const restoreHandling = ({ offset + container.scrollTop); - if (save) { - window.addEventListener("beforeunload", save); - window.addEventListener("visibilitychange", save); - } + window.addEventListener("beforeunload", save); + document.addEventListener("visibilitychange", save); window.addEventListener("popstate", () => set(getStatePos())); return ({ opts: { hash, pop } }: { opts: NavOpts }) => { const fromState = getStatePos(); - console.log(location.pathname, pop, fromState); if (fromState) return set(fromState); if (!fromState && hash && getHashPos) return set(getHashPos(hash)); container.scrollTop = 0;