Compare commits

...

15 Commits

Author SHA1 Message Date
NicoR
4bcf2d5bef Fix failing e2e test 2025-07-07 09:51:16 -03:00
Guido D'Orsi
9aa5f47ac3 chore(ci): add tanstack form example to CI tests 2025-07-07 12:54:44 +02:00
NicoR
9c79622686 Update pnpm lockfile 2025-07-04 17:32:04 -03:00
NicoR
3ae6a5bcff Format and lint 2025-07-04 17:23:08 -03:00
NicoR
2fc8be261c Fix bug with instructions not being updated 2025-07-04 17:13:36 -03:00
NicoR
c52d50eb3c Add example to the Examples page in the homepage 2025-07-04 16:52:36 -03:00
NicoR
c80120cebc Update README 2025-07-04 16:52:22 -03:00
NicoR
40313a7ce2 Undo the changes to the form example 2025-07-04 16:32:15 -03:00
NicoR
c2ba3e9249 Copy example as tanstack-form 2025-07-04 16:30:47 -03:00
NicoR
1717c15d72 Update pnpm-lock.yaml after rebase 2025-07-04 16:12:48 -03:00
NicoR
69052be58b Use TanStack Form to create orders 2025-07-04 16:12:48 -03:00
NicoR
0473b87bab Use getZodSchema to avoid redefining the whole schema 2025-07-04 16:12:48 -03:00
NicoR
41345ace55 Add CoMapSchema.getZodSchema method 2025-07-04 16:12:48 -03:00
NicoR
497f6e6ccd Use Zod validations 2025-07-04 16:12:48 -03:00
NicoR
eed406da8f Add TanStack Form example 2025-07-04 16:12:36 -03:00
32 changed files with 1818 additions and 0 deletions

View File

@@ -30,6 +30,7 @@ jobs:
"examples/inspector",
"examples/music-player",
"examples/organization",
"examples/tanstack-form",
"starters/react-passkey-auth",
"starters/svelte-passkey-auth",
"tests/jazz-svelte"

33
examples/tanstack-form/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -0,0 +1,858 @@
# form
## 0.1.60
### Patch Changes
- Updated dependencies [048ac1d]
- jazz-tools@0.14.22
- jazz-react@0.14.22
## 0.1.59
### Patch Changes
- Updated dependencies [e7e505e]
- Updated dependencies [13b57aa]
- Updated dependencies [5662faa]
- Updated dependencies [2116a59]
- jazz-tools@0.14.21
- jazz-react@0.14.21
## 0.1.58
### Patch Changes
- Updated dependencies [6f72419]
- Updated dependencies [04b20c2]
- jazz-tools@0.14.20
- jazz-react@0.14.20
## 0.1.57
### Patch Changes
- jazz-react@0.14.19
- jazz-tools@0.14.19
## 0.1.56
### Patch Changes
- Updated dependencies [4b950bc]
- Updated dependencies [d6d9c0a]
- Updated dependencies [c559054]
- jazz-tools@0.14.18
- jazz-react@0.14.18
## 0.1.55
### Patch Changes
- Updated dependencies [e512df4]
- jazz-tools@0.14.17
- jazz-react@0.14.17
## 0.1.54
### Patch Changes
- jazz-react@0.14.16
- jazz-tools@0.14.16
## 0.1.53
### Patch Changes
- Updated dependencies [f9590f9]
- jazz-react@0.14.15
- jazz-tools@0.14.15
## 0.1.52
### Patch Changes
- Updated dependencies [e32a1f7]
- jazz-tools@0.14.14
- jazz-react@0.14.14
## 0.1.51
### Patch Changes
- jazz-react@0.14.13
## 0.1.50
### Patch Changes
- jazz-react@0.14.12
## 0.1.49
### Patch Changes
- Updated dependencies [dc746a2]
- Updated dependencies [f869d9a]
- Updated dependencies [3fe6832]
- hash-slash@0.2.3
- jazz-react@0.14.10
- jazz-tools@0.14.10
## 0.1.48
### Patch Changes
- Updated dependencies [22c2600]
- jazz-tools@0.14.9
- jazz-react@0.14.9
## 0.1.47
### Patch Changes
- Updated dependencies [637ae13]
- jazz-tools@0.14.8
- jazz-react@0.14.8
## 0.1.46
### Patch Changes
- Updated dependencies [365b0ea]
- jazz-tools@0.14.7
- jazz-react@0.14.7
## 0.1.45
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.1.44
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.1.43
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.1.42
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.1.41
### Patch Changes
- Updated dependencies [cdfc105]
- jazz-tools@0.14.1
- jazz-react@0.14.1
## 0.1.40
### Patch Changes
- Updated dependencies [5835ed1]
- jazz-tools@0.14.0
- jazz-react@0.14.0
## 0.1.39
### Patch Changes
- jazz-react@0.13.32
## 0.1.38
### Patch Changes
- Updated dependencies [e5b170f]
- jazz-tools@0.13.31
- jazz-react@0.13.31
## 0.1.37
### Patch Changes
- jazz-react@0.13.30
- jazz-tools@0.13.30
## 0.1.36
### Patch Changes
- jazz-react@0.13.29
- jazz-tools@0.13.29
## 0.1.35
### Patch Changes
- jazz-react@0.13.28
- jazz-tools@0.13.28
## 0.1.34
### Patch Changes
- jazz-react@0.13.27
- jazz-tools@0.13.27
## 0.1.33
### Patch Changes
- Updated dependencies [ff846d9]
- jazz-tools@0.13.26
- jazz-react@0.13.26
## 0.1.32
### Patch Changes
- jazz-react@0.13.25
- jazz-tools@0.13.25
## 0.1.31
### Patch Changes
- Updated dependencies [02a240c]
- jazz-tools@0.13.23
- jazz-react@0.13.23
## 0.1.30
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.1.29
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.1.28
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.1.27
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.1.26
### Patch Changes
- jazz-react@0.13.17
- jazz-tools@0.13.17
## 0.1.25
### Patch Changes
- jazz-react@0.13.16
- jazz-tools@0.13.16
## 0.1.24
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.1.23
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.1.22
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.1.21
### Patch Changes
- Updated dependencies [4547525]
- jazz-tools@0.13.12
- jazz-react@0.13.12
## 0.1.20
### Patch Changes
- Updated dependencies [17273a6]
- jazz-tools@0.13.11
- jazz-react@0.13.11
## 0.1.19
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.1.18
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.1.17
### Patch Changes
- Updated dependencies [bc3d7bb]
- jazz-tools@0.13.7
- jazz-react@0.13.7
## 0.1.16
### Patch Changes
- Updated dependencies [fe6f561]
- jazz-tools@0.13.5
- jazz-react@0.13.5
## 0.1.15
### Patch Changes
- Updated dependencies [3129982]
- jazz-react@0.13.4
- jazz-tools@0.13.4
## 0.1.14
### Patch Changes
- Updated dependencies [12f8bfa]
- Updated dependencies [bd57177]
- jazz-tools@0.13.3
- jazz-react@0.13.3
## 0.1.13
### Patch Changes
- jazz-react@0.13.2
- jazz-tools@0.13.2
## 0.1.12
### Patch Changes
- Updated dependencies [afd1374]
- jazz-tools@0.13.0
- jazz-react@0.13.0
## 0.1.11
### Patch Changes
- jazz-react@0.12.2
- jazz-tools@0.12.2
## 0.1.10
### Patch Changes
- jazz-react@0.12.1
- jazz-tools@0.12.1
## 0.1.9
### Patch Changes
- Updated dependencies [01523dc]
- Updated dependencies [4ea87dc]
- Updated dependencies [1e6da19]
- Updated dependencies [b6c6a0a]
- jazz-tools@0.12.0
- jazz-react@0.12.0
## 0.1.8
### Patch Changes
- jazz-react@0.11.8
- jazz-tools@0.11.8
## 0.1.7
### Patch Changes
- Updated dependencies [a140f55]
- Updated dependencies [4019918]
- Updated dependencies [2b0d1b0]
- jazz-tools@0.11.7
- jazz-react@0.11.7
## 0.1.6
### Patch Changes
- Updated dependencies [e7c85b7]
- Updated dependencies [8ed144e]
- jazz-react@0.11.6
- jazz-tools@0.11.6
- jazz-browser-media-images@0.11.6
## 0.1.5
### Patch Changes
- jazz-react@0.11.5
- jazz-tools@0.11.5
- jazz-browser-media-images@0.11.5
## 0.1.4
### Patch Changes
- Updated dependencies [57a3dbe]
- Updated dependencies [a717754]
- Updated dependencies [a91f343]
- jazz-tools@0.11.4
- jazz-browser-media-images@0.11.4
- jazz-react@0.11.4
## 0.1.3
### Patch Changes
- jazz-react@0.11.3
- jazz-tools@0.11.3
- jazz-browser-media-images@0.11.3
## 0.1.2
### Patch Changes
- Updated dependencies [6892dc6]
- jazz-tools@0.11.2
- jazz-react@0.11.2
- jazz-browser-media-images@0.11.2
## 0.1.1
### Patch Changes
- jazz-react@0.11.1
## 0.1.0
### Minor Changes
- 18428ea: PasskeyAuth: Sets `profile.name` only if a non-empty username is passed to `signUp`
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser-media-images@0.11.0
- jazz-react@0.11.0
## 0.0.53
### Patch Changes
- Updated dependencies [2f99de0]
- jazz-tools@0.10.15
- jazz-browser-media-images@0.10.15
- jazz-react@0.10.15
## 0.0.52
### Patch Changes
- Updated dependencies [75211e3]
- jazz-tools@0.10.14
- jazz-react@0.10.14
- jazz-browser-media-images@0.10.14
## 0.0.51
### Patch Changes
- Updated dependencies [07feedd]
- jazz-tools@0.10.13
- jazz-browser-media-images@0.10.13
- jazz-react@0.10.13
## 0.0.50
### Patch Changes
- Updated dependencies [4612e05]
- jazz-tools@0.10.12
- jazz-react@0.10.12
- jazz-browser-media-images@0.10.12
## 0.0.49
### Patch Changes
- jazz-browser-media-images@0.10.9
- jazz-react@0.10.9
## 0.0.48
### Patch Changes
- Updated dependencies [2fb6428]
- jazz-tools@0.10.8
- jazz-react@0.10.8
- jazz-browser-media-images@0.10.8
## 0.0.47
### Patch Changes
- Updated dependencies [1136d9b]
- Updated dependencies [0eed228]
- jazz-react@0.10.7
- jazz-tools@0.10.7
- jazz-browser-media-images@0.10.7
## 0.0.46
### Patch Changes
- Updated dependencies [1d71ca1]
- Updated dependencies [ada802b]
- hash-slash@0.2.2
- jazz-react@0.10.6
- jazz-tools@0.10.6
- jazz-browser-media-images@0.10.6
## 0.0.45
### Patch Changes
- Updated dependencies [59ff77e]
- jazz-tools@0.10.5
- jazz-browser-media-images@0.10.5
- jazz-react@0.10.5
## 0.0.44
### Patch Changes
- jazz-react@0.10.4
- jazz-tools@0.10.4
- jazz-browser-media-images@0.10.4
## 0.0.43
### Patch Changes
- Updated dependencies [d8582fc]
- jazz-tools@0.10.3
- jazz-browser-media-images@0.10.3
- jazz-react@0.10.3
## 0.0.42
### Patch Changes
- jazz-react@0.10.2
- jazz-tools@0.10.2
- jazz-browser-media-images@0.10.2
## 0.0.41
### Patch Changes
- Updated dependencies [5a63cba]
- jazz-tools@0.10.1
- jazz-browser-media-images@0.10.1
- jazz-react@0.10.1
## 0.0.40
### Patch Changes
- Updated dependencies [498954f]
- Updated dependencies [d42c2aa]
- Updated dependencies [dd03464]
- Updated dependencies [b426342]
- jazz-react@0.10.0
- jazz-tools@0.10.0
- jazz-browser-media-images@0.10.0
## 0.0.39
### Patch Changes
- jazz-react@0.9.23
- jazz-tools@0.9.23
- jazz-browser-media-images@0.9.23
## 0.0.38
### Patch Changes
- jazz-browser-media-images@0.9.22
- jazz-react@0.9.22
## 0.0.37
### Patch Changes
- Updated dependencies [1be017d]
- jazz-tools@0.9.21
- jazz-browser-media-images@0.9.21
- jazz-react@0.9.21
## 0.0.36
### Patch Changes
- Updated dependencies [b01cc1f]
- jazz-tools@0.9.20
- jazz-browser-media-images@0.9.20
- jazz-react@0.9.20
## 0.0.35
### Patch Changes
- jazz-react@0.9.19
- jazz-tools@0.9.19
- jazz-browser-media-images@0.9.19
## 0.0.34
### Patch Changes
- jazz-react@0.9.18
- jazz-tools@0.9.18
- jazz-browser-media-images@0.9.18
## 0.0.33
### Patch Changes
- Updated dependencies [c2ca1fe]
- Updated dependencies [1227047]
- jazz-tools@0.9.17
- jazz-browser-media-images@0.9.17
- jazz-react@0.9.17
## 0.0.32
### Patch Changes
- Updated dependencies [24b3b6a]
- jazz-tools@0.9.16
- jazz-browser-media-images@0.9.16
- jazz-react@0.9.16
## 0.0.31
### Patch Changes
- Updated dependencies [7491711]
- jazz-tools@0.9.15
- jazz-browser-media-images@0.9.15
- jazz-react@0.9.15
## 0.0.30
### Patch Changes
- Updated dependencies [3df93cc]
- jazz-tools@0.9.14
- jazz-browser-media-images@0.9.14
- jazz-react@0.9.14
## 0.0.29
### Patch Changes
- jazz-react@0.9.13
- jazz-tools@0.9.13
- jazz-browser-media-images@0.9.13
## 0.0.28
### Patch Changes
- jazz-react@0.9.12
- jazz-tools@0.9.12
- jazz-browser-media-images@0.9.12
## 0.0.27
### Patch Changes
- jazz-react@0.9.11
- jazz-tools@0.9.11
- jazz-browser-media-images@0.9.11
## 0.0.26
### Patch Changes
- Updated dependencies [5e83864]
- jazz-react@0.9.10
- jazz-tools@0.9.10
- jazz-browser-media-images@0.9.10
## 0.0.25
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-browser-media-images@0.9.9
- jazz-react@0.9.9
## 0.0.24
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-react@0.9.8
- jazz-browser-media-images@0.9.8
## 0.0.23
### Patch Changes
- jazz-react@0.9.4
## 0.0.22
### Patch Changes
- Updated dependencies [1b71969]
- jazz-react@0.9.1
- jazz-tools@0.9.1
- jazz-browser-media-images@0.9.1
## 0.0.21
### Patch Changes
- Updated dependencies [956a4d1]
- Updated dependencies [8eda792]
- jazz-react@0.9.0
- jazz-tools@0.9.0
- jazz-browser-media-images@0.9.0
## 0.0.20
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-browser-media-images@0.8.51
- jazz-react@0.8.51
## 0.0.19
### Patch Changes
- jazz-react@0.8.50
- jazz-tools@0.8.50
- jazz-browser-media-images@0.8.50
## 0.0.18
### Patch Changes
- jazz-react@0.8.49
- jazz-tools@0.8.49
- jazz-browser-media-images@0.8.49
## 0.0.17
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-browser-media-images@0.8.48
- jazz-react@0.8.48
## 0.0.16
### Patch Changes
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- jazz-tools@0.8.45
- jazz-react@0.8.45
- jazz-browser-media-images@0.8.45
## 0.0.15
### Patch Changes
- jazz-react@0.8.44
- jazz-tools@0.8.44
- jazz-browser-media-images@0.8.44
## 0.0.14
### Patch Changes
- jazz-react@0.8.41
- jazz-tools@0.8.41
- jazz-browser-media-images@0.8.41
## 0.0.13
### Patch Changes
- jazz-browser-media-images@0.8.40
- jazz-react@0.8.40

View File

@@ -0,0 +1,69 @@
# Form example with Jazz and TanStack Form
This is a simple form example that shows you how to make a form for creating and editing a `CoValue`,
called `BubbleTeaOrder`, with fields of different types such
as single-select, multi-select, date, text, and boolean.
The form is built using [TanStack Form](https://tanstack.com/form/). We leverage TanStack Form's
support for [schema validation libraries](https://tanstack.com/form/latest/docs/framework/react/guides/validation#standard-schema-libraries)
and validate the form using Jazz's Zod-based schemas.
## Getting started
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
### Using the example as a template
Create a new Jazz project, and use this example as a template.
```bash
npx create-jazz-app@latest form-app --example tanstack-form
```
Go to the new project directory.
```bash
cd form-app
```
Run the dev server.
```bash
npm run dev
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/tanstack-form/
```
Start the dev server.
```bash
pnpm dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx jazz-run sync`, and setting the `sync` parameter of `JazzReactProvider` in [./src/main.tsx](./src/main.tsx) to `{ peer: "ws://localhost:4200" }`.

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Form example</title>
</head>
<body class="h-full flex flex-col bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<div id="root" class="align-self-center flex-1"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
{
"name": "tanstack-form",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"check": "tsc --noEmit",
"preview": "vite preview",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
},
"dependencies": {
"@tanstack/react-form": "^1.0.0",
"hash-slash": "workspace:*",
"jazz-tools": "workspace:*",
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@playwright/test": "^1.50.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.1.10",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@vitejs/plugin-react": "^4.5.1",
"globals": "^15.11.0",
"is-ci": "^3.0.1",
"postcss": "^8.4.40",
"tailwindcss": "^4.1.10",
"typescript": "5.6.2",
"vite": "^6.3.5"
}
}

View File

@@ -0,0 +1,46 @@
import { defineConfig, devices } from "@playwright/test";
import isCI from "is-ci";
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: isCI,
/* Retry on CI only */
retries: isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isCI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:5173/",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
permissions: ["clipboard-read", "clipboard-write"],
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: "pnpm preview --port 5173",
url: "http://localhost:5173/",
reuseExistingServer: !isCI,
},
],
});

View File

@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,22 @@
import { useIframeHashRouter } from "hash-slash";
import { CreateOrder } from "./CreateOrder.tsx";
import { EditOrder } from "./EditOrder.tsx";
import { Orders } from "./Orders.tsx";
function App() {
const router = useIframeHashRouter();
return (
<>
<main className="max-w-xl mx-auto px-3 py-8 space-y-8">
{router.route({
"/": () => <Orders />,
"/order": () => <CreateOrder />,
"/order/:id": (id) => <EditOrder id={id} />,
})}
</main>
</>
);
}
export default App;

View File

@@ -0,0 +1,53 @@
import { useIframeHashRouter } from "hash-slash";
import { CoPlainText } from "jazz-tools";
import { useAccount } from "jazz-tools/react";
import { LinkToHome } from "./LinkToHome.tsx";
import { OrderForm, OrderFormData } from "./OrderForm.tsx";
import {
BubbleTeaOrder,
JazzAccount,
ListOfBubbleTeaAddOns,
} from "./schema.ts";
export function CreateOrder() {
const { me } = useAccount(JazzAccount, {
resolve: { root: { orders: true } },
});
const router = useIframeHashRouter();
if (!me?.root) return;
const addOrder = (draft: OrderFormData) => {
const newOrder = BubbleTeaOrder.create({
baseTea: draft.baseTea,
deliveryDate: draft.deliveryDate,
withMilk: draft.withMilk,
addOns: ListOfBubbleTeaAddOns.create(draft.addOns),
instructions: draft.instructions
? CoPlainText.create(draft.instructions)
: undefined,
});
me.root.orders.push(newOrder);
router.navigate("/");
};
const draftOrder: Partial<OrderFormData> = {
baseTea: "Black",
addOns: [],
withMilk: false,
};
return (
<>
<LinkToHome />
<h1 className="text-lg">
<strong>Make a new bubble tea order 🧋</strong>
</h1>
<OrderForm order={draftOrder} onSubmit={addOrder} validateOn="submit" />
</>
);
}

View File

@@ -0,0 +1,59 @@
import { CoPlainText, Loaded } from "jazz-tools";
import { useCoState } from "jazz-tools/react";
import { LinkToHome } from "./LinkToHome.tsx";
import { OrderForm, OrderFormData } from "./OrderForm.tsx";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { BubbleTeaOrder } from "./schema.ts";
export type LoadedBubbleTeaOrder = Loaded<
typeof BubbleTeaOrder,
{ addOns: { $each: true }; instructions: true }
>;
export function EditOrder(props: { id: string }) {
const order = useCoState(BubbleTeaOrder, props.id, {
resolve: { addOns: true, instructions: true },
});
if (!order) return;
const onSubmit = (updatedOrder: OrderFormData) => {
// Apply changes to the original Jazz order
order.baseTea = updatedOrder.baseTea;
order.deliveryDate = updatedOrder.deliveryDate;
order.withMilk = updatedOrder.withMilk;
order.addOns.applyDiff(updatedOrder.addOns);
// `applyDiff` requires nested objects to be CoValues as well
order.instructions ??= CoPlainText.create("");
if (updatedOrder.instructions) {
order.instructions.applyDiff(updatedOrder.instructions);
}
};
const originalOrder: OrderFormData = order.toJSON();
// Convert timestamp to Date
originalOrder.deliveryDate = new Date(originalOrder.deliveryDate);
return (
<>
<LinkToHome />
<div>
<p>Saved order:</p>
<OrderThumbnail order={order} />
</div>
<h1 className="text-lg">
<strong>Edit your bubble tea order 🧋</strong>
</h1>
<OrderForm
order={originalOrder}
onSubmit={onSubmit}
validateOn="change"
/>
</>
);
}

View File

@@ -0,0 +1,7 @@
export function LinkToHome() {
return (
<a href="/#" className="text-sm text-stone-600">
&lt; Back to all orders
</a>
);
}

View File

@@ -0,0 +1,210 @@
import { useForm } from "@tanstack/react-form";
import { z } from "jazz-tools";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import {
BubbleTeaAddOnTypes,
BubbleTeaBaseTeaTypes,
BubbleTeaOrder,
} from "./schema.ts";
// TanStack Form can leverage Jazz's Zod schema to validate the form
const orderZodSchemaShape = BubbleTeaOrder.getZodSchema().shape;
const orderFormSchema = z.object({
...orderZodSchemaShape,
baseTea: orderZodSchemaShape.baseTea.refine((value) => value, {
error: "Please select your preferred base tea.",
}),
deliveryDate: z.date("Plese select a delivery date."),
// TanStack Form doesn't support CoList fields, so we need to convert them to arrays
addOns: z
.array(z.enum(BubbleTeaAddOnTypes))
.min(1, "Please select at least one add-on."),
// TanStack Form doesn't support CoPlainText fields, so we need to convert them to strings
instructions: z.string().optional(),
});
export type OrderFormData = z.infer<typeof orderFormSchema>;
export function OrderForm({
order: originalOrder,
onSubmit,
validateOn,
}: {
order: Partial<OrderFormData>;
onSubmit: (order: OrderFormData) => void;
validateOn: "submit" | "change";
}) {
const form = useForm({
defaultValues: originalOrder,
validators: {
onSubmit: validateOn === "submit" ? orderFormSchema : undefined,
onChange: validateOn === "change" ? orderFormSchema : undefined,
},
onSubmit: ({ value }) => {
// If the form is not valid according to orderFormSchema, the value will not be submitted
onSubmit(value as OrderFormData);
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
className="grid gap-5"
>
<div>
<p>Unsaved order preview:</p>
<form.Subscribe
selector={(state) => [state.values]}
children={([values]) => <OrderThumbnail order={values} />}
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="baseTea">Base tea</label>
<form.Field
name="baseTea"
children={(field) => (
<>
<select
id="baseTea"
className="dark:bg-transparent"
value={field.state.value}
onChange={(e) =>
field.handleChange(e.target.value as typeof field.state.value)
}
onBlur={field.handleBlur}
>
<option value="" disabled>
Please select your preferred base tea
</option>
{BubbleTeaBaseTeaTypes.map((teaType) => (
<option key={teaType} value={teaType}>
{teaType}
</option>
))}
</select>
{field.state.meta.errors.length > 0 && (
<span className="text-red-500 text-sm">
{field.state.meta.errors[0]?.message}
</span>
)}
</>
)}
/>
</div>
<fieldset>
<legend className="mb-2">Add-ons</legend>
<form.Field
name="addOns"
mode="array"
children={(field) => (
<>
{BubbleTeaAddOnTypes.map((addOn) => (
<div key={addOn} className="flex items-center gap-2">
<input
type="checkbox"
id={addOn}
checked={field.state.value?.includes(addOn)}
onChange={(e) => {
const currentValue = field.state.value ?? [];
const updatedValue = e.target.checked
? [...currentValue, addOn]
: currentValue.filter((item) => item !== addOn);
field.handleChange(updatedValue);
}}
/>
<label htmlFor={addOn}>{addOn}</label>
</div>
))}
{field.state.meta.errors.length > 0 && (
<span className="text-red-500 text-sm">
{field.state.meta.errors[0]?.message}
</span>
)}
</>
)}
/>
</fieldset>
<div className="flex flex-col gap-2">
<label htmlFor="deliveryDate">Delivery date</label>
<form.Field
name="deliveryDate"
children={(field) => {
// Check if the date is valid
const dateString =
field.state.value && !isNaN(field.state.value.getTime())
? field.state.value.toISOString().split("T")[0]
: "";
return (
<>
<input
type="date"
id="deliveryDate"
className="dark:bg-transparent"
value={dateString}
onChange={(e) => field.handleChange(new Date(e.target.value))}
onBlur={field.handleBlur}
/>
{field.state.meta.errors.length > 0 && (
<span className="text-red-500 text-sm">
{field.state.meta.errors[0]?.message}
</span>
)}
</>
);
}}
/>
</div>
<div className="flex items-center gap-2">
<form.Field
name="withMilk"
children={(field) => (
<input
type="checkbox"
id="withMilk"
checked={field.state.value}
onChange={(e) => field.handleChange(e.target.checked)}
/>
)}
/>
<label htmlFor="withMilk">With milk?</label>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="instructions">Special instructions</label>
<form.Field
name="instructions"
children={(field) => (
<textarea
id="instructions"
className="dark:bg-transparent"
value={field.state.value || ""}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
/>
)}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button
type="submit"
disabled={!canSubmit}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:bg-gray-400"
>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
)}
/>
</form>
);
}

View File

@@ -0,0 +1,36 @@
import { Loaded } from "jazz-tools";
import { OrderFormData } from "./OrderForm.tsx";
import { BubbleTeaOrder } from "./schema.ts";
export function OrderThumbnail({
order,
}: {
order: Loaded<typeof BubbleTeaOrder> | Partial<OrderFormData>;
}) {
const { baseTea, addOns, instructions, deliveryDate, withMilk } = order;
const date = deliveryDate?.toLocaleDateString("en-US", {
timeZone: "UTC",
});
return (
<a
href={"id" in order ? `/#/order/${order.id}` : undefined}
className="border p-3 flex justify-between items-start gap-3"
>
<div>
<strong>
{baseTea} {withMilk ? "milk " : ""} tea
</strong>
{addOns && addOns?.length > 0 && (
<p className="text-sm text-stone-600">
with {addOns?.join(", ").toLowerCase()}
</p>
)}
{instructions && (
<p className="text-sm text-stone-600 italic">{instructions}</p>
)}
</div>
<div className="text-sm text-stone-600">{date}</div>
</a>
);
}

View File

@@ -0,0 +1,36 @@
import { useAccount } from "jazz-tools/react";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { JazzAccount } from "./schema.ts";
export function Orders() {
const { me } = useAccount(JazzAccount, {
resolve: { root: { orders: true } },
});
return (
<>
<section className="space-y-5">
<a
href={`/#/order`}
className="block relative p-3 bg-white border border-stone-200 text-center rounded-md dark:bg-stone-900 dark:border-stone-900"
>
<strong>Add new order</strong>
</a>
<div className="space-y-3">
<h1 className="text-lg pb-2 border-b mb-3 border-stone-200 dark:border-stone-700">
<strong>Your orders 🧋</strong>
</h1>
{me?.root?.orders?.length ? (
me?.root?.orders.map((order) =>
order ? <OrderThumbnail key={order.id} order={order} /> : null,
)
) : (
<p>You have no orders yet.</p>
)}
</div>
</section>
</>
);
}

View File

@@ -0,0 +1 @@
export const apiKey = "tanstack-form-example@garden.co";

View File

@@ -0,0 +1,26 @@
@import "tailwindcss";
@plugin "@tailwindcss/forms";
@layer base {
:root {
--border-default: var(--color-stone-200);
}
.dark {
--border-default: var(--color-stone-900);
}
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--border-default, currentColor);
}
}
@layer components {
strong {
@apply font-semibold text-stone-900 dark:text-white;
}
}

View File

@@ -0,0 +1,22 @@
import { JazzInspector } from "jazz-tools/inspector";
import { JazzReactProvider } from "jazz-tools/react";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { apiKey } from "./apiKey";
import { JazzAccount } from "./schema.ts";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<JazzReactProvider
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
AccountSchema={JazzAccount}
>
<App />
<JazzInspector />
</JazzReactProvider>
</StrictMode>,
);

View File

@@ -0,0 +1,44 @@
import { co, z } from "jazz-tools";
export const BubbleTeaAddOnTypes = [
"Pearl",
"Lychee jelly",
"Red bean",
"Brown sugar",
"Taro",
] as const;
export const BubbleTeaBaseTeaTypes = [
"Black",
"Oolong",
"Jasmine",
"Thai",
] as const;
export const ListOfBubbleTeaAddOns = co.list(z.enum(BubbleTeaAddOnTypes));
export const BubbleTeaOrder = co.map({
baseTea: z.enum(BubbleTeaBaseTeaTypes),
addOns: ListOfBubbleTeaAddOns,
deliveryDate: z.date(),
withMilk: z.boolean(),
instructions: z.optional(co.plainText()),
});
/** The root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export const AccountRoot = co.map({
orders: co.list(BubbleTeaOrder),
});
export const JazzAccount = co
.account({
root: AccountRoot,
profile: co.profile(),
})
.withMigration((account) => {
if (!account.root) {
const orders = co.list(BubbleTeaOrder).create([], account);
account.root = AccountRoot.create({ orders }, account);
}
});

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,42 @@
import { expect, test } from "@playwright/test";
test("create and edit an order", async ({ page }) => {
await page.goto("/");
// start an order
await page.getByRole("link", { name: "Add new order" }).click();
await page.getByLabel("Base tea").selectOption("Oolong");
await page.getByLabel("Pearl").check();
await page.getByLabel("Taro").check();
await page.getByLabel("Delivery date").fill("2024-12-21");
await page.getByLabel("With milk?").check();
await page.getByLabel("Special instructions").fill("25% sugar");
await page.getByRole("button", { name: "Submit" }).click();
await page.waitForURL("/");
// check if order was created correctly
const firstOrder = page.getByRole("link", { name: "Oolong milk tea" });
await expect(firstOrder).toHaveText(/25% sugar/);
await expect(firstOrder).toHaveText(/12\/21\/2024/);
await expect(firstOrder).toHaveText(/with pearl, taro/);
// edit order
await firstOrder.click();
await page.getByLabel("Base tea").selectOption("Jasmine");
await page.getByLabel("Red bean").check();
await page.getByLabel("Brown sugar").check();
await page.getByLabel("Delivery date").fill("2024-12-25");
await page.getByLabel("With milk?").uncheck();
await page.getByLabel("Special instructions").fill("10% sugar");
await page.getByRole("button", { name: "Submit" }).click();
await page.getByRole("link", { name: /Back to all orders/ }).click();
// check if order was edited correctly
const editedOrder = page.getByRole("link", { name: "Jasmine tea" });
await expect(editedOrder).toHaveText(/10% sugar/);
await expect(editedOrder).toHaveText(/12\/25\/2024/);
await expect(editedOrder).toHaveText(
/with pearl, taro, red bean, brown sugar/,
);
});

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,3 @@
{
"ignoreCommand": "npx turbo-ignore"
}

View File

@@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

View File

@@ -0,0 +1,7 @@
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
});

View File

@@ -468,6 +468,15 @@ const reactExamples: Example[] = [
demoUrl: "https://form.demo.jazz.tools",
illustration: <FormIllustration />,
},
{
name: "TanStack Form",
slug: "tanstack-form",
description:
"A form example for creating and editing CoValues using TanStack Form",
tech: [tech.react],
demoUrl: "https://tanstack-form.demo.jazz.tools",
illustration: <FormIllustration />,
},
{
name: "Organization/Team",
slug: "organization",

View File

@@ -146,6 +146,8 @@ export type CoMapSchema<
): CoMapSchema<Shape, Config, Owner>;
getCoSchema: () => typeof CoMap;
getZodSchema: () => z.ZodObject<Shape, Config>;
};
export type optionalKeys<Shape extends z.core.$ZodLooseShape> = {

View File

@@ -64,6 +64,9 @@ function enrichCoMapSchema<Shape extends z.core.$ZodLooseShape>(
getCoSchema: () => {
return coSchema;
},
getZodSchema: () => {
return schema;
},
}) as unknown as CoMapSchema<Shape>;
// Needs to be derived from the enriched schema

110
pnpm-lock.yaml generated
View File

@@ -1423,6 +1423,64 @@ importers:
specifier: 6.3.5
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
examples/tanstack-form:
dependencies:
'@tanstack/react-form':
specifier: ^1.0.0
version: 1.12.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
hash-slash:
specifier: workspace:*
version: link:../../packages/hash-slash
jazz-tools:
specifier: workspace:*
version: link:../../packages/jazz-tools
react:
specifier: 19.0.0
version: 19.0.0
react-dom:
specifier: 19.0.0
version: 19.0.0(react@19.0.0)
devDependencies:
'@biomejs/biome':
specifier: 1.9.4
version: 1.9.4
'@playwright/test':
specifier: ^1.50.1
version: 1.50.1
'@tailwindcss/forms':
specifier: ^0.5.10
version: 0.5.10(tailwindcss@4.1.10)
'@tailwindcss/postcss':
specifier: ^4.1.10
version: 4.1.10
'@types/react':
specifier: 19.0.0
version: 19.0.0
'@types/react-dom':
specifier: 19.0.0
version: 19.0.0
'@vitejs/plugin-react':
specifier: ^4.5.1
version: 4.5.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
globals:
specifier: ^15.11.0
version: 15.15.0
is-ci:
specifier: ^3.0.1
version: 3.0.1
postcss:
specifier: ^8.4.40
version: 8.5.4
tailwindcss:
specifier: ^4.1.10
version: 4.1.10
typescript:
specifier: 5.6.2
version: 5.6.2
vite:
specifier: 6.3.5
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
examples/todo:
dependencies:
'@faker-js/faker':
@@ -5776,10 +5834,25 @@ packages:
peerDependencies:
vite: 6.3.5
'@tanstack/form-core@1.12.4':
resolution: {integrity: sha512-BhfNI5sEjI68Im1Vqezf9w68fJL4EB80cqW5w0zb/MV1erHHsXNRwLGmljF88VnCx1t/xd4fmF0D08wNajBauQ==}
'@tanstack/history@1.115.0':
resolution: {integrity: sha512-K7JJNrRVvyjAVnbXOH2XLRhFXDkeP54Kt2P4FR1Kl2KDGlIbkua5VqZQD2rot3qaDrpufyUa63nuLai1kOLTsQ==}
engines: {node: '>=12'}
'@tanstack/react-form@1.12.4':
resolution: {integrity: sha512-MsWHTTUl1Db7tcawbREEMjUtnjK1wC9HnwEITFFhO6e9jN4vR8gb7qRM6TDKg0tkBf42fd5jhEI5qCYA8Sl2pQ==}
peerDependencies:
'@tanstack/react-start': ^1.112.0
react: 19.0.0
vinxi: ^0.5.0
peerDependenciesMeta:
'@tanstack/react-start':
optional: true
vinxi:
optional: true
'@tanstack/react-router-devtools@1.116.0':
resolution: {integrity: sha512-PsJZWPjcmwZGe71kUvH4bI1ozkv1FgBuBEE0hTYlTCSJ3uG+qv3ndGEI+AiFyuF5OStrbfg0otW1OxeNq5vdGQ==}
engines: {node: '>=12'}
@@ -5801,6 +5874,12 @@ packages:
react: 19.0.0
react-dom: 19.0.0
'@tanstack/react-store@0.7.1':
resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==}
peerDependencies:
react: 19.0.0
react-dom: 19.0.0
'@tanstack/router-core@1.115.3':
resolution: {integrity: sha512-gynHs72LHVg05fuJTwZZYhDL4VNEAK0sXz7IqiBv7a3qsYeEmIZsGaFr9sVjTkuF1kbrFBdJd5JYutzBh9Uuhw==}
engines: {node: '>=12'}
@@ -5854,6 +5933,9 @@ packages:
'@tanstack/store@0.7.0':
resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==}
'@tanstack/store@0.7.1':
resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==}
'@tanstack/virtual-file-routes@1.115.0':
resolution: {integrity: sha512-XLUh1Py3AftcERrxkxC5Y5m5mfllRH3YR6YVlyjFgI2Tc2Ssy2NKmQFQIafoxfW459UJ8Dn81nWKETEIJifE4g==}
engines: {node: '>=12'}
@@ -7587,6 +7669,9 @@ packages:
decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
decode-formdata@0.9.0:
resolution: {integrity: sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==}
decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'}
@@ -17819,8 +17904,22 @@ snapshots:
tailwindcss: 4.1.10
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
'@tanstack/form-core@1.12.4':
dependencies:
'@tanstack/store': 0.7.1
'@tanstack/history@1.115.0': {}
'@tanstack/react-form@1.12.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@tanstack/form-core': 1.12.4
'@tanstack/react-store': 0.7.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
decode-formdata: 0.9.0
devalue: 5.1.1
react: 19.0.0
transitivePeerDependencies:
- react-dom
'@tanstack/react-router-devtools@1.116.0(@tanstack/react-router@1.116.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tiny-invariant@1.3.3)':
dependencies:
'@tanstack/react-router': 1.116.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -17851,6 +17950,13 @@ snapshots:
react-dom: 19.0.0(react@19.0.0)
use-sync-external-store: 1.5.0(react@19.0.0)
'@tanstack/react-store@0.7.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@tanstack/store': 0.7.1
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
use-sync-external-store: 1.5.0(react@19.0.0)
'@tanstack/router-core@1.115.3':
dependencies:
'@tanstack/history': 1.115.0
@@ -17910,6 +18016,8 @@ snapshots:
'@tanstack/store@0.7.0': {}
'@tanstack/store@0.7.1': {}
'@tanstack/virtual-file-routes@1.115.0': {}
'@testing-library/dom@10.4.0':
@@ -20042,6 +20150,8 @@ snapshots:
decimal.js@10.4.3: {}
decode-formdata@0.9.0: {}
decode-uri-component@0.2.2: {}
decode-uri-component@0.4.1: {}