From 460fb3cb72910646d5669e82aefe95296a340214 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 8 Oct 2025 16:34:18 -0400 Subject: [PATCH 1/3] Add auto-focus --- .../Sources/EditorConfiguration.swift | 16 +++++++ .../Sources/EditorViewController.swift | 45 ++++++++----------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift index 3284af0b..91ee1cfd 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift @@ -16,6 +16,9 @@ public struct EditorConfiguration: Sendable { public let shouldUsePlugins: Bool /// Toggles visibility of the title field public let shouldHideTitle: Bool + /// If enabled, the editor automatically becomes focused when initialized + /// with an empty content. + public let shouldAutoFocus: Bool /// Root URL for the site public let siteURL: String /// Root URL for the site API @@ -42,6 +45,7 @@ public struct EditorConfiguration: Sendable { shouldUseThemeStyles: Bool, shouldUsePlugins: Bool, shouldHideTitle: Bool, + shouldAutoFocus: Bool, siteURL: String, siteApiRoot: String, siteApiNamespace: [String], @@ -58,6 +62,7 @@ public struct EditorConfiguration: Sendable { self.shouldUseThemeStyles = shouldUseThemeStyles self.shouldUsePlugins = shouldUsePlugins self.shouldHideTitle = shouldHideTitle + self.shouldAutoFocus = shouldAutoFocus self.siteURL = siteURL self.siteApiRoot = siteApiRoot self.siteApiNamespace = siteApiNamespace @@ -77,6 +82,7 @@ public struct EditorConfiguration: Sendable { shouldUseThemeStyles: shouldUseThemeStyles, shouldUsePlugins: shouldUsePlugins, shouldHideTitle: shouldHideTitle, + shouldAutoFocus: shouldAutoFocus, siteURL: siteURL, siteApiRoot: siteApiRoot, siteApiNamespace: siteApiNamespace, @@ -107,6 +113,7 @@ public struct EditorConfigurationBuilder { private var shouldUseThemeStyles: Bool private var shouldUsePlugins: Bool private var shouldHideTitle: Bool + private var shouldAutoFocus: Bool private var siteURL: String private var siteApiRoot: String private var siteApiNamespace: [String] @@ -124,6 +131,7 @@ public struct EditorConfigurationBuilder { shouldUseThemeStyles: Bool = false, shouldUsePlugins: Bool = false, shouldHideTitle: Bool = false, + shouldAutoFocus: Bool = true, siteURL: String = "", siteApiRoot: String = "", siteApiNamespace: [String] = [], @@ -140,6 +148,7 @@ public struct EditorConfigurationBuilder { self.shouldUseThemeStyles = shouldUseThemeStyles self.shouldUsePlugins = shouldUsePlugins self.shouldHideTitle = shouldHideTitle + self.shouldAutoFocus = shouldAutoFocus self.siteURL = siteURL self.siteApiRoot = siteApiRoot self.siteApiNamespace = siteApiNamespace @@ -192,6 +201,12 @@ public struct EditorConfigurationBuilder { return copy } + public func setShouldAutoFocus(_ shouldAutoFocus: Bool) -> EditorConfigurationBuilder { + var copy = self + copy.shouldAutoFocus = shouldAutoFocus + return copy + } + public func setSiteUrl(_ siteUrl: String) -> EditorConfigurationBuilder { var copy = self copy.siteURL = siteUrl @@ -271,6 +286,7 @@ public struct EditorConfigurationBuilder { shouldUseThemeStyles: shouldUseThemeStyles, shouldUsePlugins: shouldUsePlugins, shouldHideTitle: shouldHideTitle, + shouldAutoFocus: shouldAutoFocus, siteURL: siteURL, siteApiRoot: siteApiRoot, siteApiNamespace: siteApiNamespace, diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index 25392ba0..dfa96032 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -86,33 +86,6 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro setUpEditor() loadEditor() } - - // TODO: register it when editor is loaded -// service.$rawBlockTypesResponseData.compactMap({ $0 }).sink { [weak self] data in -// guard let self else { return } -// assert(Thread.isMainThread) -// -// }.store(in: &cancellables) - } - - // TODO: move - private func registerBlockTypes(data: Data) async { - guard let string = String(data: data, encoding: .utf8), - let escapedString = string.addingPercentEncoding(withAllowedCharacters: .alphanumerics) else { - assertionFailure("invalid block types") - return - } - do { - // TODO: simplify this - try await webView.evaluateJavaScript(""" - const blockTypes = JSON.parse(decodeURIComponent('\(escapedString)')); - editor.registerBlocks(blockTypes); - "done"; - """) - } catch { - NSLog("failed to register blocks \(error)") - // TOOD: relay to the client - } } private func setUpEditor() { @@ -347,6 +320,24 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro let duration = CFAbsoluteTimeGetCurrent() - timestampInit print("gutenbergkit-measure_editor-first-render:", duration) delegate?.editorDidLoad(self) + + if configuration.shouldAutoFocus, configuration.content.isEmpty { + autoFocusEditor() + } + } + + // MARK: - Helpers + + private func autoFocusEditor() { + evaluate(""" + (function() { + const editable = document.querySelector('[contenteditable="true"]'); + if (editable) { + editable.focus(); + editable.click(); + } + })(); + """) } // MARK: - Warmup From 63c1315a533f63d00aad2ebd65d04b44f47da95f Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Thu, 9 Oct 2025 07:58:55 -0400 Subject: [PATCH 2/3] Remove from configuration and add focus to JS --- .../Sources/EditorConfiguration.swift | 16 ---------------- .../Sources/EditorViewController.swift | 18 ++---------------- src/components/editor/use-host-bridge.js | 10 ++++++++++ 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift index 91ee1cfd..3284af0b 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorConfiguration.swift @@ -16,9 +16,6 @@ public struct EditorConfiguration: Sendable { public let shouldUsePlugins: Bool /// Toggles visibility of the title field public let shouldHideTitle: Bool - /// If enabled, the editor automatically becomes focused when initialized - /// with an empty content. - public let shouldAutoFocus: Bool /// Root URL for the site public let siteURL: String /// Root URL for the site API @@ -45,7 +42,6 @@ public struct EditorConfiguration: Sendable { shouldUseThemeStyles: Bool, shouldUsePlugins: Bool, shouldHideTitle: Bool, - shouldAutoFocus: Bool, siteURL: String, siteApiRoot: String, siteApiNamespace: [String], @@ -62,7 +58,6 @@ public struct EditorConfiguration: Sendable { self.shouldUseThemeStyles = shouldUseThemeStyles self.shouldUsePlugins = shouldUsePlugins self.shouldHideTitle = shouldHideTitle - self.shouldAutoFocus = shouldAutoFocus self.siteURL = siteURL self.siteApiRoot = siteApiRoot self.siteApiNamespace = siteApiNamespace @@ -82,7 +77,6 @@ public struct EditorConfiguration: Sendable { shouldUseThemeStyles: shouldUseThemeStyles, shouldUsePlugins: shouldUsePlugins, shouldHideTitle: shouldHideTitle, - shouldAutoFocus: shouldAutoFocus, siteURL: siteURL, siteApiRoot: siteApiRoot, siteApiNamespace: siteApiNamespace, @@ -113,7 +107,6 @@ public struct EditorConfigurationBuilder { private var shouldUseThemeStyles: Bool private var shouldUsePlugins: Bool private var shouldHideTitle: Bool - private var shouldAutoFocus: Bool private var siteURL: String private var siteApiRoot: String private var siteApiNamespace: [String] @@ -131,7 +124,6 @@ public struct EditorConfigurationBuilder { shouldUseThemeStyles: Bool = false, shouldUsePlugins: Bool = false, shouldHideTitle: Bool = false, - shouldAutoFocus: Bool = true, siteURL: String = "", siteApiRoot: String = "", siteApiNamespace: [String] = [], @@ -148,7 +140,6 @@ public struct EditorConfigurationBuilder { self.shouldUseThemeStyles = shouldUseThemeStyles self.shouldUsePlugins = shouldUsePlugins self.shouldHideTitle = shouldHideTitle - self.shouldAutoFocus = shouldAutoFocus self.siteURL = siteURL self.siteApiRoot = siteApiRoot self.siteApiNamespace = siteApiNamespace @@ -201,12 +192,6 @@ public struct EditorConfigurationBuilder { return copy } - public func setShouldAutoFocus(_ shouldAutoFocus: Bool) -> EditorConfigurationBuilder { - var copy = self - copy.shouldAutoFocus = shouldAutoFocus - return copy - } - public func setSiteUrl(_ siteUrl: String) -> EditorConfigurationBuilder { var copy = self copy.siteURL = siteUrl @@ -286,7 +271,6 @@ public struct EditorConfigurationBuilder { shouldUseThemeStyles: shouldUseThemeStyles, shouldUsePlugins: shouldUsePlugins, shouldHideTitle: shouldHideTitle, - shouldAutoFocus: shouldAutoFocus, siteURL: siteURL, siteApiRoot: siteApiRoot, siteApiNamespace: siteApiNamespace, diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index dfa96032..a92eae01 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -321,25 +321,11 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro print("gutenbergkit-measure_editor-first-render:", duration) delegate?.editorDidLoad(self) - if configuration.shouldAutoFocus, configuration.content.isEmpty { - autoFocusEditor() + if configuration.content.isEmpty { + evaluate("editor.focus();") } } - // MARK: - Helpers - - private func autoFocusEditor() { - evaluate(""" - (function() { - const editable = document.querySelector('[contenteditable="true"]'); - if (editable) { - editable.focus(); - editable.click(); - } - })(); - """) - } - // MARK: - Warmup /// Calls this at any moment before showing the actual editor. The warmup diff --git a/src/components/editor/use-host-bridge.js b/src/components/editor/use-host-bridge.js index de4b7d40..a5e0a6d5 100644 --- a/src/components/editor/use-host-bridge.js +++ b/src/components/editor/use-host-bridge.js @@ -108,6 +108,16 @@ export function useHostBridge( post, editorRef ) { ); }; + window.editor.focus = () => { + const editable = document.querySelector( + '[contenteditable="true"]' + ); + if ( editable ) { + editable.focus(); + editable.click(); + } + }; + window.editor.appendTextAtCursor = ( text ) => { const selectedBlockClientId = getSelectedBlockClientId(); From 3c5ca0035ca068c245f3afbf480763ed845c2e47 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Thu, 9 Oct 2025 10:31:04 -0400 Subject: [PATCH 3/3] feat: Auto-focus Android editor when content is empty Match the iOS UX. --- .../java/org/wordpress/gutenberg/GutenbergView.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index d30b18cc..3977977e 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -10,6 +10,7 @@ import android.os.Looper import android.util.AttributeSet import android.util.Log import android.view.View +import android.view.inputmethod.InputMethodManager import android.webkit.ConsoleMessage import android.webkit.CookieManager import android.webkit.JavascriptInterface @@ -477,6 +478,18 @@ class GutenbergView : WebView { .alpha(1f) .setDuration(300) .start() + + if (configuration.content.isEmpty()) { + // Focus the editor content + this.evaluateJavascript("editor.focus();", null) + + // Request focus on the WebView and show the soft keyboard + handler.postDelayed({ + this.requestFocus() + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) + }, 100) + } } } }