Skip to content

Commit 3ec7c2a

Browse files
Merge pull request #2193 from inertiajs/backport-1.x-fixes
Backport 1.x fixes
2 parents 9e97591 + da7ec4c commit 3ec7c2a

File tree

8 files changed

+76
-31
lines changed

8 files changed

+76
-31
lines changed

packages/core/src/history.ts

+36-20
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { SessionStorage } from './sessionStorage'
55
import { Page, ScrollRegion } from './types'
66

77
const isServer = typeof window === 'undefined'
8-
98
const queue = new Queue<Promise<void>>()
9+
const isChromeIOS = !isServer && /CriOS/.test(window.navigator.userAgent)
1010

1111
class History {
1212
public rememberedState = 'rememberedState' as const
@@ -39,23 +39,25 @@ class History {
3939

4040
if (this.preserveUrl) {
4141
cb && cb()
42-
4342
return
4443
}
4544

4645
this.current = page
4746

4847
queue.add(() => {
4948
return this.getPageData(page).then((data) => {
50-
window.history.pushState(
51-
{
52-
page: data,
53-
},
54-
'',
55-
page.url,
56-
)
57-
58-
cb && cb()
49+
// Defer history.pushState to the next event loop tick to prevent timing conflicts.
50+
// Ensure any previous history.replaceState completes before pushState is executed.
51+
const doPush = () => {
52+
this.doPushState({ page: data }, page.url)
53+
cb && cb()
54+
}
55+
56+
if (isChromeIOS) {
57+
setTimeout(doPush)
58+
} else {
59+
doPush()
60+
}
5961
})
6062
})
6163
}
@@ -141,22 +143,25 @@ class History {
141143

142144
if (this.preserveUrl) {
143145
cb && cb()
144-
145146
return
146147
}
147148

148149
this.current = page
149150

150151
queue.add(() => {
151152
return this.getPageData(page).then((data) => {
152-
this.doReplaceState(
153-
{
154-
page: data,
155-
},
156-
page.url,
157-
)
158-
159-
cb && cb()
153+
// Defer history.replaceState to the next event loop tick to prevent timing conflicts.
154+
// Ensure any previous history.pushState completes before replaceState is executed.
155+
const doReplace = () => {
156+
this.doReplaceState({ page: data }, page.url)
157+
cb && cb()
158+
}
159+
160+
if (isChromeIOS) {
161+
setTimeout(doReplace)
162+
} else {
163+
doReplace()
164+
}
160165
})
161166
})
162167
}
@@ -180,6 +185,17 @@ class History {
180185
)
181186
}
182187

188+
protected doPushState(
189+
data: {
190+
page: Page | ArrayBuffer
191+
scrollRegions?: ScrollRegion[]
192+
documentScrollPosition?: ScrollRegion
193+
},
194+
url: string,
195+
): void {
196+
window.history.pushState(data, '', url)
197+
}
198+
183199
public getState<T>(key: keyof Page, defaultValue?: T): any {
184200
return this.current?.[key] ?? defaultValue
185201
}

packages/core/src/initialVisit.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ export class InitialVisit {
9292
currentPage.setUrlHash(window.location.hash)
9393
}
9494

95-
currentPage.set(currentPage.get(), { preserveState: true }).then(() => {
95+
currentPage.set(currentPage.get(), { preserveScroll: true, preserveState: true }).then(() => {
96+
Scroll.restore(history.getScrollRegions())
9697
fireNavigateEvent(currentPage.get())
9798
})
9899
}

packages/core/src/navigationType.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@ class NavigationType {
22
protected type: NavigationTimingType
33

44
public constructor() {
5-
if (typeof window !== 'undefined' && window?.performance.getEntriesByType('navigation').length > 0) {
6-
this.type = (window.performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming).type
7-
} else {
8-
this.type = 'navigate'
5+
this.type = this.resolveType()
6+
}
7+
8+
protected resolveType(): NavigationTimingType {
9+
if (typeof window === 'undefined') {
10+
return 'navigate'
911
}
12+
13+
if (
14+
window.performance &&
15+
window.performance.getEntriesByType &&
16+
window.performance.getEntriesByType('navigation').length > 0
17+
) {
18+
return (window.performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming).type
19+
}
20+
21+
return 'navigate'
1022
}
1123

1224
public get(): NavigationTimingType {

packages/core/src/page.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ class CurrentPage {
127127
}
128128

129129
public setUrlHash(hash: string): void {
130-
this.page.url += hash
130+
if (!this.page.url.includes(hash)) {
131+
this.page.url += hash
132+
}
131133
}
132134

133135
public remember(data: Page['rememberedState']): void {

packages/core/src/router.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ export class Router {
114114
type: TEventName,
115115
callback: (event: GlobalEvent<TEventName>) => GlobalEventResult<TEventName>,
116116
): VoidFunction {
117+
if (typeof window === 'undefined') {
118+
return () => {}
119+
}
120+
117121
return eventHandler.onGlobalEvent(type, callback)
118122
}
119123

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

270-
const props = typeof params.props === 'function' ? params.props(current.props) : params.props ?? current.props
274+
const props = typeof params.props === 'function' ? params.props(current.props) : (params.props ?? current.props)
271275

272276
currentPage.set(
273277
{

packages/core/src/shouldIntercept.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
export default function shouldIntercept(event: MouseEvent | KeyboardEvent): boolean {
1+
// The actual event passed to this function could be a native JavaScript event
2+
// or a React synthetic event, so we are picking just the keys needed here (that
3+
// are present in both types).
4+
5+
export default function shouldIntercept(
6+
event: Pick<
7+
MouseEvent,
8+
'altKey' | 'ctrlKey' | 'defaultPrevented' | 'target' | 'currentTarget' | 'metaKey' | 'shiftKey' | 'button'
9+
>,
10+
): boolean {
211
const isLink = (event.currentTarget as HTMLElement).tagName.toLowerCase() === 'a'
12+
313
return !(
414
(event.target && (event?.target as HTMLElement).isContentEditable) ||
515
event.defaultPrevented ||
6-
(isLink && event.which > 1) ||
716
(isLink && event.altKey) ||
817
(isLink && event.ctrlKey) ||
918
(isLink && event.metaKey) ||

packages/vue3/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export { default as Deferred } from './deferred'
55
export { default as Head } from './head'
66
export { InertiaLinkProps, default as Link } from './link'
77
export * from './types'
8-
export { InertiaForm, default as useForm } from './useForm'
8+
export { InertiaForm, InertiaFormProps, default as useForm } from './useForm'
99
export { default as usePoll } from './usePoll'
1010
export { default as usePrefetch } from './usePrefetch'
1111
export { default as useRemember } from './useRemember'

packages/vue3/src/useForm.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { reactive, watch } from 'vue'
66
type FormDataType = Record<string, FormDataConvertible>
77
type FormOptions = Omit<VisitOptions, 'data'>
88

9-
interface InertiaFormProps<TForm extends FormDataType> {
9+
export interface InertiaFormProps<TForm extends FormDataType> {
1010
isDirty: boolean
1111
errors: Partial<Record<keyof TForm, string>>
1212
hasErrors: boolean
@@ -80,6 +80,7 @@ export default function useForm<TForm extends FormDataType>(
8080

8181
if (typeof fieldOrFields === 'undefined') {
8282
defaults = this.data()
83+
this.isDirty = false
8384
} else {
8485
defaults = Object.assign(
8586
{},

0 commit comments

Comments
 (0)