From 67e39d0e2e9f01737f93697878986745a2e5e72a Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Wed, 24 May 2023 17:37:12 +0200 Subject: [PATCH 01/25] introduce "frameId" to router.visit --- packages/core/src/router.ts | 8 ++++++-- packages/core/src/types.ts | 6 +++++- packages/svelte/src/Frame.svelte | 20 ++++++++++++++++++++ packages/svelte/src/createInertiaApp.js | 10 ++++++++-- packages/svelte/src/index.js | 1 + packages/svelte/src/store.js | 1 + 6 files changed, 41 insertions(+), 5 deletions(-) create mode 100755 packages/svelte/src/Frame.svelte diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 2851b2aa6..5dfa8f331 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -263,6 +263,7 @@ export class Router { headers = {}, errorBag = '', forceFormData = false, + frameId = null, onCancelToken = () => {}, onBefore = () => {}, onStart = () => {}, @@ -296,6 +297,7 @@ export class Router { only, headers, errorBag, + frameId, forceFormData, queryStringArrayFormat, cancelled: false, @@ -387,7 +389,7 @@ export class Router { responseUrl.hash = requestUrl.hash pageResponse.url = responseUrl.href } - return this.setPage(pageResponse, { visitId, replace, preserveScroll, preserveState }) + return this.setPage(pageResponse, { frameId, visitId, replace, preserveScroll, preserveState }) }) .then(() => { const errors = this.page.props.errors || {} @@ -442,11 +444,13 @@ export class Router { replace = false, preserveScroll = false, preserveState = false, + frameId = null, }: { visitId?: VisitId replace?: boolean preserveScroll?: PreserveStateOption preserveState?: PreserveStateOption + frameId?: string | null } = {}, ): Promise { return Promise.resolve(this.resolveComponent(page.component)).then((component) => { @@ -455,7 +459,7 @@ export class Router { page.rememberedState = page.rememberedState || {} replace = replace || hrefToUrl(page.url).href === window.location.href replace ? this.replaceState(page) : this.pushState(page) - this.swapComponent({ component, page, preserveState }).then(() => { + this.swapComponent({ frameId, component, page, preserveState }).then(() => { if (!preserveScroll) { this.resetScrollPositions() } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2fc1d6025..90899b58d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -29,6 +29,7 @@ export interface PageProps { } export interface Page { + id: string | null // null for the root/parent frame component: string props: PageProps & SharedProps & { @@ -49,10 +50,12 @@ export type PageHandler = ({ component, page, preserveState, + frameId, }: { component: Component page: Page - preserveState: PreserveStateOption + preserveState: PreserveStateOption, + frameId: string | null }) => Promise export type PreserveStateOption = boolean | string | ((page: Page) => boolean) @@ -73,6 +76,7 @@ export type Visit = { headers: Record errorBag: string | null forceFormData: boolean + frameId: string | null queryStringArrayFormat: 'indices' | 'brackets' } diff --git a/packages/svelte/src/Frame.svelte b/packages/svelte/src/Frame.svelte new file mode 100755 index 000000000..cdc6e51b0 --- /dev/null +++ b/packages/svelte/src/Frame.svelte @@ -0,0 +1,20 @@ + + + diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index facb5e625..a0bef27c9 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -20,8 +20,14 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro router.init({ initialPage, resolveComponent, - swapComponent: async ({ component, page, preserveState }) => { - store.update((current) => ({ + swapComponent: async ({ frameId, component, page, preserveState }) => { + console.log('swapComponent') + console.log(frameId, component, page, preserveState) + if (frameId) store.update((current) => ({ + ...current, + frames: { ...current.frames, [frameId]: {component, props: page.props} } + })) + else store.update((current) => ({ component, page, key: preserveState ? current.key : Date.now(), diff --git a/packages/svelte/src/index.js b/packages/svelte/src/index.js index 0a886940b..dd6c05461 100755 --- a/packages/svelte/src/index.js +++ b/packages/svelte/src/index.js @@ -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' diff --git a/packages/svelte/src/store.js b/packages/svelte/src/store.js index abe2cdeae..489bb1a8d 100755 --- a/packages/svelte/src/store.js +++ b/packages/svelte/src/store.js @@ -5,6 +5,7 @@ const store = writable({ layout: [], page: {}, key: null, + frames: {} }) export default store From adc01deb8ca1e85c8070f13ba15e1473841e70c3 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Wed, 24 May 2023 18:10:02 +0200 Subject: [PATCH 02/25] cleanup --- packages/core/src/types.ts | 1 - packages/svelte/src/createInertiaApp.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 90899b58d..9fd962ff7 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -29,7 +29,6 @@ export interface PageProps { } export interface Page { - id: string | null // null for the root/parent frame component: string props: PageProps & SharedProps & { diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index a0bef27c9..c291d19f3 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -21,8 +21,6 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro initialPage, resolveComponent, swapComponent: async ({ frameId, component, page, preserveState }) => { - console.log('swapComponent') - console.log(frameId, component, page, preserveState) if (frameId) store.update((current) => ({ ...current, frames: { ...current.frames, [frameId]: {component, props: page.props} } From 42ee7a3a050695e9f1ea4b84fdbae1daba0fdc24 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 25 May 2023 10:01:37 +0200 Subject: [PATCH 03/25] rename frameId to target --- packages/core/src/router.ts | 18 ++++++++++-------- packages/core/src/types.ts | 6 +++--- packages/svelte/src/Frame.svelte | 2 +- packages/svelte/src/createInertiaApp.js | 6 +++--- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 5dfa8f331..4d6a83ed0 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -263,7 +263,7 @@ export class Router { headers = {}, errorBag = '', forceFormData = false, - frameId = null, + target = null, onCancelToken = () => {}, onBefore = () => {}, onStart = () => {}, @@ -297,7 +297,7 @@ export class Router { only, headers, errorBag, - frameId, + target, forceFormData, queryStringArrayFormat, cancelled: false, @@ -389,7 +389,7 @@ export class Router { responseUrl.hash = requestUrl.hash pageResponse.url = responseUrl.href } - return this.setPage(pageResponse, { frameId, visitId, replace, preserveScroll, preserveState }) + return this.setPage(pageResponse, { target, visitId, replace, preserveScroll, preserveState }) }) .then(() => { const errors = this.page.props.errors || {} @@ -444,22 +444,24 @@ export class Router { replace = false, preserveScroll = false, preserveState = false, - frameId = null, + target = null, }: { visitId?: VisitId replace?: boolean preserveScroll?: PreserveStateOption preserveState?: PreserveStateOption - frameId?: string | null + target?: string | null } = {}, ): Promise { 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) - this.swapComponent({ frameId, component, page, preserveState }).then(() => { + if (!target) { + replace = replace || hrefToUrl(page.url).href === window.location.href + replace ? this.replaceState(page) : this.pushState(page) + } + this.swapComponent({ target, component, page, preserveState }).then(() => { if (!preserveScroll) { this.resetScrollPositions() } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9fd962ff7..29d59a296 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -49,12 +49,12 @@ export type PageHandler = ({ component, page, preserveState, - frameId, + target, }: { component: Component page: Page preserveState: PreserveStateOption, - frameId: string | null + target?: string | null }) => Promise export type PreserveStateOption = boolean | string | ((page: Page) => boolean) @@ -75,7 +75,7 @@ export type Visit = { headers: Record errorBag: string | null forceFormData: boolean - frameId: string | null + target: string | null queryStringArrayFormat: 'indices' | 'brackets' } diff --git a/packages/svelte/src/Frame.svelte b/packages/svelte/src/Frame.svelte index cdc6e51b0..6d8ad33af 100755 --- a/packages/svelte/src/Frame.svelte +++ b/packages/svelte/src/Frame.svelte @@ -12,7 +12,7 @@ onMount(() => { router.visit(src, { - frameId: id + target: id }) }) diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index c291d19f3..d78cb11e7 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -20,10 +20,10 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro router.init({ initialPage, resolveComponent, - swapComponent: async ({ frameId, component, page, preserveState }) => { - if (frameId) store.update((current) => ({ + swapComponent: async ({ target, component, page, preserveState }) => { + if (target) store.update((current) => ({ ...current, - frames: { ...current.frames, [frameId]: {component, props: page.props} } + frames: { ...current.frames, [target]: {component, props: page.props} } })) else store.update((current) => ({ component, From 25d9db20115e8196498a5db4c4d1841c6aedd331 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 25 May 2023 10:14:04 +0200 Subject: [PATCH 04/25] move target into page object --- packages/core/src/router.ts | 4 +++- packages/core/src/types.ts | 11 +++++------ packages/svelte/src/createInertiaApp.js | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 4d6a83ed0..2d535d375 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -457,11 +457,13 @@ export class Router { if (visitId === this.visitId) { page.scrollRegions = page.scrollRegions || [] page.rememberedState = page.rememberedState || {} + page.target = target if (!target) { replace = replace || hrefToUrl(page.url).href === window.location.href replace ? this.replaceState(page) : this.pushState(page) } - this.swapComponent({ target, component, page, preserveState }).then(() => { + + this.swapComponent({ component, page, preserveState }).then(() => { if (!preserveScroll) { this.resetScrollPositions() } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 29d59a296..dbf8c151a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -31,12 +31,13 @@ export interface PageProps { export interface Page { 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 */ @@ -49,12 +50,10 @@ export type PageHandler = ({ component, page, preserveState, - target, }: { component: Component page: Page preserveState: PreserveStateOption, - target?: string | null }) => Promise export type PreserveStateOption = boolean | string | ((page: Page) => boolean) diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index d78cb11e7..c2b8224ec 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -20,10 +20,10 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro router.init({ initialPage, resolveComponent, - swapComponent: async ({ target, component, page, preserveState }) => { - if (target) store.update((current) => ({ + swapComponent: async ({ component, page, preserveState }) => { + if (page.target) store.update((current) => ({ ...current, - frames: { ...current.frames, [target]: {component, props: page.props} } + frames: { ...current.frames, [page.target]: {component, props: page.props} } })) else store.update((current) => ({ component, From 8dabf42242fb1f17b0ac363f20be20cbceb56745 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 25 May 2023 11:25:09 +0200 Subject: [PATCH 05/25] encapsulate link clicks and form submissions within frame --- packages/core/src/router.ts | 17 ++++++++++++++++- packages/svelte/src/Frame.svelte | 13 +++++++++---- packages/svelte/src/useForm.js | 3 +++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 2d535d375..43fff7e19 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -89,6 +89,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 + const anchorElement = target.closest('a') + const frameId = target.closest('[data-inertia-frame-id]')?.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'], + target: anchorElement.dataset['target'] || frameId, + }) + } + }) } protected scrollRegions(): NodeListOf { @@ -462,7 +477,7 @@ export class Router { replace = replace || hrefToUrl(page.url).href === window.location.href replace ? this.replaceState(page) : this.pushState(page) } - + this.swapComponent({ component, page, preserveState }).then(() => { if (!preserveScroll) { this.resetScrollPositions() diff --git a/packages/svelte/src/Frame.svelte b/packages/svelte/src/Frame.svelte index 6d8ad33af..ff8d085e0 100755 --- a/packages/svelte/src/Frame.svelte +++ b/packages/svelte/src/Frame.svelte @@ -3,18 +3,23 @@ import Render, { h } from './Render.svelte' import store from './store' import { router } from '@inertiajs/svelte' - - export let src + import { setContext } from 'svelte'; + export let src + export let id = Math.random() + setContext('inertia:frame-id', id) $: components = $store.frames?.[id] && h($store.frames[id].component.default, $store.frames[id].props) - + onMount(() => { router.visit(src, { target: id }) }) + - +
+ +
diff --git a/packages/svelte/src/useForm.js b/packages/svelte/src/useForm.js index d624f65d6..d38a0db96 100644 --- a/packages/svelte/src/useForm.js +++ b/packages/svelte/src/useForm.js @@ -2,11 +2,13 @@ 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') let defaults = cloneDeep(data) let cancelToken = null let recentlySuccessfulTimeoutId = null @@ -91,6 +93,7 @@ function useForm(...args) { const data = transform(this.data()) const _options = { ...options, + target: typeof(options.target) !== 'undefined' ? options.target : frameId, onCancelToken: (token) => { cancelToken = token From 469d9232288059b7ace9439abffa89537476e33e Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 25 May 2023 11:36:48 +0200 Subject: [PATCH 06/25] fix typescript errors --- packages/core/src/router.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 43fff7e19..7b34b1142 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -20,6 +20,7 @@ import { GlobalEventNames, GlobalEventResult, LocationVisit, + Method, Page, PageHandler, PageResolver, @@ -90,16 +91,16 @@ export class Router { 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 + const target = event.target as Element const anchorElement = target.closest('a') - const frameId = target.closest('[data-inertia-frame-id]')?.dataset.inertiaFrameId + 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'], + method: anchorElement.dataset['method'] as Method, target: anchorElement.dataset['target'] || frameId, }) } From 90571dd4c5cbc6cba2cee83e62be729ba6001e97 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Fri, 23 Jun 2023 15:03:33 +0200 Subject: [PATCH 07/25] preserve frames through navigation --- packages/svelte/src/Frame.svelte | 2 +- packages/svelte/src/createInertiaApp.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/Frame.svelte b/packages/svelte/src/Frame.svelte index ff8d085e0..8e9deb310 100755 --- a/packages/svelte/src/Frame.svelte +++ b/packages/svelte/src/Frame.svelte @@ -11,7 +11,7 @@ setContext('inertia:frame-id', id) $: components = $store.frames?.[id] && h($store.frames[id].component.default, $store.frames[id].props) - + onMount(() => { router.visit(src, { target: id diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index c2b8224ec..764b58c4f 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -28,6 +28,7 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro else store.update((current) => ({ component, page, + frames: current.frames, key: preserveState ? current.key : Date.now(), })) }, From 1212783cddb8374880f1f49f231a2b04415dfccc Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Tue, 12 Sep 2023 11:20:21 +0200 Subject: [PATCH 08/25] allow controller to override frame --- packages/core/src/router.ts | 22 +++++++++++++++------- packages/svelte/src/createInertiaApp.js | 5 +++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index 7b34b1142..c8eedb665 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -391,9 +391,14 @@ export class Router { } const pageResponse: Page = response.data + + // we can overwrite the target frame in the controller by setting a flash + if (response.data.props.flash.target_frame) target = response.data.props.flash.target_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) { @@ -407,15 +412,15 @@ export class Router { } 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)) { @@ -468,16 +473,18 @@ export class Router { preserveState?: PreserveStateOption target?: string | null } = {}, - ): Promise { + ): Promise { return Promise.resolve(this.resolveComponent(page.component)).then((component) => { if (visitId === this.visitId) { page.scrollRegions = page.scrollRegions || [] page.rememberedState = page.rememberedState || {} - page.target = target - if (!target) { + 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) { @@ -488,6 +495,7 @@ export class Router { } }) } + return page }) } diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index 764b58c4f..58da40b1b 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -21,9 +21,10 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro initialPage, resolveComponent, swapComponent: async ({ component, page, preserveState }) => { - if (page.target) store.update((current) => ({ + const targetFrame = page.target + if (targetFrame) store.update((current) => ({ ...current, - frames: { ...current.frames, [page.target]: {component, props: page.props} } + frames: { ...current.frames, [targetFrame]: {component, props: page.props} } })) else store.update((current) => ({ component, From fb7f864c2756cfb93a79812d81346dcdf0292ca2 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Tue, 12 Sep 2023 16:55:44 +0200 Subject: [PATCH 09/25] send framesrc to controller to redirect back to it --- packages/svelte/src/Frame.svelte | 1 + packages/svelte/src/useForm.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/svelte/src/Frame.svelte b/packages/svelte/src/Frame.svelte index 8e9deb310..7cbd59237 100755 --- a/packages/svelte/src/Frame.svelte +++ b/packages/svelte/src/Frame.svelte @@ -9,6 +9,7 @@ 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) diff --git a/packages/svelte/src/useForm.js b/packages/svelte/src/useForm.js index d38a0db96..e2163bdb4 100644 --- a/packages/svelte/src/useForm.js +++ b/packages/svelte/src/useForm.js @@ -9,6 +9,7 @@ function useForm(...args) { 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 @@ -91,6 +92,7 @@ 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, From efda93a90994cf5810cd8b4b830a6dafbf112a3e Mon Sep 17 00:00:00 2001 From: PedroAugustoRamalhoDuarte Date: Sat, 16 Dec 2023 19:16:36 -0300 Subject: [PATCH 10/25] create frame context --- packages/react/src/FrameContext.ts | 6 ++++++ packages/react/src/useFrame.ts | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100755 packages/react/src/FrameContext.ts create mode 100755 packages/react/src/useFrame.ts diff --git a/packages/react/src/FrameContext.ts b/packages/react/src/FrameContext.ts new file mode 100755 index 000000000..4c97d6672 --- /dev/null +++ b/packages/react/src/FrameContext.ts @@ -0,0 +1,6 @@ +import { createContext } from 'react' + +const frameContext = createContext(undefined) +frameContext.displayName = 'InertiaFrameContext' + +export default frameContext diff --git a/packages/react/src/useFrame.ts b/packages/react/src/useFrame.ts new file mode 100755 index 000000000..3aa55001f --- /dev/null +++ b/packages/react/src/useFrame.ts @@ -0,0 +1,6 @@ +import { useContext } from 'react' +import FrameContext from './FrameContext' + +export default function useFrame(): any { + return useContext(FrameContext) +} From d2039178bcd2bb9a6e29d8f4c13892b1ed2eb5e2 Mon Sep 17 00:00:00 2001 From: PedroAugustoRamalhoDuarte Date: Sat, 16 Dec 2023 19:17:55 -0300 Subject: [PATCH 11/25] create Frame component for React Adapter --- packages/react/src/App.ts | 40 ++++++++++++++++++++++++++----------- packages/react/src/Frame.ts | 24 ++++++++++++++++++++++ packages/react/src/index.ts | 1 + 3 files changed, 53 insertions(+), 12 deletions(-) create mode 100755 packages/react/src/Frame.ts diff --git a/packages/react/src/App.ts b/packages/react/src/App.ts index c073d3b8a..0acdb472e 100755 --- a/packages/react/src/App.ts +++ b/packages/react/src/App.ts @@ -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, @@ -14,6 +15,7 @@ export default function App({ const [current, setCurrent] = useState({ component: initialComponent || null, page: initialPage, + frames: null, key: null, }) @@ -21,7 +23,8 @@ export default function App({ return createHeadManager( typeof window === 'undefined', titleCallback || ((title) => title), - onHeadUpdate || (() => {}), + onHeadUpdate || (() => { + }), ) }, []) @@ -29,12 +32,21 @@ export default function App({ 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(), + })) + } }, }) @@ -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, + }), + ) ), ) } diff --git a/packages/react/src/Frame.ts b/packages/react/src/Frame.ts new file mode 100755 index 000000000..5db99b485 --- /dev/null +++ b/packages/react/src/Frame.ts @@ -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; \ No newline at end of file diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 6b13ec581..aacd453cd 100755 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -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' From fa6d084c25d5f56e2b9bf83d5e09e5493c8651cf Mon Sep 17 00:00:00 2001 From: PedroAugustoRamalhoDuarte Date: Sat, 16 Dec 2023 19:19:35 -0300 Subject: [PATCH 12/25] fixes router target_frame from flash --- packages/core/src/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index c8eedb665..f69086952 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -393,7 +393,7 @@ export class Router { const pageResponse: Page = response.data // we can overwrite the target frame in the controller by setting a flash - if (response.data.props.flash.target_frame) target = response.data.props.flash.target_frame + if (response.data.props.flash && response.data.props.flash.target_frame) target = response.data.props.flash.target_frame if (only.length && pageResponse.component === this.page.component) { pageResponse.props = { ...this.page.props, ...pageResponse.props } From 68fa8f66f7a90d8471fc350f6838986b217dfc71 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Tue, 26 Dec 2023 22:46:33 +0100 Subject: [PATCH 13/25] include component CSS in head, fixed inertiajs/inertia#1760 --- packages/svelte/src/createInertiaApp.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/createInertiaApp.js b/packages/svelte/src/createInertiaApp.js index facb5e625..ff285ac3a 100644 --- a/packages/svelte/src/createInertiaApp.js +++ b/packages/svelte/src/createInertiaApp.js @@ -44,11 +44,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, + ``, + ], } } } From 78b154a822eabc35295ec4dea2b8727527982485 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 16:03:23 +0200 Subject: [PATCH 14/25] Update readme.md --- readme.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 1d4b676ad..89c7b8c45 100644 --- a/readme.md +++ b/readme.md @@ -1,19 +1,23 @@ -[![Inertia.js](https://raw.githubusercontent.com/inertiajs/inertia/master/.github/LOGO.png)](https://inertiajs.com/) +# Inertia with Frames -Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers. Find full documentation at [inertiajs.com](https://inertiajs.com/). +This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. -## Contributing +This is experimental and currently only supported in Svelte. -If you're interested in contributing to Inertia.js, please read our [contributing guide](https://github.com/inertiajs/inertia/blob/master/.github/CONTRIBUTING.md). +## Frames -## Sponsors +This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, popovers, etc. -A huge thanks to all [our sponsors](https://inertiajs.com/sponsors) who help push Inertia.js development forward! In particular, we'd like to say a special thank you to our partners: +By default, hyperlinks and form submissions will load the response within the current frame. To load the response in a different frame, use the `target` attribute. To load the response in the main -

- - Laravel Forge - -

+### Example -If you'd like to become a sponsor, please [see here](https://github.com/sponsors/reinink) for more information. 💜 +```html + + + + Loading... + +``` From 2b94200999f6aa74e36bc6a99fc1dc97ab96beef Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 16:20:10 +0200 Subject: [PATCH 15/25] readme --- readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 89c7b8c45..7f94bf886 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,9 @@ This is experimental and currently only supported in Svelte. This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, popovers, etc. -By default, hyperlinks and form submissions will load the response within the current frame. To load the response in a different frame, use the `target` attribute. To load the response in the main +By default, hyperlinks and form submissions will load the response within the frame that contains the link or the form. To load the response in a different frame, add a `target` attribute. To load the response in the top (main) frame, use the `target="_top"` attribute. + +You can overwrite the targeted frame in the server-side response by sending an 'X-Inertia-Frame' header. ### Example @@ -17,7 +19,7 @@ By default, hyperlinks and form submissions will load the response within the cu import { Frame } from '@inertia/svelte' - + Loading... ``` From e2c99030dae83d0a87eac45ac40bfc44a5d72036 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 17:13:01 +0200 Subject: [PATCH 16/25] update readme --- readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7f94bf886..03660790a 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. -This is experimental and currently only supported in Svelte. +This is experimental and currently only supported in Svelte and React. ## Frames @@ -12,6 +12,8 @@ By default, hyperlinks and form submissions will load the response within the fr You can overwrite the targeted frame in the server-side response by sending an 'X-Inertia-Frame' header. +Navigation within frames does not create new history entries. To enable this, a more substantial rewrite of the Inertia router would be required. + ### Example ```html From f55e084b779c4a751dd4750640f89a2a0cbc7955 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 20:06:37 +0200 Subject: [PATCH 17/25] Update readme.md --- readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 03660790a..c6b449503 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ This is experimental and currently only supported in Svelte and React. This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, popovers, etc. -By default, hyperlinks and form submissions will load the response within the frame that contains the link or the form. To load the response in a different frame, add a `target` attribute. To load the response in the top (main) frame, use the `target="_top"` attribute. +By default, hyperlinks and form submissions will load the response within the frame that contains the link or the form. To load the response in a different frame, add a `data-target` attribute. To load the response in the top (main) frame, use the `data=target="_top"` attribute. You can overwrite the targeted frame in the server-side response by sending an 'X-Inertia-Frame' header. @@ -24,4 +24,8 @@ import { Frame } from '@inertia/svelte' Loading... + + + Edit a different user + ``` From d1831632802edb632540550408ec66930d7a8cff Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 20:07:08 +0200 Subject: [PATCH 18/25] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c6b449503..6f67bb8e3 100644 --- a/readme.md +++ b/readme.md @@ -18,7 +18,7 @@ Navigation within frames does not create new history entries. To enable this, a ```html From 2d2af4fe3c72e33e3acea1e3a5ec1b05b2b6790c Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 20:08:08 +0200 Subject: [PATCH 19/25] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6f67bb8e3..c2200bc6b 100644 --- a/readme.md +++ b/readme.md @@ -6,7 +6,7 @@ This is experimental and currently only supported in Svelte and React. ## Frames -This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, popovers, etc. +This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, wizards, search sidebars, popovers, etc. By default, hyperlinks and form submissions will load the response within the frame that contains the link or the form. To load the response in a different frame, add a `data-target` attribute. To load the response in the top (main) frame, use the `data=target="_top"` attribute. From 16aaf0ac4a2999c379e83e85f910a3a60024e0b9 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 20:17:00 +0200 Subject: [PATCH 20/25] Update readme.md --- readme.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/readme.md b/readme.md index c2200bc6b..a232cf09e 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,22 @@ You can overwrite the targeted frame in the server-side response by sending an ' Navigation within frames does not create new history entries. To enable this, a more substantial rewrite of the Inertia router would be required. +### Installation + +Clone this repo, [build it](https://github.com/inertiajs/inertia/blob/master/.github/CONTRIBUTING.md#packages), and in your `package.json`, link it like this: + +```js +{ + "devDependencies": { + '@inertiajs/core': 'file:./repo/packages/core', + '@inertiajs/svelte': 'file:./repo/packages/svelte', + '@inertiajs/react': 'file:./repo/packages/react' + } +} +``` + +Then run `npm install` again. + ### Example ```html From 2d2f50dbb4948fae3c9e0d60f19e5efb2c1c51b1 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 22:09:05 +0200 Subject: [PATCH 21/25] use x-inertia-frame header --- packages/core/src/router.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index f69086952..ba09e386f 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -392,8 +392,10 @@ export class Router { const pageResponse: Page = response.data - // we can overwrite the target frame in the controller by setting a flash - if (response.data.props.flash && response.data.props.flash.target_frame) target = response.data.props.flash.target_frame + // 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 } From 0b8dc2a80b5a6f91a57ec9c7975f6784dfcdc204 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 23:28:36 +0200 Subject: [PATCH 22/25] Update readme.md --- readme.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index a232cf09e..499e9c9ab 100644 --- a/readme.md +++ b/readme.md @@ -8,13 +8,17 @@ This is experimental and currently only supported in Svelte and React. This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, wizards, search sidebars, popovers, etc. -By default, hyperlinks and form submissions will load the response within the frame that contains the link or the form. To load the response in a different frame, add a `data-target` attribute. To load the response in the top (main) frame, use the `data=target="_top"` attribute. +By default, hyperlinks and form submissions will render the response within the frame that contains the link or the form. To change the frame in which an Inertia response is rendered, do one of the following: -You can overwrite the targeted frame in the server-side response by sending an 'X-Inertia-Frame' header. +- Add a `data-target="frame-id"` attribute to an `a` tag. +- Pass a `{target: frameId}` to `router.visit()` or `form.submit()` +- Specify the frame ID in an `X-Inertia-Frame` header from the server. + +To target the top (main) frame, use `_top` as the frame ID. Navigation within frames does not create new history entries. To enable this, a more substantial rewrite of the Inertia router would be required. -### Installation +### Try locally Clone this repo, [build it](https://github.com/inertiajs/inertia/blob/master/.github/CONTRIBUTING.md#packages), and in your `package.json`, link it like this: From 59486591242c84962db7e8691210e1874a6d3f41 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 23:35:43 +0200 Subject: [PATCH 23/25] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 499e9c9ab..a789ba80d 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. -This is experimental and currently only supported in Svelte and React. +The current implementation is experimental (and very hacky) and currently only supported in Svelte and React. ## Frames From efe9692a6013ebd69c3d58cc4d619872776ea2d6 Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Thu, 4 Apr 2024 23:37:42 +0200 Subject: [PATCH 24/25] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index a789ba80d..6a9d4f9c2 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,6 @@ # Inertia with Frames -This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. - -The current implementation is experimental (and very hacky) and currently only supported in Svelte and React. +This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. The current implementation is experimental (and very hacky) and currently only supported in Svelte and React. ## Frames @@ -18,6 +16,8 @@ To target the top (main) frame, use `_top` as the frame ID. Navigation within frames does not create new history entries. To enable this, a more substantial rewrite of the Inertia router would be required. +Frames are loaded when the component is mounted. That means, that only the initial frame placeholder content will be rendered during SSR. + ### Try locally Clone this repo, [build it](https://github.com/inertiajs/inertia/blob/master/.github/CONTRIBUTING.md#packages), and in your `package.json`, link it like this: From 09779f3fb003fa6c61882b421067290a29d3d44e Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Sun, 7 Apr 2024 00:35:38 +0200 Subject: [PATCH 25/25] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 6a9d4f9c2..480d2eb8a 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,10 @@ # Inertia with Frames -This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. The current implementation is experimental (and very hacky) and currently only supported in Svelte and React. +This is a modified version of [Inertia](https://github.com/inertiajs/inertia) that adds support for Frames. It's currently supported in Svelte and React. ## Frames -This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, wizards, search sidebars, popovers, etc. +This fork introduces the `` component. This component is used to encapsulate an Inertia page within another Inertia page. This is useful for creating modal dialogs, wizards, search sidebars, popovers, etc. Don't worry: Besides the name, it has nothing to do with conventional browser frames. By default, hyperlinks and form submissions will render the response within the frame that contains the link or the form. To change the frame in which an Inertia response is rendered, do one of the following: