fix(ios): keyboard overlay exit + build chunk fix#74
Draft
moodyjmz wants to merge 2 commits into
Draft
Conversation
When the user taps Done to exit edit mode, the iOS contenteditable overlay (used for keyboard persistence) was not being deactivated. Any subsequent tap in the document refocused the contenteditable and showed the keyboard unexpectedly. Call preventVirtualKeyboard_Hard() in turnOnViewerMode to disable the overlay, mirroring the showKeyboard() call in turnOffViewerMode. Uses window.AscCommon.AscBrowser.isSafariMobile rather than Device.ios as Device is minified in the production bundle. Note: dist/js/app.js is gitignored. The deployed bundle was patched directly from the original production image. A full rebuild via vendor/framework7-react/build (deploy-word) produces an extra CSS chunk (apps_..._less.js) that breaks the FAB on all platforms — this build config issue needs investigation before the source change can be rebuilt cleanly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
Dynamic import('./less/app.less') caused webpack to emit a separate
apps_documenteditor_mobile_src_less_app_less.js chunk. This chunk is
not referenced by index.html, so deploying app.js without it broke
CSS loading entirely — no FAB, black document area on all platforms.
Change to a static import so MiniCssExtractPlugin extracts the LESS
to the main CSS output file with no separate JS chunk. A clean build
now produces only app.js + css/app.[hash].css, matching the original
deployment structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two fixes for the iOS keyboard integration:
Keyboard persists after exiting edit mode —
turnOnViewerMode()was not disabling the iOS contenteditable overlay, so any subsequent tap refocused it and showed keyboard. AddspreventVirtualKeyboard_Hard()on exit.Spurious webpack chunk — dynamic
import('./less/app.less')was creating a separate JS chunk not referenced byindex.html. CSS loading silently broke (no FAB, black document area) when deployingapp.jsalone. Changed to static import.Painful level of detail
iOS Keyboard Investigation — EuroOffice Mobile Editor
Status: RESOLVED for browser / partially open for Nextcloud iOS app (2026-06-02)
Keyboard shows, stays up, cursor moves on tap, hides when exiting edit mode — verified in Mobile Safari and desktop Chrome mobile emulation via the same-origin proxy.
Nextcloud iOS app: keyboard fix works, but a separate FAB (edit button) regression exists with the new
app.jsbuild — see "Known Regressions" below.iOS Simulator — software keyboard may be off
If the keyboard shows only a thin bar / tick mark (~20px) and immediately dismisses, check whether the Simulator software keyboard is enabled:
Hardware → Keyboard → Toggle Software Keyboard(or⌘K)Typing slowness — re-test after keyboard fix
Earlier sessions noted "typing slowness" and attributed it to WebSocket round-trips. This should be re-tested: the keyboard was broken during those sessions (repeated dismiss/re-show cycles would cause erratic input processing). With the fix in place the slowness may be gone.
Problem
On iOS (Mobile Safari, WKWebView), tapping in the document canvas in edit mode caused the keyboard to briefly flash then dismiss.
Environment
http://192.168.178.126:8081/ds/http://192.168.178.126:8081Root Cause (confirmed)
iOS evaluates the gesture at touchend (~300ms). If the tap started on a canvas (not a text input), iOS decides "not a text input tap" → native UIKit keyboard dismissal (empty JS stack trace on blur).
The fix: place a transparent
contenteditablediv covering the entire canvas area so every user tap lands on a text input element. iOS then treats it as a text-input tap and keeps the keyboard.Critical detail:
opacity:0causes iOS to exclude elements from hit-testing entirely — iOS still dismisses keyboard even for a tap on anopacity:0contenteditable. Must usecolor:transparent; caret-color:transparent; background:transparent(opacity:1) instead.Solution — sdkjs changes (
fix/ios-wkwebview-keyboardbranch)common/browser.js(commit 745db40)Added
AscBrowser.isSafariMobile = (isSafari || isAppleDevices) && isMobileto cover iOS WKWebView apps (Nextcloud iOS) that omit "safari" from their UA.common/text_input2.jsElementType switch: For
isSafariMobile, useContentEditableDivinstead ofTextArea:CSS: Must use
color:transparentNOTopacity:0:DOM placement: Append inside
oHtmlParent(=id_main_view) NOToHtmlParent.parentNode. This makes the overlay a direct sibling ofid_viewer_overlayat the correct z-index level.Touch forwarding: Dispatch
PointerEvent(NOTTouchEvent) toid_viewer_overlay. The mobile touch manager registerspointerdown/pointermove/pointeruphandlers there (becauseisUsePointerEvents = truefor modern iOS Safari).showKeyboard(): For iOS ContentEditableDiv, enable overlay via
setReadOnlyWrapper(false)and return without callingfocusHtmlElement()— programmatic focus is dismissed by UIKit outside a direct user gesture.setInterfaceEnableKeyEvents guard: Ignore
falsecalls while contenteditable has focus — these come from Framework7 sheet/popover events, not real exit-edit-mode.setReadOnlyWrapper: For ContentEditableDiv, call
HtmlArea.blur()when disabling so iOS hides keyboard on edit mode exit.move(): Return early for ContentEditableDiv — full-canvas overlay doesn't need repositioning.
common/Scrolls/mobileTouchManagerBase.jsSuppress the bottom formatting sheet when keyboard is visible — the sheet would steal focus and dismiss keyboard:
Key Architecture Discoveries
DOM structure (word editor)
HtmlPage.initEventsMobile()(isUseOldMobileVersion=true path):area_id_main.style.zIndex = 10(viaTextBoxBackground.HtmlElement.parentNode.parentNode)MobileTouchManager.initEvents("area_id")— registers iScroll on area_idEvent routing
mainOnTouchStart/mainOnTouchEndare registered onid_viewer_overlayviapointerdown/pointerupisUsePointerEvents = truefor Safari ≥ 15 → touch manager uses pointer events, not touch eventsPointerEvent, notTouchEventisTrusted=falsebut no isTrusted check exists in the touch managerWhy cross-origin iframe was a red herring
iOS Safari does restrict keyboard in cross-origin iframes, but that only prevented document loading. The keyboard issue exists on same-origin too because the fundamental problem is the canvas tap not being recognized as a text-input tap.
Local Dev Proxy (same-origin setup)
iOS Safari blocks keyboard in cross-origin iframes. For local dev, proxy EuroOffice through Nextcloud:
File:
develop/setup/eo-proxy.conf(Apache config, mounted into nextcloud container)occ settings:
Persistence: Add to docker-compose.override.yml:
Then in the container:
a2enmod proxy proxy_http proxy_wstunnel headers && a2enconf eo-proxyNote:
X-Forwarded-Prefix: /dsis critical — without it, the DS constructs cache/Editor.bin URLs without the/dsprefix and they 404.Related Repos
iOS app —
ios-builds/iosContext doc:
/Users/jamesmanuel/ios-builds/ios/.claude/eurooffice.mdRelevant branches:
feature/eurooffice-fixes: double-tap guard (isNavigatingMetadata), spinner-on-exit fix,isScrollEnabled=true,isInspectable=trueNCViewerDirectEditingKeyboard.swift) — investigated but not needed with this fixweb-apps —
DocumentServer/web-appsEuroOffice-specific additions in
apps/documenteditor/mobile/src/:page/main.jsx:showKeyboard()called inturnOffViewerMode()for iOSview/Toolbar.jsx:⌨keyboard toggle button (ctx.HtmlArea.focus()inonTouchEnd) — this is what triggers keyboard on FAB tap since programmatic focus fromshowKeyboard()alone can't show keyboardsdkjs —
DocumentServer/sdkjsBranch:
fix/ios-wkwebview-keyboard2 commits ahead of main:
745db40—isSafariMobiledetectionApproaches That Failed
<a>elements — doesn't prevent iOS auto-focusWhat Remains
editorId === Word); cell and slide have different canvas z-index layouts and forwarding targets that have not been validatedKnown Regressions (2026-06-02)
web-apps new build breaks FAB in Nextcloud iOS app
Symptom: FAB (edit/pencil button) does not appear in the Nextcloud iOS native app when using the
app.jsproduced bynpm run deploy-wordfrom the current source. The FAB IS present in Mobile Safari and desktop Chrome. The original Docker image'sapp.js(with our Python-patched iOS fix) works correctly everywhere.What we ruled out:
window.native.editorConfig.mobileForceViewinjection —window.nativeisundefinedin the editor frame; not the causecustomization.mobile.forceViewfrom PHP — not set in the PHP appwindow.nativeinjection to work —window.nativeis still undefined with same-originchangeViewerMode,isForceView,isViewer,isEdit,p=FAB condition code is byte-for-byte identical between original and new bundleindex.htmldifference — only the CSS hash differs; identical otherwiseMost likely cause: Module execution order. The original bundle has async CSS chunk loading infrastructure (
__webpack_require__.e(526)) from the dynamicimport('./less/app.less'). Our static import change removed this async runtime. This shifts the order in which webpack modules initialise, causingchangeViewerMode(false)to fire at a different point in the lifecycle relative to React's first render. ThelocalStoragedebug patch (which would have confirmed the exact call stack) couldn't be applied because the debug bundle caused the iOS app to open documents in the browser instead.Current mitigation: The deployed
app.jsis the original Docker image bundle with our iOS fix Python-patched in. This works correctly on all tested platforms. The new build'sapp.jsis not deployed.Source state:
web-apps/apps/documenteditor/mobile/src/app.jshas the static import (import './less/app.less').dist/js/app.jsis the working patched bundle. These are intentionally out of sync.dist/is gitignored so this is safe; the discrepancy is documented here.To investigate properly:
localStoragedebug approach (patchingchangeViewerModeto persist call stacks) is the right tool — just needs to be deployed without the JWT error that caused browser redirectapp.jsback to dynamic import (import('./less/app.less')) and test whether the new build then works in the iOS app — if yes, confirms execution-order hypothesisNextcloud iOS app — direct edit mode / FAB visibility
When the Nextcloud iOS app opens a document, it starts with
isViewer = true(FAB shown) and the FAB transitions visible briefly, then may be removed ifchangeViewerMode(false)fires before the first paint. This is distinct from the build regression above and depends on the initialization race condition.The FAB is shown when:
a.isViewer && !u.disabledSettings && !u.disabledControls && !e.users.isDisconnected && d && a.isEdit && (!a.isProtected || ...). All these conditions are satisfied for normal editable documents.