UX: Refactor iOS composer layout
This addresses the following issues: - on iPad, with keyboard attached, the composer is no longer forced to full screen - on iPad, with keyboard attached, the topic no longer scrolls when starting a reply and then cancelling it - switching between inputs and buttons (formatting, emojis, categories/tags, etc.) no longer causes layout to bounce around
This commit is contained in:
parent
d527f3a723
commit
c3a5a8e095
|
@ -142,11 +142,6 @@ export default Ember.Component.extend(KeyEnterEscape, {
|
||||||
viewportResize() {
|
viewportResize() {
|
||||||
const composerVH = window.visualViewport.height * 0.01;
|
const composerVH = window.visualViewport.height * 0.01;
|
||||||
|
|
||||||
if (window.visualViewport.height !== window.innerHeight) {
|
|
||||||
document.documentElement.classList.add("keyboard-visible");
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.remove("keyboard-visible");
|
|
||||||
}
|
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
"--composer-vh",
|
"--composer-vh",
|
||||||
`${composerVH}px`
|
`${composerVH}px`
|
||||||
|
|
|
@ -85,18 +85,12 @@ function positioningWorkaround($fixedElement) {
|
||||||
const fixedElement = $fixedElement[0];
|
const fixedElement = $fixedElement[0];
|
||||||
const oldHeight = fixedElement.style.height;
|
const oldHeight = fixedElement.style.height;
|
||||||
|
|
||||||
var done = false;
|
|
||||||
var originalScrollTop = 0;
|
var originalScrollTop = 0;
|
||||||
|
let lastTouchedElement = null;
|
||||||
|
|
||||||
positioningWorkaround.blur = function(evt) {
|
positioningWorkaround.blur = function(evt) {
|
||||||
if (workaroundActive) {
|
if (workaroundActive) {
|
||||||
done = true;
|
$("body").removeClass("ios-safari-composer-hacks");
|
||||||
|
|
||||||
$("#main-outlet").show();
|
|
||||||
$("header").show();
|
|
||||||
|
|
||||||
fixedElement.style.position = "";
|
|
||||||
fixedElement.style.top = "";
|
|
||||||
|
|
||||||
if (!iOSWithVisualViewport()) {
|
if (!iOSWithVisualViewport()) {
|
||||||
fixedElement.style.height = oldHeight;
|
fixedElement.style.height = oldHeight;
|
||||||
|
@ -116,15 +110,17 @@ function positioningWorkaround($fixedElement) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var blurredNow = function(evt) {
|
var blurredNow = function(evt) {
|
||||||
|
// we cannot use evt.relatedTarget to get the last focused element in safari iOS
|
||||||
|
// document.activeElement is also unreliable (iOS does not mark buttons as focused)
|
||||||
|
// so instead, we store the last touched element and check against it
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!done &&
|
lastTouchedElement &&
|
||||||
evt.srcElement !== document.activeElement &&
|
($(lastTouchedElement).hasClass("select-kit-header") ||
|
||||||
$(document.activeElement)
|
["span", "svg", "button"].includes(
|
||||||
.parents()
|
lastTouchedElement.nodeName.toLowerCase()
|
||||||
.toArray()
|
))
|
||||||
.indexOf(fixedElement) > -1
|
|
||||||
) {
|
) {
|
||||||
// something in focus so skip
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,60 +130,77 @@ function positioningWorkaround($fixedElement) {
|
||||||
var blurred = debounce(blurredNow, 250);
|
var blurred = debounce(blurredNow, 250);
|
||||||
|
|
||||||
var positioningHack = function(evt) {
|
var positioningHack = function(evt) {
|
||||||
done = false;
|
|
||||||
|
|
||||||
// we need this, otherwise changing focus means we never clear
|
// we need this, otherwise changing focus means we never clear
|
||||||
this.addEventListener("blur", blurred);
|
this.addEventListener("blur", blurred);
|
||||||
|
|
||||||
if (fixedElement.style.top === "0px") {
|
// resets focus out of select-kit elements
|
||||||
if (this !== document.activeElement) {
|
// might become redundant after select-kit refactoring
|
||||||
evt.preventDefault();
|
$fixedElement.find(".select-kit.is-expanded > button").trigger("click");
|
||||||
|
$fixedElement
|
||||||
// this tricks safari into assuming current input is at top of the viewport
|
.find(".select-kit > button.is-focused")
|
||||||
// via https://stackoverflow.com/questions/38017771/mobile-safari-prevent-scroll-page-when-focus-on-input
|
.removeClass("is-focused");
|
||||||
this.style.transform = "translateY(-200px)";
|
|
||||||
this.focus();
|
|
||||||
let _this = this;
|
|
||||||
setTimeout(function() {
|
|
||||||
_this.style.transform = "none";
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't trigger keyboard on disabled element (happens when a category is required)
|
|
||||||
if (this.disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
originalScrollTop = $(window).scrollTop();
|
originalScrollTop = $(window).scrollTop();
|
||||||
|
|
||||||
// take care of body
|
setTimeout(function() {
|
||||||
|
if (iOSWithVisualViewport()) {
|
||||||
$("#main-outlet").hide();
|
// disable hacks when using a hardware keyboard
|
||||||
$("header").hide();
|
// by default, a hardware keyboard will show the keyboard accessory bar
|
||||||
|
// whose height is currently 55px (using 75 for a bit of a buffer)
|
||||||
$(window).scrollTop(0);
|
let heightDiff = window.innerHeight - window.visualViewport.height;
|
||||||
|
if (heightDiff < 75) {
|
||||||
let i = 20;
|
return;
|
||||||
let interval = setInterval(() => {
|
}
|
||||||
$(window).scrollTop(0);
|
|
||||||
if (i-- === 0) {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
}
|
||||||
}, 10);
|
|
||||||
|
|
||||||
fixedElement.style.top = "0px";
|
if (fixedElement.style.top === "0px") {
|
||||||
|
if (this !== document.activeElement) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
if (!iOSWithVisualViewport()) {
|
// this tricks safari into assuming current input is at top of the viewport
|
||||||
const height = calcHeight();
|
// via https://stackoverflow.com/questions/38017771/mobile-safari-prevent-scroll-page-when-focus-on-input
|
||||||
fixedElement.style.height = height + "px";
|
this.style.transform = "translateY(-200px)";
|
||||||
$(fixedElement).addClass("no-transition");
|
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) {
|
||||||
|
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";
|
||||||
|
$(fixedElement).addClass("no-transition");
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.preventDefault();
|
||||||
|
this.focus();
|
||||||
|
workaroundActive = true;
|
||||||
|
}, 350);
|
||||||
|
};
|
||||||
|
|
||||||
|
var lastTouched = function(evt) {
|
||||||
|
if (evt && evt.target) {
|
||||||
|
lastTouchedElement = evt.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
evt.preventDefault();
|
|
||||||
this.focus();
|
|
||||||
workaroundActive = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function attachTouchStart(elem, fn) {
|
function attachTouchStart(elem, fn) {
|
||||||
|
@ -198,30 +211,8 @@ function positioningWorkaround($fixedElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkForInputs = debounce(function() {
|
const checkForInputs = debounce(function() {
|
||||||
$fixedElement
|
attachTouchStart(fixedElement, lastTouched);
|
||||||
.find(
|
|
||||||
"button:not(.hide-preview),a:not(.mobile-file-upload):not(.toggle-toolbar)"
|
|
||||||
)
|
|
||||||
.each(function(idx, elem) {
|
|
||||||
if ($(elem).parents(".emoji-picker").length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($(elem).parents(".autocomplete").length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($(elem).parents(".d-editor-button-bar").length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachTouchStart(this, function(evt) {
|
|
||||||
done = true;
|
|
||||||
$(document.activeElement).blur();
|
|
||||||
evt.preventDefault();
|
|
||||||
$(this).click();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$fixedElement.find("input[type=text],textarea").each(function() {
|
$fixedElement.find("input[type=text],textarea").each(function() {
|
||||||
attachTouchStart(this, positioningHack);
|
attachTouchStart(this, positioningHack);
|
||||||
});
|
});
|
||||||
|
|
|
@ -483,3 +483,19 @@ div.ac-wrap {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.ios-safari-composer-hacks {
|
||||||
|
#main-outlet,
|
||||||
|
header,
|
||||||
|
.grippie,
|
||||||
|
html:not(.fullscreen-composer) & .toggle-fullscreen {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reply-control {
|
||||||
|
top: 0px;
|
||||||
|
&.open {
|
||||||
|
height: calc(var(--composer-vh, 1vh) * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -230,16 +230,6 @@ a.toggle-preview {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.keyboard-visible {
|
|
||||||
.grippie,
|
|
||||||
&:not(.fullscreen-composer) .toggle-fullscreen {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#reply-control.open {
|
|
||||||
height: calc(var(--composer-vh, 1vh) * 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fullscreen composer styles
|
// fullscreen composer styles
|
||||||
.fullscreen-composer {
|
.fullscreen-composer {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.keyboard-visible &.open {
|
body.ios-safari-composer-hacks &.open {
|
||||||
height: calc(var(--composer-vh, 1vh) * 100);
|
height: calc(var(--composer-vh, 1vh) * 100);
|
||||||
.reply-area {
|
.reply-area {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
|
|
Loading…
Reference in New Issue