waymark 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,48 +0,0 @@
1
- import { getHref, normalizeSearch } from "../utils";
2
- export class BrowserHistory {
3
- static patchKey = Symbol.for("waymark_history_patch_v01");
4
- constructor() {
5
- if (typeof history !== "undefined" &&
6
- !Object.hasOwn(window, BrowserHistory.patchKey)) {
7
- for (const type of [pushStateEvent, replaceStateEvent]) {
8
- const original = history[type];
9
- history[type] = function (...args) {
10
- const result = original.apply(this, args);
11
- const event = new Event(type);
12
- event.arguments = args;
13
- dispatchEvent(event);
14
- return result;
15
- };
16
- }
17
- Object.assign(window, {
18
- [BrowserHistory.patchKey]: true
19
- });
20
- }
21
- }
22
- getPath = () => location.pathname;
23
- getSearch = () => normalizeSearch(location.search);
24
- getState = () => history.state;
25
- go = (delta) => history.go(delta);
26
- push = (options) => {
27
- const { path, search, replace, state } = options;
28
- const href = getHref(path, search);
29
- history[replace ? replaceStateEvent : pushStateEvent](state, "", href);
30
- };
31
- subscribe = (listener) => {
32
- events.forEach(event => window.addEventListener(event, listener));
33
- return () => {
34
- events.forEach(event => window.removeEventListener(event, listener));
35
- };
36
- };
37
- }
38
- // Events
39
- const popStateEvent = "popstate";
40
- const pushStateEvent = "pushState";
41
- const replaceStateEvent = "replaceState";
42
- const hashChangeEvent = "hashchange";
43
- const events = [
44
- popStateEvent,
45
- pushStateEvent,
46
- replaceStateEvent,
47
- hashChangeEvent
48
- ];
@@ -1,3 +0,0 @@
1
- export * from "./router";
2
- export * from "./browser-history";
3
- export * from "./memory-history";
@@ -1,3 +0,0 @@
1
- export * from "./router";
2
- export * from "./browser-history";
3
- export * from "./memory-history";
@@ -1,18 +0,0 @@
1
- import { type HistoryLike, type HistoryPushOptions } from "../utils";
2
- export interface MemoryLocation {
3
- path: string;
4
- search: string;
5
- state: any;
6
- }
7
- export declare class MemoryHistory implements HistoryLike {
8
- private stack;
9
- private index;
10
- private listeners;
11
- constructor(initial?: Partial<MemoryLocation>);
12
- getPath: () => string;
13
- getSearch: () => string;
14
- getState: () => any;
15
- go: (delta: number) => void;
16
- push: (options: HistoryPushOptions) => void;
17
- subscribe: (listener: () => void) => () => void;
18
- }
@@ -1,39 +0,0 @@
1
- import { clamp, normalizeSearch } from "../utils";
2
- export class MemoryHistory {
3
- stack = [];
4
- index = 0;
5
- listeners = new Set();
6
- constructor(initial = {}) {
7
- const { path = "/", search = "", state } = initial;
8
- this.stack.push({ path, search: normalizeSearch(search), state });
9
- }
10
- getPath = () => this.stack[this.index].path;
11
- getSearch = () => this.stack[this.index].search;
12
- getState = () => this.stack[this.index].state;
13
- go = (delta) => {
14
- this.index = clamp(this.index + delta, 0, this.stack.length - 1);
15
- this.listeners.forEach(listener => listener());
16
- };
17
- push = (options) => {
18
- const { path, search = "", replace, state } = options;
19
- const location = {
20
- path,
21
- search: normalizeSearch(search),
22
- state
23
- };
24
- this.stack = this.stack.slice(0, this.index + 1);
25
- if (replace) {
26
- this.stack[this.index] = location;
27
- }
28
- else {
29
- this.index = this.stack.push(location) - 1;
30
- }
31
- this.listeners.forEach(listener => listener());
32
- };
33
- subscribe = (listener) => {
34
- this.listeners.add(listener);
35
- return () => {
36
- this.listeners.delete(listener);
37
- };
38
- };
39
- }
@@ -1,31 +0,0 @@
1
- import type { LinkOptions } from "../react";
2
- import { type Routes, type PatternRoute, type RouteList, type Patterns, type NavigateOptions, type HistoryLike, type RouteParams, type RouteSearch } from "../utils";
3
- export interface RouterOptions {
4
- history?: HistoryLike;
5
- basePath?: string;
6
- routes: RouteList;
7
- defaultLinkOptions?: LinkOptions;
8
- }
9
- export declare class Router {
10
- history: HistoryLike;
11
- basePath: string;
12
- routes: RouteList;
13
- defaultLinkOptions?: LinkOptions;
14
- _: {
15
- routeMap: Map<string, Routes>;
16
- };
17
- constructor(options: RouterOptions);
18
- getPath(cpath: string): string;
19
- getCanonicalPath(path: string): string;
20
- matchPath(path: string): Routes | undefined;
21
- getRoute<P extends Patterns>(pattern: P): PatternRoute<P>;
22
- composePath<P extends Patterns>(options: NavigateOptions<P>): {
23
- path: string;
24
- search: string;
25
- };
26
- decomposePath<R extends Routes>(route: R, path: string, search: string): {
27
- params: RouteParams<R>;
28
- search: RouteSearch<R>;
29
- };
30
- navigate<P extends Patterns>(options: NavigateOptions<P> | number): void;
31
- }
@@ -1,64 +0,0 @@
1
- import { inject } from "regexparam";
2
- import { BrowserHistory } from "./browser-history";
3
- import { normalizePath, extract, stringifySearch, parseSearch } from "../utils";
4
- export class Router {
5
- history;
6
- basePath;
7
- routes;
8
- defaultLinkOptions;
9
- _;
10
- constructor(options) {
11
- this.history = options.history ?? new BrowserHistory();
12
- this.basePath = normalizePath(options.basePath ?? "/");
13
- this.routes = options.routes;
14
- this.defaultLinkOptions = options.defaultLinkOptions;
15
- this._ = {
16
- routeMap: new Map(options.routes.map(route => [route._.pattern, route]))
17
- };
18
- }
19
- getPath(cpath) {
20
- return normalizePath(`${this.basePath}/${cpath}`);
21
- }
22
- getCanonicalPath(path) {
23
- if (path === this.basePath || path.startsWith(`${this.basePath}/`)) {
24
- path = path.slice(this.basePath.length) || "/";
25
- }
26
- return path;
27
- }
28
- matchPath(path) {
29
- const cpath = this.getCanonicalPath(path);
30
- return this.routes.find(route => route._.regex.test(cpath));
31
- }
32
- getRoute(pattern) {
33
- const route = this._.routeMap.get(pattern);
34
- if (!route) {
35
- throw new Error(`[Waymark] Route not found for pattern: ${pattern}`);
36
- }
37
- return route;
38
- }
39
- composePath(options) {
40
- const { to, params, search } = options;
41
- return {
42
- path: this.getPath(params ? inject(to, params) : to),
43
- search: search ? stringifySearch(search) : ""
44
- };
45
- }
46
- decomposePath(route, path, search) {
47
- const { keys, looseRegex, mapSearch } = route._;
48
- const cpath = this.getCanonicalPath(path);
49
- return {
50
- params: extract(cpath, looseRegex, keys),
51
- search: mapSearch(parseSearch(search))
52
- };
53
- }
54
- navigate(options) {
55
- if (typeof options === "number") {
56
- this.history.go(options);
57
- }
58
- else {
59
- const { path, search } = this.composePath(options);
60
- const { replace, state } = options;
61
- this.history.push({ path, search, replace, state });
62
- }
63
- }
64
- }
@@ -1,5 +0,0 @@
1
- export * from "./path";
2
- export * from "./router";
3
- export * from "./search";
4
- export * from "./react";
5
- export * from "./misc";
@@ -1,5 +0,0 @@
1
- export * from "./path";
2
- export * from "./router";
3
- export * from "./search";
4
- export * from "./react";
5
- export * from "./misc";
@@ -1,17 +0,0 @@
1
- import type { Simplify, EmptyObject } from "type-fest";
2
- import type { StandardSchemaV1 } from "@standard-schema/spec";
3
- export type MaybeKey<K extends string, T> = T extends EmptyObject ? {
4
- [P in K]?: undefined;
5
- } : {} extends T ? {
6
- [P in K]?: T;
7
- } : {
8
- [P in K]: T;
9
- };
10
- export type OptionalOnUndefined<T extends object> = Simplify<{
11
- [K in keyof T as undefined extends T[K] ? never : K]: T[K];
12
- } & {
13
- [K in keyof T as undefined extends T[K] ? K : never]?: T[K];
14
- }>;
15
- export declare function getHref(path: string, search?: string): string;
16
- export declare function clamp(value: number, min: number, max: number): number;
17
- export declare function validator<Input, Output>(validate: ((input: Input) => Output) | StandardSchemaV1<Input, Output>): (input: Input) => Output;
@@ -1,27 +0,0 @@
1
- export function getHref(path, search) {
2
- return `${path}${search ? `?${search}` : ""}`;
3
- }
4
- export function clamp(value, min, max) {
5
- return Math.max(min, Math.min(value, max));
6
- }
7
- export function validator(validate) {
8
- if (typeof validate === "function") {
9
- return validate;
10
- }
11
- else {
12
- return (input) => {
13
- const result = validate["~standard"].validate(input);
14
- if (result instanceof Promise) {
15
- throw new Error("[Waymark] Validation must be synchronous");
16
- }
17
- else if (result.issues) {
18
- throw new Error("[Waymark] Validation failed", {
19
- cause: result.issues
20
- });
21
- }
22
- else {
23
- return result.value;
24
- }
25
- };
26
- }
27
- }
@@ -1,9 +0,0 @@
1
- import type { RouteParams } from "regexparam";
2
- import type { Simplify } from "type-fest";
3
- export type ParsePattern<P extends string> = Simplify<RouteParams<P>>;
4
- export type NormalizePath<P extends string> = RemoveTrailingSlash<DedupSlashes<`/${P}`>>;
5
- type DedupSlashes<P extends string> = P extends `${infer Prefix}//${infer Rest}` ? `${Prefix}${DedupSlashes<`/${Rest}`>}` : P;
6
- type RemoveTrailingSlash<P extends string> = P extends `${infer Prefix}/` ? Prefix extends "" ? "/" : Prefix : P;
7
- export declare function normalizePath<P extends string>(path: P): NormalizePath<P>;
8
- export declare function extract(cpath: string, looseRegex: RegExp, keys: string[]): Record<string, string>;
9
- export {};
@@ -1,19 +0,0 @@
1
- export function normalizePath(path) {
2
- const normalized = `/${path}`
3
- .replaceAll(/\/+/g, "/")
4
- .replace(/(.+)\/$/, "$1");
5
- return normalized;
6
- }
7
- export function extract(cpath, looseRegex, keys) {
8
- const out = {};
9
- const matches = looseRegex.exec(cpath);
10
- if (matches) {
11
- keys.forEach((key, i) => {
12
- const match = matches[i + 1];
13
- if (match) {
14
- out[key] = match;
15
- }
16
- });
17
- }
18
- return out;
19
- }
@@ -1,10 +0,0 @@
1
- import { type Ref, type ComponentType } from "react";
2
- export type Updater<T extends object> = Partial<T> | ((prev: T) => Partial<T>);
3
- export type ComponentLoader = () => Promise<ComponentType | {
4
- default: ComponentType;
5
- }>;
6
- export declare function defaultLinkActive(currentPath: string, targetPath: string): boolean;
7
- export declare function mergeRefs<T>(...inputRefs: (Ref<T> | undefined)[]): Ref<T>;
8
- export declare function errorBoundary(component: ComponentType<{
9
- error: unknown;
10
- }>): ComponentType;
@@ -1,50 +0,0 @@
1
- import { createElement, Component } from "react";
2
- import { useOutlet } from "../react";
3
- export function defaultLinkActive(currentPath, targetPath) {
4
- return currentPath.startsWith(targetPath);
5
- }
6
- export function mergeRefs(...inputRefs) {
7
- const filtered = inputRefs.filter(r => !!r);
8
- if (filtered.length <= 1) {
9
- return filtered[0] ?? null;
10
- }
11
- return value => {
12
- const cleanups = [];
13
- for (const ref of filtered) {
14
- const cleanup = assignRef(ref, value);
15
- cleanups.push(cleanup ?? (() => assignRef(ref, null)));
16
- }
17
- return () => cleanups.forEach(cleanup => cleanup());
18
- };
19
- }
20
- function assignRef(ref, value) {
21
- if (typeof ref === "function") {
22
- return ref(value);
23
- }
24
- else if (ref) {
25
- ref.current = value;
26
- }
27
- }
28
- export function errorBoundary(component) {
29
- class Catch extends Component {
30
- constructor(props) {
31
- super(props);
32
- this.state = { children: props.children, error: null };
33
- }
34
- static getDerivedStateFromError(error) {
35
- return { error: [error] };
36
- }
37
- static getDerivedStateFromProps(props, state) {
38
- if (props.children !== state.children) {
39
- return { children: props.children, error: null };
40
- }
41
- return state;
42
- }
43
- render() {
44
- return this.state.error
45
- ? createElement(component, { error: this.state.error[0] })
46
- : this.props.children;
47
- }
48
- }
49
- return () => createElement(Catch, { children: useOutlet() });
50
- }
@@ -1,37 +0,0 @@
1
- import type { Route } from "../route";
2
- import type { MaybeKey } from "./misc";
3
- export interface RegisterRoutes {
4
- }
5
- export type RouteList = RegisterRoutes extends {
6
- routes: infer RouteList extends ReadonlyArray<Route<string, any, any>>;
7
- } ? RouteList : ReadonlyArray<Route<string, any, any>>;
8
- export type Routes = RouteList[number];
9
- export type Patterns = Routes["_"]["pattern"];
10
- export type RouteParams<R extends Routes> = NonNullable<R["_"]["_params"]>;
11
- export type RouteSearch<R extends Routes> = NonNullable<R["_"]["_search"]>;
12
- export type PatternParams<P extends Patterns> = RouteParams<PatternRoute<P>>;
13
- export type PatternSearch<P extends Patterns> = RouteSearch<PatternRoute<P>>;
14
- export type PatternRoute<P extends Patterns> = Extract<Routes, {
15
- _: {
16
- pattern: P;
17
- };
18
- }>;
19
- export type NavigateOptions<P extends Patterns> = {
20
- to: P;
21
- replace?: boolean;
22
- state?: any;
23
- } & MaybeKey<"params", PatternParams<P>> & MaybeKey<"search", PatternSearch<P>>;
24
- export interface HistoryPushOptions {
25
- path: string;
26
- search?: string;
27
- replace?: boolean;
28
- state?: any;
29
- }
30
- export interface HistoryLike {
31
- getPath: () => string;
32
- getSearch: () => string;
33
- getState: () => any;
34
- go: (delta: number) => void;
35
- push: (options: HistoryPushOptions) => void;
36
- subscribe: (listener: () => void) => () => void;
37
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- export declare function normalizeSearch(search: string): string;
2
- export declare function stringifySearch(search: Record<string, unknown>): string;
3
- export declare function parseSearch(search: string): Record<string, unknown>;
@@ -1,31 +0,0 @@
1
- export function normalizeSearch(search) {
2
- return search.startsWith("?") ? search.slice(1) : search;
3
- }
4
- export function stringifySearch(search) {
5
- return Object.entries(search)
6
- .filter(([_, value]) => value !== undefined)
7
- .map(([key, value]) => `${key}=${encodeURIComponent(toValueString(value))}`)
8
- .join("&");
9
- }
10
- export function parseSearch(search) {
11
- const urlSearch = new URLSearchParams(search);
12
- return Object.fromEntries([...urlSearch.entries()].map(([key, value]) => {
13
- value = decodeURIComponent(value);
14
- return [key, isJSONString(value) ? JSON.parse(value) : value];
15
- }));
16
- }
17
- function toValueString(value) {
18
- if (typeof value === "string" && !isJSONString(value)) {
19
- return value;
20
- }
21
- return JSON.stringify(value);
22
- }
23
- function isJSONString(value) {
24
- try {
25
- JSON.parse(value);
26
- return true;
27
- }
28
- catch {
29
- return false;
30
- }
31
- }