diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts index a60f5558d..72eec1632 100644 --- a/packages/core/src/router.ts +++ b/packages/core/src/router.ts @@ -260,6 +260,7 @@ export class Router { preserveScroll = false, preserveState = false, only = [], + except = [], headers = {}, errorBag = '', forceFormData = false, @@ -294,6 +295,7 @@ export class Router { preserveScroll, preserveState, only, + except, headers, errorBag, forceFormData, @@ -339,6 +341,8 @@ export class Router { fireStartEvent(visit) onStart(visit) + const isPartial = !!(only.length || except.length) + Axios({ method, url: urlWithoutHash(url).href, @@ -350,12 +354,21 @@ export class Router { Accept: 'text/html, application/xhtml+xml', 'X-Requested-With': 'XMLHttpRequest', 'X-Inertia': true, - ...(only.length + ...(isPartial ? { 'X-Inertia-Partial-Component': this.page.component, + } + : {}), + ...(only.length + ? { 'X-Inertia-Partial-Data': only.join(','), } : {}), + ...(except.length + ? { + 'X-Inertia-Partial-Except': except.join(','), + } + : {}), ...(errorBag && errorBag.length ? { 'X-Inertia-Error-Bag': errorBag } : {}), ...(this.page.version ? { 'X-Inertia-Version': this.page.version } : {}), }, @@ -373,7 +386,7 @@ export class Router { } const pageResponse: Page = response.data - if (only.length && pageResponse.component === this.page.component) { + if (isPartial && pageResponse.component === this.page.component) { pageResponse.props = { ...this.page.props, ...pageResponse.props } } preserveScroll = this.resolvePreserveOption(preserveScroll, pageResponse) as boolean diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2fc1d6025..cba9d687b 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -70,6 +70,7 @@ export type Visit = { preserveScroll: PreserveStateOption preserveState: PreserveStateOption only: Array + except: Array headers: Record errorBag: string | null forceFormData: boolean diff --git a/packages/react/src/Link.ts b/packages/react/src/Link.ts index d92d81d0d..061b7792c 100755 --- a/packages/react/src/Link.ts +++ b/packages/react/src/Link.ts @@ -22,6 +22,7 @@ interface BaseInertiaLinkProps { preserveState?: PreserveStateOption replace?: boolean only?: string[] + except?: string[] onCancelToken?: (cancelToken: import('axios').CancelTokenSource) => void onBefore?: () => void onStart?: () => void @@ -49,6 +50,7 @@ const Link = forwardRef( preserveState = null, replace = false, only = [], + except = [], headers = {}, queryStringArrayFormat = 'brackets', onClick = noop, @@ -78,6 +80,7 @@ const Link = forwardRef( preserveState: preserveState ?? method !== 'get', replace, only, + except, headers, onCancelToken, onBefore, @@ -98,6 +101,7 @@ const Link = forwardRef( preserveState, replace, only, + except, headers, onClick, onCancelToken, diff --git a/packages/svelte/src/Link.svelte b/packages/svelte/src/Link.svelte index c98c20225..7641af37e 100755 --- a/packages/svelte/src/Link.svelte +++ b/packages/svelte/src/Link.svelte @@ -10,6 +10,7 @@ export let preserveScroll = false export let preserveState = null export let only = [] + export let except = [] export let headers = {} export let queryStringArrayFormat = 'brackets' @@ -33,6 +34,7 @@ preserveScroll, preserveState: preserveState ?? method !== 'get', only, + except, headers, queryStringArrayFormat, }} diff --git a/packages/vue2/src/link.ts b/packages/vue2/src/link.ts index 53b34a526..52f727072 100755 --- a/packages/vue2/src/link.ts +++ b/packages/vue2/src/link.ts @@ -20,6 +20,7 @@ export interface InertiaLinkProps { preserveState?: PreserveStateOption replace?: boolean only?: string[] + except?: string[] onCancelToken?: (cancelToken: import('axios').CancelTokenSource) => void onBefore?: () => void onStart?: () => void @@ -66,6 +67,10 @@ const Link: InertiaLink = { type: Array, default: () => [], }, + except: { + type: Array, + default: () => [], + }, headers: { type: Object, default: () => ({}), @@ -127,6 +132,7 @@ const Link: InertiaLink = { preserveScroll: props.preserveScroll, preserveState: props.preserveState ?? method !== 'get', only: props.only, + except: props.except, headers: props.headers, // @ts-expect-error onCancelToken: data.on.cancelToken, diff --git a/packages/vue2/tests/app/Pages/Links/PartialReloads.vue b/packages/vue2/tests/app/Pages/Links/PartialReloads.vue index 18b0f417c..0d6adbfb5 100644 --- a/packages/vue2/tests/app/Pages/Links/PartialReloads.vue +++ b/packages/vue2/tests/app/Pages/Links/PartialReloads.vue @@ -13,6 +13,12 @@ 'Only' baz + 'Except' foo + bar + 'Except' baz diff --git a/packages/vue2/tests/app/helpers.js b/packages/vue2/tests/app/helpers.js index ddee400f6..73aa86dad 100644 --- a/packages/vue2/tests/app/helpers.js +++ b/packages/vue2/tests/app/helpers.js @@ -19,15 +19,14 @@ module.exports = { } const partialDataHeader = req.headers['x-inertia-partial-data'] || '' + const partialExceptHeader = req.headers['x-inertia-partial-except'] || '' const partialComponentHeader = req.headers['x-inertia-partial-component'] || '' + + const isPartial = partialComponentHeader && partialComponentHeader === data.component + data.props = Object.keys(data.props) - .filter( - (key) => - !partialComponentHeader || - partialComponentHeader !== data.component || - !partialDataHeader || - partialDataHeader.split(',').indexOf(key) > -1, - ) + .filter((key) => !isPartial || !partialDataHeader || partialDataHeader.split(',').indexOf(key) > -1) + .filter((key) => !isPartial || !partialExceptHeader || partialExceptHeader.split(',').indexOf(key) == -1) .reduce((carry, key) => { carry[key] = typeof data.props[key] === 'function' ? data.props[key](data.props) : data.props[key] diff --git a/packages/vue2/tests/cypress/integration/links.test.js b/packages/vue2/tests/cypress/integration/links.test.js index 7aea95505..0815a69b0 100644 --- a/packages/vue2/tests/cypress/integration/links.test.js +++ b/packages/vue2/tests/cypress/integration/links.test.js @@ -1002,6 +1002,20 @@ describe('Links', () => { cy.get('.bar-text').should('have.text', 'Bar is now 4') cy.get('.baz-text').should('have.text', 'Baz is now 5') }) + + it('it only updates props that are not passed through "except"', () => { + cy.get('.except-foo-bar').click() + cy.url().should('eq', Cypress.config().baseUrl + '/links/partial-reloads') + cy.get('.foo-text').should('have.text', 'Foo is now 1') + cy.get('.bar-text').should('have.text', 'Bar is now 2') + cy.get('.baz-text').should('have.text', 'Baz is now 4') + + cy.get('.except-baz').click() + cy.url().should('eq', Cypress.config().baseUrl + '/links/partial-reloads') + cy.get('.foo-text').should('have.text', 'Foo is now 2') + cy.get('.bar-text').should('have.text', 'Bar is now 3') + cy.get('.baz-text').should('have.text', 'Baz is now 4') + }) }) describe('Redirects', () => { diff --git a/packages/vue2/tests/cypress/integration/manual-visits.test.js b/packages/vue2/tests/cypress/integration/manual-visits.test.js index 54da6d85a..3bfc65d9b 100644 --- a/packages/vue2/tests/cypress/integration/manual-visits.test.js +++ b/packages/vue2/tests/cypress/integration/manual-visits.test.js @@ -1291,7 +1291,7 @@ describe('Manual Visits', () => { }) }) - it('has headers specific to partial reloads', () => { + it('has headers specific to "only" partial reloads', () => { cy.get('.visit-foo-bar').click() cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') @@ -1314,6 +1314,29 @@ describe('Manual Visits', () => { }) }) + it('has headers specific to "except" partial reloads', () => { + cy.get('.visit-except-foo-bar').click() + cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') + + cy.window().should('have.property', '_inertia_props') + cy.window() + .then((window) => window._inertia_props) + .then(({ headers }) => { + expect(headers).to.contain.keys([ + 'accept', + 'x-requested-with', + 'x-inertia', + 'x-inertia-partial-component', + 'x-inertia-partial-except', + ]) + expect(headers['accept']).to.eq('text/html, application/xhtml+xml') + expect(headers['x-requested-with']).to.eq('XMLHttpRequest') + expect(headers['x-inertia']).to.eq('true') + expect(headers['x-inertia-partial-except']).to.eq('foo,bar') + expect(headers['x-inertia-partial-component']).to.eq('Visits/PartialReloads') + }) + }) + it('it updates all props when the feature is not being used', () => { cy.get('.visit').click() cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') @@ -1342,6 +1365,20 @@ describe('Manual Visits', () => { cy.get('.bar-text').should('have.text', 'Bar is now 4') cy.get('.baz-text').should('have.text', 'Baz is now 5') }) + + it('it only updates props that are not passed through "except"', () => { + cy.get('.visit-except-foo-bar').click() + cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') + cy.get('.foo-text').should('have.text', 'Foo is now 1') + cy.get('.bar-text').should('have.text', 'Bar is now 2') + cy.get('.baz-text').should('have.text', 'Baz is now 4') + + cy.get('.visit-except-baz').click() + cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') + cy.get('.foo-text').should('have.text', 'Foo is now 2') + cy.get('.bar-text').should('have.text', 'Bar is now 3') + cy.get('.baz-text').should('have.text', 'Baz is now 4') + }) }) describe('GET-method', () => { @@ -1408,6 +1445,20 @@ describe('Manual Visits', () => { cy.get('.bar-text').should('have.text', 'Bar is now 4') cy.get('.baz-text').should('have.text', 'Baz is now 5') }) + + it('it only updates props that are not passed through "except"', () => { + cy.get('.get-except-foo-bar').click() + cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') + cy.get('.foo-text').should('have.text', 'Foo is now 1') + cy.get('.bar-text').should('have.text', 'Bar is now 2') + cy.get('.baz-text').should('have.text', 'Baz is now 4') + + cy.get('.get-except-baz').click() + cy.url().should('eq', Cypress.config().baseUrl + '/visits/partial-reloads') + cy.get('.foo-text').should('have.text', 'Foo is now 2') + cy.get('.bar-text').should('have.text', 'Bar is now 3') + cy.get('.baz-text').should('have.text', 'Baz is now 4') + }) }) }) diff --git a/packages/vue3/src/link.ts b/packages/vue3/src/link.ts index 9bc3949db..1c783aaeb 100755 --- a/packages/vue3/src/link.ts +++ b/packages/vue3/src/link.ts @@ -12,6 +12,7 @@ export interface InertiaLinkProps { preserveState?: boolean | ((props: PageProps) => boolean) | null replace?: boolean only?: string[] + except?: string[] onCancelToken?: (cancelToken: import('axios').CancelTokenSource) => void onBefore?: () => void onStart?: () => void @@ -59,6 +60,10 @@ const Link: InertiaLink = defineComponent({ type: Array, default: () => [], }, + except: { + type: Array, + default: () => [], + }, headers: { type: Object, default: () => ({}), @@ -96,6 +101,7 @@ const Link: InertiaLink = defineComponent({ preserveScroll: props.preserveScroll, preserveState: props.preserveState ?? method !== 'get', only: props.only, + except: props.except, headers: props.headers, // @ts-expect-error onCancelToken: attrs.onCancelToken || (() => ({})),