mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-07 02:28:54 +08:00
Splitpanes.vue refactoring
This commit is contained in:
@@ -3,13 +3,13 @@
|
|||||||
ref="container"
|
ref="container"
|
||||||
:class="[
|
:class="[
|
||||||
'splitpanes',
|
'splitpanes',
|
||||||
`splitpanes--${horizontal ? 'horizontal' : 'vertical'}`,
|
`splitpanes-${horizontal ? 'horizontal' : 'vertical'}`,
|
||||||
{ 'splitpanes--dragging': touch.dragging }
|
{ 'splitpanes-dragging': dragging }
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="movable-splitter" ref="movableSplitter" :style="movableSplitterStyle" />
|
<div class="movable-splitter" ref="movableSplitter" :style="movableSplitterStyle" />
|
||||||
<div
|
<div
|
||||||
class="splitpanes__pane"
|
class="splitpanes-pane"
|
||||||
ref="left"
|
ref="left"
|
||||||
:size="paneBefore.size"
|
:size="paneBefore.size"
|
||||||
max-size="30"
|
max-size="30"
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Splitter start-->
|
<!-- Splitter start-->
|
||||||
<div
|
<div
|
||||||
class="splitpanes__splitter"
|
class="splitpanes-splitter"
|
||||||
@mousedown="onMouseDown"
|
@mousedown="onMouseDown"
|
||||||
@touchstart="onMouseDown"
|
@touchstart="onMouseDown"
|
||||||
>
|
>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="after.max === 100 && after.size > 0"
|
v-if="after.max === 100 && after.size > 0"
|
||||||
class="toggle-btn"
|
class="toggle-btn"
|
||||||
@click="togglePane('before')"
|
@click="togglePane(paneBefore)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="direction-icon"
|
class="direction-icon"
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="before.max === 100 && before.size > 0"
|
v-if="before.max === 100 && before.size > 0"
|
||||||
class="toggle-btn"
|
class="toggle-btn"
|
||||||
@click="togglePane('after')"
|
@click="togglePane(paneAfter)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="direction-icon"
|
class="direction-icon"
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- splitter end -->
|
<!-- splitter end -->
|
||||||
<div
|
<div
|
||||||
class="splitpanes__pane"
|
class="splitpanes-pane"
|
||||||
ref="right"
|
ref="right"
|
||||||
:style="styles.after"
|
:style="styles.after"
|
||||||
>
|
>
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import splitter from '@/splitter'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Splitpanes',
|
name: 'Splitpanes',
|
||||||
@@ -82,10 +83,7 @@ export default {
|
|||||||
before: this.before.size,
|
before: this.before.size,
|
||||||
after: this.after.size
|
after: this.after.size
|
||||||
},
|
},
|
||||||
touch: {
|
dragging: false,
|
||||||
mouseDown: false,
|
|
||||||
dragging: false
|
|
||||||
},
|
|
||||||
movableSplitter: {
|
movableSplitter: {
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -109,71 +107,49 @@ export default {
|
|||||||
directionBeforeIconStyle () {
|
directionBeforeIconStyle () {
|
||||||
const expanded = this.paneBefore.size !== 0
|
const expanded = this.paneBefore.size !== 0
|
||||||
const translation = 'translate(-50%, -50%) '
|
const translation = 'translate(-50%, -50%) '
|
||||||
|
let rotation = ''
|
||||||
|
|
||||||
if (this.horizontal) {
|
if (this.horizontal) {
|
||||||
return {
|
rotation = expanded ? 'rotate(90deg)' : 'rotate(-90deg)'
|
||||||
transform: `${translation} ${expanded ? 'rotate(90deg)' : 'rotate(-90deg)'}`
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
rotation = expanded ? 'rotate(0deg)' : 'rotate(180deg)'
|
||||||
transform: `${translation} ${expanded ? 'rotate(0deg)' : 'rotate(180deg)'}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
transform: translation + rotation
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directionAfterIconStyle () {
|
directionAfterIconStyle () {
|
||||||
const expanded = this.paneAfter.size !== 0
|
const expanded = this.paneAfter.size !== 0
|
||||||
const translation = 'translate(-50%, -50%)'
|
const translation = 'translate(-50%, -50%)'
|
||||||
|
let rotation = ''
|
||||||
|
|
||||||
if (this.horizontal) {
|
if (this.horizontal) {
|
||||||
return {
|
rotation = expanded ? 'rotate(-90deg)' : 'rotate(90deg)'
|
||||||
transform: `${translation} ${expanded ? 'rotate(-90deg)' : 'rotate(90deg)'}`
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
rotation = expanded ? 'rotate(180deg)' : 'rotate(0deg)'
|
||||||
transform: `${translation} ${expanded ? 'rotate(180deg)' : 'rotate(0deg)'}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
transform: translation + rotation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
bindEvents () {
|
|
||||||
document.addEventListener('mousemove', this.onMouseMove, { passive: false })
|
|
||||||
document.addEventListener('mouseup', this.onMouseUp)
|
|
||||||
|
|
||||||
// Passive: false to prevent scrolling while touch dragging.
|
|
||||||
if ('ontouchstart' in window) {
|
|
||||||
document.addEventListener('touchmove', this.onMouseMove, { passive: false })
|
|
||||||
document.addEventListener('touchend', this.onMouseUp)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
unbindEvents () {
|
|
||||||
document.removeEventListener('mousemove', this.onMouseMove, { passive: false })
|
|
||||||
document.removeEventListener('mouseup', this.onMouseUp)
|
|
||||||
|
|
||||||
if ('ontouchstart' in window) {
|
|
||||||
document.removeEventListener('touchmove', this.onMouseMove, { passive: false })
|
|
||||||
document.removeEventListener('touchend', this.onMouseUp)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseDown () {
|
onMouseDown () {
|
||||||
this.bindEvents()
|
splitter.bindEvents(this.onMouseMove, this.onMouseUp)
|
||||||
this.touch.mouseDown = true
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseMove (event) {
|
onMouseMove (event) {
|
||||||
if (this.touch.mouseDown) {
|
|
||||||
// Prevent scrolling while touch dragging (only works with an active event, eg. passive: false).
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.touch.dragging = true
|
this.dragging = true
|
||||||
this.$set(this.movableSplitter, 'visibility', 'visible')
|
this.$set(this.movableSplitter, 'visibility', 'visible')
|
||||||
this.moveSplitter(event)
|
this.moveSplitter(event)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseUp () {
|
onMouseUp () {
|
||||||
this.touch.mouseDown = false
|
if (this.dragging) {
|
||||||
if (this.touch.dragging) {
|
|
||||||
const dragPercentage = this.horizontal
|
const dragPercentage = this.horizontal
|
||||||
? this.movableSplitter.top
|
? this.movableSplitter.top
|
||||||
: this.movableSplitter.left
|
: this.movableSplitter.left
|
||||||
@@ -186,65 +162,32 @@ export default {
|
|||||||
left: 0,
|
left: 0,
|
||||||
visibility: 'hidden'
|
visibility: 'hidden'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dragging = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep dragging flag until click event is finished (click happens immediately after mouseup)
|
splitter.unbindEvents(this.onMouseMove, this.onMouseUp)
|
||||||
// in order to prevent emitting `splitter-click` event if splitter was dragged.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.touch.dragging = false
|
|
||||||
this.unbindEvents()
|
|
||||||
}, 100)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get the cursor position relative to the splitpane container.
|
|
||||||
getCurrentMouseDrag (event) {
|
|
||||||
const rect = this.container.getBoundingClientRect()
|
|
||||||
const { clientX, clientY } = ('ontouchstart' in window && event.touches)
|
|
||||||
? event.touches[0]
|
|
||||||
: event
|
|
||||||
return {
|
|
||||||
x: clientX - rect.left,
|
|
||||||
y: clientY - rect.top
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns the drag percentage of the splitter relative to the 2 panes it's inbetween.
|
|
||||||
// if the sum of size of the 2 cells is 60%, the dragPercentage range will be 0 to 100% of this 60%.
|
|
||||||
getCurrentDragPercentage (drag) {
|
|
||||||
drag = drag[this.horizontal ? 'y' : 'x']
|
|
||||||
// In the code bellow 'size' refers to 'width' for vertical and 'height' for horizontal layout.
|
|
||||||
const containerSize = this.container[this.horizontal ? 'clientHeight' : 'clientWidth']
|
|
||||||
return drag * 100 / containerSize
|
|
||||||
},
|
},
|
||||||
|
|
||||||
moveSplitter (event) {
|
moveSplitter (event) {
|
||||||
const dragPercentage = this.getCurrentDragPercentage(this.getCurrentMouseDrag(event))
|
const dragPercentage = splitter
|
||||||
const paneBefore = this.paneBefore
|
.getCurrentDragPercentage(event, this.container, this.horizontal)
|
||||||
const paneAfter = this.paneAfter
|
|
||||||
|
|
||||||
const paneBeforeMaxReached = paneBefore.max < 100 && (dragPercentage >= paneBefore.max)
|
const paneBeforeMax = this.paneBefore.max
|
||||||
const paneAfterMaxReached = paneAfter.max < 100 && (dragPercentage <= 100 - paneAfter.max)
|
const paneAfterMax = this.paneAfter.max
|
||||||
|
|
||||||
const dir = this.horizontal ? 'top' : 'left'
|
|
||||||
|
|
||||||
// Prevent dragging beyond pane max.
|
// Prevent dragging beyond pane max.
|
||||||
if (paneBeforeMaxReached || paneAfterMaxReached) {
|
const offset = splitter.calculateOffset(paneBeforeMax, paneAfterMax, dragPercentage)
|
||||||
if (paneBeforeMaxReached) {
|
const dir = this.horizontal ? 'top' : 'left'
|
||||||
this.$set(this.movableSplitter, dir, paneBefore.max)
|
this.$set(this.movableSplitter, dir, offset)
|
||||||
} else {
|
|
||||||
this.$set(this.movableSplitter, dir, Math.max(100 - paneAfter.max, 0))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$set(this.movableSplitter, dir, Math.min(Math.max(dragPercentage, 0), paneBefore.max))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
togglePane (toggledPane) {
|
|
||||||
const pane = toggledPane === 'before' ? this.paneBefore : this.paneAfter
|
togglePane (pane) {
|
||||||
if (pane.size > 0) {
|
if (pane.size > 0) {
|
||||||
this.beforeMinimising.before = this.paneBefore.size
|
this.beforeMinimising.before = this.paneBefore.size
|
||||||
this.beforeMinimising.after = this.paneAfter.size
|
this.beforeMinimising.after = this.paneAfter.size
|
||||||
pane.size = 0
|
pane.size = 0
|
||||||
const otherPane = toggledPane === 'before' ? this.paneAfter : this.paneBefore
|
const otherPane = pane === this.paneBefore ? this.paneAfter : this.paneBefore
|
||||||
otherPane.size = 100 - pane.size
|
otherPane.size = 100 - pane.size
|
||||||
} else {
|
} else {
|
||||||
this.paneBefore.size = this.beforeMinimising.before
|
this.paneBefore.size = this.beforeMinimising.before
|
||||||
@@ -265,11 +208,11 @@ export default {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical {flex-direction: row;}
|
.splitpanes-vertical {flex-direction: row;}
|
||||||
.splitpanes--horizontal {flex-direction: column;}
|
.splitpanes-horizontal {flex-direction: column;}
|
||||||
.splitpanes--dragging * {user-select: none;}
|
.splitpanes-dragging * {user-select: none;}
|
||||||
|
|
||||||
.splitpanes__pane {
|
.splitpanes-pane {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -277,16 +220,16 @@ export default {
|
|||||||
|
|
||||||
/* Splitter */
|
/* Splitter */
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter,
|
.splitpanes-vertical > .splitpanes-splitter,
|
||||||
.splitpanes--vertical.splitpanes--dragging {
|
.splitpanes-vertical.splitpanes-dragging {
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
.splitpanes--horizontal > .splitpanes__splitter,
|
.splitpanes-horizontal > .splitpanes-splitter,
|
||||||
.splitpanes--horizontal.splitpanes--dragging {
|
.splitpanes-horizontal.splitpanes-dragging {
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes__splitter {
|
.splitpanes-splitter {
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
background-color: var(--color-bg-light);
|
background-color: var(--color-bg-light);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -295,12 +238,12 @@ export default {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--horizontal > .splitpanes__splitter {
|
.splitpanes-horizontal > .splitpanes-splitter {
|
||||||
border-top: 1px solid var(--color-border-light);
|
border-top: 1px solid var(--color-border-light);
|
||||||
border-bottom: 1px solid var(--color-border-light);
|
border-bottom: 1px solid var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter {
|
.splitpanes-vertical > .splitpanes-splitter {
|
||||||
border-left: 1px solid var(--color-border-light);
|
border-left: 1px solid var(--color-border-light);
|
||||||
border-right: 1px solid var(--color-border-light);
|
border-right: 1px solid var(--color-border-light);
|
||||||
}
|
}
|
||||||
@@ -310,15 +253,15 @@ export default {
|
|||||||
background-color:rgba(162, 177, 198, 0.5);
|
background-color:rgba(162, 177, 198, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter,
|
.splitpanes-vertical > .splitpanes-splitter,
|
||||||
.splitpanes--vertical > .movable-splitter {
|
.splitpanes-vertical > .movable-splitter {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
height: 100%
|
height: 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--horizontal > .splitpanes__splitter,
|
.splitpanes-horizontal > .splitpanes-splitter,
|
||||||
.splitpanes--horizontal > .movable-splitter {
|
.splitpanes-horizontal > .movable-splitter {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
@@ -332,11 +275,11 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter .toggle-btns {
|
.splitpanes-vertical > .splitpanes-splitter .toggle-btns {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--horizontal > .splitpanes__splitter .toggle-btns {
|
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,12 +292,12 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter .toggle-btn {
|
.splitpanes-vertical > .splitpanes-splitter .toggle-btn {
|
||||||
height: 49px;
|
height: 49px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--horizontal > .splitpanes__splitter .toggle-btn {
|
.splitpanes-horizontal > .splitpanes-splitter .toggle-btn {
|
||||||
width: 49px;
|
width: 49px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
@@ -365,20 +308,20 @@ export default {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--horizontal > .splitpanes__splitter .toggle-btns.both .toggle-btn:first-child {
|
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child {
|
||||||
border-radius: var(--border-radius-small) 0 0 var(--border-radius-small);
|
border-radius: var(--border-radius-small) 0 0 var(--border-radius-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--horizontal > .splitpanes__splitter .toggle-btns.both .toggle-btn:last-child {
|
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child {
|
||||||
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
|
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter .toggle-btns.both .toggle-btn:first-child {
|
.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child {
|
||||||
border-radius: var(--border-radius-small) var(--border-radius-small) 0 0;
|
border-radius: var(--border-radius-small) var(--border-radius-small) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpanes--vertical > .splitpanes__splitter .toggle-btns.both .toggle-btn:last-child {
|
.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child {
|
||||||
border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
|
border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/splitter.js
Normal file
54
src/splitter.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
export default {
|
||||||
|
bindEvents (onMouseMove, onMouseUp) {
|
||||||
|
// Passive: false to prevent scrolling while touch dragging.
|
||||||
|
document.addEventListener('mousemove', onMouseMove, { passive: false })
|
||||||
|
document.addEventListener('mouseup', onMouseUp)
|
||||||
|
|
||||||
|
if ('ontouchstart' in window) {
|
||||||
|
document.addEventListener('touchmove', onMouseMove, { passive: false })
|
||||||
|
document.addEventListener('touchend', onMouseUp)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unbindEvents (onMouseMove, onMouseUp) {
|
||||||
|
document.removeEventListener('mousemove', onMouseMove, { passive: false })
|
||||||
|
document.removeEventListener('mouseup', onMouseUp)
|
||||||
|
|
||||||
|
if ('ontouchstart' in window) {
|
||||||
|
document.removeEventListener('touchmove', onMouseMove, { passive: false })
|
||||||
|
document.removeEventListener('touchend', onMouseUp)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get the cursor position relative to the splitpane container.
|
||||||
|
getCurrentMouseDrag (event, container) {
|
||||||
|
const rect = container.getBoundingClientRect()
|
||||||
|
const { clientX, clientY } = ('ontouchstart' in window && event.touches)
|
||||||
|
? event.touches[0]
|
||||||
|
: event
|
||||||
|
return {
|
||||||
|
x: clientX - rect.left,
|
||||||
|
y: clientY - rect.top
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Returns the drag percentage of the splitter relative to the 2 panes it's inbetween.
|
||||||
|
getCurrentDragPercentage (event, container, isHorisontal) {
|
||||||
|
let drag = this.getCurrentMouseDrag(event, container)
|
||||||
|
drag = drag[isHorisontal ? 'y' : 'x']
|
||||||
|
const containerSize = container[isHorisontal ? 'clientHeight' : 'clientWidth']
|
||||||
|
return drag * 100 / containerSize
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateOffset (paneBeforeMax, paneAfterMax, dragPercentage) {
|
||||||
|
const paneBeforeMaxReached = paneBeforeMax < 100 && (dragPercentage >= paneBeforeMax)
|
||||||
|
const paneAfterMaxReached = paneAfterMax < 100 && (dragPercentage <= 100 - paneAfterMax)
|
||||||
|
|
||||||
|
// Prevent dragging beyond pane max.
|
||||||
|
if (paneBeforeMaxReached || paneAfterMaxReached) {
|
||||||
|
return paneBeforeMaxReached ? paneBeforeMax : Math.max(100 - paneAfterMax, 0)
|
||||||
|
} else {
|
||||||
|
return Math.min(Math.max(dragPercentage, 0), paneBeforeMax)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user