remix 3.0.0-beta.1 → 3.0.0-beta.3

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.
Files changed (87) hide show
  1. package/dist/headers/accept-encoding.d.ts +2 -0
  2. package/dist/headers/accept-encoding.d.ts.map +1 -0
  3. package/dist/headers/accept-encoding.js +2 -0
  4. package/dist/headers/accept-language.d.ts +2 -0
  5. package/dist/headers/accept-language.d.ts.map +1 -0
  6. package/dist/headers/accept-language.js +2 -0
  7. package/dist/headers/accept.d.ts +2 -0
  8. package/dist/headers/accept.d.ts.map +1 -0
  9. package/dist/headers/accept.js +2 -0
  10. package/dist/headers/cache-control.d.ts +2 -0
  11. package/dist/headers/cache-control.d.ts.map +1 -0
  12. package/dist/headers/cache-control.js +2 -0
  13. package/dist/headers/content-disposition.d.ts +2 -0
  14. package/dist/headers/content-disposition.d.ts.map +1 -0
  15. package/dist/headers/content-disposition.js +2 -0
  16. package/dist/headers/content-range.d.ts +2 -0
  17. package/dist/headers/content-range.d.ts.map +1 -0
  18. package/dist/headers/content-range.js +2 -0
  19. package/dist/headers/content-type.d.ts +2 -0
  20. package/dist/headers/content-type.d.ts.map +1 -0
  21. package/dist/headers/content-type.js +2 -0
  22. package/dist/headers/cookie.d.ts +2 -0
  23. package/dist/headers/cookie.d.ts.map +1 -0
  24. package/dist/headers/cookie.js +2 -0
  25. package/dist/headers/if-match.d.ts +2 -0
  26. package/dist/headers/if-match.d.ts.map +1 -0
  27. package/dist/headers/if-match.js +2 -0
  28. package/dist/headers/if-none-match.d.ts +2 -0
  29. package/dist/headers/if-none-match.d.ts.map +1 -0
  30. package/dist/headers/if-none-match.js +2 -0
  31. package/dist/headers/if-range.d.ts +2 -0
  32. package/dist/headers/if-range.d.ts.map +1 -0
  33. package/dist/headers/if-range.js +2 -0
  34. package/dist/headers/range.d.ts +2 -0
  35. package/dist/headers/range.d.ts.map +1 -0
  36. package/dist/headers/range.js +2 -0
  37. package/dist/headers/raw-headers.d.ts +2 -0
  38. package/dist/headers/raw-headers.d.ts.map +1 -0
  39. package/dist/headers/raw-headers.js +2 -0
  40. package/dist/headers/set-cookie.d.ts +2 -0
  41. package/dist/headers/set-cookie.d.ts.map +1 -0
  42. package/dist/headers/set-cookie.js +2 -0
  43. package/dist/headers/vary.d.ts +2 -0
  44. package/dist/headers/vary.d.ts.map +1 -0
  45. package/{src/node-serve.ts → dist/headers/vary.js} +1 -1
  46. package/package.json +102 -47
  47. package/src/csrf-middleware/README.md +5 -12
  48. package/src/data-schema/README.md +3 -9
  49. package/src/data-table/README.md +6 -14
  50. package/src/data-table-mysql/README.md +5 -11
  51. package/src/data-table-postgres/README.md +2 -4
  52. package/src/data-table-sqlite/README.md +2 -4
  53. package/src/fetch-proxy/README.md +1 -2
  54. package/src/file-storage/README.md +10 -4
  55. package/src/file-storage-s3/README.md +2 -3
  56. package/src/form-data-middleware/README.md +1 -2
  57. package/src/form-data-parser/README.md +7 -12
  58. package/src/headers/README.md +33 -1
  59. package/src/headers/accept-encoding.ts +2 -0
  60. package/src/headers/accept-language.ts +2 -0
  61. package/src/headers/accept.ts +2 -0
  62. package/src/headers/cache-control.ts +2 -0
  63. package/src/headers/content-disposition.ts +2 -0
  64. package/src/headers/content-range.ts +2 -0
  65. package/src/headers/content-type.ts +2 -0
  66. package/src/headers/cookie.ts +2 -0
  67. package/src/headers/if-match.ts +2 -0
  68. package/src/headers/if-none-match.ts +2 -0
  69. package/src/headers/if-range.ts +2 -0
  70. package/src/headers/range.ts +2 -0
  71. package/src/headers/raw-headers.ts +2 -0
  72. package/src/headers/set-cookie.ts +2 -0
  73. package/{dist/node-serve.js → src/headers/vary.ts} +1 -1
  74. package/src/node-fetch-server/README.md +1 -6
  75. package/src/node-tsx/README.md +3 -8
  76. package/src/route-pattern/README.md +18 -13
  77. package/src/session-storage-redis/README.md +1 -2
  78. package/src/test/README.md +2 -5
  79. package/src/ui/README.md +9 -7
  80. package/src/ui/anchor/README.md +11 -3
  81. package/src/ui/animation/README.md +7 -5
  82. package/src/ui/combobox/README.md +1 -1
  83. package/src/ui/menu/README.md +26 -2
  84. package/src/ui/server/README.md +4 -2
  85. package/dist/node-serve.d.ts +0 -2
  86. package/dist/node-serve.d.ts.map +0 -1
  87. package/src/node-serve/README.md +0 -253
@@ -1,12 +1,11 @@
1
1
  # file-storage-s3
2
2
 
3
- S3 backend for [`remix/file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage).
4
- Use this package when you want the `FileStorage` API backed by AWS S3 or an S3-compatible provider.
3
+ S3 backend for [`remix/file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage). Use this package when you want the `FileStorage` API backed by AWS S3 or an S3-compatible provider.
5
4
 
6
5
  ## Features
7
6
 
8
7
  - **S3-Compatible API** - Works with AWS S3 and S3-compatible APIs (e.g. MinIO, LocalStack)
9
- - **Metadata Preservation** - Preserves `File` metadata (`name`, `type`, `lastModified`)
8
+ - **Metadata Preservation** - Preserves `File` metadata (`name`, `type`, `size`, `lastModified`)
10
9
  - **Runtime-Agnostic Signing** - Uses [`aws4fetch`](https://github.com/mhart/aws4fetch) for SigV4 signing
11
10
 
12
11
  ## Installation
@@ -69,8 +69,7 @@ let router = createRouter({
69
69
 
70
70
  ### Limit Multipart Growth
71
71
 
72
- `formData()` forwards multipart limit options to `parseFormData()`, so you can cap uploads with
73
- `maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, and `maxTotalSize`.
72
+ `formData()` forwards multipart limit options to `parseFormData()`, so you can cap uploads with `maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, and `maxTotalSize`.
74
73
 
75
74
  ```ts
76
75
  let router = createRouter({
@@ -72,8 +72,7 @@ async function requestHandler(request: Request) {
72
72
  }
73
73
  ```
74
74
 
75
- To validate the resulting `FormData` object with `remix/data-schema`, use the
76
- `remix/data-schema/form-data` helpers.
75
+ To validate the resulting `FormData` object with `remix/data-schema`, use the `remix/data-schema/form-data` helpers.
77
76
 
78
77
  To limit the overall shape of multipart requests, use the `maxHeaderSize`, `maxFileSize`, `maxFiles`, `maxParts`, and `maxTotalSize` options. By default, `parseFormData()` uses `maxFiles = 20`, `maxParts = 1000`, and `maxTotalSize = maxFiles * maxFileSize + 1 MiB`.
79
78
 
@@ -118,15 +117,15 @@ try {
118
117
  }
119
118
  ```
120
119
 
121
- If you're looking for a more flexible storage solution for `File` objects that are uploaded, this library pairs really well with [the `file-storage` library](https://github.com/remix-run/remix/tree/main/packages/file-storage) for keeping files in various storage backends.
120
+ If you're looking for a more flexible storage solution for `FileUpload` objects, this library pairs really well with [the `file-storage` library](https://github.com/remix-run/remix/tree/main/packages/file-storage) for keeping files in various storage backends.
122
121
 
123
122
  ```ts
124
- import { LocalFileStorage } from 'remix/file-storage/local'
123
+ import { createFsFileStorage } from 'remix/file-storage/fs'
125
124
  import type { FileUpload } from 'remix/form-data-parser'
126
125
  import { parseFormData } from 'remix/form-data-parser'
127
126
 
128
127
  // Set up storage for uploaded files
129
- const fileStorage = new LocalFileStorage('/uploads/user-avatars')
128
+ const fileStorage = createFsFileStorage('/uploads/user-avatars')
130
129
 
131
130
  // Define how to handle incoming file uploads
132
131
  async function uploadHandler(fileUpload: FileUpload) {
@@ -134,11 +133,8 @@ async function uploadHandler(fileUpload: FileUpload) {
134
133
  if (fileUpload.fieldName === 'user-avatar') {
135
134
  let storageKey = `user-${user.id}-avatar`
136
135
 
137
- // Put the file in storage
138
- await fileStorage.set(storageKey, fileUpload)
139
-
140
- // Return a lazy File object that can access the stored file when needed
141
- return fileStorage.get(storageKey)
136
+ // Put the file in storage and return the stored LazyFile
137
+ return fileStorage.put(storageKey, fileUpload)
142
138
  }
143
139
 
144
140
  // Ignore unrecognized fields
@@ -153,8 +149,7 @@ The [`demos` directory](https://github.com/remix-run/remix/tree/main/packages/fo
153
149
 
154
150
  ## Related Packages
155
151
 
156
- - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Tiny,
157
- standards-aligned validation with a `form-data` export for `FormData` and `URLSearchParams`
152
+ - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Tiny, standards-aligned validation with a `form-data` export for `FormData` and `URLSearchParams`
158
153
  - [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) - A simple key/value interface for storing `FileUpload` objects you get from the parser
159
154
  - [`multipart-parser`](https://github.com/remix-run/remix/tree/main/packages/multipart-parser) - The parser used internally for parsing `multipart/form-data` HTTP messages
160
155
 
@@ -53,6 +53,26 @@ headers.get('Content-Type') // no typed parse needed
53
53
  headers.contentType.mediaType // parses Content-Type lazily
54
54
  ```
55
55
 
56
+ Use `apply()` to apply a `SuperHeadersInit` value to an existing instance with header-aware semantics:
57
+
58
+ ```ts
59
+ let headers = new Headers({
60
+ contentType: 'text/html',
61
+ setCookie: { name: 'session', value: 'abc' },
62
+ vary: 'Accept-Encoding',
63
+ })
64
+
65
+ headers.apply({
66
+ contentType: 'application/json',
67
+ setCookie: { name: 'theme', value: 'dark' },
68
+ vary: ['Accept-Encoding', 'Accept-Language'],
69
+ })
70
+
71
+ headers.get('Content-Type') // 'application/json'
72
+ headers.get('Vary') // 'accept-encoding, accept-language'
73
+ headers.getSetCookie() // ['session=abc', 'theme=dark']
74
+ ```
75
+
56
76
  ## Individual Header Utilities
57
77
 
58
78
  Each supported header has a class that represents the header value. Use the static `from()` method to parse header values. Each class has a `toString()` method that returns the header value as a string, which you can either call manually, or will be called automatically when the header class is used in a context that expects a string.
@@ -74,6 +94,16 @@ The following headers are currently supported:
74
94
  - [Set-Cookie](./README.md#set-cookie)
75
95
  - [Vary](./README.md#vary)
76
96
 
97
+ If you only need a specific header parser (for example, just `Content-Type`), import that parser directly from its subpath. This avoids pulling the package barrel and `SuperHeaders`:
98
+
99
+ ```ts
100
+ import { ContentType } from 'remix/headers/content-type'
101
+ import { SetCookie } from 'remix/headers/set-cookie'
102
+
103
+ let contentType = ContentType.from('text/plain; charset=utf-8')
104
+ let setCookie = new SetCookie('session=abc; Path=/')
105
+ ```
106
+
77
107
  ### Accept
78
108
 
79
109
  Parse, manipulate and stringify [`Accept` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept).
@@ -325,7 +355,7 @@ headers.set('Content-Type', new ContentType({ mediaType: 'text/html', charset: '
325
355
 
326
356
  Parse, manipulate and stringify [`Cookie` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie).
327
357
 
328
- Implements `Map<name, value>`.
358
+ Implements an ordered list of name/value pairs. Duplicate cookie names are preserved, such as when cookies with the same name were set for different paths.
329
359
 
330
360
  ```ts
331
361
  import { Cookie } from 'remix/headers'
@@ -334,6 +364,7 @@ import { Cookie } from 'remix/headers'
334
364
  let cookie = Cookie.from(request.headers.get('Cookie'))
335
365
 
336
366
  cookie.get('session_id') // 'abc123'
367
+ cookie.getAll('session_id') // ['abc123']
337
368
  cookie.get('theme') // 'dark'
338
369
  cookie.has('session_id') // true
339
370
  cookie.size // 2
@@ -345,6 +376,7 @@ for (let [name, value] of cookie) {
345
376
 
346
377
  // Modify and set header
347
378
  cookie.set('theme', 'light')
379
+ cookie.append('session_id', 'def456')
348
380
  cookie.delete('session_id')
349
381
  headers.set('Cookie', cookie)
350
382
 
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/accept-encoding'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/accept-language'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/accept'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/cache-control'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/content-disposition'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/content-range'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/content-type'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/cookie'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/if-match'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/if-none-match'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/if-range'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/range'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/raw-headers'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/set-cookie'
@@ -1,2 +1,2 @@
1
1
  // IMPORTANT: This file is auto-generated, please do not edit manually.
2
- export * from '@remix-run/node-serve';
2
+ export * from '@remix-run/headers/vary'
@@ -124,8 +124,7 @@ async function handler(request: Request) {
124
124
 
125
125
  ### Custom Hostname Configuration
126
126
 
127
- Configure custom hostnames for deployment on VPS or custom environments. `node-fetch-server` uses
128
- the `host` option when constructing `request.url`.
127
+ Configure custom hostnames for deployment on VPS or custom environments. `node-fetch-server` uses the `host` option when constructing `request.url`.
129
128
 
130
129
  ```ts
131
130
  import * as http from 'node:http'
@@ -291,7 +290,6 @@ The [`demos` directory](https://github.com/remix-run/remix/tree/main/packages/no
291
290
 
292
291
  ## Related Packages
293
292
 
294
- - [`node-serve`](https://github.com/remix-run/remix/tree/main/packages/node-serve) - Build high-performance Fetch API servers for Node.js
295
293
  - [`fetch-proxy`](https://github.com/remix-run/remix/tree/main/packages/fetch-proxy) - Build HTTP proxy servers using the web fetch API
296
294
 
297
295
  ## Benchmarks
@@ -322,7 +320,6 @@ Simple HTML response benchmarks without inspecting the incoming request.
322
320
 
323
321
  | Server | Version | Requests/sec | Avg latency | Transfer/sec |
324
322
  | ------------------------- | --------: | -----------: | ----------: | -----------: |
325
- | `remix/node-serve` | `0.0.0` | `62,225` | `6.45ms` | `9.85MB` |
326
323
  | `node:http` | `24.15.0` | `47,110` | `10.66ms` | `9.66MB` |
327
324
  | `remix/node-fetch-server` | `0.13.0` | `43,317` | `11.69ms` | `8.80MB` |
328
325
  | `express` | `5.2.1` | `39,752` | `13.69ms` | `9.59MB` |
@@ -333,7 +330,6 @@ POST benchmarks that read and print the request method, headers, and a small bod
333
330
 
334
331
  | Server | Version | Requests/sec | Avg latency | Transfer/sec |
335
332
  | ------------------------- | --------: | -----------: | ----------: | -----------: |
336
- | `remix/node-serve` | `0.0.0` | `31,213` | `12.75ms` | `4.94MB` |
337
333
  | `remix/node-fetch-server` | `0.13.0` | `25,430` | `24.25ms` | `5.17MB` |
338
334
  | `node:http` | `24.15.0` | `25,088` | `23.89ms` | `5.14MB` |
339
335
  | `express` | `5.2.1` | `22,845` | `27.16ms` | `5.51MB` |
@@ -344,7 +340,6 @@ POST benchmarks that read and print the request method, headers, and a 1 MB body
344
340
 
345
341
  | Server | Version | Requests/sec | Avg latency | Transfer/sec |
346
342
  | ------------------------- | --------: | -----------: | ----------: | -----------: |
347
- | `remix/node-serve` | `0.0.0` | `1,148` | `327.72ms` | `186.03KB` |
348
343
  | `remix/node-fetch-server` | `0.13.0` | `1,086` | `217.69ms` | `225.87KB` |
349
344
  | `node:http` | `24.15.0` | `1,079` | `198.67ms` | `226.54KB` |
350
345
  | `express` | `5.2.1` | `1,022` | `216.07ms` | `252.51KB` |
@@ -2,13 +2,9 @@
2
2
 
3
3
  Run Node.js with TypeScript and JSX syntax support in `.ts`, `.tsx`, and `.jsx` files.
4
4
 
5
- `node-tsx` transforms supported source files before Node.js executes them, including
6
- TypeScript syntax that requires JavaScript code generation such as enums, runtime
7
- namespaces, and parameter properties. JSX compiler options are read from the nearest
8
- `tsconfig.json` for each loaded file.
5
+ `node-tsx` transforms supported source files before Node.js executes them, including TypeScript syntax that requires JavaScript code generation such as enums, runtime namespaces, and parameter properties. JSX compiler options are read from the nearest `tsconfig.json` for each loaded file.
9
6
 
10
- The loader does not type check, change Node.js module resolution, apply TypeScript
11
- path aliases, or downlevel JavaScript syntax for older runtimes.
7
+ The loader does not type check, change Node.js module resolution, apply TypeScript path aliases, or downlevel JavaScript syntax for older runtimes.
12
8
 
13
9
  ## Installation
14
10
 
@@ -47,8 +43,7 @@ Since import resolution still follows Node.js, configure type checking to match
47
43
  - [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) requires type-only imports and exports to be marked so runtime imports are unambiguous.
48
44
  - [`rewriteRelativeImportExtensions`](https://www.typescriptlang.org/tsconfig/#rewriteRelativeImportExtensions) preserves a `tsc` emit path by rewriting relative `.ts` and `.tsx` imports to JavaScript extensions.
49
45
 
50
- Do not enable `erasableSyntaxOnly` if you want TypeScript to accept the same
51
- transform-only syntax that `node-tsx` can execute.
46
+ Do not enable `erasableSyntaxOnly` if you want TypeScript to accept the same transform-only syntax that `node-tsx` can execute.
52
47
 
53
48
  ### Programmatic usage
54
49
 
@@ -55,15 +55,13 @@ createHref('http(s)://:region.cdn.com/assets/*file.:ext', {
55
55
 
56
56
  ## API at a glance
57
57
 
58
- **remix/route-pattern** - Parse and stringify patterns.
59
-
60
- **remix/route-pattern/href** - Generate hrefs for patterns with type safe params.
61
-
62
- **remix/route-pattern/match** - Match against one pattern with type inference for params. Or match against many patterns with deterministic ranking and attached data.
63
-
64
- **remix/route-pattern/join** - Combine two patterns into one. Override protocol, hostname, port. Join pathnames. Merge search constraints.
65
-
66
- **remix/route-pattern/specificity** - Rank matches by [specificity](#ranking-matches-by-specificity).
58
+ | Import | Description |
59
+ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
60
+ | `remix/route-pattern` | Parse and stringify patterns. |
61
+ | `remix/route-pattern/href` | Generate hrefs for patterns with type safe params. |
62
+ | `remix/route-pattern/match` | Match against one pattern with type inference for params, or match against many patterns with deterministic ranking and attached data. |
63
+ | `remix/route-pattern/join` | Combine two patterns into one. Override protocol, hostname, port. Join pathnames. Merge search constraints. |
64
+ | `remix/route-pattern/specificity` | Rank matches by [specificity](#ranking-matches-by-specificity). |
67
65
 
68
66
  For in-depth reference, visit the [`route-pattern` API docs](https://api.remix.run/api/remix/route-pattern)
69
67
 
@@ -113,6 +111,15 @@ While variables, wilcards, and optionals are most prevalent in pathnames, you ca
113
111
  '(:locale.)example.com/docs(/:section)' // matches en.example.com/docs, en.example.com/docs/guides
114
112
  ```
115
113
 
114
+ **Escape characters** with `\`:
115
+
116
+ ```ts
117
+ 'time/12\\:30' // matches /time/12:30
118
+ 'calculator/2\\*3' // matches /calculator/2*3
119
+ 'wiki/Mercury_\\(planet\\)' // matches /wiki/Mercury_(planet)
120
+ 'wiki/AC\\/DC' // matches /wiki/AC%2FDC
121
+ ```
122
+
116
123
  ### Search
117
124
 
118
125
  **Search constraints** narrow matches using `?key` or `?key=value`:
@@ -175,8 +182,7 @@ When multiple patterns match the same URL, `route-pattern` chooses the most spec
175
182
 
176
183
  This is the same ranking used by `createMultiMatcher`.
177
184
 
178
- For advanced use cases, `/specificity` provides comparison utilities: `lessThan`, `greaterThan`, `equal`, `descending`, `ascending`, `compare`.
179
- For example:
185
+ For advanced use cases, `/specificity` provides comparison utilities: `lessThan`, `greaterThan`, `equal`, `descending`, `ascending`, `compare`. For example:
180
186
 
181
187
  ```ts
182
188
  import { createMultiMatcher } from 'remix/route-pattern/match'
@@ -195,8 +201,7 @@ matches.sort(descending).map((match) => match.pattern.toString())
195
201
 
196
202
  ## Generate hrefs
197
203
 
198
- `createHref` turns a pattern and params into a URL string.
199
- Required variables and wildcards must be provided, while params inside optional groups may be omitted.
204
+ `createHref` turns a pattern and params into a URL string. Required variables and wildcards must be provided, while params inside optional groups may be omitted.
200
205
 
201
206
  ```ts
202
207
  import { createHref } from 'remix/route-pattern/href'
@@ -1,7 +1,6 @@
1
1
  # session-storage-redis
2
2
 
3
- Redis-backed session storage for [`remix/session`](https://github.com/remix-run/remix/tree/main/packages/session).
4
- Use this package when app servers need to share session state through Redis.
3
+ Redis-backed session storage for [`remix/session`](https://github.com/remix-run/remix/tree/main/packages/session). Use this package when app servers need to share session state through Redis.
5
4
 
6
5
  ## Installation
7
6
 
@@ -209,8 +209,7 @@ suite('My Test Suite', () => {
209
209
 
210
210
  ### Programmatic runner
211
211
 
212
- `remix/test/cli` exports `runRemixTest()` for tools that want to run the test runner without
213
- exiting the current process:
212
+ `remix/test/cli` exports `runRemixTest()` for tools that want to run the test runner without exiting the current process:
214
213
 
215
214
  ```ts
216
215
  import { runRemixTest } from 'remix/test/cli'
@@ -221,9 +220,7 @@ let exitCode = await runRemixTest({
221
220
  })
222
221
  ```
223
222
 
224
- `runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls
225
- `process.exit()` with that code when the run finishes so open workers, browsers, or project handles
226
- cannot keep the CLI alive.
223
+ `runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls `process.exit()` with that code when the run finishes so open workers, browsers, or project handles cannot keep the CLI alive.
227
224
 
228
225
  ### Test Context
229
226
 
package/src/ui/README.md CHANGED
@@ -135,13 +135,15 @@ let Theme = createTheme({
135
135
  Render the theme once near the top of your document:
136
136
 
137
137
  ```tsx
138
- function Layout(props: { children: RemixNode }) {
139
- return (
138
+ import type { Handle, RemixNode } from 'remix/ui'
139
+
140
+ function Layout(handle: Handle<{ children: RemixNode }>) {
141
+ return () => (
140
142
  <html>
141
143
  <head>
142
144
  <Theme />
143
145
  </head>
144
- <body>{props.children}</body>
146
+ <body>{handle.props.children}</body>
145
147
  </html>
146
148
  )
147
149
  }
@@ -168,13 +170,13 @@ let card = css({
168
170
  Render shared glyphs separately from the theme styles:
169
171
 
170
172
  ```tsx
171
- import type { RemixNode } from 'remix/ui'
173
+ import type { Handle, RemixNode } from 'remix/ui'
172
174
  import { Button } from 'remix/ui/button'
173
175
  import { Glyph } from 'remix/ui/glyph'
174
176
  import { RMX_01, RMX_01_GLYPHS } from 'remix/ui/theme'
175
177
 
176
- function Layout(props: { children: RemixNode }) {
177
- return (
178
+ function Layout(handle: Handle<{ children: RemixNode }>) {
179
+ return () => (
178
180
  <html>
179
181
  <head>
180
182
  <RMX_01 />
@@ -184,7 +186,7 @@ function Layout(props: { children: RemixNode }) {
184
186
  <Button startIcon={<Glyph name="add" />} tone="primary">
185
187
  New project
186
188
  </Button>
187
- {props.children}
189
+ {handle.props.children}
188
190
  </body>
189
191
  </html>
190
192
  )
@@ -1,6 +1,6 @@
1
1
  # anchor
2
2
 
3
- `anchor` positions a floating element against an anchor element and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
3
+ `anchor` positions a floating element against an anchor element or viewport coordinates and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
4
4
 
5
5
  ## Usage
6
6
 
@@ -49,11 +49,19 @@ popover.addEventListener('beforetoggle', (event) => {
49
49
  })
50
50
  ```
51
51
 
52
+ Anchor to coordinates when the surface should open at a pointer location.
53
+
54
+ ```tsx
55
+ let cleanup = anchor(popover, { x: event.clientX, y: event.clientY }, { placement: 'bottom-start' })
56
+ ```
57
+
52
58
  ## `anchor.*`
53
59
 
54
- - `anchor(floatingElement, anchorElement, options)`: positions `floatingElement` against `anchorElement`, starts animation-frame polling for geometry changes, and returns a cleanup function.
60
+ - `anchor(floatingElement, anchorTarget, options)`: positions `floatingElement` against an element or coordinate target, starts animation-frame polling for geometry changes, and returns a cleanup function.
55
61
  - `AnchorOptions`: placement, inset, relative alignment, and offset options.
62
+ - `AnchorPoint`: viewport coordinate target with `x`, `y`, and optional `width`/`height`.
56
63
  - `AnchorPlacement`: exported placement names for the main sides and top/bottom start/end alignment.
64
+ - `AnchorTarget`: an `HTMLElement` or `AnchorPoint`.
57
65
 
58
66
  ## Placements
59
67
 
@@ -149,5 +157,5 @@ anchor(listbox, trigger, {
149
157
  - Oversized inset surfaces with `relativeTo` preserve alignment by scrolling the nearest scrollable descendant when possible.
150
158
  - `offset`, `offsetX`, and `offsetY` may be numbers or functions that receive the floating element.
151
159
  - `relativeTo` lets a surface align to an inner element, which is useful for selected options inside popovers.
152
- - `anchor` polls on animation frames for anchor or floating geometry changes and repositions when either changes.
160
+ - `anchor` polls on animation frames for anchor target or floating geometry changes and repositions when either changes.
153
161
  - The returned cleanup function cancels animation-frame polling.
@@ -52,12 +52,13 @@ function Toast() {
52
52
  `animateExit` keeps a removed keyed element in the DOM long enough to animate from its natural styles to the provided keyframe.
53
53
 
54
54
  ```tsx
55
+ import type { Handle } from 'remix/ui'
55
56
  import { animateExit } from 'remix/ui/animation'
56
57
 
57
- function Item({ id, label }: { id: string; label: string }) {
58
+ function Item(handle: Handle<{ id: string; label: string }>) {
58
59
  return () => (
59
60
  <li
60
- key={id}
61
+ key={handle.props.id}
61
62
  mix={[
62
63
  animateExit({
63
64
  opacity: 0,
@@ -67,7 +68,7 @@ function Item({ id, label }: { id: string; label: string }) {
67
68
  }),
68
69
  ]}
69
70
  >
70
- {label}
71
+ {handle.props.label}
71
72
  </li>
72
73
  )
73
74
  }
@@ -105,12 +106,13 @@ Exit animations can reclaim a removed keyed node if the same keyed element is re
105
106
  `animateLayout` animates layout changes with a FLIP-style transform projection. Use it on elements whose position or size can change between renders.
106
107
 
107
108
  ```tsx
109
+ import type { Handle } from 'remix/ui'
108
110
  import { animateLayout, spring } from 'remix/ui/animation'
109
111
 
110
- function Card({ expanded }: { expanded: boolean }) {
112
+ function Card(handle: Handle<{ expanded: boolean }>) {
111
113
  return () => (
112
114
  <section
113
- class={expanded ? 'card expanded' : 'card'}
115
+ class={handle.props.expanded ? 'card expanded' : 'card'}
114
116
  mix={[
115
117
  animateLayout({
116
118
  ...spring('smooth'),
@@ -8,7 +8,7 @@ Use it when the user should type draft text, filter a popup list, and still comm
8
8
 
9
9
  ```tsx
10
10
  import { css, type Handle } from 'remix/ui'
11
- import { Combobox, ComboboxOption, onComboboxChange } from 'remix/ui'
11
+ import { Combobox, ComboboxOption, onComboboxChange } from 'remix/ui/combobox'
12
12
 
13
13
  let airports = [
14
14
  {
@@ -68,6 +68,29 @@ Use `menuLabel` when the menu surface needs a different accessible label from th
68
68
  </Menu>
69
69
  ```
70
70
 
71
+ Use `menu.contextTrigger()` with `menu.Context` and `MenuList` when a menu should open at the right-click location of an element.
72
+
73
+ ```tsx
74
+ import * as menu from 'remix/ui/menu'
75
+ import { MenuItem, MenuList } from 'remix/ui/menu'
76
+
77
+ export function FileContextMenu(handle: Handle) {
78
+ return () => (
79
+ <menu.Context label="File actions">
80
+ <div mix={menu.contextTrigger()} tabIndex={0}>
81
+ File.txt
82
+ </div>
83
+ <MenuList>
84
+ <MenuItem name="rename">Rename</MenuItem>
85
+ <MenuItem name="delete">Delete</MenuItem>
86
+ </MenuList>
87
+ </menu.Context>
88
+ )
89
+ }
90
+ ```
91
+
92
+ Attach `onMenuSelect(...)` to `MenuList` or a shared ancestor when using lower-level context menu composition.
93
+
71
94
  ## `menu.*`
72
95
 
73
96
  - `Menu`: composed trigger, popover, and list component for the common menu case.
@@ -77,13 +100,14 @@ Use `menuLabel` when the menu surface needs a different accessible label from th
77
100
  - `onMenuSelect(...)`: event mixin for the bubbling `MenuSelectEvent`.
78
101
  - `MenuSelectEvent`: bubbling event class whose `item` describes the selected item.
79
102
  - `MenuSelectItem`: selected item shape with `checked`, `id`, `label`, `name`, `type`, and `value`.
80
- - `menu.Context`, `menu.trigger()`, `menu.popover()`, `menu.list()`, `menu.item(...)`, and `menu.submenuTrigger(...)`: lower-level composition primitives.
103
+ - `menu.Context`, `menu.trigger()`, `menu.contextTrigger()`, `menu.popover()`, `menu.list()`, `menu.item(...)`, and `menu.submenuTrigger(...)`: lower-level composition primitives.
81
104
  - `buttonStyle`, `popoverStyle`, `listStyle`, `itemStyle`, `itemSlotStyle`, `itemLabelStyle`, `itemGlyphStyle`, and `triggerGlyphStyle`: flat style mixins used by the wrappers.
82
- - `MenuProps`, `MenuItemProps`, `MenuListProps`, `MenuProviderProps`, `MenuTriggerOptions`, `MenuItemOptions`, and `SubmenuProps`: public TypeScript props and option types.
105
+ - `MenuProps`, `MenuItemProps`, `MenuListProps`, `MenuProviderProps`, `MenuTriggerOptions`, `MenuContextTriggerOptions`, `MenuItemOptions`, and `SubmenuProps`: public TypeScript props and option types.
83
106
 
84
107
  ## Behavior Notes
85
108
 
86
109
  - Click opens the root menu and focuses the list; clicking the trigger again closes it and restores focus.
110
+ - `menu.contextTrigger()` opens the root menu from a `contextmenu` event at the pointer coordinates and supports keyboard opening with the Context Menu key or Shift+F10.
87
111
  - `ArrowDown` opens from the trigger at the first enabled item. `ArrowUp` opens at the last enabled item. Enter and Space open the menu with focus on the list.
88
112
  - Keyboard navigation skips disabled items and does not wrap past the first or last enabled item.
89
113
  - `Home` and `End` move to the first and last enabled item. Enter and Space activate the highlighted item.
@@ -1,6 +1,6 @@
1
1
  # Server
2
2
 
3
- Remix Component can render to HTML on the server using two APIs:
3
+ Remix UI can render to HTML on the server using two APIs:
4
4
 
5
5
  - `renderToString` - Returns a complete HTML string. Simple, but buffers the entire response.
6
6
  - `renderToStream` - Returns a `ReadableStream<Uint8Array>`. Sends the initial HTML immediately and streams frame content as it resolves.
@@ -26,6 +26,7 @@ import { renderToStream } from 'remix/ui/server'
26
26
 
27
27
  let stream = renderToStream(<App />, {
28
28
  frameSrc: request.url,
29
+ signal: request.signal,
29
30
  resolveFrame(src, _target, context) {
30
31
  let frameUrl = new URL(src, context?.currentFrameSrc ?? request.url)
31
32
  return fetchHtml(frameUrl)
@@ -44,6 +45,7 @@ return new Response(stream, {
44
45
 
45
46
  - **`frameSrc`** - Seeds SSR frame state for the current render. When provided, server-rendered components can read `handle.frame.src` and `handle.frames.top.src` during SSR.
46
47
  - **`topFrameSrc`** - Overrides the root frame URL used for `handle.frames.top.src`. This is mainly useful when calling `renderToStream()` from inside `resolveFrame()` for a nested frame render.
48
+ - **`signal`** - Cancels pending server rendering work. Pass `request.signal` so client disconnects can stop unresolved frame work without invoking `onError` for the disconnect itself.
47
49
  - **`resolveFrame(src, target, context)`** - Called when a `<Frame>` needs its content. Return a string of HTML, a `ReadableStream<Uint8Array>`, or a promise of either. `context.currentFrameSrc` is the URL for the frame that contains the `<Frame>`, and `context.topFrameSrc` is the outer document URL. Required if your component tree contains `<Frame>` elements.
48
50
  - **`onError(error)`** - Called when a rendering error occurs. If not provided, the stream rejects with the error.
49
51
 
@@ -80,7 +82,7 @@ function ProductPage() {
80
82
 
81
83
  ## CSS
82
84
 
83
- Components using the `css` prop have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
85
+ Components using the `css(...)` mixin through `mix` have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
84
86
 
85
87
  ## See Also
86
88
 
@@ -1,2 +0,0 @@
1
- export * from '@remix-run/node-serve';
2
- //# sourceMappingURL=node-serve.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"node-serve.d.ts","sourceRoot":"","sources":["../src/node-serve.ts"],"names":[],"mappings":"AACA,cAAc,uBAAuB,CAAA"}