Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Frames #1565

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
67e39d0
introduce "frameId" to router.visit
buhrmi May 24, 2023
adc01de
cleanup
buhrmi May 24, 2023
42ee7a3
rename frameId to target
buhrmi May 25, 2023
25d9db2
move target into page object
buhrmi May 25, 2023
8dabf42
encapsulate link clicks and form submissions within frame
buhrmi May 25, 2023
469d923
fix typescript errors
buhrmi May 25, 2023
90571dd
preserve frames through navigation
buhrmi Jun 23, 2023
1212783
allow controller to override frame
buhrmi Sep 12, 2023
fb7f864
send framesrc to controller to redirect back to it
buhrmi Sep 12, 2023
efda93a
create frame context
PedroAugustoRamalhoDuarte Dec 16, 2023
d203917
create Frame component for React Adapter
PedroAugustoRamalhoDuarte Dec 16, 2023
fa6d084
fixes router target_frame from flash
PedroAugustoRamalhoDuarte Dec 16, 2023
ed74ff7
Merge pull request #1 from PedroAugustoRamalhoDuarte/master
buhrmi Dec 17, 2023
68fa8f6
include component CSS in head, fixed inertiajs/inertia#1760
buhrmi Dec 26, 2023
98168e7
Merge branch 'inertiajs:master' into frames
buhrmi Jan 17, 2024
1e3517d
Merge pull request #2 from buhrmi/master
buhrmi Jan 17, 2024
009e183
Merge branch 'inertiajs:master' into frames
buhrmi Apr 4, 2024
78b154a
Update readme.md
buhrmi Apr 4, 2024
2b94200
readme
buhrmi Apr 4, 2024
e2c9903
update readme
buhrmi Apr 4, 2024
f55e084
Update readme.md
buhrmi Apr 4, 2024
d183163
Update readme.md
buhrmi Apr 4, 2024
2d2af4f
Update readme.md
buhrmi Apr 4, 2024
16aaf0a
Update readme.md
buhrmi Apr 4, 2024
2d2f50d
use x-inertia-frame header
buhrmi Apr 4, 2024
0b8dc2a
Update readme.md
buhrmi Apr 4, 2024
5948659
Update readme.md
buhrmi Apr 4, 2024
efe9692
Update readme.md
buhrmi Apr 4, 2024
09779f3
Update readme.md
buhrmi Apr 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions packages/core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
GlobalEventNames,
GlobalEventResult,
LocationVisit,
Method,
Page,
PageHandler,
PageResolver,
Expand Down Expand Up @@ -89,6 +90,21 @@ export class Router {
protected setupEventListeners(): void {
window.addEventListener('popstate', this.handlePopstateEvent.bind(this))
document.addEventListener('scroll', debounce(this.handleScrollEvent.bind(this), 100), true)
document.addEventListener('click', (event) => {
const target = event.target as Element
const anchorElement = target.closest('a')
const frameId = (target.closest('[data-inertia-frame-id]') as HTMLElement)?.dataset.inertiaFrameId
if (!anchorElement || anchorElement.rel == 'external' || anchorElement.target == '_blank') return

if (anchorElement.href && anchorElement.href.startsWith(location.origin)) {
event.preventDefault()
event.stopPropagation()
this.visit(anchorElement.href, {
method: anchorElement.dataset['method'] as Method,
target: anchorElement.dataset['target'] || frameId,
})
}
})
}

protected scrollRegions(): NodeListOf<Element> {
Expand Down Expand Up @@ -263,6 +279,7 @@ export class Router {
headers = {},
errorBag = '',
forceFormData = false,
target = null,
onCancelToken = () => {},
onBefore = () => {},
onStart = () => {},
Expand Down Expand Up @@ -296,6 +313,7 @@ export class Router {
only,
headers,
errorBag,
target,
forceFormData,
queryStringArrayFormat,
cancelled: false,
Expand Down Expand Up @@ -373,9 +391,16 @@ export class Router {
}

const pageResponse: Page = response.data

// if an X-Inertia-Frame header is present, use its value to override target frame id
if (response.headers['x-inertia-frame']) {
target = response.headers['x-inertia-frame']
}

if (only.length && pageResponse.component === this.page.component) {
pageResponse.props = { ...this.page.props, ...pageResponse.props }
}

preserveScroll = this.resolvePreserveOption(preserveScroll, pageResponse) as boolean
preserveState = this.resolvePreserveOption(preserveState, pageResponse)
if (preserveState && window.history.state?.rememberedState && pageResponse.component === this.page.component) {
Expand All @@ -387,17 +412,17 @@ export class Router {
responseUrl.hash = requestUrl.hash
pageResponse.url = responseUrl.href
}
return this.setPage(pageResponse, { visitId, replace, preserveScroll, preserveState })
return this.setPage(pageResponse, { target, visitId, replace, preserveScroll, preserveState })
})
.then(() => {
const errors = this.page.props.errors || {}
.then((page: Page) => {
const errors = page.props.errors || {}
if (Object.keys(errors).length > 0) {
const scopedErrors = errorBag ? (errors[errorBag] ? errors[errorBag] : {}) : errors
fireErrorEvent(scopedErrors)
return onError(scopedErrors)
}
fireSuccessEvent(this.page)
return onSuccess(this.page)
fireSuccessEvent(page)
return onSuccess(page)
})
.catch((error) => {
if (this.isInertiaResponse(error.response)) {
Expand Down Expand Up @@ -442,19 +467,27 @@ export class Router {
replace = false,
preserveScroll = false,
preserveState = false,
target = null,
}: {
visitId?: VisitId
replace?: boolean
preserveScroll?: PreserveStateOption
preserveState?: PreserveStateOption
target?: string | null
} = {},
): Promise<void> {
): Promise<Page> {
return Promise.resolve(this.resolveComponent(page.component)).then((component) => {
if (visitId === this.visitId) {
page.scrollRegions = page.scrollRegions || []
page.rememberedState = page.rememberedState || {}
replace = replace || hrefToUrl(page.url).href === window.location.href
replace ? this.replaceState(page) : this.pushState(page)
if (!target || target === '_top' || target === '_parent' || target === 'main') {
replace = replace || hrefToUrl(page.url).href === window.location.href
replace ? this.replaceState(page) : this.pushState(page)
}
else {
page.target = target
}

this.swapComponent({ component, page, preserveState }).then(() => {
if (!preserveScroll) {
this.resetScrollPositions()
Expand All @@ -464,6 +497,7 @@ export class Router {
}
})
}
return page
})
}

Expand Down
12 changes: 7 additions & 5 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ export interface PageProps {
export interface Page<SharedProps extends PageProps = PageProps> {
component: string
props: PageProps &
SharedProps & {
errors: Errors & ErrorBag
}
SharedProps & {
errors: Errors & ErrorBag
}
url: string
target?: string | null
version: string | null

/** @internal */
scrollRegions: Array<{ top: number; left: number }>
/** @internal */
Expand All @@ -52,7 +53,7 @@ export type PageHandler = ({
}: {
component: Component
page: Page
preserveState: PreserveStateOption
preserveState: PreserveStateOption,
}) => Promise<unknown>

export type PreserveStateOption = boolean | string | ((page: Page) => boolean)
Expand All @@ -73,6 +74,7 @@ export type Visit = {
headers: Record<string, string>
errorBag: string | null
forceFormData: boolean
target: string | null
queryStringArrayFormat: 'indices' | 'brackets'
}

Expand Down
40 changes: 28 additions & 12 deletions packages/react/src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createHeadManager, router } from '@inertiajs/core'
import { createElement, useEffect, useMemo, useState } from 'react'
import HeadContext from './HeadContext'
import PageContext from './PageContext'
import FrameContext from "./FrameContext";

export default function App({
children,
Expand All @@ -14,27 +15,38 @@ export default function App({
const [current, setCurrent] = useState({
component: initialComponent || null,
page: initialPage,
frames: null,
key: null,
})

const headManager = useMemo(() => {
return createHeadManager(
typeof window === 'undefined',
titleCallback || ((title) => title),
onHeadUpdate || (() => {}),
onHeadUpdate || (() => {
}),
)
}, [])

useEffect(() => {
router.init({
initialPage,
resolveComponent,
swapComponent: async ({ component, page, preserveState }) => {
setCurrent((current) => ({
component,
page,
key: preserveState ? current.key : Date.now(),
}))
swapComponent: async ({component, page, preserveState}) => {
const targetFrame = page.target;
if (targetFrame) {
setCurrent((current) => ({
...current,
frames: {...current.frames, [targetFrame]: {component, props: page.props}},
}))
} else {
setCurrent((current) => ({
component,
page,
frames: current.frames,
key: preserveState ? current.key : Date.now(),
}))
}
},
})

Expand Down Expand Up @@ -74,11 +86,15 @@ export default function App({
createElement(
PageContext.Provider,
{ value: current.page },
renderChildren({
Component: current.component,
key: current.key,
props: current.page.props,
}),
createElement(
FrameContext.Provider,
{ value: current.frames },
renderChildren({
Component: current.component,
key: current.key,
props: current.page.props,
}),
)
),
)
}
Expand Down
24 changes: 24 additions & 0 deletions packages/react/src/Frame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {useEffect, createElement} from 'react';
import {router} from './index';
import useFrame from "./useFrame";

const Frame = ({src, id = Math.random(), children}) => {
const frames = useFrame()
const component = frames?.[id] && frames[id].component;

useEffect(() => {
// inertia.set('frame-id', id);
// inertia.set('frame-src', src);

router.visit(src, {
target: id.toString(),
});
}, []);

return createElement("div",
{'data-inertia-frame-id': id},
component ? createElement(component, frames[id].props) : children)
};

Frame.displayName = 'InertiaFrame';
export default Frame;
6 changes: 6 additions & 0 deletions packages/react/src/FrameContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createContext } from 'react'

const frameContext = createContext(undefined)
frameContext.displayName = 'InertiaFrameContext'

export default frameContext
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as Link, InertiaLinkProps } from './Link'
export { default as useForm } from './useForm'
export { default as usePage } from './usePage'
export { default as useRemember } from './useRemember'
export { default as Frame } from './Frame'
6 changes: 6 additions & 0 deletions packages/react/src/useFrame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useContext } from 'react'
import FrameContext from './FrameContext'

export default function useFrame(): any {
return useContext(FrameContext)
}
26 changes: 26 additions & 0 deletions packages/svelte/src/Frame.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script>
import { onMount } from 'svelte'
import Render, { h } from './Render.svelte'
import store from './store'
import { router } from '@inertiajs/svelte'
import { setContext } from 'svelte';

export let src

export let id = Math.random()
setContext('inertia:frame-id', id)
$: setContext('inertia:frame-src', src)

$: components = $store.frames?.[id] && h($store.frames[id].component.default, $store.frames[id].props)

onMount(() => {
router.visit(src, {
target: id
})
})

</script>

<div data-inertia-frame-id={id}>
<Render {...components} />
</div>
15 changes: 12 additions & 3 deletions packages/svelte/src/createInertiaApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro
initialPage,
resolveComponent,
swapComponent: async ({ component, page, preserveState }) => {
store.update((current) => ({
const targetFrame = page.target
if (targetFrame) store.update((current) => ({
...current,
frames: { ...current.frames, [targetFrame]: {component, props: page.props} }
}))
else store.update((current) => ({
component,
page,
frames: current.frames,
key: preserveState ? current.key : Date.now(),
}))
},
Expand All @@ -44,11 +50,14 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro
}

if (isServer) {
const { html, head } = SSR.render({ id, initialPage })
const { html, head, css } = SSR.render({ id, initialPage })

return {
body: html,
head: [head],
head: [
head,
`<style data-vite-css>${css.code}</style>`,
],
}
}
}
1 change: 1 addition & 0 deletions packages/svelte/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { router } from '@inertiajs/core'
export { default as createInertiaApp } from './createInertiaApp'
export { default as inertia } from './link'
export { default as Link } from './Link.svelte'
export { default as Frame } from './Frame.svelte'
export { default as page } from './page'
export { default as remember } from './remember'
export { default as useForm } from './useForm'
1 change: 1 addition & 0 deletions packages/svelte/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const store = writable({
layout: [],
page: {},
key: null,
frames: {}
})

export default store
5 changes: 5 additions & 0 deletions packages/svelte/src/useForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { router } from '@inertiajs/core'
import isEqual from 'lodash.isequal'
import cloneDeep from 'lodash.clonedeep'
import { writable } from 'svelte/store'
import { getContext } from 'svelte'

function useForm(...args) {
const rememberKey = typeof args[0] === 'string' ? args[0] : null
const data = (typeof args[0] === 'string' ? args[1] : args[0]) || {}
const restored = rememberKey ? router.restore(rememberKey) : null
const frameId = getContext('inertia:frame-id')
const frameSrc = getContext('inertia:frame-src')
let defaults = cloneDeep(data)
let cancelToken = null
let recentlySuccessfulTimeoutId = null
Expand Down Expand Up @@ -89,8 +92,10 @@ function useForm(...args) {
},
submit(method, url, options = {}) {
const data = transform(this.data())
if (frameSrc) data.frameSrc = frameSrc
const _options = {
...options,
target: typeof(options.target) !== 'undefined' ? options.target : frameId,
onCancelToken: (token) => {
cancelToken = token

Expand Down
Loading
Loading