From ff0107d6b90f4aa3b77e002c97d4a53166c71510 Mon Sep 17 00:00:00 2001 From: Dan Popescu Date: Fri, 16 Jul 2021 21:22:55 +0300 Subject: [PATCH] fix(QTabs): fix RTL mode; improve tabs scrolling #8578, #10034 #8578, #10034 --- ui/src/components/tabs/QTab.js | 4 +- ui/src/components/tabs/QTabs.js | 155 ++++++++++++++++++++---------- ui/src/components/tabs/QTabs.sass | 12 +-- ui/src/components/tabs/QTabs.styl | 12 +-- 4 files changed, 116 insertions(+), 67 deletions(-) diff --git a/ui/src/components/tabs/QTab.js b/ui/src/components/tabs/QTab.js index 7a442683a6ee..f1b7ec3df5f2 100644 --- a/ui/src/components/tabs/QTab.js +++ b/ui/src/components/tabs/QTab.js @@ -120,7 +120,7 @@ export default Vue.extend({ watch: { name (newName, oldName) { this.__unregisterTab(oldName) - this.__registerTab(newName) + this.__registerTab(newName, this.$el) } }, @@ -239,7 +239,7 @@ export default Vue.extend({ }, mounted () { - this.__registerTab(this.name) + this.__registerTab(this.name, this.$el) }, beforeDestroy () { diff --git a/ui/src/components/tabs/QTabs.js b/ui/src/components/tabs/QTabs.js index ee8ef67f6334..f7a9591929d9 100644 --- a/ui/src/components/tabs/QTabs.js +++ b/ui/src/components/tabs/QTabs.js @@ -9,6 +9,7 @@ import ListenersMixin from '../../mixins/listeners.js' import { stop, noop } from '../../utils/event.js' import { slot } from '../../utils/slot.js' import cache from '../../utils/cache.js' +import { isBuggyRTLScroll } from '../../utils/scroll.js' function getIndicatorClass (color, top, vertical) { const pos = vertical === true @@ -116,8 +117,8 @@ export default Vue.extend({ noCaps: this.noCaps }, scrollable: false, - leftArrow: true, - rightArrow: false, + startArrow: true, + endArrow: false, justify: false } }, @@ -177,6 +178,17 @@ export default Vue.extend({ return this.$q.platform.is.desktop === true || this.mobileArrows === true }, + arrowIcons () { + const sides = this.vertical === true || this.$q.lang.rtl !== true + ? [ 'start', 'end' ] + : [ 'end', 'start' ] + + return { + [sides[0]]: this.leftIcon || (this.vertical === true ? this.$q.iconSet.tabs.up : this.$q.iconSet.tabs.left), + [sides[1]]: this.rightIcon || (this.vertical === true ? this.$q.iconSet.tabs.down : this.$q.iconSet.tabs.right) + } + }, + alignClass () { const align = this.scrollable === true ? 'left' @@ -297,6 +309,35 @@ export default Vue.extend({ } }, + __getScrollPosition (el) { + if (this.vertical === true) { + return el.scrollTop + } + return this.$q.lang.rtl !== true + ? el.scrollLeft + : (this.isBuggyRTLScroll === true ? el.scrollWidth - el.clientWidth : 1) - el.scrollLeft + }, + + __getScrollOffset (el) { + if (this.vertical === true) { + return el.offsetTop + } + return this.$q.lang.rtl !== true || this.isBuggyRTLScroll === true + ? el.offsetLeft + : el.offsetParent.offsetWidth - el.offsetLeft - el.clientWidth + }, + + __setScrollPosition (el, value) { + if (this.vertical === true) { + el.scrollTop = value + } + else { + el.scrollLeft = this.$q.lang.rtl !== true + ? value + : (this.isBuggyRTLScroll === true ? el.scrollWidth - el.clientWidth : 1) - value + } + }, + __animate (oldName, newName) { const oldTab = oldName !== void 0 && oldName !== null && oldName !== '' @@ -336,36 +377,26 @@ export default Vue.extend({ } if (newTab && this.scrollable === true) { - const - { left, width, top, height } = this.$refs.content.getBoundingClientRect(), - newPos = newTab.$el.getBoundingClientRect() - - let offset = this.vertical === true ? newPos.top - top : newPos.left - left - - if (offset < 0) { - this.$refs.content[this.vertical === true ? 'scrollTop' : 'scrollLeft'] += Math.floor(offset) - this.__updateArrows() - return - } - - offset += this.vertical === true ? newPos.height - height : newPos.width - width - if (offset > 0) { - this.$refs.content[this.vertical === true ? 'scrollTop' : 'scrollLeft'] += Math.ceil(offset) - this.__updateArrows() - } + this.__scrollToTab(newTab.$el, void 0, true) + } + else { + this.__updateArrows() } }, __updateArrowsFn () { - const - content = this.$refs.content, - rect = content.getBoundingClientRect(), - pos = this.vertical === true ? content.scrollTop : content.scrollLeft + const { content } = this.$refs + + if (content !== void 0) { + const + rect = content.getBoundingClientRect(), + pos = this.__getScrollPosition(content) - this.leftArrow = pos > 0 - this.rightArrow = this.vertical === true - ? Math.ceil(pos + rect.height) < content.scrollHeight - : Math.ceil(pos + rect.width) < content.scrollWidth + this.startArrow = pos > (this.vertical === true || this.$q.lang.rtl !== true ? 0 : 1) + this.endArrow = this.vertical === true + ? Math.ceil(pos + rect.height) < content.scrollHeight + : Math.ceil(pos + rect.width) < content.scrollWidth + } }, __animScrollTo (value, onEnd) { @@ -402,7 +433,7 @@ export default Vue.extend({ const content = this.$refs.content const max = this.vertical === true ? content.scrollHeight - content.offsetHeight : content.scrollWidth - content.offsetWidth - let pos = this.vertical === true ? content.scrollTop : content.scrollLeft + let pos = this.__getScrollPosition(content) let done = false value = Math.max(0, Math.min(max, value)) @@ -418,7 +449,7 @@ export default Vue.extend({ pos = value } - content[this.vertical === true ? 'scrollTop' : 'scrollLeft'] = pos + this.__setScrollPosition(content, pos) this.__updateArrows() return done @@ -430,32 +461,45 @@ export default Vue.extend({ } const content = this.$refs.content - const startContent = this.vertical === true ? content.scrollTop : content.scrollLeft + const startContent = this.__getScrollPosition(content) const sizeContent = this.vertical === true ? content.offsetHeight : content.offsetWidth - const offsetContent = this.vertical === true || this.$q.lang.rtl !== true ? 0 : content.scrollWidth - sizeContent + const sizeScroll = this.vertical === true ? content.scrollHeight : content.scrollWidth - const startTab = this.vertical === true ? tab.offsetTop : tab.offsetLeft + offsetContent + const startTab = this.__getScrollOffset(tab) const endTab = startTab + (this.vertical === true ? tab.offsetHeight : tab.offsetWidth) const startsBefore = startTab < startContent const endsAfter = endTab > startContent + sizeContent - if (startsBefore === true || endsAfter === true) { - if (alignEnd === void 0) { - alignEnd = endsAfter === true && startsBefore !== true + if (startsBefore !== true && endsAfter !== true) { + alignEnd = void 0 + } + else if (alignEnd === void 0) { + if (endTab >= sizeScroll - 1) { + alignEnd = true + } + else if (startsBefore === true || (endsAfter === true && startTab < endTab - sizeContent)) { + alignEnd = false } + else if (startsBefore !== endsAfter) { + alignEnd = endsAfter + } + } + if (alignEnd !== void 0) { this.__animScrollTo( - alignEnd === true ? endTab - sizeContent : startTab, - () => { - setTimeout(() => { - skipFocus !== true && tab && tab.focus() - }) - } + alignEnd === true ? (endTab >= sizeScroll - 1 ? sizeScroll : endTab - sizeContent) : (startTab <= 1 ? 0 : startTab), + skipFocus !== true + ? () => { + setTimeout(() => { + tab && tab.focus() + }) + } + : void 0 ) } - else { - skipFocus !== true && tab && tab.focus() + else if (skipFocus !== true) { + tab.focus() } }, @@ -522,19 +566,20 @@ export default Vue.extend({ return false } - this.__scrollToTab(tabs[index], dir > 0) + this.__scrollToTab(tabs[index], dir === rtlDir) this.__recalculateScroll() return true } }, - __registerTab (name) { + __registerTab (name, el) { if (this.tabNames.indexOf(name) === -1) { this.tabNames.push(name) this.tabs.hasCurrent = this.tabNames.indexOf(this.tabs.current) > -1 } this.__recalculateScroll() + this.value === name && el && this.__scrollToTab(el, void 0, true) }, __unregisterTab (name) { @@ -560,6 +605,10 @@ export default Vue.extend({ : noop }, + mounted () { + this.isBuggyRTLScroll = isBuggyRTLScroll() + }, + beforeDestroy () { clearTimeout(this.bufferTimer) clearTimeout(this.animateTimer) @@ -581,10 +630,10 @@ export default Vue.extend({ this.arrowsEnabled === true && child.push( h(QIcon, { - staticClass: 'q-tabs__arrow q-tabs__arrow--left absolute q-tab__icon', - class: this.leftArrow === true ? '' : 'q-tabs__arrow--faded', - props: { name: this.leftIcon || (this.vertical === true ? this.$q.iconSet.tabs.up : this.$q.iconSet.tabs.left) }, - on: cache(this, 'onL', { + staticClass: 'q-tabs__arrow q-tabs__arrow--start absolute q-tab__icon', + class: this.startArrow === true ? '' : 'q-tabs__arrow--faded', + props: { name: this.arrowIcons.start }, + on: cache(this, 'onS', { mousedown: this.__scrollToStart, touchstart: this.__scrollToStart, mouseup: this.__stopAnimScroll, @@ -594,10 +643,10 @@ export default Vue.extend({ }), h(QIcon, { - staticClass: 'q-tabs__arrow q-tabs__arrow--right absolute q-tab__icon', - class: this.rightArrow === true ? '' : 'q-tabs__arrow--faded', - props: { name: this.rightIcon || (this.vertical === true ? this.$q.iconSet.tabs.down : this.$q.iconSet.tabs.right) }, - on: cache(this, 'onR', { + staticClass: 'q-tabs__arrow q-tabs__arrow--end absolute q-tab__icon', + class: this.endArrow === true ? '' : 'q-tabs__arrow--faded', + props: { name: this.arrowIcons.end }, + on: cache(this, 'onE', { mousedown: this.__scrollToEnd, touchstart: this.__scrollToEnd, mouseup: this.__stopAnimScroll, diff --git a/ui/src/components/tabs/QTabs.sass b/ui/src/components/tabs/QTabs.sass index 415406f540e9..1aa021af5468 100644 --- a/ui/src/components/tabs/QTabs.sass +++ b/ui/src/components/tabs/QTabs.sass @@ -127,14 +127,14 @@ &--horizontal .q-tabs__arrow height: 100% - &--left + &--start top: 0 - left: 0 #{"/* rtl:ignore */"} + left: 0 bottom: 0 - &--right + &--end top: 0 - right: 0 #{"/* rtl:ignore */"} + right: 0 bottom: 0 &--vertical @@ -150,12 +150,12 @@ height: 36px text-align: center - &--left + &--start top: 0 left: 0 right: 0 - &--right + &--end left: 0 right: 0 bottom: 0 diff --git a/ui/src/components/tabs/QTabs.styl b/ui/src/components/tabs/QTabs.styl index 4c329fb1350d..1c9b81c76fec 100644 --- a/ui/src/components/tabs/QTabs.styl +++ b/ui/src/components/tabs/QTabs.styl @@ -127,14 +127,14 @@ &--horizontal .q-tabs__arrow height: 100% - &--left + &--start top: 0 - left: 0 /* rtl:ignore */ + left: 0 bottom: 0 - &--right + &--end top: 0 - right: 0 /* rtl:ignore */ + right: 0 bottom: 0 &--vertical @@ -150,12 +150,12 @@ height: 36px text-align: center - &--left + &--start top: 0 left: 0 right: 0 - &--right + &--end left: 0 right: 0 bottom: 0