UX: Set focus when launching composer on iOS (#9443)
This commit is contained in:
parent
5a60a4233e
commit
6fad04635b
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
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,
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue