UX: Set focus when launching composer on iOS (#9443)

This commit is contained in:
Penar Musaraj 2020-04-16 20:19:23 -04:00 committed by GitHub
parent 5a60a4233e
commit 6fad04635b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 80 additions and 72 deletions

View File

@ -54,6 +54,7 @@
//= require ./discourse/lib/autocomplete
//= require ./discourse/lib/after-transition
//= require ./discourse/lib/safari-hacks
//= require ./discourse/lib/put-cursor-at-end
//= require_tree ./discourse/adapters
//= require ./discourse/models/post-action-type
//= require ./discourse/models/post

View File

@ -150,28 +150,27 @@ export default Component.extend(KeyEnterEscape, {
},
viewportResize() {
const composerVH = window.visualViewport.height * 0.01;
const composerVH = window.visualViewport.height * 0.01,
doc = document.documentElement;
document.documentElement.style.setProperty(
"--composer-vh",
`${composerVH}px`
);
doc.style.setProperty("--composer-vh", `${composerVH}px`);
const viewportWindowDiff =
window.innerHeight - window.visualViewport.height;
viewportWindowDiff
? doc.classList.add("keyboard-visible")
: doc.classList.remove("keyboard-visible");
// adds bottom padding when using a hardware keyboard and the accessory bar is visible
// accessory bar height is 55px, using 75 allows a small buffer
if (viewportWindowDiff > 0 && viewportWindowDiff < 75) {
document.documentElement.style.setProperty(
if (viewportWindowDiff < 75) {
doc.style.setProperty(
"--composer-ipad-padding",
`${viewportWindowDiff}px`
);
} else {
document.documentElement.style.setProperty(
"--composer-ipad-padding",
"0px"
);
doc.style.setProperty("--composer-ipad-padding", "0px");
}
},

View File

@ -28,11 +28,10 @@ import {
tinyAvatar,
formatUsername,
clipboardData,
safariHacksDisabled,
caretPosition,
inCodeBlock,
putCursorAtEnd
inCodeBlock
} from "discourse/lib/utilities";
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
import {
validateUploadedFiles,
authorizesOneOrMoreImageExtensions,
@ -210,10 +209,7 @@ export default Component.extend({
}
// Focus on the body unless we have a title
if (
!this.get("composer.canEditTitle") &&
(!this.capabilities.isIOS || safariHacksDisabled())
) {
if (!this.get("composer.canEditTitle")) {
putCursorAtEnd(this.element.querySelector(".d-editor-input"));
}

View File

@ -7,7 +7,7 @@ import { lookupCache } from "pretty-text/oneboxer-cache";
import { ajax } from "discourse/lib/ajax";
import ENV from "discourse-common/config/environment";
import EmberObject from "@ember/object";
import { putCursorAtEnd } from "discourse/lib/utilities";
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
export default Component.extend({
classNames: ["title-input"],

View File

@ -1,7 +1,7 @@
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { putCursorAtEnd } from "discourse/lib/utilities";
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
export default Component.extend({
showSelector: true,

View File

@ -13,7 +13,7 @@ import discourseComputed, {
on
} from "discourse-common/utils/decorators";
import { getOwner } from "discourse-common/lib/get-owner";
import { escapeExpression, safariHacksDisabled } from "discourse/lib/utilities";
import { escapeExpression } from "discourse/lib/utilities";
import {
authorizesOneOrMoreExtensions,
uploadIcon
@ -136,10 +136,6 @@ export default Controller.extend({
"model.composeState"
)
focusTarget(replyingToTopic, creatingPM, usernames, composeState) {
if (this.capabilities.isIOS && !safariHacksDisabled()) {
return "none";
}
// Focus on usernames if it's blank or if it's just you
usernames = usernames || "";
if (

View File

@ -0,0 +1,16 @@
import positioningWorkaround from "discourse/lib/safari-hacks";
import { isAppleDevice } from "discourse/lib/utilities";
export default function(element) {
if (isAppleDevice() && positioningWorkaround.touchstartEvent) {
positioningWorkaround.touchstartEvent(element);
} else {
element.focus();
}
const len = element.value.length;
element.setSelectionRange(len, len);
// Scroll to the bottom, in case we're in a tall textarea
element.scrollTop = 999999;
}

View File

@ -84,6 +84,12 @@ function positioningWorkaround($fixedElement) {
return;
}
document.addEventListener("scroll", () => {
if (!caps.isIpadOS && workaroundActive) {
document.documentElement.scrollTop = 0;
}
});
const fixedElement = $fixedElement[0];
const oldHeight = fixedElement.style.height;
@ -94,17 +100,17 @@ function positioningWorkaround($fixedElement) {
if (workaroundActive) {
$("body").removeClass("ios-safari-composer-hacks");
$(window).scrollTop(originalScrollTop);
if (evt && evt.target) {
evt.target.removeEventListener("blur", blurred);
}
workaroundActive = false;
if (!iOSWithVisualViewport()) {
fixedElement.style.height = oldHeight;
later(() => $(fixedElement).removeClass("no-transition"), 500);
}
$(window).scrollTop(originalScrollTop);
if (evt) {
evt.target.removeEventListener("blur", blurred);
}
workaroundActive = false;
}
};
@ -113,8 +119,9 @@ function positioningWorkaround($fixedElement) {
// document.activeElement is also unreliable (iOS does not mark buttons as focused)
// so instead, we store the last touched element and check against it
// cancel blur event if user is:
// cancel blur event when:
// - switching to another iOS app
// - displaying title field
// - invoking a select-kit dropdown
// - invoking mentions
// - invoking emoji dropdown via : and hitting return
@ -123,6 +130,7 @@ function positioningWorkaround($fixedElement) {
if (
lastTouchedElement &&
(document.visibilityState === "hidden" ||
$fixedElement.hasClass("edit-title") ||
$(lastTouchedElement).hasClass("select-kit-header") ||
$(lastTouchedElement).closest(".autocomplete").length ||
(lastTouchedElement.nodeName.toLowerCase() === "textarea" &&
@ -140,6 +148,12 @@ function positioningWorkaround($fixedElement) {
var blurred = discourseDebounce(blurredNow, INPUT_DELAY);
var positioningHack = function(evt) {
let _this = this;
if (evt === undefined) {
evt = new CustomEvent("no-op");
}
// we need this, otherwise changing focus means we never clear
this.addEventListener("blur", blurred);
@ -150,12 +164,22 @@ function positioningWorkaround($fixedElement) {
.find(".select-kit > button.is-focused")
.removeClass("is-focused");
if ($(window).scrollTop() > 0) {
originalScrollTop = $(window).scrollTop();
const elementRect = _this.getBoundingClientRect();
if (elementRect.top > 100) {
// this tricks iOS safari into assuming input/textarea is at top of the viewport
// via https://stackoverflow.com/questions/38017771/mobile-safari-prevent-scroll-page-when-focus-on-input
_this.style.transform = "translateY(-400px)";
setTimeout(function() {
_this.style.transform = "none";
}, 30);
}
let delay = caps.isIpadOS ? 350 : 150;
setTimeout(function() {
if (iOSWithVisualViewport()) {
if (caps.isIpadOS && iOSWithVisualViewport()) {
// disable hacks when using a hardware keyboard
// by default, a hardware keyboard will show the keyboard accessory bar
// whose height is currently 55px (using 75 for a bit of a buffer)
@ -165,38 +189,14 @@ function positioningWorkaround($fixedElement) {
}
}
if (fixedElement.style.top === "0px") {
if (this !== document.activeElement) {
evt.preventDefault();
// this tricks safari into assuming current input is at top of the viewport
// via https://stackoverflow.com/questions/38017771/mobile-safari-prevent-scroll-page-when-focus-on-input
this.style.transform = "translateY(-200px)";
this.focus();
let _this = this;
setTimeout(function() {
_this.style.transform = "none";
}, 30);
}
return;
}
// don't trigger keyboard on disabled element (happens when a category is required)
if (this.disabled) {
if (_this.disabled) {
return;
}
$("body").addClass("ios-safari-composer-hacks");
$(window).scrollTop(0);
let i = 20;
let interval = setInterval(() => {
$(window).scrollTop(0);
if (i-- === 0) {
clearInterval(interval);
}
}, 10);
if (!iOSWithVisualViewport()) {
const height = calcHeight();
fixedElement.style.height = height + "px";
@ -204,9 +204,9 @@ function positioningWorkaround($fixedElement) {
}
evt.preventDefault();
this.focus();
_this.focus();
workaroundActive = true;
}, 350);
}, delay);
};
var lastTouched = function(evt) {
@ -230,6 +230,11 @@ function positioningWorkaround($fixedElement) {
});
}, 100);
positioningWorkaround.touchstartEvent = function(element) {
var triggerHack = positioningHack.bind(element);
triggerHack();
};
const config = {
childList: true,
subtree: true,

View File

@ -444,11 +444,5 @@ export function inCodeBlock(text, pos) {
return result;
}
export function putCursorAtEnd(element) {
element.focus();
const len = element.value.length;
element.setSelectionRange(len, len);
}
// This prevents a mini racer crash
export default {};

View File

@ -26,9 +26,10 @@
body.ios-safari-composer-hacks &.open {
height: calc(var(--composer-vh, 1vh) * 100);
.reply-area {
padding-bottom: 0px;
}
.keyboard-visible body.ios-safari-composer-hacks &.open .reply-area {
padding-bottom: 0px;
}
.reply-to {