remix 3.0.0-beta.2 → 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 (79) 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/dist/headers/vary.js +2 -0
  46. package/package.json +95 -35
  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-s3/README.md +1 -2
  55. package/src/form-data-middleware/README.md +1 -2
  56. package/src/form-data-parser/README.md +2 -4
  57. package/src/headers/README.md +10 -0
  58. package/src/headers/accept-encoding.ts +2 -0
  59. package/src/headers/accept-language.ts +2 -0
  60. package/src/headers/accept.ts +2 -0
  61. package/src/headers/cache-control.ts +2 -0
  62. package/src/headers/content-disposition.ts +2 -0
  63. package/src/headers/content-range.ts +2 -0
  64. package/src/headers/content-type.ts +2 -0
  65. package/src/headers/cookie.ts +2 -0
  66. package/src/headers/if-match.ts +2 -0
  67. package/src/headers/if-none-match.ts +2 -0
  68. package/src/headers/if-range.ts +2 -0
  69. package/src/headers/range.ts +2 -0
  70. package/src/headers/raw-headers.ts +2 -0
  71. package/src/headers/set-cookie.ts +2 -0
  72. package/src/headers/vary.ts +2 -0
  73. package/src/node-fetch-server/README.md +1 -2
  74. package/src/node-tsx/README.md +3 -8
  75. package/src/route-pattern/README.md +18 -13
  76. package/src/session-storage-redis/README.md +1 -2
  77. package/src/test/README.md +2 -5
  78. package/src/ui/anchor/README.md +11 -3
  79. package/src/ui/menu/README.md +26 -2
@@ -0,0 +1,2 @@
1
+ export * from '@remix-run/headers/accept-encoding';
2
+ //# sourceMappingURL=accept-encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept-encoding.d.ts","sourceRoot":"","sources":["../../src/headers/accept-encoding.ts"],"names":[],"mappings":"AACA,cAAc,oCAAoC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/accept-language';
2
+ //# sourceMappingURL=accept-language.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept-language.d.ts","sourceRoot":"","sources":["../../src/headers/accept-language.ts"],"names":[],"mappings":"AACA,cAAc,oCAAoC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/accept';
2
+ //# sourceMappingURL=accept.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept.d.ts","sourceRoot":"","sources":["../../src/headers/accept.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAA"}
@@ -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
+ export * from '@remix-run/headers/cache-control';
2
+ //# sourceMappingURL=cache-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-control.d.ts","sourceRoot":"","sources":["../../src/headers/cache-control.ts"],"names":[],"mappings":"AACA,cAAc,kCAAkC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/content-disposition';
2
+ //# sourceMappingURL=content-disposition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-disposition.d.ts","sourceRoot":"","sources":["../../src/headers/content-disposition.ts"],"names":[],"mappings":"AACA,cAAc,wCAAwC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/content-range';
2
+ //# sourceMappingURL=content-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-range.d.ts","sourceRoot":"","sources":["../../src/headers/content-range.ts"],"names":[],"mappings":"AACA,cAAc,kCAAkC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/content-type';
2
+ //# sourceMappingURL=content-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../../src/headers/content-type.ts"],"names":[],"mappings":"AACA,cAAc,iCAAiC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/cookie';
2
+ //# sourceMappingURL=cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../../src/headers/cookie.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAA"}
@@ -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
+ export * from '@remix-run/headers/if-match';
2
+ //# sourceMappingURL=if-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"if-match.d.ts","sourceRoot":"","sources":["../../src/headers/if-match.ts"],"names":[],"mappings":"AACA,cAAc,6BAA6B,CAAA"}
@@ -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
+ export * from '@remix-run/headers/if-none-match';
2
+ //# sourceMappingURL=if-none-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"if-none-match.d.ts","sourceRoot":"","sources":["../../src/headers/if-none-match.ts"],"names":[],"mappings":"AACA,cAAc,kCAAkC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/if-range';
2
+ //# sourceMappingURL=if-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"if-range.d.ts","sourceRoot":"","sources":["../../src/headers/if-range.ts"],"names":[],"mappings":"AACA,cAAc,6BAA6B,CAAA"}
@@ -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
+ export * from '@remix-run/headers/range';
2
+ //# sourceMappingURL=range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range.d.ts","sourceRoot":"","sources":["../../src/headers/range.ts"],"names":[],"mappings":"AACA,cAAc,0BAA0B,CAAA"}
@@ -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
+ export * from '@remix-run/headers/raw-headers';
2
+ //# sourceMappingURL=raw-headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw-headers.d.ts","sourceRoot":"","sources":["../../src/headers/raw-headers.ts"],"names":[],"mappings":"AACA,cAAc,gCAAgC,CAAA"}
@@ -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
+ export * from '@remix-run/headers/set-cookie';
2
+ //# sourceMappingURL=set-cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-cookie.d.ts","sourceRoot":"","sources":["../../src/headers/set-cookie.ts"],"names":[],"mappings":"AACA,cAAc,+BAA+B,CAAA"}
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/set-cookie';
@@ -0,0 +1,2 @@
1
+ export * from '@remix-run/headers/vary';
2
+ //# sourceMappingURL=vary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vary.d.ts","sourceRoot":"","sources":["../../src/headers/vary.ts"],"names":[],"mappings":"AACA,cAAc,yBAAyB,CAAA"}
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/vary';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0-beta.3",
4
4
  "description": "The Remix web framework",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -177,6 +177,66 @@
177
177
  "types": "./dist/headers.d.ts",
178
178
  "default": "./dist/headers.js"
179
179
  },
180
+ "./headers/accept": {
181
+ "types": "./dist/headers/accept.d.ts",
182
+ "default": "./dist/headers/accept.js"
183
+ },
184
+ "./headers/accept-encoding": {
185
+ "types": "./dist/headers/accept-encoding.d.ts",
186
+ "default": "./dist/headers/accept-encoding.js"
187
+ },
188
+ "./headers/accept-language": {
189
+ "types": "./dist/headers/accept-language.d.ts",
190
+ "default": "./dist/headers/accept-language.js"
191
+ },
192
+ "./headers/cache-control": {
193
+ "types": "./dist/headers/cache-control.d.ts",
194
+ "default": "./dist/headers/cache-control.js"
195
+ },
196
+ "./headers/content-disposition": {
197
+ "types": "./dist/headers/content-disposition.d.ts",
198
+ "default": "./dist/headers/content-disposition.js"
199
+ },
200
+ "./headers/content-range": {
201
+ "types": "./dist/headers/content-range.d.ts",
202
+ "default": "./dist/headers/content-range.js"
203
+ },
204
+ "./headers/content-type": {
205
+ "types": "./dist/headers/content-type.d.ts",
206
+ "default": "./dist/headers/content-type.js"
207
+ },
208
+ "./headers/cookie": {
209
+ "types": "./dist/headers/cookie.d.ts",
210
+ "default": "./dist/headers/cookie.js"
211
+ },
212
+ "./headers/if-match": {
213
+ "types": "./dist/headers/if-match.d.ts",
214
+ "default": "./dist/headers/if-match.js"
215
+ },
216
+ "./headers/if-none-match": {
217
+ "types": "./dist/headers/if-none-match.d.ts",
218
+ "default": "./dist/headers/if-none-match.js"
219
+ },
220
+ "./headers/if-range": {
221
+ "types": "./dist/headers/if-range.d.ts",
222
+ "default": "./dist/headers/if-range.js"
223
+ },
224
+ "./headers/range": {
225
+ "types": "./dist/headers/range.d.ts",
226
+ "default": "./dist/headers/range.js"
227
+ },
228
+ "./headers/raw-headers": {
229
+ "types": "./dist/headers/raw-headers.d.ts",
230
+ "default": "./dist/headers/raw-headers.js"
231
+ },
232
+ "./headers/set-cookie": {
233
+ "types": "./dist/headers/set-cookie.d.ts",
234
+ "default": "./dist/headers/set-cookie.js"
235
+ },
236
+ "./headers/vary": {
237
+ "types": "./dist/headers/vary.d.ts",
238
+ "default": "./dist/headers/vary.js"
239
+ },
180
240
  "./html-template": {
181
241
  "types": "./dist/html-template.d.ts",
182
242
  "default": "./dist/html-template.js"
@@ -469,50 +529,50 @@
469
529
  "typescript": "^5.9.3"
470
530
  },
471
531
  "dependencies": {
472
- "@remix-run/assets": "^0.4.1",
473
- "@remix-run/async-context-middleware": "^0.3.1",
474
- "@remix-run/auth": "^0.2.3",
475
- "@remix-run/ui": "^0.2.0",
476
- "@remix-run/cop-middleware": "^0.1.4",
477
- "@remix-run/compression-middleware": "^0.1.9",
478
- "@remix-run/cookie": "^0.5.3",
479
- "@remix-run/auth-middleware": "^0.2.1",
480
- "@remix-run/cors-middleware": "^0.1.4",
481
- "@remix-run/csrf-middleware": "^0.1.4",
532
+ "@remix-run/assets": "^0.4.2",
533
+ "@remix-run/auth": "^0.2.4",
534
+ "@remix-run/compression-middleware": "^0.1.10",
535
+ "@remix-run/cop-middleware": "^0.1.5",
536
+ "@remix-run/csrf-middleware": "^0.1.5",
537
+ "@remix-run/cors-middleware": "^0.1.5",
538
+ "@remix-run/ui": "^0.3.0",
539
+ "@remix-run/async-context-middleware": "^0.3.2",
540
+ "@remix-run/auth-middleware": "^0.2.2",
541
+ "@remix-run/cookie": "^0.5.4",
482
542
  "@remix-run/data-schema": "^0.3.0",
483
- "@remix-run/data-table": "^0.3.0",
484
- "@remix-run/data-table-mysql": "^0.4.0",
485
543
  "@remix-run/data-table-postgres": "^0.4.0",
486
- "@remix-run/data-table-sqlite": "^0.5.0",
487
- "@remix-run/fetch-proxy": "^0.8.2",
488
- "@remix-run/fetch-router": "^0.19.1",
489
- "@remix-run/file-storage": "^0.13.5",
490
- "@remix-run/form-data-middleware": "^0.3.1",
491
- "@remix-run/form-data-parser": "^0.17.2",
492
- "@remix-run/file-storage-s3": "^0.1.2",
493
- "@remix-run/fs": "^0.4.4",
544
+ "@remix-run/data-table-sqlite": "^0.5.1",
545
+ "@remix-run/data-table-mysql": "^0.4.0",
546
+ "@remix-run/data-table": "^0.3.0",
547
+ "@remix-run/fetch-router": "^0.19.2",
548
+ "@remix-run/file-storage": "^0.13.6",
549
+ "@remix-run/fetch-proxy": "^0.8.3",
550
+ "@remix-run/file-storage-s3": "^0.1.3",
551
+ "@remix-run/form-data-middleware": "^0.3.2",
552
+ "@remix-run/form-data-parser": "^0.17.3",
553
+ "@remix-run/fs": "^0.4.5",
554
+ "@remix-run/headers": "^0.21.1",
555
+ "@remix-run/lazy-file": "^5.0.5",
494
556
  "@remix-run/html-template": "^0.3.1",
495
- "@remix-run/headers": "^0.21.0",
496
- "@remix-run/lazy-file": "^5.0.4",
497
- "@remix-run/logger-middleware": "^0.3.1",
557
+ "@remix-run/logger-middleware": "^0.3.2",
558
+ "@remix-run/method-override-middleware": "^0.1.10",
559
+ "@remix-run/multipart-parser": "^0.16.3",
498
560
  "@remix-run/mime": "^0.4.1",
499
- "@remix-run/multipart-parser": "^0.16.2",
500
- "@remix-run/method-override-middleware": "^0.1.9",
501
561
  "@remix-run/node-fetch-server": "^0.13.3",
562
+ "@remix-run/response": "^0.3.6",
502
563
  "@remix-run/node-tsx": "^0.1.1",
503
- "@remix-run/route-pattern": "^0.21.1",
564
+ "@remix-run/route-pattern": "^0.22.0",
504
565
  "@remix-run/session": "^0.4.2",
505
- "@remix-run/response": "^0.3.5",
506
- "@remix-run/session-middleware": "^0.3.1",
507
- "@remix-run/session-storage-memcache": "^0.1.1",
508
- "@remix-run/static-middleware": "^0.4.10",
566
+ "@remix-run/session-middleware": "^0.3.2",
567
+ "@remix-run/session-storage-memcache": "^0.1.2",
568
+ "@remix-run/static-middleware": "^0.4.11",
509
569
  "@remix-run/session-storage-redis": "^0.1.1",
510
570
  "@remix-run/tar-parser": "^0.7.1",
571
+ "@remix-run/cli": "^0.3.2",
511
572
  "@remix-run/assert": "^0.2.1",
512
- "@remix-run/cli": "^0.3.1",
513
- "@remix-run/render-middleware": "^0.1.1",
514
573
  "@remix-run/terminal": "^0.1.1",
515
- "@remix-run/test": "^0.4.1"
574
+ "@remix-run/test": "^0.4.2",
575
+ "@remix-run/render-middleware": "^0.1.2"
516
576
  },
517
577
  "bin": {
518
578
  "remix": "./dist/cli-entry.js"
@@ -521,7 +581,7 @@
521
581
  "mysql2": "^3.15.3",
522
582
  "pg": "^8.16.3",
523
583
  "redis": "^5.10.0",
524
- "playwright": "^1.59.0"
584
+ "playwright": "^1.60.0"
525
585
  },
526
586
  "peerDependenciesMeta": {
527
587
  "mysql2": {
@@ -74,18 +74,11 @@ For unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`), the middleware validates
74
74
 
75
75
  ## Why This Exists
76
76
 
77
- Modern browsers now provide stronger cross-origin signals like `Sec-Fetch-Site`, and explicit
78
- `SameSite=Lax` cookies already block many CSRF attacks. We have considered the lighter,
79
- tokenless model used by Go's `CrossOriginProtection`, and we think it is a good fit when a
80
- deployment can make all of the guarantees that model depends on.
81
-
82
- Remix cannot assume those guarantees for every app. `csrf()` still exists as the conservative
83
- option for apps that want synchronizer tokens in addition to origin checks, especially for
84
- session-backed HTML form workflows and mixed deployment environments.
85
-
86
- If your deployment can guarantee the prerequisites for the tokenless model, this middleware is
87
- optional. In that case, [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware)
88
- may be a better fit.
77
+ Modern browsers now provide stronger cross-origin signals like `Sec-Fetch-Site`, and explicit `SameSite=Lax` cookies already block many CSRF attacks. We have considered the lighter, tokenless model used by Go's `CrossOriginProtection`, and we think it is a good fit when a deployment can make all of the guarantees that model depends on.
78
+
79
+ Remix cannot assume those guarantees for every app. `csrf()` still exists as the conservative option for apps that want synchronizer tokens in addition to origin checks, especially for session-backed HTML form workflows and mixed deployment environments.
80
+
81
+ If your deployment can guarantee the prerequisites for the tokenless model, this middleware is optional. In that case, [`cop-middleware`](https://github.com/remix-run/remix/tree/main/packages/cop-middleware) may be a better fit.
89
82
 
90
83
  ## Related Packages
91
84
 
@@ -71,8 +71,7 @@ if (!result.success) {
71
71
 
72
72
  Both `parse` and `parseSafe` accept any [Standard Schema](https://standardschema.dev/) v1 schema, not just data-schema's own schemas. You can pass a Zod, Valibot, or ArkType schema and they'll work.
73
73
 
74
- For `FormData` and `URLSearchParams`, use the `remix/data-schema/form-data` helpers to build
75
- schemas that plug into the same `parse()` / `parseSafe()` flow:
74
+ For `FormData` and `URLSearchParams`, use the `remix/data-schema/form-data` helpers to build schemas that plug into the same `parse()` / `parseSafe()` flow:
76
75
 
77
76
  ```ts
78
77
  import * as s from 'remix/data-schema'
@@ -95,11 +94,7 @@ let filters = s.parse(
95
94
  )
96
95
  ```
97
96
 
98
- `f.object(...)` is the root schema for `FormData` and `URLSearchParams`.
99
- Use `f.field(...)` for one text value, `f.fields(...)` for repeated text values,
100
- `f.file(...)` for one uploaded file, and `f.files(...)` for repeated files.
101
- When you want a fallback value, prefer `s.defaulted(s.string(), '')`.
102
- File helpers are intended for `FormData`; `URLSearchParams` only supports text values.
97
+ `f.object(...)` is the root schema for `FormData` and `URLSearchParams`. Use `f.field(...)` for one text value, `f.fields(...)` for repeated text values, `f.file(...)` for one uploaded file, and `f.files(...)` for repeated files. When you want a fallback value, prefer `s.defaulted(s.string(), '')`. File helpers are intended for `FormData`; `URLSearchParams` only supports text values.
103
98
 
104
99
  You can also customize built-in validation messages with `errorMap`:
105
100
 
@@ -127,8 +122,7 @@ let result = parseSafe(User, input, {
127
122
  })
128
123
  ```
129
124
 
130
- `errorMap` receives `{ code, defaultMessage, path, values, input, locale }`.
131
- Return `undefined` to keep the default message.
125
+ `errorMap` receives `{ code, defaultMessage, path, values, input, locale }`. Return `undefined` to keep the default message.
132
126
 
133
127
  ## Primitives
134
128
 
@@ -249,8 +249,7 @@ Return behavior:
249
249
 
250
250
  ### Validation and Lifecycle
251
251
 
252
- Validation is optional and table-scoped. Define `validate(context)` to validate/coerce write
253
- payloads, and add lifecycle callbacks when you need custom read/write/delete behavior.
252
+ Validation is optional and table-scoped. Define `validate(context)` to validate/coerce write payloads, and add lifecycle callbacks when you need custom read/write/delete behavior.
254
253
 
255
254
  ```ts
256
255
  import { column as c, fail, table } from 'remix/data-table'
@@ -344,10 +343,7 @@ await db.transaction(async (tx) => {
344
343
 
345
344
  ## Migrations
346
345
 
347
- `data-table` ships a SQL-first migration system under `remix/data-table/migrations`. Each migration
348
- is a directory containing hand-written `up.sql` and (optionally) `down.sql`. The runner journals
349
- applied migrations, detects checksum drift, and wraps each migration in a transaction when the
350
- adapter supports transactional DDL.
346
+ `data-table` ships a SQL-first migration system under `remix/data-table/migrations`. Each migration is a directory containing hand-written `up.sql` and (optionally) `down.sql`. The runner journals applied migrations, detects checksum drift, and wraps each migration in a transaction when the adapter supports transactional DDL.
351
347
 
352
348
  ### Example Setup
353
349
 
@@ -392,8 +388,7 @@ drop table if exists users;
392
388
 
393
389
  ### Multi-Statement Driver Configuration
394
390
 
395
- The runner sends each migration to the adapter as a single multi-statement script. Make sure the
396
- underlying driver accepts multiple statements:
391
+ The runner sends each migration to the adapter as a single multi-statement script. Make sure the underlying driver accepts multiple statements:
397
392
 
398
393
  - `better-sqlite3`: works out of the box (`db.exec`).
399
394
  - `pg`: works out of the box when no parameter array is passed.
@@ -476,8 +471,7 @@ for (let script of plan.sql) {
476
471
 
477
472
  ### Transaction Modes
478
473
 
479
- By default each migration is wrapped in a transaction when the adapter supports transactional DDL.
480
- Override per migration with a directive on the first non-blank line of `up.sql`:
474
+ By default each migration is wrapped in a transaction when the adapter supports transactional DDL. Override per migration with a directive on the first non-blank line of `up.sql`:
481
475
 
482
476
  ```sql
483
477
  -- data-table/transaction: none
@@ -488,11 +482,9 @@ Supported modes:
488
482
 
489
483
  - `auto` (default): wrap when the adapter supports transactional DDL.
490
484
  - `required`: wrap; the runner throws if the adapter cannot support it.
491
- - `none`: never wrap. Use this for statements like postgres `CREATE INDEX CONCURRENTLY` that
492
- cannot run inside a transaction.
485
+ - `none`: never wrap. Use this for statements like postgres `CREATE INDEX CONCURRENTLY` that cannot run inside a transaction.
493
486
 
494
- You can also set `transaction` directly on a `MigrationDescriptor` when registering migrations
495
- programmatically.
487
+ You can also set `transaction` directly on a `MigrationDescriptor` when registering migrations programmatically.
496
488
 
497
489
  ### Programmatic Registration
498
490
 
@@ -1,7 +1,6 @@
1
1
  # data-table-mysql
2
2
 
3
- MySQL adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table).
4
- Use this package when you want `data-table` APIs backed by `mysql2`.
3
+ MySQL adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table). Use this package when you want `data-table` APIs backed by `mysql2`.
5
4
 
6
5
  ## Features
7
6
 
@@ -33,8 +32,7 @@ let pool = createPool(process.env.DATABASE_URL as string)
33
32
  let db = createDatabase(createMysqlDatabaseAdapter(pool))
34
33
  ```
35
34
 
36
- Use `db.query(...)`, relation loading, and transactions from `remix/data-table`.
37
- Import any driver-specific types you need directly from `mysql2/promise`.
35
+ Use `db.query(...)`, relation loading, and transactions from `remix/data-table`. Import any driver-specific types you need directly from `mysql2/promise`.
38
36
 
39
37
  ## Adapter Capabilities
40
38
 
@@ -50,9 +48,7 @@ Import any driver-specific types you need directly from `mysql2/promise`.
50
48
 
51
49
  ### Multi-Statement Migrations
52
50
 
53
- `remix/data-table/migrations` sends each migration to the adapter as a single multi-statement SQL
54
- script. mysql2 only accepts multi-statement scripts when the connection is created with
55
- `multipleStatements: true`:
51
+ `remix/data-table/migrations` sends each migration to the adapter as a single multi-statement SQL script. mysql2 only accepts multi-statement scripts when the connection is created with `multipleStatements: true`:
56
52
 
57
53
  ```ts
58
54
  import { createPool } from 'mysql2/promise'
@@ -65,11 +61,9 @@ let pool = createPool({
65
61
 
66
62
  ### `returning` On MySQL
67
63
 
68
- MySQL does not natively support SQL `RETURNING`. In this adapter, using `returning` on write
69
- operations throws `DataTableQueryError`.
64
+ MySQL does not natively support SQL `RETURNING`. In this adapter, using `returning` on write operations throws `DataTableQueryError`.
70
65
 
71
- Use write metadata (`affectedRows`, `insertId`) on MySQL, or switch adapters when returned rows
72
- are required.
66
+ Use write metadata (`affectedRows`, `insertId`) on MySQL, or switch adapters when returned rows are required.
73
67
 
74
68
  ```ts
75
69
  import { DataTableQueryError } from 'remix/data-table'
@@ -1,7 +1,6 @@
1
1
  # data-table-postgres
2
2
 
3
- PostgreSQL adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table).
4
- Use this package when you want `data-table` APIs backed by `pg`.
3
+ PostgreSQL adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table). Use this package when you want `data-table` APIs backed by `pg`.
5
4
 
6
5
  ## Features
7
6
 
@@ -36,8 +35,7 @@ let pool = new Pool({
36
35
  let db = createDatabase(createPostgresDatabaseAdapter(pool))
37
36
  ```
38
37
 
39
- Use `db.query(...)`, relation loading, and transactions from `remix/data-table`.
40
- Import any driver-specific types you need directly from `pg`.
38
+ Use `db.query(...)`, relation loading, and transactions from `remix/data-table`. Import any driver-specific types you need directly from `pg`.
41
39
 
42
40
  ## Adapter Capabilities
43
41
 
@@ -1,7 +1,6 @@
1
1
  # data-table-sqlite
2
2
 
3
- SQLite adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table).
4
- Use this package when you want `data-table` APIs backed by a synchronous SQLite client.
3
+ SQLite adapter for [`remix/data-table`](https://github.com/remix-run/remix/tree/main/packages/data-table). Use this package when you want `data-table` APIs backed by a synchronous SQLite client.
5
4
 
6
5
  ## Features
7
6
 
@@ -46,8 +45,7 @@ let sqlite = new Database('app.db')
46
45
  let db = createDatabase(createSqliteDatabaseAdapter(sqlite))
47
46
  ```
48
47
 
49
- This is a good fit for local development, embedded deployments, and single-node services.
50
- Import any driver-specific types you need directly from your runtime's SQLite module.
48
+ This is a good fit for local development, embedded deployments, and single-node services. Import any driver-specific types you need directly from your runtime's SQLite module.
51
49
 
52
50
  ## Adapter Capabilities
53
51
 
@@ -1,7 +1,6 @@
1
1
  # fetch-proxy
2
2
 
3
- HTTP proxy utilities built on the web [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
4
- Use `fetch-proxy` to create `fetch` handlers that forward requests to target servers while optionally rewriting headers and cookies.
3
+ HTTP proxy utilities built on the web [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Use `fetch-proxy` to create `fetch` handlers that forward requests to target servers while optionally rewriting headers and cookies.
5
4
 
6
5
  ## Features
7
6
 
@@ -1,7 +1,6 @@
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
 
@@ -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
 
@@ -150,8 +149,7 @@ The [`demos` directory](https://github.com/remix-run/remix/tree/main/packages/fo
150
149
 
151
150
  ## Related Packages
152
151
 
153
- - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Tiny,
154
- 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`
155
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
156
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
157
155
 
@@ -94,6 +94,16 @@ The following headers are currently supported:
94
94
  - [Set-Cookie](./README.md#set-cookie)
95
95
  - [Vary](./README.md#vary)
96
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
+
97
107
  ### Accept
98
108
 
99
109
  Parse, manipulate and stringify [`Accept` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept).
@@ -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'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
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'
@@ -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
 
@@ -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.
@@ -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.