@tempots/ui 3.0.0 → 4.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/index.cjs +1 -1
- package/index.js +185 -179
- package/package.json +1 -1
- package/renderables/appearance.d.ts +11 -0
- package/renderables/autofocus.d.ts +57 -3
- package/renderables/autoselect.d.ts +63 -3
- package/renderables/hidden-when-empty.d.ts +0 -1
- package/renderables/inviewport.d.ts +116 -10
- package/renderables/resource.d.ts +115 -8
- package/renderables/router/location.d.ts +71 -3
- package/renderables/router/router.d.ts +93 -4
package/package.json
CHANGED
|
@@ -18,3 +18,14 @@ export type AppearanceType = 'light' | 'dark';
|
|
|
18
18
|
* @public
|
|
19
19
|
*/
|
|
20
20
|
export declare const Appearance: Provider<Signal<AppearanceType>>;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a signal that represents the current appearance (light or dark) based on the user's system
|
|
23
|
+
* preferences.
|
|
24
|
+
*
|
|
25
|
+
* The appearance is updated whenever the user's system preferences change, and the signal is cleaned
|
|
26
|
+
* up when it is no longer needed.
|
|
27
|
+
*
|
|
28
|
+
* @returns A signal representing the current appearance.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export declare function useAppearance(): Signal<AppearanceType>;
|
|
@@ -1,9 +1,63 @@
|
|
|
1
1
|
import { Renderable } from '@tempots/dom';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Automatically focuses an element when it's rendered to the DOM.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* This utility is commonly used for form inputs, modals, or any interactive element
|
|
6
|
+
* that should receive focus immediately when it appears. The small default delay
|
|
7
|
+
* ensures the element is fully rendered before attempting to focus.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Auto-focus a text input
|
|
12
|
+
* html.input(
|
|
13
|
+
* AutoFocus(),
|
|
14
|
+
* attr.placeholder('Enter your name'),
|
|
15
|
+
* attr.type('text')
|
|
16
|
+
* )
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Auto-focus with custom delay
|
|
22
|
+
* html.textarea(
|
|
23
|
+
* AutoFocus(100), // Wait 100ms before focusing
|
|
24
|
+
* attr.placeholder('Enter your message')
|
|
25
|
+
* )
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Use in a modal dialog
|
|
31
|
+
* const showModal = prop(false)
|
|
32
|
+
*
|
|
33
|
+
* When(showModal,
|
|
34
|
+
* () => html.div(
|
|
35
|
+
* attr.class('modal'),
|
|
36
|
+
* html.input(
|
|
37
|
+
* AutoFocus(), // Focus when modal opens
|
|
38
|
+
* attr.placeholder('Search...')
|
|
39
|
+
* ),
|
|
40
|
+
* html.button(
|
|
41
|
+
* on.click(() => showModal.value = false),
|
|
42
|
+
* 'Close'
|
|
43
|
+
* )
|
|
44
|
+
* )
|
|
45
|
+
* )
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* // Combine with other input enhancements
|
|
51
|
+
* html.input(
|
|
52
|
+
* AutoFocus(),
|
|
53
|
+
* AutoSelect(), // Also select the text
|
|
54
|
+
* attr.value(searchTerm),
|
|
55
|
+
* on.input(emitValue(value => searchTerm.value = value))
|
|
56
|
+
* )
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param delay - Delay in milliseconds before focusing (default: 10ms)
|
|
60
|
+
* @returns A renderable that focuses the element when rendered
|
|
7
61
|
* @public
|
|
8
62
|
*/
|
|
9
63
|
export declare const AutoFocus: (delay?: number) => Renderable;
|
|
@@ -1,8 +1,68 @@
|
|
|
1
1
|
import { Renderable } from '@tempots/dom';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Automatically selects all text content in an input element when it's rendered.
|
|
4
|
+
*
|
|
5
|
+
* This utility is particularly useful for input fields where you want the user to be able
|
|
6
|
+
* to immediately start typing to replace the existing content, or easily copy the current value.
|
|
7
|
+
* The small default delay ensures the element is fully rendered before attempting to select.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Auto-select text in an input
|
|
12
|
+
* const username = prop('john_doe')
|
|
13
|
+
*
|
|
14
|
+
* html.input(
|
|
15
|
+
* AutoSelect(),
|
|
16
|
+
* attr.value(username),
|
|
17
|
+
* attr.type('text')
|
|
18
|
+
* )
|
|
19
|
+
* // When rendered, 'john_doe' will be automatically selected
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Combine with AutoFocus for complete UX
|
|
25
|
+
* html.input(
|
|
26
|
+
* AutoFocus(), // Focus the input
|
|
27
|
+
* AutoSelect(), // Select all text
|
|
28
|
+
* attr.value(editableValue),
|
|
29
|
+
* attr.placeholder('Enter value')
|
|
30
|
+
* )
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // Use in an edit dialog
|
|
36
|
+
* const editMode = prop(false)
|
|
37
|
+
* const itemName = prop('Default Name')
|
|
38
|
+
*
|
|
39
|
+
* When(editMode,
|
|
40
|
+
* () => html.div(
|
|
41
|
+
* html.label('Edit name:'),
|
|
42
|
+
* html.input(
|
|
43
|
+
* AutoFocus(),
|
|
44
|
+
* AutoSelect(), // Select existing name for easy editing
|
|
45
|
+
* attr.value(itemName),
|
|
46
|
+
* on.keydown(e => {
|
|
47
|
+
* if (e.key === 'Enter') editMode.value = false
|
|
48
|
+
* if (e.key === 'Escape') editMode.value = false
|
|
49
|
+
* })
|
|
50
|
+
* )
|
|
51
|
+
* )
|
|
52
|
+
* )
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* // Custom delay for specific timing needs
|
|
58
|
+
* html.input(
|
|
59
|
+
* AutoSelect(50), // Wait 50ms before selecting
|
|
60
|
+
* attr.value(sensitiveData)
|
|
61
|
+
* )
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @param delay - Delay in milliseconds before selecting text (default: 10ms)
|
|
65
|
+
* @returns A renderable that selects all text in the input element when rendered
|
|
6
66
|
* @public
|
|
7
67
|
*/
|
|
8
68
|
export declare const AutoSelect: (delay?: number) => Renderable;
|
|
@@ -2,7 +2,6 @@ import { Renderable } from '@tempots/dom';
|
|
|
2
2
|
/**
|
|
3
3
|
* Hides the element when it is empty and restores its initial state when necessary.
|
|
4
4
|
*
|
|
5
|
-
* @param ctx - The DOM context.
|
|
6
5
|
* @returns A function that can be used to restore the initial state of the element.
|
|
7
6
|
* @public
|
|
8
7
|
*/
|
|
@@ -13,22 +13,128 @@ export type InViewportOptions = {
|
|
|
13
13
|
once?: boolean;
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
|
-
* Creates a
|
|
16
|
+
* Creates a component that tracks whether an element is visible in the viewport.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* This component uses the Intersection Observer API to efficiently detect when an element
|
|
19
|
+
* enters or exits the viewport. It's perfect for implementing lazy loading, infinite scrolling,
|
|
20
|
+
* animations on scroll, and other viewport-based interactions.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Basic viewport detection
|
|
25
|
+
* InViewport(
|
|
26
|
+
* { mode: 'partial' },
|
|
27
|
+
* (isVisible) => html.div(
|
|
28
|
+
* attr.class(isVisible.map(v => v ? 'visible' : 'hidden')),
|
|
29
|
+
* 'This element changes class when visible'
|
|
30
|
+
* )
|
|
31
|
+
* )
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* // Lazy load images
|
|
37
|
+
* const imageUrl = 'https://example.com/large-image.jpg'
|
|
38
|
+
*
|
|
39
|
+
* InViewport(
|
|
40
|
+
* { mode: 'partial', once: true },
|
|
41
|
+
* (isVisible) => isVisible.value
|
|
42
|
+
* ? html.img(attr.src(imageUrl), attr.alt('Lazy loaded image'))
|
|
43
|
+
* : html.div(attr.class('placeholder'), 'Loading...')
|
|
44
|
+
* )
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Infinite scrolling trigger
|
|
50
|
+
* const loadMore = () => {
|
|
51
|
+
* // Load more data
|
|
52
|
+
* console.log('Loading more items...')
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* html.div(
|
|
56
|
+
* ForEach(items, item => ItemComponent(item)),
|
|
57
|
+
* InViewport(
|
|
58
|
+
* { mode: 'partial' },
|
|
59
|
+
* (isVisible) => {
|
|
60
|
+
* // Trigger load more when sentinel comes into view
|
|
61
|
+
* isVisible.on(visible => {
|
|
62
|
+
* if (visible) loadMore()
|
|
63
|
+
* })
|
|
64
|
+
* return html.div(attr.class('loading-sentinel'), 'Loading more...')
|
|
65
|
+
* }
|
|
66
|
+
* )
|
|
67
|
+
* )
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // Animation on scroll
|
|
73
|
+
* InViewport(
|
|
74
|
+
* { mode: 'full' }, // Only trigger when fully visible
|
|
75
|
+
* (isVisible) => html.div(
|
|
76
|
+
* attr.class(isVisible.map(v =>
|
|
77
|
+
* v ? 'animate-fade-in' : 'opacity-0'
|
|
78
|
+
* )),
|
|
79
|
+
* 'This animates when fully in view'
|
|
80
|
+
* )
|
|
81
|
+
* )
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @param options - Configuration options for viewport detection
|
|
85
|
+
* @param options.mode - 'partial' (any part visible) or 'full' (completely visible)
|
|
86
|
+
* @param options.once - If true, stops observing after first intersection
|
|
87
|
+
* @param fn - Function that receives the visibility signal and returns content to render
|
|
88
|
+
* @returns A renderable component that tracks viewport visibility
|
|
21
89
|
* @public
|
|
22
90
|
*/
|
|
23
91
|
export declare const InViewport: ({ mode, once }: InViewportOptions, fn: (value: Signal<boolean>) => TNode) => Renderable;
|
|
24
92
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
93
|
+
* Conditionally renders content based on whether an element is in the viewport.
|
|
94
|
+
*
|
|
95
|
+
* This is a convenience wrapper around `InViewport` that provides a simpler API
|
|
96
|
+
* for cases where you just want to show/hide content based on viewport visibility.
|
|
97
|
+
* It's perfect for simple lazy loading or reveal animations.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* // Simple lazy loading
|
|
102
|
+
* WhenInViewport(
|
|
103
|
+
* { mode: 'partial', once: true },
|
|
104
|
+
* () => html.img(
|
|
105
|
+
* attr.src('https://example.com/image.jpg'),
|
|
106
|
+
* attr.alt('Lazy loaded image')
|
|
107
|
+
* ),
|
|
108
|
+
* () => html.div(attr.class('skeleton'), 'Loading...')
|
|
109
|
+
* )
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* // Reveal animation
|
|
115
|
+
* WhenInViewport(
|
|
116
|
+
* { mode: 'full' },
|
|
117
|
+
* () => html.div(
|
|
118
|
+
* attr.class('animate-slide-up'),
|
|
119
|
+
* 'This content slides up when visible'
|
|
120
|
+
* )
|
|
121
|
+
* )
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* // Load expensive component only when needed
|
|
127
|
+
* WhenInViewport(
|
|
128
|
+
* { mode: 'partial', once: true },
|
|
129
|
+
* () => ExpensiveChart({ data: chartData }),
|
|
130
|
+
* () => html.div('Chart will load when visible')
|
|
131
|
+
* )
|
|
132
|
+
* ```
|
|
27
133
|
*
|
|
28
|
-
* @param options -
|
|
29
|
-
* @param then -
|
|
30
|
-
* @param otherwise -
|
|
31
|
-
* @returns
|
|
134
|
+
* @param options - Configuration options for viewport detection
|
|
135
|
+
* @param then - Function that returns content to render when element is in viewport
|
|
136
|
+
* @param otherwise - Optional function that returns content when element is not in viewport
|
|
137
|
+
* @returns A renderable component that conditionally shows content based on viewport visibility
|
|
32
138
|
* @public
|
|
33
139
|
*/
|
|
34
140
|
export declare const WhenInViewport: (options: InViewportOptions, then: () => TNode, otherwise?: () => TNode) => Renderable;
|
|
@@ -28,16 +28,123 @@ export interface ResourceDisplayOptions<V, E> {
|
|
|
28
28
|
*/
|
|
29
29
|
export declare const ResourceDisplay: <V, E>(resource: AsyncResource<V, E>, options: ResourceDisplayOptions<V, E>) => Renderable<import('@tempots/dom').DOMContext>;
|
|
30
30
|
/**
|
|
31
|
-
* Creates
|
|
31
|
+
* Creates a reactive resource component for handling asynchronous data loading.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* This component provides a declarative way to handle async operations with proper
|
|
34
|
+
* loading, success, and error states. It automatically manages the lifecycle of
|
|
35
|
+
* async requests and provides reload functionality.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* // Basic API data loading
|
|
40
|
+
* const userId = prop(1)
|
|
41
|
+
*
|
|
42
|
+
* const UserProfile = Resource({
|
|
43
|
+
* request: userId,
|
|
44
|
+
* load: async ({ request }) => {
|
|
45
|
+
* const response = await fetch(`/api/users/${request}`)
|
|
46
|
+
* if (!response.ok) throw new Error('Failed to load user')
|
|
47
|
+
* return response.json()
|
|
48
|
+
* }
|
|
49
|
+
* })({
|
|
50
|
+
* loading: () => html.div('Loading user...'),
|
|
51
|
+
* failure: (error, reload) => html.div(
|
|
52
|
+
* 'Error: ', error,
|
|
53
|
+
* html.button(on.click(reload), 'Retry')
|
|
54
|
+
* ),
|
|
55
|
+
* success: (user) => html.div(
|
|
56
|
+
* html.h2(user.map(u => u.name)),
|
|
57
|
+
* html.p(user.map(u => u.email))
|
|
58
|
+
* )
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Resource with dependencies
|
|
65
|
+
* const searchQuery = prop('')
|
|
66
|
+
* const filters = prop({ category: 'all', sort: 'name' })
|
|
67
|
+
*
|
|
68
|
+
* const SearchResults = Resource({
|
|
69
|
+
* request: computed(() => ({
|
|
70
|
+
* query: searchQuery.value,
|
|
71
|
+
* ...filters.value
|
|
72
|
+
* })),
|
|
73
|
+
* load: async ({ request, abortSignal }) => {
|
|
74
|
+
* const params = new URLSearchParams(request)
|
|
75
|
+
* const response = await fetch(`/api/search?${params}`, {
|
|
76
|
+
* signal: abortSignal
|
|
77
|
+
* })
|
|
78
|
+
* return response.json()
|
|
79
|
+
* },
|
|
80
|
+
* mapError: (error) => error instanceof Error ? error.message : 'Unknown error'
|
|
81
|
+
* })({
|
|
82
|
+
* loading: (previous) => html.div(
|
|
83
|
+
* 'Searching...',
|
|
84
|
+
* previous.value && html.div('Previous results:', previous.value.length)
|
|
85
|
+
* ),
|
|
86
|
+
* failure: (error, reload) => html.div(
|
|
87
|
+
* attr.class('error'),
|
|
88
|
+
* 'Search failed: ', error,
|
|
89
|
+
* html.button(on.click(reload), 'Try again')
|
|
90
|
+
* ),
|
|
91
|
+
* success: (results, reload) => html.div(
|
|
92
|
+
* html.button(on.click(reload), 'Refresh'),
|
|
93
|
+
* ForEach(results, result => SearchResultItem(result))
|
|
94
|
+
* )
|
|
95
|
+
* })
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* // File upload resource
|
|
101
|
+
* const selectedFile = prop<File | null>(null)
|
|
102
|
+
*
|
|
103
|
+
* const FileUpload = Resource({
|
|
104
|
+
* request: selectedFile,
|
|
105
|
+
* load: async ({ request }) => {
|
|
106
|
+
* if (!request) throw new Error('No file selected')
|
|
107
|
+
*
|
|
108
|
+
* const formData = new FormData()
|
|
109
|
+
* formData.append('file', request)
|
|
110
|
+
*
|
|
111
|
+
* const response = await fetch('/api/upload', {
|
|
112
|
+
* method: 'POST',
|
|
113
|
+
* body: formData
|
|
114
|
+
* })
|
|
115
|
+
*
|
|
116
|
+
* if (!response.ok) throw new Error('Upload failed')
|
|
117
|
+
* return response.json()
|
|
118
|
+
* }
|
|
119
|
+
* })({
|
|
120
|
+
* loading: () => html.div(
|
|
121
|
+
* attr.class('upload-progress'),
|
|
122
|
+
* 'Uploading file...'
|
|
123
|
+
* ),
|
|
124
|
+
* failure: (error, reload) => html.div(
|
|
125
|
+
* attr.class('upload-error'),
|
|
126
|
+
* 'Upload failed: ', error,
|
|
127
|
+
* html.button(on.click(reload), 'Retry upload')
|
|
128
|
+
* ),
|
|
129
|
+
* success: (result) => html.div(
|
|
130
|
+
* attr.class('upload-success'),
|
|
131
|
+
* 'File uploaded successfully!',
|
|
132
|
+
* html.a(
|
|
133
|
+
* attr.href(result.map(r => r.url)),
|
|
134
|
+
* 'View file'
|
|
135
|
+
* )
|
|
136
|
+
* )
|
|
137
|
+
* })
|
|
138
|
+
* ```
|
|
36
139
|
*
|
|
37
|
-
* @
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
140
|
+
* @template R - The type of the request parameter
|
|
141
|
+
* @template V - The type of the successful result value
|
|
142
|
+
* @template E - The type of the error (defaults to unknown)
|
|
143
|
+
* @param config - Configuration object for the resource
|
|
144
|
+
* @param config.request - Signal or value representing the request parameters
|
|
145
|
+
* @param config.load - Async function that loads the resource
|
|
146
|
+
* @param config.mapError - Optional function to transform errors into a specific type
|
|
147
|
+
* @returns Function that takes display options and returns a renderable component
|
|
41
148
|
* @public
|
|
42
149
|
*/
|
|
43
150
|
export declare const Resource: <R, V, E = unknown>({ request, load, mapError, }: {
|
|
@@ -1,9 +1,77 @@
|
|
|
1
1
|
import { Prop, Provider } from '@tempots/dom';
|
|
2
2
|
import { LocationData } from './location-data';
|
|
3
3
|
/**
|
|
4
|
-
* Provider for
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Provider for browser location and navigation functionality.
|
|
5
|
+
*
|
|
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'))
|
|
67
|
+
*
|
|
68
|
+
* return Ensure(productId,
|
|
69
|
+
* (id) => ProductDetail({ id }),
|
|
70
|
+
* () => html.div('Product ID required')
|
|
71
|
+
* )
|
|
72
|
+
* })
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
7
75
|
* @public
|
|
8
76
|
*/
|
|
9
77
|
export declare const Location: Provider<Prop<LocationData>>;
|
|
@@ -1,11 +1,100 @@
|
|
|
1
1
|
import { TNode, Renderable, Signal } from '@tempots/dom';
|
|
2
2
|
import { ExtractParams, MakeParams, RouteInfo } from './route-info';
|
|
3
3
|
/**
|
|
4
|
-
* Creates a router that maps
|
|
4
|
+
* Creates a client-side router that maps URL patterns to renderable components.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The Router component provides declarative routing for single-page applications.
|
|
7
|
+
* It automatically extracts route parameters from URLs and passes them to the
|
|
8
|
+
* corresponding route handlers. The router integrates with the browser's history
|
|
9
|
+
* API and updates the UI when the URL changes.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Basic routing setup
|
|
14
|
+
* const AppRouter = Router({
|
|
15
|
+
* '/': () => html.div('Home Page'),
|
|
16
|
+
* '/about': () => html.div('About Page'),
|
|
17
|
+
* '/contact': () => html.div('Contact Page'),
|
|
18
|
+
* '*': () => html.div('404 - Page Not Found')
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* render(AppRouter, document.body)
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Routes with parameters
|
|
27
|
+
* const BlogRouter = Router({
|
|
28
|
+
* '/': () => html.div('Blog Home'),
|
|
29
|
+
* '/posts/:id': (info) => {
|
|
30
|
+
* const postId = info.$.params.$.id
|
|
31
|
+
* return html.div(
|
|
32
|
+
* html.h1('Post ID: ', postId),
|
|
33
|
+
* html.p('Loading post...')
|
|
34
|
+
* )
|
|
35
|
+
* },
|
|
36
|
+
* '/users/:userId/posts/:postId': (info) => {
|
|
37
|
+
* const userId = info.$.params.$.userId
|
|
38
|
+
* const postId = info.$.params.$.postId
|
|
39
|
+
* return html.div(
|
|
40
|
+
* html.h1('User ', userId, ' - Post ', postId),
|
|
41
|
+
* UserPost({ userId, postId })
|
|
42
|
+
* )
|
|
43
|
+
* },
|
|
44
|
+
* '*': () => html.div('Page not found')
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* // Using route info for navigation and data
|
|
51
|
+
* const ProductRouter = Router({
|
|
52
|
+
* '/products': () => ProductList(),
|
|
53
|
+
* '/products/:id': (info) => {
|
|
54
|
+
* const productId = info.$.params.$.id
|
|
55
|
+
* const searchParams = info.$.search
|
|
56
|
+
*
|
|
57
|
+
* return html.div(
|
|
58
|
+
* html.h1('Product ', productId),
|
|
59
|
+
* html.p('Search params: ', searchParams),
|
|
60
|
+
* ProductDetail({
|
|
61
|
+
* id: productId,
|
|
62
|
+
* variant: new URLSearchParams(searchParams.value).get('variant')
|
|
63
|
+
* })
|
|
64
|
+
* )
|
|
65
|
+
* },
|
|
66
|
+
* '/products/:id/reviews': (info) => {
|
|
67
|
+
* const productId = info.$.params.$.id
|
|
68
|
+
* return ProductReviews({ productId })
|
|
69
|
+
* }
|
|
70
|
+
* })
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // Programmatic navigation
|
|
76
|
+
* import { Location } from '@tempots/ui'
|
|
77
|
+
*
|
|
78
|
+
* const Navigation = () => html.nav(
|
|
79
|
+
* html.button(
|
|
80
|
+
* on.click(() => Location.navigate('/')),
|
|
81
|
+
* 'Home'
|
|
82
|
+
* ),
|
|
83
|
+
* html.button(
|
|
84
|
+
* on.click(() => Location.navigate('/about')),
|
|
85
|
+
* 'About'
|
|
86
|
+
* ),
|
|
87
|
+
* html.button(
|
|
88
|
+
* on.click(() => Location.navigate('/products/123')),
|
|
89
|
+
* 'Product 123'
|
|
90
|
+
* )
|
|
91
|
+
* )
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @template T - The type of the routes configuration object
|
|
95
|
+
* @param routes - Object mapping route patterns to handler functions
|
|
96
|
+
* @returns A renderable router component that handles URL routing
|
|
97
|
+
* @throws {Error} When no matching route is found for the current URL
|
|
9
98
|
* @public
|
|
10
99
|
*/
|
|
11
100
|
export declare const Router: <T extends { [K in keyof T]: (info: K extends string ? Signal<RouteInfo<MakeParams<ExtractParams<K>>, K>> : never) => TNode; }>(routes: T) => Renderable;
|