Use `resized-content` in meta tag to standardize behaviour
Previously we were we were setting `overlaysContent = true` for Chrome only, which made it very hard for the same CSS to work well across all three major browsers. With `interactive-widget=resizes-content`, there sems to be better consistency, we can use bottom fixed-positioning in Firefox and Chrome for Android and a CSS workaround for Safari. See also https://developer.chrome.com/blog/viewport-resize-behavior
This commit is contained in:
parent
f2b4baff36
commit
5c702c8429
|
@ -8,19 +8,14 @@ export default class DComposerPosition extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
if (!window.visualViewport) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
html.classList.contains("ios-device") ||
|
html.classList.contains("mobile-device") ||
|
||||||
html.classList.contains("ipados-device")
|
html.classList.contains("ipados-device")
|
||||||
) {
|
) {
|
||||||
window.addEventListener("scroll", this._correctScrollPosition);
|
window.addEventListener("scroll", this._correctScrollPosition);
|
||||||
this._correctScrollPosition();
|
this._correctScrollPosition();
|
||||||
|
|
||||||
const editor = document.querySelector(".d-editor-input");
|
const editor = document.querySelector(".d-editor-input");
|
||||||
editor?.addEventListener("touchmove", this._textareaTouchMove);
|
editor?.addEventListener("touchmove", this._textareaTouchMove);
|
||||||
}
|
}
|
||||||
|
@ -29,10 +24,6 @@ export default class DComposerPosition extends Component {
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
|
|
||||||
if (!window.visualViewport) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -69,7 +60,7 @@ export default class DComposerPosition extends Component {
|
||||||
|
|
||||||
_textareaTouchMove(event) {
|
_textareaTouchMove(event) {
|
||||||
// This is an alternative to locking up the body
|
// This is an alternative to locking up the body
|
||||||
// It stops scrolls from bubbling up to the body
|
// It stops scrolling in the given element from bubbling up to the body
|
||||||
// when the textarea does not have any content to scroll
|
// when the textarea does not have any content to scroll
|
||||||
if (event.target) {
|
if (event.target) {
|
||||||
const notScrollable =
|
const notScrollable =
|
||||||
|
|
|
@ -5,13 +5,13 @@ import isZoomed from "discourse/lib/zoom-check";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const FF_KEYBOARD_DETECT_THRESHOLD = 150;
|
|
||||||
|
|
||||||
export default class DVirtualHeight extends Component {
|
export default class DVirtualHeight extends Component {
|
||||||
@service site;
|
@service site;
|
||||||
@service capabilities;
|
@service capabilities;
|
||||||
@service appEvents;
|
@service appEvents;
|
||||||
|
|
||||||
|
MIN_THRESHOLD = 120;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ export default class DVirtualHeight extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle device rotation
|
|
||||||
this.windowInnerHeight = window.innerHeight;
|
this.windowInnerHeight = window.innerHeight;
|
||||||
|
|
||||||
scheduleOnce("afterRender", this, this.debouncedOnViewportResize);
|
scheduleOnce("afterRender", this, this.debouncedOnViewportResize);
|
||||||
|
@ -32,13 +31,6 @@ export default class DVirtualHeight extends Component {
|
||||||
"resize",
|
"resize",
|
||||||
this.debouncedOnViewportResize
|
this.debouncedOnViewportResize
|
||||||
);
|
);
|
||||||
if ("virtualKeyboard" in navigator) {
|
|
||||||
navigator.virtualKeyboard.overlaysContent = true;
|
|
||||||
navigator.virtualKeyboard.addEventListener(
|
|
||||||
"geometrychange",
|
|
||||||
this.debouncedOnViewportResize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
|
@ -50,13 +42,6 @@ export default class DVirtualHeight extends Component {
|
||||||
"resize",
|
"resize",
|
||||||
this.debouncedOnViewportResize
|
this.debouncedOnViewportResize
|
||||||
);
|
);
|
||||||
if ("virtualKeyboard" in navigator) {
|
|
||||||
navigator.virtualKeyboard.overlaysContent = false;
|
|
||||||
navigator.virtualKeyboard.removeEventListener(
|
|
||||||
"geometrychange",
|
|
||||||
this.debouncedOnViewportResize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setVH() {
|
setVH() {
|
||||||
|
@ -64,15 +49,7 @@ export default class DVirtualHeight extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let height;
|
const height = Math.round(window.visualViewport.height);
|
||||||
if ("virtualKeyboard" in navigator) {
|
|
||||||
height =
|
|
||||||
window.visualViewport.height -
|
|
||||||
navigator.virtualKeyboard.boundingRect.height;
|
|
||||||
} else {
|
|
||||||
const activeWindow = window.visualViewport || window;
|
|
||||||
height = activeWindow?.height || window.innerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.previousHeight && Math.abs(this.previousHeight - height) <= 1) {
|
if (this.previousHeight && Math.abs(this.previousHeight - height) <= 1) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -84,8 +61,6 @@ export default class DVirtualHeight extends Component {
|
||||||
"--composer-vh",
|
"--composer-vh",
|
||||||
`${height / 100}px`
|
`${height / 100}px`
|
||||||
);
|
);
|
||||||
|
|
||||||
document.documentElement.style.setProperty("--vvh", `${height}px`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
|
@ -104,26 +79,12 @@ export default class DVirtualHeight extends Component {
|
||||||
const docEl = document.documentElement;
|
const docEl = document.documentElement;
|
||||||
|
|
||||||
let keyboardVisible = false;
|
let keyboardVisible = false;
|
||||||
if ("virtualKeyboard" in navigator) {
|
|
||||||
if (navigator.virtualKeyboard.boundingRect.height > 0) {
|
let viewportWindowDiff =
|
||||||
keyboardVisible = true;
|
this.windowInnerHeight - window.visualViewport.height;
|
||||||
}
|
|
||||||
} else if (this.capabilities.isFirefox && this.capabilities.isAndroid) {
|
if (viewportWindowDiff > this.MIN_THRESHOLD) {
|
||||||
if (
|
keyboardVisible = true;
|
||||||
Math.abs(
|
|
||||||
this.windowInnerHeight -
|
|
||||||
Math.min(window.innerHeight, window.visualViewport.height)
|
|
||||||
) > FF_KEYBOARD_DETECT_THRESHOLD
|
|
||||||
) {
|
|
||||||
keyboardVisible = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let viewportWindowDiff =
|
|
||||||
this.windowInnerHeight - window.visualViewport.height;
|
|
||||||
const MIN_THRESHOLD = 20;
|
|
||||||
if (viewportWindowDiff > MIN_THRESHOLD) {
|
|
||||||
keyboardVisible = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appEvents.trigger("keyboard-visibility-change", keyboardVisible);
|
this.appEvents.trigger("keyboard-visibility-change", keyboardVisible);
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
html.composer-open.not-mobile-device {
|
html.composer-open {
|
||||||
#main-outlet {
|
#main-outlet {
|
||||||
padding-bottom: var(--composer-height);
|
padding-bottom: var(--composer-height);
|
||||||
transition: padding-bottom 250ms ease;
|
transition: padding-bottom 250ms ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.composer-open {
|
|
||||||
--d-min-composer-reply-height: 35vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#reply-control {
|
#reply-control {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -44,7 +40,7 @@ html.composer-open {
|
||||||
}
|
}
|
||||||
z-index: z("composer", "content");
|
z-index: z("composer", "content");
|
||||||
transition: height 0.2s, max-width 0.2s, padding-bottom 0.2s, top 0.2s,
|
transition: height 0.2s, max-width 0.2s, padding-bottom 0.2s, top 0.2s,
|
||||||
min-height 0.2s;
|
transform 0.2s, min-height 0.2s;
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
box-shadow: var(--shadow-composer);
|
box-shadow: var(--shadow-composer);
|
||||||
|
|
||||||
|
@ -71,7 +67,6 @@ html.composer-open {
|
||||||
&.open {
|
&.open {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: var(--composer-height);
|
height: var(--composer-height);
|
||||||
min-height: var(--d-min-composer-reply-height);
|
|
||||||
max-height: calc(100vh - var(--header-offset, 4em));
|
max-height: calc(100vh - var(--header-offset, 4em));
|
||||||
padding-bottom: var(--composer-ipad-padding);
|
padding-bottom: var(--composer-ipad-padding);
|
||||||
}
|
}
|
||||||
|
@ -632,28 +627,17 @@ div.ac-wrap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The composer on mobile is fixed-positioned, same as on desktop because
|
||||||
|
// we are using interactive-widget=resizes-content in the viewport meta tag
|
||||||
.ipados-device,
|
.ipados-device,
|
||||||
.mobile-device {
|
.mobile-device {
|
||||||
// Let's have the composer be top-anchored for mobile and iPad.
|
|
||||||
// Safari in iOS/iPad and Firefox/Android do not handle well a bottom:0 fixed-positioned element,
|
|
||||||
// especially while the software keyboard is visible.
|
|
||||||
#reply-control {
|
#reply-control {
|
||||||
bottom: unset;
|
|
||||||
height: 0;
|
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
// these two properties below are equivalent to bottom: 0
|
|
||||||
top: calc(var(--composer-vh, 1vh) * 100);
|
|
||||||
transform: translateY(-100%);
|
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
z-index: z("mobile-composer");
|
z-index: z("mobile-composer");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.composer-action-edit,
|
|
||||||
&.edit-title {
|
|
||||||
height: calc(var(--composer-vh, 1vh) * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.draft,
|
&.draft,
|
||||||
&.saving {
|
&.saving {
|
||||||
z-index: z("ipad-header-nav") + 1;
|
z-index: z("ipad-header-nav") + 1;
|
||||||
|
@ -671,19 +655,6 @@ div.ac-wrap {
|
||||||
// this prevents touch events bubbling up to the browser, i.e. accidental scrolls
|
// this prevents touch events bubbling up to the browser, i.e. accidental scrolls
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When an element (input, textearea) gets focus, iOS Safari tries to put it in the center
|
|
||||||
// by scrolling and zooming. We handle zooming with a meta tag. We used to handle scrolling
|
|
||||||
// using a complicated JS hack.
|
|
||||||
//
|
|
||||||
// However, iOS Safari doesn't scroll when the input has opacity of 0 or is clipped.
|
|
||||||
// We use this quirk and temporarily hide the element on scroll and quickly show it again
|
|
||||||
//
|
|
||||||
// Source https://gist.github.com/kiding/72721a0553fa93198ae2bb6eefaa3299
|
|
||||||
input:focus,
|
|
||||||
textarea:focus {
|
|
||||||
animation: blink_input_opacity_to_prevent_scrolling_when_focus 0.01s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.keyboard-visible #reply-control.open {
|
&.keyboard-visible #reply-control.open {
|
||||||
|
@ -691,6 +662,32 @@ div.ac-wrap {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.composer-open .with-topic-progress {
|
&.composer-open .with-topic-progress {
|
||||||
bottom: var(--d-min-composer-reply-height);
|
bottom: calc(var(--composer-height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari in iOS/iPad does not handle well a bottom:0 fixed-positioned element,
|
||||||
|
// especially while the software keyboard is visible, so we top-anchor it here
|
||||||
|
// and shift it using transform
|
||||||
|
.ipados-device,
|
||||||
|
.ios-device {
|
||||||
|
#reply-control {
|
||||||
|
// the two properties below are equivalent to bottom: 0
|
||||||
|
top: calc(var(--composer-vh, 1vh) * 100);
|
||||||
|
transform: translateY(-100%);
|
||||||
|
bottom: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When an element (input, textearea) gets focus, iOS Safari tries to put it in the center
|
||||||
|
// by scrolling and zooming. We handle zooming with a meta tag. We used to handle scrolling
|
||||||
|
// using a complicated JS hack.
|
||||||
|
//
|
||||||
|
// However, iOS Safari doesn't scroll when the input has opacity of 0 or is clipped.
|
||||||
|
// We use this quirk and temporarily hide the element on scroll and quickly show it again
|
||||||
|
//
|
||||||
|
// Source https://gist.github.com/kiding/72721a0553fa93198ae2bb6eefaa3299
|
||||||
|
input:focus,
|
||||||
|
textarea:focus {
|
||||||
|
animation: blink_input_opacity_to_prevent_scrolling_when_focus 0.01s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<%- end %>
|
<%- end %>
|
||||||
<%= discourse_theme_color_meta_tags %>
|
<%= discourse_theme_color_meta_tags %>
|
||||||
<%= discourse_color_scheme_meta_tag %>
|
<%= discourse_color_scheme_meta_tag %>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=yes, viewport-fit=cover, interactive-widget=resizes-visual">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=yes, viewport-fit=cover, interactive-widget=resizes-content">
|
||||||
<%- if Discourse.base_path.present? %>
|
<%- if Discourse.base_path.present? %>
|
||||||
<meta name="discourse-base-uri" content="<%= Discourse.base_path %>">
|
<meta name="discourse-base-uri" content="<%= Discourse.base_path %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
Loading…
Reference in New Issue