@tempots/ui 9.0.0 → 10.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempots/ui",
3
- "version": "9.0.0",
3
+ "version": "10.0.0",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -41,6 +41,6 @@
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@tempots/std": "0.24.0",
44
- "@tempots/dom": "30.1.0"
44
+ "@tempots/dom": "31.0.0"
45
45
  }
46
46
  }
@@ -1,4 +1,5 @@
1
1
  import { TNode, Value, Renderable } from '@tempots/dom';
2
+ import { NavigationOptions } from './router/location';
2
3
  import { HandleAnchorClickOptions } from '../dom/handle-anchor-click';
3
4
  import { Merge } from '@tempots/std';
4
5
  /**
@@ -11,11 +12,7 @@ export type AnchorOptions = Merge<{
11
12
  * Can be a string or a Signal containing a string.
12
13
  */
13
14
  href: Value<string>;
14
- /**
15
- * Whether to use a view transition when navigating to the anchor.
16
- */
17
- withViewTransition?: boolean;
18
- }, HandleAnchorClickOptions>;
15
+ } & NavigationOptions, HandleAnchorClickOptions>;
19
16
  /**
20
17
  * Represents either a string value (or Signal of string) for the href,
21
18
  * or a full AnchorOptions object.
@@ -1,12 +1,15 @@
1
+ import { Prop } from '@tempots/dom';
2
+ import { NavigationOptions } from './navigation-options';
1
3
  import { LocationData } from './location-data';
2
- /**
3
- * Creates a location prop that represents the current browser location.
4
- * The location prop is updated whenever the browser location changes.
5
- *
6
- * @returns The location prop.
7
- * @internal
8
- */
9
- export declare const makeBrowserLocationProp: () => {
10
- value: import('@tempots/dom').Prop<LocationData>;
4
+ type HistoryAction = 'pushState' | 'replaceState';
5
+ export type BrowserLocationSource = {
6
+ location: Prop<LocationData>;
11
7
  dispose: () => void;
8
+ commit: (data: LocationData, options: NavigationOptions | undefined, action: HistoryAction) => void;
9
+ go: (delta: number, options?: NavigationOptions) => void;
10
+ back: (options?: NavigationOptions) => void;
11
+ forward: (options?: NavigationOptions) => void;
12
+ resolve: (url: string) => LocationData;
12
13
  };
14
+ export declare const makeBrowserLocationSource: () => BrowserLocationSource;
15
+ export {};
@@ -1,13 +1,14 @@
1
- import { HeadlessContext } from '@tempots/dom';
1
+ import { HeadlessContext, Prop } from '@tempots/dom';
2
+ import { NavigationOptions } from './navigation-options';
2
3
  import { LocationData } from './location-data';
3
- export declare const isAbsoluteURL: (url: string) => boolean;
4
- /**
5
- * Provides the location context to the child component.
6
- * @param child - The child component to be wrapped with the location context.
7
- * @returns The wrapped component with the location context.
8
- * @public
9
- */
10
- export declare const makeHeadlessLocationProp: (ctx: HeadlessContext) => {
11
- value: import('@tempots/dom').Prop<LocationData>;
4
+ export type HeadlessLocationSource = {
5
+ location: Prop<LocationData>;
12
6
  dispose: () => void;
7
+ commit: (data: LocationData, options: NavigationOptions | undefined, action: 'pushState' | 'replaceState') => void;
8
+ go: (delta: number, options?: NavigationOptions) => void;
9
+ back: (options?: NavigationOptions) => void;
10
+ forward: (options?: NavigationOptions) => void;
11
+ resolve: (url: string) => LocationData;
13
12
  };
13
+ export declare const isAbsoluteURL: (url: string) => boolean;
14
+ export declare const makeHeadlessLocationSource: (ctx: HeadlessContext) => HeadlessLocationSource;
@@ -1,4 +1,3 @@
1
- import { Prop } from '@tempots/dom';
2
1
  /**
3
2
  * Represents the data for a location.
4
3
  *
@@ -33,14 +32,6 @@ export declare const areLocationsEqual: (a: LocationData, b: LocationData) => bo
33
32
  * @public
34
33
  */
35
34
  export declare const locationFromURL: (url: string, baseUrl?: string) => LocationData;
36
- /**
37
- * Sets the location from the given URL and updates the specified property.
38
- * @param prop - The property to update with the new location.
39
- * @param url - The URL from which to extract the location.
40
- * @returns The updated property.
41
- * @public
42
- */
43
- export declare const setLocationFromUrl: (prop: Prop<LocationData>, url: string) => Prop<LocationData>;
44
35
  /**
45
36
  * Returns the full URL based on the provided location data.
46
37
  *
@@ -1,77 +1,112 @@
1
- import { Prop, Provider } from '@tempots/dom';
1
+ import { Provider, Signal } from '@tempots/dom';
2
2
  import { LocationData } from './location-data';
3
+ import { NavigationOptions } from './navigation-options';
4
+ export type { NavigationOptions } from './navigation-options';
3
5
  /**
4
- * Provider for browser location and navigation functionality.
6
+ * A read/write navigation handle exposed by the Location provider.
5
7
  *
6
- * The Location provider gives components access to the current URL and provides
7
- * methods for programmatic navigation. It automatically detects whether it's
8
- * running in a browser or headless environment and provides the appropriate
9
- * implementation.
10
- *
11
- * @example
12
- * ```typescript
13
- * // Use location in a component
14
- * const CurrentPath = () =>
15
- * Use(Location, location => html.div(
16
- * 'Current path: ', location.$.pathname,
17
- * html.br(),
18
- * 'Search params: ', location.$.search,
19
- * html.br(),
20
- * 'Hash: ', location.$.hash
21
- * ))
22
- * ```
23
- *
24
- * @example
25
- * ```typescript
26
- * // Navigation buttons
27
- * const Navigation = () =>
28
- * Use(Location, location => html.nav(
29
- * html.button(
30
- * on.click(() => location.navigate('/')),
31
- * 'Home'
32
- * ),
33
- * html.button(
34
- * on.click(() => location.navigate('/about')),
35
- * 'About'
36
- * ),
37
- * html.button(
38
- * on.click(() => location.back()),
39
- * 'Back'
40
- * ),
41
- * html.button(
42
- * on.click(() => location.forward()),
43
- * 'Forward'
44
- * )
45
- * ))
46
- * ```
47
- *
48
- * @example
49
- * ```typescript
50
- * // Conditional rendering based on path
51
- * const ConditionalContent = () =>
52
- * Use(Location, location =>
53
- * When(location.map(loc => loc.pathname.startsWith('/admin')),
54
- * () => AdminPanel(),
55
- * () => PublicContent()
56
- * )
57
- * )
58
- * ```
59
- *
60
- * @example
61
- * ```typescript
62
- * // URL parameter extraction
63
- * const ProductPage = () =>
64
- * Use(Location, location => {
65
- * const searchParams = location.map(loc => new URLSearchParams(loc.search))
66
- * const productId = searchParams.map(params => params.get('id'))
8
+ * @public
9
+ */
10
+ export type LocationHandle = {
11
+ /**
12
+ * Reactive signal containing the structured location (pathname, search, hash).
13
+ */
14
+ readonly location: Signal<LocationData>;
15
+ /**
16
+ * Reactive signal containing the full URL string derived from the location.
17
+ */
18
+ readonly url: Signal<string>;
19
+ /**
20
+ * Reactive signal for the current pathname.
21
+ */
22
+ readonly pathname: Signal<string>;
23
+ /**
24
+ * Reactive signal with the parsed search parameters.
25
+ */
26
+ readonly search: Signal<Record<string, string>>;
27
+ /**
28
+ * Reactive signal for the current hash fragment (without the leading `#`).
29
+ */
30
+ readonly hash: Signal<string | undefined>;
31
+ /**
32
+ * Replaces the entire location with the provided data.
33
+ */
34
+ setLocation: (data: LocationData, options?: NavigationOptions) => void;
35
+ /**
36
+ * Applies a transformer to the current location and commits the result.
37
+ */
38
+ updateLocation: (updater: (curr: LocationData) => LocationData, options?: NavigationOptions) => void;
39
+ /**
40
+ * Navigates to the provided URL string, resolving relative paths when needed.
41
+ */
42
+ navigate: (url: string, options?: NavigationOptions) => void;
43
+ /**
44
+ * Navigates to the URL string, forcing a history replace even if options do not specify it.
45
+ */
46
+ replace: (url: string, options?: NavigationOptions) => void;
47
+ /**
48
+ * Moves backward in history, when supported by the underlying source.
49
+ */
50
+ back: (options?: NavigationOptions) => void;
51
+ /**
52
+ * Moves forward in history, when supported by the underlying source.
53
+ */
54
+ forward: (options?: NavigationOptions) => void;
55
+ /**
56
+ * Performs a relative history navigation offset.
57
+ */
58
+ go: (delta: number, options?: NavigationOptions) => void;
59
+ /**
60
+ * Updates only the pathname portion of the current location.
61
+ */
62
+ setPathname: (pathname: string, options?: NavigationOptions) => void;
63
+ /**
64
+ * Updates the hash fragment, normalising empty values to `undefined`.
65
+ */
66
+ setHash: (hash: string | undefined | null, options?: NavigationOptions) => void;
67
+ /**
68
+ * Removes the current hash fragment.
69
+ */
70
+ clearHash: (options?: NavigationOptions) => void;
71
+ /**
72
+ * Merges the provided entries into the current search parameters (null/undefined removes keys).
73
+ */
74
+ setSearch: (entries: Record<string, string | null | undefined>, options?: NavigationOptions) => void;
75
+ /**
76
+ * Convenience helper for updating a single search parameter.
77
+ */
78
+ setSearchParam: (key: string, value: string | null | undefined, options?: NavigationOptions) => void;
79
+ /**
80
+ * Applies a transformer to the search parameters, committing the result.
81
+ */
82
+ updateSearch: (updater: (curr: Record<string, string>) => Record<string, string>, options?: NavigationOptions) => void;
83
+ /**
84
+ * Returns a derived signal for a single search parameter.
85
+ */
86
+ queryParam: (key: string) => Signal<string | undefined>;
87
+ /**
88
+ * Runs mutations against a draft copy and commits once after the callback finishes.
89
+ */
90
+ run: (mutate: (draft: LocationDraft) => void, options?: NavigationOptions) => void;
91
+ };
92
+ /**
93
+ * Draft object provided inside `LocationHandle.run` for staged updates.
67
94
  *
68
- * return Ensure(productId,
69
- * (id) => ProductDetail({ id }),
70
- * () => html.div('Product ID required')
71
- * )
72
- * })
73
- * ```
95
+ * @public
96
+ */
97
+ export type LocationDraft = {
98
+ readonly location: LocationData;
99
+ setLocation: (data: LocationData) => LocationDraft;
100
+ setPathname: (pathname: string) => LocationDraft;
101
+ setHash: (hash: string | undefined | null) => LocationDraft;
102
+ clearHash: () => LocationDraft;
103
+ setSearch: (entries: Record<string, string | null | undefined>) => LocationDraft;
104
+ setSearchParam: (key: string, value: string | null | undefined) => LocationDraft;
105
+ updateSearch: (updater: (curr: Record<string, string>) => Record<string, string>) => LocationDraft;
106
+ };
107
+ /**
108
+ * Provider exposing the LocationHandle for navigation and location state.
74
109
  *
75
110
  * @public
76
111
  */
77
- export declare const Location: Provider<Prop<LocationData>>;
112
+ export declare const Location: Provider<LocationHandle>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Configuration applied when committing a navigation update.
3
+ *
4
+ * @public
5
+ */
6
+ export type NavigationOptions = {
7
+ /**
8
+ * Optional history state to associate with the navigation entry.
9
+ */
10
+ state?: unknown;
11
+ /**
12
+ * Scroll behaviour after navigation. Defaults to preserving the current scroll position.
13
+ */
14
+ scroll?: 'preserve' | 'auto';
15
+ /**
16
+ * Enable view transitions when supported by the browser.
17
+ */
18
+ viewTransition?: boolean;
19
+ /**
20
+ * Force replacement of the current history entry instead of pushing a new one.
21
+ */
22
+ replace?: boolean;
23
+ };
@@ -68,6 +68,10 @@ export type RouteCatchAll = {
68
68
  * The type of the catch-all
69
69
  */
70
70
  type: 'catch-all';
71
+ /**
72
+ * Optional parameter name that captures the remaining path.
73
+ */
74
+ name?: string;
71
75
  };
72
76
  /**
73
77
  * Represents a segment of a route.
@@ -101,7 +105,7 @@ export type MakeParams<P> = P extends string[] ? {
101
105
  * @returns An array of parameter names extracted from the tuple type.
102
106
  * @public
103
107
  */
104
- export type ExtractParamsFromTuple<S extends unknown[]> = S extends [] ? [] : S extends [infer H, ...infer R] ? H extends `:${infer P}` ? [P, ...ExtractParamsFromTuple<R>] : ExtractParamsFromTuple<R> : never;
108
+ export type ExtractParamsFromTuple<S extends unknown[]> = S extends [] ? [] : S extends [infer H, ...infer R] ? H extends `:${infer P}` ? [P, ...ExtractParamsFromTuple<R>] : H extends `*${infer C}` ? C extends '' ? ExtractParamsFromTuple<R> : [C, ...ExtractParamsFromTuple<R>] : ExtractParamsFromTuple<R> : never;
105
109
  /**
106
110
  * Extracts the parameters from a string literal representing a route path.
107
111
  * @typeParam S - The string literal representing the route path.