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
@@ -0,0 +1,66 @@
/**
* @vitest-environment happy-dom
*/
import { afterEach, describe, expect, test } from 'vitest';
import { shouldDeactivateEditorOnFocusOut } from '../../inline/range/active.js';
import { RANGE_SYNC_EXCLUDE_ATTR } from '../../inline/range/consts.js';
afterEach(() => {
document.body.replaceChildren();
});
describe('editor active helpers', () => {
test('keeps editor active when focus moves into an excluded widget', () => {
const host = document.createElement('editor-host');
const paragraph = document.createElement('div');
host.append(paragraph);
const widgetRoot = document.createElement('div');
widgetRoot.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
const widgetButton = document.createElement('button');
widgetRoot.append(widgetButton);
document.body.append(host, widgetRoot);
expect(shouldDeactivateEditorOnFocusOut(host, widgetButton)).toBe(false);
});
test('keeps editor active when focus stays inside the editor host', () => {
const host = document.createElement('editor-host');
const input = document.createElement('input');
host.append(input);
document.body.append(host);
expect(shouldDeactivateEditorOnFocusOut(host, input)).toBe(false);
});
test('deactivates editor when focus moves to a regular external control', () => {
const host = document.createElement('editor-host');
const paragraph = document.createElement('div');
host.append(paragraph);
const externalButton = document.createElement('button');
document.body.append(host, externalButton);
expect(shouldDeactivateEditorOnFocusOut(host, externalButton)).toBe(true);
});
test('keeps editor active on focusout when related target is null', () => {
// Focus leaving to nowhere (e.g. block-level selection) must not deactivate
// the editor via `focusout`; the host `blur` handler owns that case.
const host = document.createElement('editor-host');
document.body.append(host);
expect(shouldDeactivateEditorOnFocusOut(host, null)).toBe(false);
});
test('keeps editor active when the related target is not a DOM node', () => {
const host = document.createElement('editor-host');
document.body.append(host);
expect(shouldDeactivateEditorOnFocusOut(host, new EventTarget())).toBe(
false
);
});
});