diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ea80f1516..8fa67a99a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -9,6 +9,7 @@ declare module 'axios' { export type DefaultInertiaConfig = { errorValueType: string + sharedPageProps: PageProps } /** * Designed to allow overriding of some core types using TypeScript @@ -20,6 +21,10 @@ export type DefaultInertiaConfig = { * declare module '@inertiajs/core' { * export interface InertiaConfig { * errorValueType: string[] + * sharedPageProps: { + * auth: { user: User | null } + * flash: { success?: string; error?: string } + * } * } * } * ``` @@ -29,6 +34,7 @@ export type InertiaConfigFor = Key exten ? InertiaConfig[Key] : DefaultInertiaConfig[Key] export type ErrorValue = InertiaConfigFor<'errorValueType'> +export type SharedPageProps = InertiaConfigFor<'sharedPageProps'> export type Errors = Record export type ErrorBag = Record diff --git a/packages/react/src/usePage.ts b/packages/react/src/usePage.ts index b587b25bb..53abbdc80 100755 --- a/packages/react/src/usePage.ts +++ b/packages/react/src/usePage.ts @@ -1,13 +1,13 @@ -import { Page, PageProps } from '@inertiajs/core' +import { Page, PageProps, SharedPageProps } from '@inertiajs/core' import { useContext } from 'react' import PageContext from './PageContext' -export default function usePage(): Page { +export default function usePage(): Page { const page = useContext(PageContext) if (!page) { throw new Error('usePage must be used within the Inertia component') } - return page + return page as Page } diff --git a/packages/react/test-app/Pages/TypeScriptProps.tsx b/packages/react/test-app/Pages/TypeScriptProps.tsx new file mode 100644 index 000000000..9d2fa7c19 --- /dev/null +++ b/packages/react/test-app/Pages/TypeScriptProps.tsx @@ -0,0 +1,35 @@ +// This component is used for checking the TypeScript implementation; there is no Playwright test depending on it. +import { usePage } from '@inertiajs/react' + +declare module '@inertiajs/core' { + export interface InertiaConfig { + sharedPageProps: { + flash: { success?: string; error?: string } + } + } +} + +type PageProps = { + posts: { id: number; title: string }[] +} + +export default function TypeScriptProps() { + const page = usePage() + + const error = page.props.flash.error + const postTitles = page.props.posts.map((post) => post.title) + + // @ts-expect-error - 'message' does not exist on flash + const flashMessage = page.props.flash.message + // @ts-expect-error - 'users' does not exist on page props + const userNames = page.props.users.map((user) => user.name) + + console.log({ + error, + postTitles, + flashMessage, + userNames, + }) + + return null +} diff --git a/packages/svelte/src/index.ts b/packages/svelte/src/index.ts index 48f847625..3db981f16 100644 --- a/packages/svelte/src/index.ts +++ b/packages/svelte/src/index.ts @@ -6,7 +6,7 @@ export { default as Link } from './components/Link.svelte' export { default as WhenVisible } from './components/WhenVisible.svelte' export { default as createInertiaApp } from './createInertiaApp' export { default as inertia } from './link' -export { default as page } from './page' +export { default as page, usePage } from './page' export { type ResolvedComponent } from './types' export { default as useForm, type InertiaForm, type InertiaFormProps } from './useForm' export { default as usePoll } from './usePoll' diff --git a/packages/svelte/src/page.ts b/packages/svelte/src/page.ts index 9480306b7..b4c2a7913 100644 --- a/packages/svelte/src/page.ts +++ b/packages/svelte/src/page.ts @@ -1,8 +1,8 @@ -import { type Page } from '@inertiajs/core' -import { writable } from 'svelte/store' +import { type Page, type PageProps, type SharedPageProps } from '@inertiajs/core' +import { derived, writable, type Readable } from 'svelte/store' -type SveltePage = Omit & { - props: Page['props'] & { +type SveltePage = Omit, 'props'> & { + props: Page['props'] & { [key: string]: any } } @@ -12,3 +12,7 @@ const { set, subscribe } = writable() export const setPage = set export default { subscribe } + +export function usePage(): Readable> { + return derived({ subscribe }, ($page) => $page as SveltePage) +} diff --git a/packages/svelte/test-app/Pages/Svelte/PropsAndPageStore.svelte b/packages/svelte/test-app/Pages/Svelte/PropsAndPageStore.svelte index 482295a99..ae592ebdf 100644 --- a/packages/svelte/test-app/Pages/Svelte/PropsAndPageStore.svelte +++ b/packages/svelte/test-app/Pages/Svelte/PropsAndPageStore.svelte @@ -1,29 +1,34 @@
@@ -31,6 +36,7 @@

foo prop is {foo}

$page.props.foo is {$page.props.foo}

pageProps.foo is {pageProps.foo}

+

$sveltePage.props.foo is {$sveltePage.props.foo}

Bar Baz diff --git a/packages/svelte/test-app/Pages/TypeScriptProps.svelte b/packages/svelte/test-app/Pages/TypeScriptProps.svelte new file mode 100644 index 000000000..acdd894c4 --- /dev/null +++ b/packages/svelte/test-app/Pages/TypeScriptProps.svelte @@ -0,0 +1,35 @@ + + + diff --git a/packages/vue3/src/app.ts b/packages/vue3/src/app.ts index 7ae2e79c9..c08483f43 100755 --- a/packages/vue3/src/app.ts +++ b/packages/vue3/src/app.ts @@ -5,6 +5,7 @@ import { Page, PageProps, router, + SharedPageProps, } from '@inertiajs/core' import { computed, @@ -133,7 +134,7 @@ export const plugin: Plugin = { }, } -export function usePage(): Page { +export function usePage(): Page { return reactive({ props: computed(() => page.value?.props), url: computed(() => page.value?.url), @@ -147,5 +148,5 @@ export function usePage(): Page { matchPropsOn: computed(() => page.value?.matchPropsOn), rememberedState: computed(() => page.value?.rememberedState), encryptHistory: computed(() => page.value?.encryptHistory), - }) + }) as Page } diff --git a/packages/vue3/src/types.ts b/packages/vue3/src/types.ts index 6c9237411..2295b1e83 100644 --- a/packages/vue3/src/types.ts +++ b/packages/vue3/src/types.ts @@ -1,4 +1,4 @@ -import { createHeadManager, Page, PageHandler, router } from '@inertiajs/core' +import { createHeadManager, Page, PageHandler, PageProps, router, SharedPageProps } from '@inertiajs/core' import { ComponentPublicInstance } from 'vue' import useForm from './useForm' @@ -15,7 +15,7 @@ declare module '@inertiajs/core' { declare module 'vue' { export interface ComponentCustomProperties { $inertia: typeof router - $page: Page + $page: Page $headManager: ReturnType } diff --git a/packages/vue3/test-app/Pages/TypeScriptProps.vue b/packages/vue3/test-app/Pages/TypeScriptProps.vue new file mode 100644 index 000000000..d428b2e75 --- /dev/null +++ b/packages/vue3/test-app/Pages/TypeScriptProps.vue @@ -0,0 +1,40 @@ + + + diff --git a/tests/svelte.spec.ts b/tests/svelte.spec.ts index 9682a4a7b..fba22eab0 100644 --- a/tests/svelte.spec.ts +++ b/tests/svelte.spec.ts @@ -13,14 +13,19 @@ test('props and page store are in sync', async ({ page }) => { await expect(page.getByText('foo prop is default')).toBeVisible() await expect(page.getByText('$page.props.foo is default')).toBeVisible() await expect(page.getByText('pageProps.foo is default')).toBeVisible() - await expect(consoleMessages.messages).toHaveLength(7) + await expect(page.getByText('$sveltePage.props.foo is default')).toBeVisible() + await expect(consoleMessages.messages).toHaveLength(11) await expect(consoleMessages.messages[0]).toBe('[script] foo prop is default') await expect(consoleMessages.messages[1]).toBe('[script] $page.props.foo is default') - await expect(consoleMessages.messages[2]).toBe('[reactive expression] foo prop is default') - await expect(consoleMessages.messages[3]).toBe('[reactive expression] $page.props.foo is default') - await expect(consoleMessages.messages[4]).toBe('[onMount] foo prop is default') - await expect(consoleMessages.messages[5]).toBe('[onMount] $page.props.foo is default') - await expect(consoleMessages.messages[6]).toBe('[reactive expression] $page.props.foo is default') + await expect(consoleMessages.messages[2]).toBe('[script] $sveltePage.props.foo is default') + await expect(consoleMessages.messages[3]).toBe('[reactive expression] foo prop is default') + await expect(consoleMessages.messages[4]).toBe('[reactive expression] $page.props.foo is default') + await expect(consoleMessages.messages[5]).toBe('[reactive expression] $sveltePage.props.foo is default') + await expect(consoleMessages.messages[6]).toBe('[onMount] foo prop is default') + await expect(consoleMessages.messages[7]).toBe('[onMount] $page.props.foo is default') + await expect(consoleMessages.messages[8]).toBe('[onMount] $sveltePage.props.foo is default') + await expect(consoleMessages.messages[9]).toBe('[reactive expression] $page.props.foo is default') + await expect(consoleMessages.messages[10]).toBe('[reactive expression] $sveltePage.props.foo is default') await expect(await page.locator('#input').inputValue()).toEqual('default') consoleMessages.messages = [] @@ -28,14 +33,20 @@ test('props and page store are in sync', async ({ page }) => { await expect(page.getByText('foo prop is bar')).toBeVisible() await expect(page.getByText('$page.props.foo is bar')).toBeVisible() - await expect(consoleMessages.messages).toHaveLength(7) + await expect(page.getByText('pageProps.foo is bar')).toBeVisible() + await expect(page.getByText('$sveltePage.props.foo is bar')).toBeVisible() + await expect(consoleMessages.messages).toHaveLength(11) await expect(consoleMessages.messages[0]).toBe('[reactive expression] $page.props.foo is bar') - await expect(consoleMessages.messages[1]).toBe('[script] foo prop is bar') - await expect(consoleMessages.messages[2]).toBe('[script] $page.props.foo is bar') - await expect(consoleMessages.messages[3]).toBe('[reactive expression] foo prop is bar') - await expect(consoleMessages.messages[4]).toBe('[reactive expression] $page.props.foo is bar') - await expect(consoleMessages.messages[5]).toBe('[onMount] foo prop is bar') - await expect(consoleMessages.messages[6]).toBe('[onMount] $page.props.foo is bar') + await expect(consoleMessages.messages[1]).toBe('[reactive expression] $sveltePage.props.foo is bar') + await expect(consoleMessages.messages[2]).toBe('[script] foo prop is bar') + await expect(consoleMessages.messages[3]).toBe('[script] $page.props.foo is bar') + await expect(consoleMessages.messages[4]).toBe('[script] $sveltePage.props.foo is bar') + await expect(consoleMessages.messages[5]).toBe('[reactive expression] foo prop is bar') + await expect(consoleMessages.messages[6]).toBe('[reactive expression] $page.props.foo is bar') + await expect(consoleMessages.messages[7]).toBe('[reactive expression] $sveltePage.props.foo is bar') + await expect(consoleMessages.messages[8]).toBe('[onMount] foo prop is bar') + await expect(consoleMessages.messages[9]).toBe('[onMount] $page.props.foo is bar') + await expect(consoleMessages.messages[10]).toBe('[onMount] $sveltePage.props.foo is bar') await expect(await page.locator('#input').inputValue()).toEqual('bar') consoleMessages.messages = [] @@ -43,14 +54,20 @@ test('props and page store are in sync', async ({ page }) => { await expect(page.getByText('foo prop is baz')).toBeVisible() await expect(page.getByText('$page.props.foo is baz')).toBeVisible() - await expect(consoleMessages.messages).toHaveLength(7) + await expect(page.getByText('pageProps.foo is baz')).toBeVisible() + await expect(page.getByText('$sveltePage.props.foo is baz')).toBeVisible() + await expect(consoleMessages.messages).toHaveLength(11) await expect(consoleMessages.messages[0]).toBe('[reactive expression] $page.props.foo is baz') - await expect(consoleMessages.messages[1]).toBe('[script] foo prop is baz') - await expect(consoleMessages.messages[2]).toBe('[script] $page.props.foo is baz') - await expect(consoleMessages.messages[3]).toBe('[reactive expression] foo prop is baz') - await expect(consoleMessages.messages[4]).toBe('[reactive expression] $page.props.foo is baz') - await expect(consoleMessages.messages[5]).toBe('[onMount] foo prop is baz') - await expect(consoleMessages.messages[6]).toBe('[onMount] $page.props.foo is baz') + await expect(consoleMessages.messages[1]).toBe('[reactive expression] $sveltePage.props.foo is baz') + await expect(consoleMessages.messages[2]).toBe('[script] foo prop is baz') + await expect(consoleMessages.messages[3]).toBe('[script] $page.props.foo is baz') + await expect(consoleMessages.messages[4]).toBe('[script] $sveltePage.props.foo is baz') + await expect(consoleMessages.messages[5]).toBe('[reactive expression] foo prop is baz') + await expect(consoleMessages.messages[6]).toBe('[reactive expression] $page.props.foo is baz') + await expect(consoleMessages.messages[7]).toBe('[reactive expression] $sveltePage.props.foo is baz') + await expect(consoleMessages.messages[8]).toBe('[onMount] foo prop is baz') + await expect(consoleMessages.messages[9]).toBe('[onMount] $page.props.foo is baz') + await expect(consoleMessages.messages[10]).toBe('[onMount] $sveltePage.props.foo is baz') await expect(await page.locator('#input').inputValue()).toEqual('baz') await page.getByRole('link', { name: 'Home' }).click() @@ -62,11 +79,16 @@ test('props and page store are in sync', async ({ page }) => { await expect(page.getByText('foo prop is baz')).toBeVisible() await expect(page.getByText('$page.props.foo is baz')).toBeVisible() - await expect(consoleMessages.messages).toHaveLength(6) + await expect(page.getByText('pageProps.foo is baz')).toBeVisible() + await expect(page.getByText('$sveltePage.props.foo is baz')).toBeVisible() + await expect(consoleMessages.messages).toHaveLength(9) await expect(consoleMessages.messages[0]).toBe('[script] foo prop is baz') await expect(consoleMessages.messages[1]).toBe('[script] $page.props.foo is baz') - await expect(consoleMessages.messages[2]).toBe('[reactive expression] foo prop is baz') - await expect(consoleMessages.messages[3]).toBe('[reactive expression] $page.props.foo is baz') - await expect(consoleMessages.messages[4]).toBe('[onMount] foo prop is baz') - await expect(consoleMessages.messages[5]).toBe('[onMount] $page.props.foo is baz') + await expect(consoleMessages.messages[2]).toBe('[script] $sveltePage.props.foo is baz') + await expect(consoleMessages.messages[3]).toBe('[reactive expression] foo prop is baz') + await expect(consoleMessages.messages[4]).toBe('[reactive expression] $page.props.foo is baz') + await expect(consoleMessages.messages[5]).toBe('[reactive expression] $sveltePage.props.foo is baz') + await expect(consoleMessages.messages[6]).toBe('[onMount] foo prop is baz') + await expect(consoleMessages.messages[7]).toBe('[onMount] $page.props.foo is baz') + await expect(consoleMessages.messages[8]).toBe('[onMount] $sveltePage.props.foo is baz') })