mirror of
https://github.com/home-assistant/iOS.git
synced 2026-04-13 01:12:53 -05:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> Introduce keyboard handling for WKWebView: add WebViewKeyboardAvoidance helpers for animation metrics and overlap calculation, a WKWebView extension to run a JS command that scrolls the focused editable element into view, and a managed bottom constraint for web views so the view is animated above the keyboard. Wire up keyboard observers and DispatchWorkItem scheduling/cleanup in WebViewController and OnboardingAuthLoginViewController, and add the scrollFocusedElementIntoView JavaScript to WebViewJavascriptCommands. Add unit tests verifying the bottom constraint creation and JS contents. ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. -->
109 lines
2.8 KiB
Swift
109 lines
2.8 KiB
Swift
import Foundation
|
|
|
|
enum WebViewJavascriptCommands {
|
|
static var searchEntitiesKeyEvent = """
|
|
var event = new KeyboardEvent('keydown', {
|
|
key: 'e',
|
|
code: 'KeyE',
|
|
keyCode: 69,
|
|
which: 69,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
"""
|
|
|
|
static var quickSearchKeyEvent = """
|
|
var event = new KeyboardEvent('keydown', {
|
|
key: 'k',
|
|
code: 'KeyK',
|
|
keyCode: 75,
|
|
which: 75,
|
|
metaKey: true,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
"""
|
|
|
|
static var searchDevicesKeyEvent = """
|
|
var event = new KeyboardEvent('keydown', {
|
|
key: 'd',
|
|
code: 'KeyD',
|
|
keyCode: 68,
|
|
which: 68,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
"""
|
|
|
|
static var searchCommandsKeyEvent = """
|
|
var event = new KeyboardEvent('keydown', {
|
|
key: 'c',
|
|
code: 'KeyC',
|
|
keyCode: 67,
|
|
which: 67,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
"""
|
|
|
|
static var assistKeyEvent = """
|
|
var event = new KeyboardEvent('keydown', {
|
|
key: 'a',
|
|
code: 'KeyA',
|
|
keyCode: 65,
|
|
which: 65,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
"""
|
|
|
|
static var scrollFocusedElementIntoView = """
|
|
(function() {
|
|
function activeElement(root) {
|
|
let element = root.activeElement;
|
|
|
|
while (element && element.shadowRoot && element.shadowRoot.activeElement) {
|
|
element = element.shadowRoot.activeElement;
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
const element = activeElement(document);
|
|
if (!element) {
|
|
return false;
|
|
}
|
|
|
|
const tagName = element.tagName ? element.tagName.toUpperCase() : '';
|
|
const isEditable = element.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName);
|
|
if (!isEditable) {
|
|
return false;
|
|
}
|
|
|
|
const viewport = window.visualViewport;
|
|
const viewportHeight = viewport ? viewport.height : window.innerHeight;
|
|
const viewportOffsetTop = viewport ? viewport.offsetTop : 0;
|
|
const padding = 24;
|
|
const rect = element.getBoundingClientRect();
|
|
const visibleTop = viewportOffsetTop + padding;
|
|
const visibleBottom = viewportOffsetTop + viewportHeight - padding;
|
|
|
|
if (rect.top >= visibleTop && rect.bottom <= visibleBottom) {
|
|
return true;
|
|
}
|
|
|
|
element.scrollIntoView({
|
|
block: 'center',
|
|
inline: 'nearest',
|
|
behavior: 'auto'
|
|
});
|
|
return true;
|
|
})();
|
|
"""
|
|
}
|