diff --git a/.changeset/json-text-encoding.md b/.changeset/json-text-encoding.md new file mode 100644 index 00000000000..2def7b4345f --- /dev/null +++ b/.changeset/json-text-encoding.md @@ -0,0 +1,5 @@ +--- +"@remix-run/react": minor +--- + +Support `application/json` and `text/plain` submission encodings in `useSubmit`/`fetcher.submit` diff --git a/integration/fetcher-test.ts b/integration/fetcher-test.ts index 7c7dfda98cc..2bca2fcbf15 100644 --- a/integration/fetcher-test.ts +++ b/integration/fetcher-test.ts @@ -148,13 +148,22 @@ test.describe("useFetcher", () => { `, "app/routes/fetcher-echo.jsx": js` - import { json } from "@remix-run/node"; - import { useFetcher } from "@remix-run/react"; + import { json } from "@remix-run/node"; + import { useFetcher } from "@remix-run/react"; export async function action({ request }) { await new Promise(r => setTimeout(r, 1000)); - let value = (await request.formData()).get('value'); - return json({ data: "ACTION " + value }) + let contentType = request.headers.get('Content-Type'); + let value; + if (contentType.includes('application/json')) { + let json = await request.json(); + value = json === null ? json : json.value; + } else if (contentType.includes('text/plain')) { + value = await request.text(); + } else { + value = (await request.formData()).get('value'); + } + return json({ data: "ACTION (" + contentType + ") " + value }) } export async function loader({ request }) { @@ -190,6 +199,20 @@ test.describe("useFetcher", () => { let value = document.getElementById('fetcher-input').value; fetcher.submit({ value }, { method: 'post', action: '/fetcher-echo' }) }}>Submit + + + + {fetcher.state === 'idle' ?

IDLE

: null}
{JSON.stringify(fetcherValues)}
@@ -253,6 +276,46 @@ test.describe("useFetcher", () => { await page.waitForSelector(`pre:has-text("${CHEESESTEAK}")`); }); + test("submit can hit an action with json", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/fetcher-echo", true); + await page.fill("#fetcher-input", "input value"); + await app.clickElement("#fetcher-submit-json"); + await page.waitForSelector(`#fetcher-idle`); + expect(await app.getHtml()).toMatch( + 'ACTION (application/json) input value"' + ); + }); + + test("submit can hit an action with null json", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/fetcher-echo", true); + await app.clickElement("#fetcher-submit-json-null"); + await new Promise((r) => setTimeout(r, 1000)); + await page.waitForSelector(`#fetcher-idle`); + expect(await app.getHtml()).toMatch('ACTION (application/json) null"'); + }); + + test("submit can hit an action with text", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/fetcher-echo", true); + await page.fill("#fetcher-input", "input value"); + await app.clickElement("#fetcher-submit-text"); + await page.waitForSelector(`#fetcher-idle`); + expect(await app.getHtml()).toMatch( + 'ACTION (text/plain;charset=UTF-8) input value"' + ); + }); + + test("submit can hit an action with empty text", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/fetcher-echo", true); + await app.clickElement("#fetcher-submit-text-empty"); + await new Promise((r) => setTimeout(r, 1000)); + await page.waitForSelector(`#fetcher-idle`); + expect(await app.getHtml()).toMatch('ACTION (text/plain;charset=UTF-8) "'); + }); + test("submit can hit an action only route", async ({ page }) => { let app = new PlaywrightFixture(appFixture, page); await app.goto("/fetcher-action-only-call"); @@ -333,8 +396,8 @@ test.describe("useFetcher", () => { JSON.stringify([ "idle/undefined", "submitting/undefined", - "loading/ACTION 1", - "idle/ACTION 1", + "loading/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 1", + "idle/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 1", ]) ); @@ -345,11 +408,12 @@ test.describe("useFetcher", () => { JSON.stringify([ "idle/undefined", "submitting/undefined", - "loading/ACTION 1", - "idle/ACTION 1", - "submitting/ACTION 1", // Preserves old data during resubmissions - "loading/ACTION 2", - "idle/ACTION 2", + "loading/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 1", + "idle/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 1", + // Preserves old data during resubmissions + "submitting/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 1", + "loading/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 2", + "idle/ACTION (application/x-www-form-urlencoded;charset=UTF-8) 2", ]) ); }); diff --git a/integration/form-test.ts b/integration/form-test.ts index 2a873f96bd6..9c0e5391a8f 100644 --- a/integration/form-test.ts +++ b/integration/form-test.ts @@ -1006,12 +1006,7 @@ test.describe("Forms", () => { test("submits the submitter's value(s) in tree order in the form data", async ({ page, - javaScriptEnabled, }) => { - test.fail( - Boolean(javaScriptEnabled), - "
doesn't serialize submit buttons correctly #4342" - ); let app = new PlaywrightFixture(appFixture, page); await app.goto("/submitter"); diff --git a/integration/hook-useSubmit-test.ts b/integration/hook-useSubmit-test.ts index 8a2b4dab938..083fb07345f 100644 --- a/integration/hook-useSubmit-test.ts +++ b/integration/hook-useSubmit-test.ts @@ -15,34 +15,78 @@ test.describe("`useSubmit()` returned function", () => { }, files: { "app/routes/_index.jsx": js` - import { useLoaderData, useSubmit } from "@remix-run/react"; + import { useLoaderData, useSubmit } from "@remix-run/react"; - export function loader({ request }) { - let url = new URL(request.url); - return url.searchParams.toString() - } + export function loader({ request }) { + let url = new URL(request.url); + return url.searchParams.toString() + } + + export default function Index() { + let submit = useSubmit(); + let handleClick = event => { + event.preventDefault() + submit(event.nativeEvent.submitter || event.currentTarget) + } + let data = useLoaderData(); + return ( + + + - export default function Index() { - let submit = useSubmit(); - let handleClick = event => { - event.preventDefault() - submit(event.nativeEvent.submitter || event.currentTarget) + + +
{data}
+
+ ) } - let data = useLoaderData(); - return ( -
- - + `, + "app/routes/action.jsx": js` + import { json } from "@remix-run/node"; + import { useActionData, useSubmit } from "@remix-run/react"; - + export async function action({ request }) { + let contentType = request.headers.get('Content-Type'); + if (contentType.includes('application/json')) { + return json({ value: await request.json() }); + } + if (contentType.includes('text/plain')) { + return json({ value: await request.text() }); + } + let fd = await request.formData(); + return json({ value: new URLSearchParams(fd.entries()).toString() }) + } -
{data}
-
- ) - } - `, + export default function Component() { + let submit = useSubmit(); + let data = useActionData(); + return ( + <> + + + + {data ?

data: {JSON.stringify(data)}

: null} + + ); + } + `, }, }); @@ -64,4 +108,28 @@ test.describe("`useSubmit()` returned function", () => { `
tasks=first&tasks=second&tasks=third
` ); }); + + test("submits json data", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/action", true); + await app.clickElement("#submit-json"); + await page.waitForSelector("#action-data"); + expect(await app.getHtml()).toMatch('data: {"value":{"key":"value"}}'); + }); + + test("submits text data", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/action", true); + await app.clickElement("#submit-text"); + await page.waitForSelector("#action-data"); + expect(await app.getHtml()).toMatch('data: {"value":"raw text"}'); + }); + + test("submits form data", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/action", true); + await app.clickElement("#submit-formData"); + await page.waitForSelector("#action-data"); + expect(await app.getHtml()).toMatch('data: {"value":"key=value"}'); + }); }); diff --git a/package.json b/package.json index 2a19ece7deb..2a88b40c1d1 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@octokit/graphql": "^4.8.0", "@octokit/plugin-paginate-rest": "^2.17.0", "@octokit/rest": "^18.12.0", - "@playwright/test": "^1.28.1", + "@playwright/test": "^1.35.1", "@remix-run/changelog-github": "^0.0.5", "@rollup/plugin-babel": "^5.2.2", "@rollup/plugin-json": "^4.1.0", diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index fe116d92bcf..c77492a548d 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -1489,8 +1489,10 @@ export function useFetchers(): Fetcher[] { data: f.data, formMethod: f.formMethod, formAction: f.formAction, - formData: f.formData, formEncType: f.formEncType, + formData: f.formData, + json: f.json, + text: f.text, " _hasFetcherDoneAnything ": f[" _hasFetcherDoneAnything "], }); addFetcherDeprecationWarnings(fetcher); @@ -1523,8 +1525,10 @@ export function useFetcher(): FetcherWithComponents< data: fetcherRR.data, formMethod: fetcherRR.formMethod, formAction: fetcherRR.formAction, - formData: fetcherRR.formData, formEncType: fetcherRR.formEncType, + formData: fetcherRR.formData, + json: fetcherRR.json, + text: fetcherRR.text, " _hasFetcherDoneAnything ": fetcherRR[" _hasFetcherDoneAnything "], }); let fetcherWithComponents = { @@ -1575,8 +1579,16 @@ function addFetcherDeprecationWarnings(fetcher: Fetcher) { function convertRouterFetcherToRemixFetcher( fetcherRR: Omit, "load" | "submit" | "Form"> ): Fetcher { - let { state, formMethod, formAction, formEncType, formData, data } = - fetcherRR; + let { + state, + formMethod, + formAction, + formEncType, + formData, + json, + text, + data, + } = fetcherRR; let isActionSubmission = formMethod != null && @@ -1589,8 +1601,10 @@ function convertRouterFetcherToRemixFetcher( type: "done", formMethod: undefined, formAction: undefined, - formData: undefined, formEncType: undefined, + formData: undefined, + json: undefined, + text: undefined, submission: undefined, data, }; @@ -1606,7 +1620,7 @@ function convertRouterFetcherToRemixFetcher( formMethod && formAction && formEncType && - formData + (formData || json !== undefined || text !== undefined) ) { if (isActionSubmission) { // Actively submitting to an action @@ -1614,14 +1628,21 @@ function convertRouterFetcherToRemixFetcher( state, type: "actionSubmission", formMethod: formMethod.toUpperCase() as ActionSubmission["method"], - formAction: formAction, - formEncType: formEncType, - formData: formData, + formAction, + formEncType, + formData, + json, + text, + // @ts-expect-error formData/json/text are mutually exclusive in the type, + // so TS can't be sure these meet that criteria, but as a straight + // assignment from the RR fetcher we know they will submission: { method: formMethod.toUpperCase() as ActionSubmission["method"], action: formAction, encType: formEncType, - formData: formData, + formData, + json, + text, key: "", }, data, @@ -1637,7 +1658,7 @@ function convertRouterFetcherToRemixFetcher( } if (state === "loading") { - if (formMethod && formAction && formEncType && formData) { + if (formMethod && formAction && formEncType) { if (isActionSubmission) { if (data) { // In a loading state but we have data - must be an actionReload @@ -1645,14 +1666,21 @@ function convertRouterFetcherToRemixFetcher( state, type: "actionReload", formMethod: formMethod.toUpperCase() as ActionSubmission["method"], - formAction: formAction, - formEncType: formEncType, - formData: formData, + formAction, + formEncType, + formData, + json, + text, + // @ts-expect-error formData/json/text are mutually exclusive in the type, + // so TS can't be sure these meet that criteria, but as a straight + // assignment from the RR fetcher we know they will submission: { method: formMethod.toUpperCase() as ActionSubmission["method"], action: formAction, encType: formEncType, - formData: formData, + formData, + json, + text, key: "", }, data, @@ -1663,14 +1691,21 @@ function convertRouterFetcherToRemixFetcher( state, type: "actionRedirect", formMethod: formMethod.toUpperCase() as ActionSubmission["method"], - formAction: formAction, - formEncType: formEncType, - formData: formData, + formAction, + formEncType, + formData, + json, + text, + // @ts-expect-error formData/json/text are mutually exclusive in the type, + // so TS can't be sure these meet that criteria, but as a straight + // assignment from the RR fetcher we know they will submission: { method: formMethod.toUpperCase() as ActionSubmission["method"], action: formAction, encType: formEncType, - formData: formData, + formData, + json, + text, key: "", }, data: undefined, @@ -1684,27 +1719,36 @@ function convertRouterFetcherToRemixFetcher( // will fix this bug. let url = new URL(formAction, window.location.origin); - // This typing override should be safe since this is only running for - // GET submissions and over in @remix-run/router we have an invariant - // if you have any non-string values in your FormData when we attempt - // to convert them to URLSearchParams - url.search = new URLSearchParams( - formData.entries() as unknown as [string, string][] - ).toString(); + if (formData) { + // This typing override should be safe since this is only running for + // GET submissions and over in @remix-run/router we have an invariant + // if you have any non-string values in your FormData when we attempt + // to convert them to URLSearchParams + url.search = new URLSearchParams( + formData.entries() as unknown as [string, string][] + ).toString(); + } // Actively "submitting" to a loader let fetcher: FetcherStates["SubmittingLoader"] = { state: "submitting", type: "loaderSubmission", formMethod: formMethod.toUpperCase() as LoaderSubmission["method"], - formAction: formAction, - formEncType: formEncType, - formData: formData, + formAction, + formEncType, + formData, + json, + text, + // @ts-expect-error formData/json/text are mutually exclusive in the type, + // so TS can't be sure these meet that criteria, but as a straight + // assignment from the RR fetcher we know they will submission: { method: formMethod.toUpperCase() as LoaderSubmission["method"], action: url.pathname + url.search, encType: formEncType, - formData: formData, + formData, + json, + text, key: "", }, data, @@ -1721,6 +1765,8 @@ function convertRouterFetcherToRemixFetcher( formMethod: undefined, formAction: undefined, formData: undefined, + json: undefined, + text: undefined, formEncType: undefined, submission: undefined, data, diff --git a/packages/remix-react/data.ts b/packages/remix-react/data.ts index 675b8413768..c2fde12f67e 100644 --- a/packages/remix-react/data.ts +++ b/packages/remix-react/data.ts @@ -40,12 +40,23 @@ export async function fetchData( init.method = request.method; let contentType = request.headers.get("Content-Type"); - init.body = - // Check between word boundaries instead of startsWith() due to the last - // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type - contentType && /\bapplication\/x-www-form-urlencoded\b/.test(contentType) - ? new URLSearchParams(await request.text()) - : await request.formData(); + + // Check between word boundaries instead of startsWith() due to the last + // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type + if (contentType && /\bapplication\/json\b/.test(contentType)) { + init.headers = { "Content-Type": contentType }; + init.body = JSON.stringify(await request.json()); + } else if (contentType && /\btext\/plain\b/.test(contentType)) { + init.headers = { "Content-Type": contentType }; + init.body = await request.text(); + } else if ( + contentType && + /\bapplication\/x-www-form-urlencoded\b/.test(contentType) + ) { + init.body = new URLSearchParams(await request.text()); + } else { + init.body = await request.formData(); + } } if (retry > 0) { diff --git a/packages/remix-react/package.json b/packages/remix-react/package.json index 29ab238282f..28de8216fba 100644 --- a/packages/remix-react/package.json +++ b/packages/remix-react/package.json @@ -16,8 +16,8 @@ "typings": "dist/index.d.ts", "module": "dist/esm/index.js", "dependencies": { - "@remix-run/router": "1.6.3", - "react-router-dom": "6.13.0" + "@remix-run/router": "1.7.0-pre.0", + "react-router-dom": "6.14.0-pre.0" }, "devDependencies": { "@remix-run/server-runtime": "1.17.1", diff --git a/packages/remix-react/transition.ts b/packages/remix-react/transition.ts index 713162e7767..ff937257562 100644 --- a/packages/remix-react/transition.ts +++ b/packages/remix-react/transition.ts @@ -75,6 +75,49 @@ export type TransitionStates = { export type Transition = TransitionStates[keyof TransitionStates]; +// Thanks https://github.com/sindresorhus/type-fest! +type JsonObject = { [Key in string]: JsonValue } & { + [Key in string]?: JsonValue | undefined; +}; +type JsonArray = JsonValue[] | readonly JsonValue[]; +type JsonPrimitive = string | number | boolean | null; +type JsonValue = JsonPrimitive | JsonObject | JsonArray; + +// Fetchers need a separate set of types to reflect the json/text submission +// support in react-router. We do not carry that into useTransition since +// it's deprecated +type FetcherSubmissionDataTypes = + | { + formData: FormData; + json: undefined; + text: undefined; + } + | { + formData: undefined; + json: JsonValue; + text: undefined; + } + | { + formData: undefined; + json: undefined; + text: string; + }; + +export type FetcherSubmission = { + action: string; + method: string; + encType: string; + key: string; +} & FetcherSubmissionDataTypes; + +export type FetcherActionSubmission = FetcherSubmission & { + method: "POST" | "PUT" | "PATCH" | "DELETE"; +}; + +export type FetcherLoaderSubmission = FetcherSubmission & { + method: "GET"; +}; + // TODO: keep data around on resubmission? export type FetcherStates = { Idle: { @@ -82,51 +125,49 @@ export type FetcherStates = { type: "init"; formMethod: undefined; formAction: undefined; - formData: undefined; formEncType: undefined; + formData: undefined; + json: undefined; + text: undefined; submission: undefined; data: undefined; }; SubmittingAction: { state: "submitting"; type: "actionSubmission"; - formMethod: ActionSubmission["method"]; + formMethod: FetcherActionSubmission["method"]; formAction: string; - formData: FormData; formEncType: string; - submission: ActionSubmission; + submission: FetcherActionSubmission; data: TData | undefined; - }; + } & FetcherSubmissionDataTypes; SubmittingLoader: { state: "submitting"; type: "loaderSubmission"; - formMethod: LoaderSubmission["method"]; + formMethod: FetcherLoaderSubmission["method"]; formAction: string; - formData: FormData; formEncType: string; - submission: LoaderSubmission; + submission: FetcherLoaderSubmission; data: TData | undefined; - }; + } & FetcherSubmissionDataTypes; ReloadingAction: { state: "loading"; type: "actionReload"; - formMethod: ActionSubmission["method"]; + formMethod: FetcherActionSubmission["method"]; formAction: string; - formData: FormData; formEncType: string; - submission: ActionSubmission; + submission: FetcherActionSubmission; data: TData; - }; + } & FetcherSubmissionDataTypes; LoadingActionRedirect: { state: "loading"; type: "actionRedirect"; - formMethod: ActionSubmission["method"]; + formMethod: FetcherActionSubmission["method"]; formAction: string; - formData: FormData; formEncType: string; - submission: ActionSubmission; + submission: FetcherActionSubmission; data: undefined; - }; + } & FetcherSubmissionDataTypes; Loading: { state: "loading"; type: "normalLoad"; @@ -134,6 +175,8 @@ export type FetcherStates = { formAction: undefined; formData: undefined; formEncType: undefined; + json: undefined; + text: undefined; submission: undefined; data: TData | undefined; }; @@ -142,8 +185,10 @@ export type FetcherStates = { type: "done"; formMethod: undefined; formAction: undefined; - formData: undefined; formEncType: undefined; + formData: undefined; + json: undefined; + text: undefined; submission: undefined; data: TData; }; @@ -165,7 +210,9 @@ export const IDLE_FETCHER: FetcherStates["Idle"] = { data: undefined, formMethod: undefined, formAction: undefined, - formData: undefined, formEncType: undefined, + formData: undefined, + json: undefined, + text: undefined, submission: undefined, }; diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json index 8d373eb52bc..222e27a0d57 100644 --- a/packages/remix-server-runtime/package.json +++ b/packages/remix-server-runtime/package.json @@ -16,7 +16,7 @@ "typings": "dist/index.d.ts", "module": "dist/esm/index.js", "dependencies": { - "@remix-run/router": "1.6.3", + "@remix-run/router": "1.7.0-pre.0", "@types/cookie": "^0.4.1", "@web3-storage/multipart-parser": "^1.0.0", "cookie": "^0.4.1", diff --git a/packages/remix-testing/package.json b/packages/remix-testing/package.json index 3d85b69276a..776a02d0424 100644 --- a/packages/remix-testing/package.json +++ b/packages/remix-testing/package.json @@ -18,8 +18,8 @@ "dependencies": { "@remix-run/node": "1.17.1", "@remix-run/react": "1.17.1", - "@remix-run/router": "1.6.3", - "react-router-dom": "6.13.0" + "@remix-run/router": "1.7.0-pre.0", + "react-router-dom": "6.14.0-pre.0" }, "devDependencies": { "@types/node": "^18.11.9", diff --git a/yarn.lock b/yarn.lock index a1ceb84ac7b..e226ee9eb70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2569,13 +2569,15 @@ tiny-glob "^0.2.9" tslib "^2.4.0" -"@playwright/test@^1.28.1": - version "1.28.1" - resolved "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz" - integrity sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ== +"@playwright/test@^1.35.1": + version "1.35.1" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz#a596b61e15b980716696f149cc7a2002f003580c" + integrity sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA== dependencies: "@types/node" "*" - playwright-core "1.28.1" + playwright-core "1.35.1" + optionalDependencies: + fsevents "2.3.2" "@remix-run/changelog-github@^0.0.5": version "0.0.5" @@ -2587,10 +2589,10 @@ "@changesets/types" "^5.0.0" dotenv "^8.1.0" -"@remix-run/router@1.6.3": - version "1.6.3" - resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz#8205baf6e17ef93be35bf62c37d2d594e9be0dad" - integrity sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q== +"@remix-run/router@1.7.0-pre.0": + version "1.7.0-pre.0" + resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0-pre.0.tgz#913e0803ac70291c47537eb20397e01532dbc1a2" + integrity sha512-i0sJFZG0glzgAt6fXfkSHk5rsKOM8DSSH063gKO5sKUHvfQJZn8av4d/BMrwymr3KaFLqkl9m3FXepbEk7XzqA== "@remix-run/web-blob@^3.0.3", "@remix-run/web-blob@^3.0.4": version "3.0.4" @@ -7167,7 +7169,7 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -10981,10 +10983,10 @@ pkg-types@^1.0.1: mlly "^1.0.0" pathe "^1.0.0" -playwright-core@1.28.1: - version "1.28.1" - resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz" - integrity sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag== +playwright-core@1.35.1: + version "1.35.1" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d" + integrity sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg== pointer-symbol@^1.0.0: version "1.0.0" @@ -11435,20 +11437,20 @@ react-refresh@^0.14.0: resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-router-dom@6.13.0: - version "6.13.0" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.13.0.tgz#6651f456bb2af42ef14f6880123b1f575539e81f" - integrity sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA== +react-router-dom@6.14.0-pre.0: + version "6.14.0-pre.0" + resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0-pre.0.tgz#8e106739c776f48c0d3710aae69bac42ddf09a3f" + integrity sha512-NndsLa0FHP8BDUePOnpiQqunDaoY1e0PljjqTQDxHMeCZqf1148S8hl54ZF6rXoA4EuACszVSrURIrAhZ++ljg== dependencies: - "@remix-run/router" "1.6.3" - react-router "6.13.0" + "@remix-run/router" "1.7.0-pre.0" + react-router "6.14.0-pre.0" -react-router@6.13.0: - version "6.13.0" - resolved "https://registry.npmjs.org/react-router/-/react-router-6.13.0.tgz#7e4427a271dae0cafbdb88c56ccbd9b1434ee93f" - integrity sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg== +react-router@6.14.0-pre.0: + version "6.14.0-pre.0" + resolved "https://registry.npmjs.org/react-router/-/react-router-6.14.0-pre.0.tgz#38d8ace45232b5d6cbcaf8b68d6af76a584f5e02" + integrity sha512-xNXcEDQ7pjKpw4ueFhmbksnVaRuyXng4OXXBg9RsM16FyJDSUUKXruygjrDiVeyGeAGjIiDDNUX/G6EwF3aS9w== dependencies: - "@remix-run/router" "1.6.3" + "@remix-run/router" "1.7.0-pre.0" react@^18.2.0: version "18.2.0"