fix(ios): stabilize keyboard toolbar, image picker, and scrolling (#15182)

## Summary
- keep the editor active when iOS keyboard toolbar interactions move
focus into range-sync excluded widgets
- use the native iOS image picker/source sheet and sync native
presentation with the app theme
- pin `ListViewKit` to `1.1.6` so the iOS workspace resolves with Xcode
16.3
- restore vertical scrolling in the iOS `WKWebView` by removing the
global `contentOffset` reset while preserving zoom prevention

## Test plan
- [x] `yarn vitest --run --config \"vitest.config.ts\"
--browser.enabled=false \"src/__tests__/inline/active.unit.spec.ts\"`
- [x] `LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 xcodebuild
-resolvePackageDependencies -workspace
\"packages/frontend/apps/ios/App/App.xcworkspace\" -scheme \"App\"`
- [x] `xcodebuild -workspace \"App.xcworkspace\" -scheme \"App\"
-destination \"generic/platform=iOS Simulator\" build
CODE_SIGNING_ALLOWED=NO ONLY_ACTIVE_ARCH=YES ARCHS=arm64`
- [x] Xcode build validation for the updated PR branch
This commit is contained in:
keepClamDown
2026-07-09 11:11:55 +08:00
committed by GitHub
parent 9f8e5c0eb3
commit a868f54eeb
19 changed files with 1095 additions and 28 deletions
@@ -14,8 +14,14 @@ interface OpenFilePickerOptions {
multiple?: boolean | undefined;
}
export type NativeImageFilesPicker = () => Promise<File[] | null>;
const NATIVE_IMAGE_FILES_PICKER_KEY =
'__AFFINE_NATIVE_IMAGE_FILES_PICKER__' as const;
declare global {
interface Window {
[NATIVE_IMAGE_FILES_PICKER_KEY]?: NativeImageFilesPicker;
// Window API: showOpenFilePicker
showOpenFilePicker?: (
options?: OpenFilePickerOptions
@@ -29,6 +35,17 @@ declare global {
}
}
export function registerNativeImageFilesPicker(
picker: NativeImageFilesPicker | null
) {
if (picker) {
window[NATIVE_IMAGE_FILES_PICKER_KEY] = picker;
return;
}
delete window[NATIVE_IMAGE_FILES_PICKER_KEY];
}
// Minimal polyfill for FileSystemDirectoryHandle to iterate over files
interface FileSystemDirectoryHandle {
kind: 'directory';
@@ -195,7 +212,18 @@ export async function snapshotFile(file: File, relativePath?: string) {
}
export async function snapshotFiles(files: File[]) {
return Promise.all(files.map(file => snapshotFile(file)));
const results = await Promise.allSettled(
files.map(file => snapshotFile(file))
);
const snapshots: File[] = [];
for (const result of results) {
if (result.status === 'fulfilled') {
snapshots.push(result.value);
} else {
console.error('Failed to snapshot file', result.reason);
}
}
return snapshots;
}
function canUseFileSystemAccessAPI(
@@ -332,7 +360,11 @@ export async function openDirectory(
if (fileHandle.getFile) {
const file = await fileHandle.getFile();
if (options?.snapshot) {
files.push(await snapshotFile(file, relativePath));
try {
files.push(await snapshotFile(file, relativePath));
} catch (error) {
console.error('Failed to snapshot file', relativePath, error);
}
} else {
Object.defineProperty(file, 'webkitRelativePath', {
value: relativePath,
@@ -410,6 +442,15 @@ export async function openSingleFileWith(
}
export async function getImageFilesFromLocal() {
const nativePicker = window[NATIVE_IMAGE_FILES_PICKER_KEY];
if (nativePicker) {
try {
return (await nativePicker()) ?? [];
} catch (err) {
console.error(err);
}
}
const files = await openFilesWith('Images');
return files ?? [];
}