Skip to content

Commit 1e074b2

Browse files
committed
Merge branch 'precognition-useform' into precognition-merge
2 parents 1b6f152 + 842278f commit 1e074b2

File tree

93 files changed

+2743
-1747
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+2743
-1747
lines changed

.github/workflows/playwright.yml renamed to .github/workflows/playwright-chromium.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
name: Playwright Tests
1+
name: Playwright Tests on Chromium
22
on: [push, pull_request]
33
jobs:
4-
test:
4+
test-chromium:
55
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
6+
name: Chromium (${{ matrix.adapter }})
67
timeout-minutes: 15
78
runs-on: ubuntu-24.04
89
strategy:
@@ -29,12 +30,6 @@ jobs:
2930
- name: Build Inertia
3031
run: pnpm -r --filter ./packages/core --filter ./packages/${{ matrix.adapter }}* build
3132

32-
- name: Type-check test-app
33-
run: cd packages/${{ matrix.adapter == 'vue' && 'vue3' || matrix.adapter }}/test-app && pnpm run type-check
34-
35-
- name: ESLint test-app
36-
run: cd packages/${{ matrix.adapter == 'vue' && 'vue3' || matrix.adapter }}/test-app && pnpm run lint
37-
3833
- name: Install Playwright Browsers
3934
run: pnpm playwright install chromium
4035

@@ -45,5 +40,5 @@ jobs:
4540
if: failure()
4641
uses: actions/upload-artifact@v4
4742
with:
48-
name: playwright-failure-screenshots-${{ matrix.adapter }}
43+
name: playwright-failure-screenshots-${{ matrix.adapter }}-chromium
4944
path: test-results
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Playwright Tests on WebKit
2+
on: [push, pull_request]
3+
jobs:
4+
test-webkit:
5+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
6+
name: WebKit (${{ matrix.adapter }} - shard ${{ matrix.shard }} of 4)
7+
timeout-minutes: 15
8+
runs-on: macos-15
9+
strategy:
10+
matrix:
11+
adapter: ['vue', 'react', 'svelte']
12+
shard: ['1', '2', '3', '4']
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Install pnpm
18+
uses: pnpm/action-setup@v3
19+
with:
20+
version: 10
21+
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: 22.14
26+
cache: pnpm
27+
28+
- name: Install dependencies
29+
run: pnpm install
30+
31+
- name: Build Inertia
32+
run: pnpm -r --filter ./packages/core --filter ./packages/${{ matrix.adapter }}* build
33+
34+
- name: Install Playwright Browsers
35+
run: pnpm playwright install webkit
36+
37+
- name: Run Playwright Tests
38+
run: pnpm test:${{ matrix.adapter }} --webkit --shard=${{ matrix.shard }}/4
39+
40+
- name: Upload failure screenshots
41+
if: failure()
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: playwright-failure-screenshots-${{ matrix.adapter }}-webkit-shard-${{ matrix.shard }}
45+
path: test-results
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Test App Quality
2+
on: [push, pull_request]
3+
jobs:
4+
test-app-quality:
5+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
6+
timeout-minutes: 15
7+
runs-on: ubuntu-24.04
8+
strategy:
9+
matrix:
10+
adapter: ['vue', 'react', 'svelte']
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Install pnpm
16+
uses: pnpm/action-setup@v3
17+
with:
18+
version: 10
19+
20+
- name: Setup Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: 22.14
24+
cache: pnpm
25+
26+
- name: Install dependencies
27+
run: pnpm install
28+
29+
- name: Build Inertia
30+
run: pnpm -r --filter ./packages/core --filter ./packages/${{ matrix.adapter }}* build
31+
32+
- name: Type-check test-app
33+
run: cd packages/${{ matrix.adapter == 'vue' && 'vue3' || matrix.adapter }}/test-app && pnpm run type-check
34+
35+
- name: ESLint test-app
36+
run: cd packages/${{ matrix.adapter == 'vue' && 'vue3' || matrix.adapter }}/test-app && pnpm run lint

CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
For changes prior to v1.0.0, see the [legacy releases](https://legacy.inertiajs.com/releases).
99

10-
## [Unreleased](https://github.com/inertiajs/inertia/compare/v2.2.15...master)
10+
## [Unreleased](https://github.com/inertiajs/inertia/compare/v2.2.16...master)
1111

1212
- Nothing yet
1313

14+
## [v2.2.16](https://github.com/inertiajs/inertia/compare/v2.2.15...v2.2.16) - 2025-11-13
15+
16+
### What's Changed
17+
18+
* Added test for `defaultValue` in Form component by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2685
19+
* Prevent navigation on right-click on `<Link>` with `prefetch="click"` by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2676
20+
* Export page component type for React adapter by [@skryukov](https://github.com/skryukov) in https://github.com/inertiajs/inertia/pull/2691
21+
* Switch `useContext` to `use` in `usePage()` hook by [@HichemTab-tech](https://github.com/HichemTab-tech) in https://github.com/inertiajs/inertia/pull/2680
22+
* Improve serialization in `formDataToObject()` when mixing numeric and non-numeric object keys by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2692
23+
* Fix `InfiniteScroll` scroll preservation by [@skryukov](https://github.com/skryukov) in https://github.com/inertiajs/inertia/pull/2689
24+
* Export Inertia `App` component by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2695
25+
* Ignore `preserveScroll` and `preserveState` when finding cached response by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2694
26+
* Upgrade Express server for test apps to v5 by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2693
27+
* Add WebKit browser testing to CI with Safari compatibility fixes by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia/pull/2696
28+
* Bump symfony/http-foundation from 7.3.4 to 7.3.7 in /playgrounds/vue3 by [@dependabot](https://github.com/dependabot)[bot] in https://github.com/inertiajs/inertia/pull/2697
29+
* Fix array keys misalignment in form data and query by [@skryukov](https://github.com/skryukov) in https://github.com/inertiajs/inertia/pull/2690
30+
31+
**Full Changelog**: https://github.com/inertiajs/inertia/compare/v2.2.15...v2.2.16
32+
1433
## [v2.2.15](https://github.com/inertiajs/inertia/compare/v2.2.14...v2.2.15) - 2025-10-30
1534

1635
### What's Changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
"type-check:test-app:react": "cd packages/react/test-app && pnpm run type-check",
2323
"type-check:test-app:svelte": "cd packages/svelte/test-app && pnpm run type-check",
2424
"type-check:test-app:vue": "cd packages/vue3/test-app && pnpm run type-check",
25-
"test:react": "PACKAGE=react playwright test",
26-
"test:svelte": "PACKAGE=svelte playwright test",
27-
"test:vue": "PACKAGE=vue3 playwright test",
25+
"test:react": "PACKAGE=react node playwright.js",
26+
"test:svelte": "PACKAGE=svelte node playwright.js",
27+
"test:vue": "PACKAGE=vue3 node playwright.js",
2828
"playground:react": "cd playgrounds/react && ./init.sh && composer run dev",
2929
"playground:svelte4": "cd playgrounds/svelte4 && ./init.sh && composer run dev",
3030
"playground:svelte5": "cd playgrounds/svelte5 && ./init.sh && composer run dev",
@@ -39,6 +39,6 @@
3939
"prettier-plugin-tailwindcss": "^0.6.14"
4040
},
4141
"optionalDependencies": {
42-
"@rollup/rollup-linux-x64-gnu": "^4.52.5"
42+
"@rollup/rollup-linux-x64-gnu": "^4.53.2"
4343
}
4444
}

packages/core/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@inertiajs/core",
3-
"version": "2.2.15",
3+
"version": "2.2.16",
44
"license": "MIT",
55
"description": "A framework for creating server-driven single page apps.",
66
"contributors": [
@@ -54,8 +54,8 @@
5454
},
5555
"dependencies": {
5656
"@types/lodash-es": "^4.17.12",
57-
"axios": "^1.13.1",
5857
"laravel-precognition": "^0.7.3",
58+
"axios": "^1.13.2",
5959
"lodash-es": "^4.17.21",
6060
"qs": "^6.14.0"
6161
},
@@ -66,7 +66,7 @@
6666
"@types/qs": "^6.14.0",
6767
"es-check": "^9.4.4",
6868
"esbuild": "^0.25.12",
69-
"esbuild-node-externals": "^1.18.0",
69+
"esbuild-node-externals": "^1.19.1",
7070
"typescript": "^5.9.3"
7171
}
72-
}
72+
}

packages/core/src/domUtils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
const elementInViewport = (el: HTMLElement) => {
2+
if (el.offsetParent === null) {
3+
// Element is not participating in layout (e.g., display: none)
4+
return false
5+
}
6+
27
const rect = el.getBoundingClientRect()
38

49
// We check both vertically and horizontally for containers that scroll in either direction
@@ -73,9 +78,13 @@ export const getScrollableParent = (element: HTMLElement | null): HTMLElement |
7378
}
7479

7580
export const getElementsInViewportFromCollection = (
76-
referenceElement: HTMLElement,
7781
elements: HTMLElement[],
82+
referenceElement?: HTMLElement,
7883
): HTMLElement[] => {
84+
if (!referenceElement) {
85+
return elements.filter((element) => elementInViewport(element))
86+
}
87+
7988
const referenceIndex = elements.indexOf(referenceElement)
8089
const upwardElements: HTMLElement[] = []
8190
const downwardElements: HTMLElement[] = []
@@ -105,3 +114,13 @@ export const getElementsInViewportFromCollection = (
105114
// Reverse upward elements to maintain DOM order, then append downward elements
106115
return [...upwardElements.reverse(), ...downwardElements]
107116
}
117+
118+
export const requestAnimationFrame = (cb: () => void, times: number = 1): void => {
119+
window.requestAnimationFrame(() => {
120+
if (times > 1) {
121+
requestAnimationFrame(cb, times - 1)
122+
} else {
123+
cb()
124+
}
125+
})
126+
}

packages/core/src/files.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { FormDataConvertible, RequestPayload } from './types'
22

3+
export const isFile = (value: unknown): boolean =>
4+
(typeof File !== 'undefined' && value instanceof File) ||
5+
value instanceof Blob ||
6+
(typeof FileList !== 'undefined' && value instanceof FileList && value.length > 0)
7+
38
export function hasFiles(data: RequestPayload | FormDataConvertible): boolean {
49
return (
5-
data instanceof File ||
6-
data instanceof Blob ||
7-
(data instanceof FileList && data.length > 0) ||
10+
isFile(data) ||
811
(data instanceof FormData && Array.from(data.values()).some((value) => hasFiles(value))) ||
912
(typeof data === 'object' && data !== null && Object.values(data).some((value) => hasFiles(value)))
1013
)

packages/core/src/formData.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
1-
import { FormDataConvertible } from './types'
1+
import type { FormDataConvertible, QueryStringArrayFormatOption } from './types'
22

33
export const isFormData = (value: any): value is FormData => value instanceof FormData
44

55
export function objectToFormData(
66
source: Record<string, FormDataConvertible>,
77
form: FormData = new FormData(),
88
parentKey: string | null = null,
9+
queryStringArrayFormat: QueryStringArrayFormatOption = 'brackets',
910
): FormData {
1011
source = source || {}
1112

1213
for (const key in source) {
1314
if (Object.prototype.hasOwnProperty.call(source, key)) {
14-
append(form, composeKey(parentKey, key), source[key])
15+
append(form, composeKey(parentKey, key, 'indices'), source[key], queryStringArrayFormat)
1516
}
1617
}
1718

1819
return form
1920
}
2021

21-
function composeKey(parent: string | null, key: string): string {
22-
return parent ? parent + '[' + key + ']' : key
22+
function composeKey(parent: string | null, key: string, format: QueryStringArrayFormatOption): string {
23+
if (!parent) {
24+
return key
25+
}
26+
27+
return format === 'brackets' ? `${parent}[]` : `${parent}[${key}]`
2328
}
2429

25-
function append(form: FormData, key: string, value: FormDataConvertible): void {
30+
function append(form: FormData, key: string, value: FormDataConvertible, format: QueryStringArrayFormatOption): void {
2631
if (Array.isArray(value)) {
27-
return Array.from(value.keys()).forEach((index) => append(form, composeKey(key, index.toString()), value[index]))
32+
return Array.from(value.keys()).forEach((index) =>
33+
append(form, composeKey(key, index.toString(), format), value[index], format),
34+
)
2835
} else if (value instanceof Date) {
2936
return form.append(key, value.toISOString())
3037
} else if (value instanceof File) {
@@ -41,5 +48,5 @@ function append(form: FormData, key: string, value: FormDataConvertible): void {
4148
return form.append(key, '')
4249
}
4350

44-
objectToFormData(value, form, key)
51+
objectToFormData(value, form, key, format)
4552
}

0 commit comments

Comments
 (0)