Skip to content

Commit

Permalink
Merge pull request #2193 from inertiajs/backport-1.x-fixes
Browse files Browse the repository at this point in the history
Backport 1.x fixes
  • Loading branch information
joetannenbaum authored Jan 15, 2025
2 parents 9e97591 + da7ec4c commit 3ec7c2a
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 31 deletions.
56 changes: 36 additions & 20 deletions packages/core/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { SessionStorage } from './sessionStorage'
import { Page, ScrollRegion } from './types'

const isServer = typeof window === 'undefined'

const queue = new Queue<Promise<void>>()
const isChromeIOS = !isServer && /CriOS/.test(window.navigator.userAgent)

class History {
public rememberedState = 'rememberedState' as const
Expand Down Expand Up @@ -39,23 +39,25 @@ class History {

if (this.preserveUrl) {
cb && cb()

return
}

this.current = page

queue.add(() => {
return this.getPageData(page).then((data) => {
window.history.pushState(
{
page: data,
},
'',
page.url,
)

cb && cb()
// Defer history.pushState to the next event loop tick to prevent timing conflicts.
// Ensure any previous history.replaceState completes before pushState is executed.
const doPush = () => {
this.doPushState({ page: data }, page.url)
cb && cb()
}

if (isChromeIOS) {
setTimeout(doPush)
} else {
doPush()
}
})
})
}
Expand Down Expand Up @@ -141,22 +143,25 @@ class History {

if (this.preserveUrl) {
cb && cb()

return
}

this.current = page

queue.add(() => {
return this.getPageData(page).then((data) => {
this.doReplaceState(
{
page: data,
},
page.url,
)

cb && cb()
// Defer history.replaceState to the next event loop tick to prevent timing conflicts.
// Ensure any previous history.pushState completes before replaceState is executed.
const doReplace = () => {
this.doReplaceState({ page: data }, page.url)
cb && cb()
}

if (isChromeIOS) {
setTimeout(doReplace)
} else {
doReplace()
}
})
})
}
Expand All @@ -180,6 +185,17 @@ class History {
)
}

protected doPushState(
data: {
page: Page | ArrayBuffer
scrollRegions?: ScrollRegion[]
documentScrollPosition?: ScrollRegion
},
url: string,
): void {
window.history.pushState(data, '', url)
}

public getState<T>(key: keyof Page, defaultValue?: T): any {
return this.current?.[key] ?? defaultValue
}
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/initialVisit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export class InitialVisit {
currentPage.setUrlHash(window.location.hash)
}

currentPage.set(currentPage.get(), { preserveState: true }).then(() => {
currentPage.set(currentPage.get(), { preserveScroll: true, preserveState: true }).then(() => {
Scroll.restore(history.getScrollRegions())
fireNavigateEvent(currentPage.get())
})
}
Expand Down
20 changes: 16 additions & 4 deletions packages/core/src/navigationType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ class NavigationType {
protected type: NavigationTimingType

public constructor() {
if (typeof window !== 'undefined' && window?.performance.getEntriesByType('navigation').length > 0) {
this.type = (window.performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming).type
} else {
this.type = 'navigate'
this.type = this.resolveType()
}

protected resolveType(): NavigationTimingType {
if (typeof window === 'undefined') {
return 'navigate'
}

if (
window.performance &&
window.performance.getEntriesByType &&
window.performance.getEntriesByType('navigation').length > 0
) {
return (window.performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming).type
}

return 'navigate'
}

public get(): NavigationTimingType {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ class CurrentPage {
}

public setUrlHash(hash: string): void {
this.page.url += hash
if (!this.page.url.includes(hash)) {
this.page.url += hash
}
}

public remember(data: Page['rememberedState']): void {
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ export class Router {
type: TEventName,
callback: (event: GlobalEvent<TEventName>) => GlobalEventResult<TEventName>,
): VoidFunction {
if (typeof window === 'undefined') {
return () => {}
}

return eventHandler.onGlobalEvent(type, callback)
}

Expand Down Expand Up @@ -267,7 +271,7 @@ export class Router {
protected clientVisit(params: ClientSideVisitOptions, { replace = false }: { replace?: boolean } = {}): void {
const current = currentPage.get()

const props = typeof params.props === 'function' ? params.props(current.props) : params.props ?? current.props
const props = typeof params.props === 'function' ? params.props(current.props) : (params.props ?? current.props)

currentPage.set(
{
Expand Down
13 changes: 11 additions & 2 deletions packages/core/src/shouldIntercept.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
export default function shouldIntercept(event: MouseEvent | KeyboardEvent): boolean {
// The actual event passed to this function could be a native JavaScript event
// or a React synthetic event, so we are picking just the keys needed here (that
// are present in both types).

export default function shouldIntercept(
event: Pick<
MouseEvent,
'altKey' | 'ctrlKey' | 'defaultPrevented' | 'target' | 'currentTarget' | 'metaKey' | 'shiftKey' | 'button'
>,
): boolean {
const isLink = (event.currentTarget as HTMLElement).tagName.toLowerCase() === 'a'

return !(
(event.target && (event?.target as HTMLElement).isContentEditable) ||
event.defaultPrevented ||
(isLink && event.which > 1) ||
(isLink && event.altKey) ||
(isLink && event.ctrlKey) ||
(isLink && event.metaKey) ||
Expand Down
2 changes: 1 addition & 1 deletion packages/vue3/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export { default as Deferred } from './deferred'
export { default as Head } from './head'
export { InertiaLinkProps, default as Link } from './link'
export * from './types'
export { InertiaForm, default as useForm } from './useForm'
export { InertiaForm, InertiaFormProps, default as useForm } from './useForm'
export { default as usePoll } from './usePoll'
export { default as usePrefetch } from './usePrefetch'
export { default as useRemember } from './useRemember'
Expand Down
3 changes: 2 additions & 1 deletion packages/vue3/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { reactive, watch } from 'vue'
type FormDataType = Record<string, FormDataConvertible>
type FormOptions = Omit<VisitOptions, 'data'>

interface InertiaFormProps<TForm extends FormDataType> {
export interface InertiaFormProps<TForm extends FormDataType> {
isDirty: boolean
errors: Partial<Record<keyof TForm, string>>
hasErrors: boolean
Expand Down Expand Up @@ -80,6 +80,7 @@ export default function useForm<TForm extends FormDataType>(

if (typeof fieldOrFields === 'undefined') {
defaults = this.data()
this.isDirty = false
} else {
defaults = Object.assign(
{},
Expand Down

0 comments on commit 3ec7c2a

Please sign in to comment.