Skip to content

Commit

Permalink
feat(QDialog): Detect iOS soft keyboard show/hide and adjust QDialog
Browse files Browse the repository at this point in the history
  • Loading branch information
pdanpdan committed Oct 31, 2019
1 parent 6c1ce66 commit 8829d8a
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 28 deletions.
10 changes: 8 additions & 2 deletions ui/dev/components/global/dialog.vue
Expand Up @@ -750,6 +750,9 @@
Subtitle
</div>
</q-card-section>
<q-card-section>
<q-input v-model="iOStext" />
</q-card-section>
<q-card-section>
<q-input v-model="iOStext" autofocus />
</q-card-section>
Expand All @@ -769,6 +772,9 @@
<q-card-section>
<q-input v-model="iOStext" autofocus />
</q-card-section>
<q-card-section>
<q-input v-model="iOStext" />
</q-card-section>
</q-card>
</q-dialog>

Expand Down Expand Up @@ -799,8 +805,8 @@
</div>
</q-card-section>
<q-card-section>
<q-input v-for="n in 15" :key="n" v-model="iOStext" />
<q-input v-model="iOStext" autofocus />
<q-input v-for="n in 15" :key="n" v-model="iOStext" :label="`${n} of 15`" />
<q-input v-model="iOStext" autofocus label="Last one" />
</q-card-section>
</q-card>
</q-dialog>
Expand Down
80 changes: 62 additions & 18 deletions ui/src/components/dialog/QDialog.js
Expand Up @@ -5,13 +5,16 @@ import ModelToggleMixin from '../../mixins/model-toggle.js'
import PortalMixin from '../../mixins/portal.js'
import PreventScrollMixin from '../../mixins/prevent-scroll.js'

import { noop } from '../../utils.js'
import { childHasFocus } from '../../utils/dom.js'
import EscapeKey from '../../utils/escape-key.js'
import slot from '../../utils/slot.js'
import { create, stop } from '../../utils/event.js'
import { create, stop, stopAndPrevent } from '../../utils/event.js'

let maximizedModals = 0

const inputTypesWithKeyboard = ['color', 'date', 'datetime-local', 'email', 'month', 'number', 'password', 'serach', 'tel', 'text', 'time', 'url', 'week', 'datetime']

const positionClass = {
standard: 'fixed-full flex-center',
top: 'fixed-top justify-center',
Expand Down Expand Up @@ -65,7 +68,8 @@ export default Vue.extend({

data () {
return {
transitionState: this.showing
transitionState: this.showing,
iosKeyboard: false
}
},

Expand Down Expand Up @@ -122,19 +126,21 @@ export default Vue.extend({
return this.persistent !== true &&
this.noRouteDismiss !== true &&
this.seamless !== true
},

__onTouchMove () {
return this.iosKeyboard === true ? noop : stopAndPrevent
}
},

methods: {
focus () {
let node = this.__getInnerNode()

if (node === void 0 || node.contains(document.activeElement) === true) {
return
if (node !== void 0 && node.contains(document.activeElement) !== true) {
node = node.querySelector('[autofocus]') || node
node.focus()
}

node = node.querySelector('[autofocus]') || node
node.focus()
},

shake () {
Expand Down Expand Up @@ -190,17 +196,25 @@ export default Vue.extend({
this.__nextTick(this.focus)
}

let delays = this.__isIos === true && this.useBackdrop === true && this.position === 'bottom'
? [ 0, 300 ]
: [ 100, 200 ]

this.__setTimeout(() => {
if (this.$q.platform.is.ios === true && document.activeElement) {
const { top } = document.activeElement.getBoundingClientRect()
if (top < 0) {
document.scrollingElement.scrollTop += top - window.innerHeight / 2
}
document.activeElement.scrollIntoView()
if (this.iosKeyboard === true && this.useBackdrop === true) {
document.scrollingElement.scrollTop = this.position === 'bottom'
? window.innerHeight
: this.position === 'top' ? 0 : (this.$q.screen.width < 400 ? 130 : 153)
}

this.$emit('show', evt)
}, 300)
this.__setTimeout(() => {
if (document.activeElement && document.activeElement.autofocus === true) {
document.activeElement.parentElement.scrollIntoView(true)
}

this.$emit('show', evt)
}, delays[1])
}, delays[0])
},

__hide (evt) {
Expand All @@ -222,6 +236,7 @@ export default Vue.extend({

__cleanup (hiding) {
clearTimeout(this.shakeTimeout)
this.__setIosKeyboard(false)

if (hiding === true || this.showing === true) {
EscapeKey.pop(this)
Expand All @@ -246,9 +261,15 @@ export default Vue.extend({
},

__preventFocusout (state) {
if (this.$q.platform.is.desktop === true) {
const action = `${state === true ? 'add' : 'remove'}EventListener`
document.body[action]('focusin', this.__onFocusChange)
const action = `${state === true ? 'add' : 'remove'}EventListener`

if (this.$q.platform.is.desktop === true || this.__isIos === true) {
document.body[action]('focusin', this.__onFocusChange, true)
}

// Required to detect keyboard closing
if (this.__isIos === true) {
document.body[action]('focusout', this.__onFocusoutChange, true)
}
},

Expand All @@ -275,6 +296,26 @@ export default Vue.extend({
) {
this.focus()
}

if (this.__isIos === true && e !== void 0 && e.target !== void 0) {
const nodeName = e.target.nodeName.toLowerCase()

this.__setIosKeyboard(nodeName === 'textarea' ||
(nodeName === 'input' && inputTypesWithKeyboard.indexOf(e.target.type.toLowerCase()) > -1) ||
(e.path !== void 0 && e.path.some(el => el.hasAttribute !== void 0 && el.hasAttribute('contenteditable')) === true)
)
}
},

// Only called on iOS to detect keyboard hide
__onFocusoutChange () {
this.iosKeyboard === true && this.__setIosKeyboard(false)
},

__setIosKeyboard (status) {
if (this.iosKeyboard !== status) {
this.iosKeyboard = status
}
},

__renderPortal (h) {
Expand Down Expand Up @@ -302,6 +343,7 @@ export default Vue.extend({
h('div', {
staticClass: 'q-dialog__backdrop fixed-full',
on: {
touchmove: this.__onTouchMove, // prevent iOS page scroll
click: this.__onBackdropClick
}
})
Expand All @@ -324,6 +366,8 @@ export default Vue.extend({

mounted () {
this.__processModelChange(this.value)
// discriminate between mac laptop and ipad with request desktop site
this.__isIos = this.$q.platform.is.ios === true || (this.$q.platform.is.mac === true && this.$q.platform.has.touch === true)
},

beforeDestroy () {
Expand Down
27 changes: 19 additions & 8 deletions ui/src/mixins/prevent-scroll.js
Expand Up @@ -49,15 +49,26 @@ function shouldPreventScroll (e) {
}

function onAppleScroll (e) {
if (e.target === document) {
// required, otherwise iOS blocks further scrolling
// until the mobile scrollbar dissapears
document.scrollingElement.scrollTop = document.scrollingElement.scrollTop // eslint-disable-line
// iPad does not need this and neither does iPhone in landscape
if (e.target === document && window.innerWidth < 768) {
if (document.scrollingElement.scrollTop < 0) {
document.scrollingElement.scrollTop -= 1
}
else if (window.innerWidth < 400 && document.scrollingElement.scrollTop > 260) {
document.scrollingElement.scrollTop = 260
}
else if (document.scrollingElement.scrollTop > 307) {
document.scrollingElement.scrollTop = 307
}
}
}

function apply (action) {
const body = document.body
const
body = document.body,
// discriminate between mac laptop and ipad with request desktop site
needIosSafariHack = Platform.is.safari === true && (Platform.is.ios === true || (Platform.is.mac === true && Platform.has.touch === true)),
needMacSafariHack = Platform.is.safari === true && Platform.is.desktop === true && Platform.is.mac === true && Platform.has.touch !== true

if (action === 'add') {
const overflowY = window.getComputedStyle(body).overflowY
Expand All @@ -74,16 +85,16 @@ function apply (action) {
}

body.classList.add('q-body--prevent-scroll')
Platform.is.ios === true && window.addEventListener('scroll', onAppleScroll, listenOpts.passiveCapture)
needIosSafariHack === true && window.addEventListener('scroll', onAppleScroll, listenOpts.passiveCapture)
}

if (Platform.is.desktop === true && Platform.is.mac === true) {
if (needMacSafariHack === true) {
// ref. https://developers.google.com/web/updates/2017/01/scrolling-intervention
window[`${action}EventListener`]('wheel', onWheel, listenOpts.notPassive)
}

if (action === 'remove') {
Platform.is.ios === true && window.removeEventListener('scroll', onAppleScroll, listenOpts.passiveCapture)
needIosSafariHack === true && window.removeEventListener('scroll', onAppleScroll, listenOpts.passiveCapture)

body.classList.remove('q-body--prevent-scroll')
body.classList.remove('q-body--force-scrollbar')
Expand Down

0 comments on commit 8829d8a

Please sign in to comment.