@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempots/ui",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -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
- * Creates a renderable function that focuses on the element after a specified delay.
3
+ * Automatically focuses an element when it's rendered to the DOM.
4
4
  *
5
- * @param delay - The delay in milliseconds before focusing on the element. Default is 10 milliseconds.
6
- * @returns A renderable function that focuses on the element.
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
- * Creates a renderable function that automatically selects the content of an input element after a specified delay.
4
- * @param delay - The delay in milliseconds before selecting the content. Default is 10 milliseconds.
5
- * @returns A renderable function that can be used with a DOMContext.
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 renderable component that tracks whether the element is in the viewport.
16
+ * Creates a component that tracks whether an element is visible in the viewport.
17
17
  *
18
- * @param options - The options for the `InViewport` component.
19
- * @param fn - A function that returns the renderable component based on the visibility signal.
20
- * @returns The renderable component that tracks the element's visibility in the viewport.
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
- * Executes the provided `then` function when the element is in the viewport.
26
- * Optionally, executes the `otherwise` function when the element is not in the viewport.
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 - The options for the `InViewport` component.
29
- * @param then - The function to execute when the element is in the viewport.
30
- * @param otherwise - The function to execute when the element is not in the viewport.
31
- * @returns The result of executing the `then` function or the `otherwise` function.
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 and displays an asynchronous resource.
31
+ * Creates a reactive resource component for handling asynchronous data loading.
32
32
  *
33
- * @template R - The type of the request.
34
- * @template V - The type of the value when the resource is successfully loaded.
35
- * @template E - The type of the error when the resource fails to load.
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
- * @param request - The request to load the resource.
38
- * @param load - The function to load the resource.
39
- * @param convertError - The function to convert an unknown error into a specific error type.
40
- * @returns A function that takes display options and returns a node representing the current state of the resource.
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 the location context.
5
- * @param child - The child component to be wrapped with the location context.
6
- * @returns The wrapped component with the location context.
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 routes to corresponding renderable components.
4
+ * Creates a client-side router that maps URL patterns to renderable components.
5
5
  *
6
- * @typeParam T - The type of the routes object.
7
- * @param routes - An object containing route handlers.
8
- * @returns - The router renderable.
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;