Skip to content

Commit

Permalink
feat(menu): new props: menuDynamicPosition, menuDefaultPosition. Smar…
Browse files Browse the repository at this point in the history
…t position for menu

re #228, #234
  • Loading branch information
iliyaZelenko committed Oct 17, 2019
1 parent 0e4f7c8 commit 6a30ed3
Show file tree
Hide file tree
Showing 14 changed files with 387 additions and 190 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The current version is `3.x`, if you are looking for `2.x`, you can find [it her
- keyboard controls (not only through the arrows)
- slots (13) allow content to be changed anywhere
- events (8) will let you know about everything
- props (28) allow you to customize a component in a various ways
- props (30) allow you to customize a component in a various ways
- validation, state of error and success
- support on mobile devices
- disabled and readonly
Expand Down
15 changes: 14 additions & 1 deletion gh-pages-src/pages/dev/Example5.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<template>
<div class="demo-page-wrap">
<div style="height: 500px;">
<div
v-if="!destroy"
style="height: 500px;"
>
<h1 style="text-align: center;">Usage Example 5</h1>

<b>Selected:</b> {{ selected || 'not chosen' }}.
<br><br>

<form @submit="onSubmit">
<cool-select
ref="select"
v-model="selected"
:items="items"
placeholder="Select name"
Expand All @@ -18,6 +22,7 @@
<br>

<cool-select
ref="select"
v-model="selected"
:items="items"
:error-message="errorMessage"
Expand Down Expand Up @@ -100,6 +105,13 @@
>
Toggle disable search
</v-btn>

<v-btn
class="ma-2"
@click="destroy = true"
>
Destroy
</v-btn>
</v-layout>
</div>
</div>
Expand All @@ -111,6 +123,7 @@ import { CoolSelect } from '../../main'
export default {
components: { CoolSelect },
data: () => ({
destroy: false,
disabled: false,
readonly: false,
disableSearch: false,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
},
"dependencies": {},
"devDependencies": {
"rollup-plugin-babel": "^4.3.2",
"@babel/preset-env": "^7.4.3",
"@semantic-release/changelog": "^3.0.2",
"@semantic-release/commit-analyzer": "^6.1.0",
Expand Down Expand Up @@ -45,6 +44,7 @@
"push-dir": "^0.4.1",
"rollup": "^1.8.0",
"rollup-plugin-alias": "^1.5.1",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.1.0",
"rollup-plugin-postcss": "^2.0.3",
Expand Down
202 changes: 136 additions & 66 deletions src/component.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,76 +79,78 @@
/>
</div>

<transition name="fade">
<div
v-for="menuPos of [MENU_POSITIONS.TOP, MENU_POSITIONS.BOTTOM]"
:key="'menu-position-' + menuPos"
:ref="'IZ-select__menu-' + menuPos"
:style="{
'pointer-events': hasMenu ? 'auto' : 'none',
...getMenuDynamicStyles(menuPos)
}"
:class="{
[`IZ-select__menu IZ-select__menu--at-${menuPos}`]: true,
'IZ-select__menu--disable-search': disableSearch
}"
>
<slot name="before-items-fixed" />

<div
v-if="hasMenu"
ref="IZ-select__menu"
:style="menuDynamicStyles"
:class="{
'IZ-select__menu': true,
'IZ-select__menu--disable-search': disableSearch
ref="IZ-select__menu-items"
:style="{
'max-height': menuItemsMaxHeight
}"
class="IZ-select__menu-items"
@scroll="onScroll"
>
<slot name="before-items-fixed" />
<slot name="before-items">
<div style="height: 8px;" />
</slot>

<!--itemsComputedWithScrollLimit-->
<div
ref="IZ-select__menu-items"
:style="{
'max-height': menuItemsMaxHeight
v-for="(item, i) in itemsComputed"
v-show="i < scrollItemsLimitCurrent || (arrowsIndex && i <= arrowsIndex)"
ref="items"
:key="'IZ-item-' + i"
:class="{
'IZ-select__item': true,
'IZ-select__item--selected': isItemSelected(item)
}"
class="IZ-select__menu-items"
@scroll="onScroll"
@click="onClickSelectItem(item)"
>
<slot name="before-items">
<div style="height: 8px;" />
</slot>

<!--itemsComputedWithScrollLimit-->
<div
v-for="(item, i) in itemsComputed"
v-show="i < scrollItemsLimitCurrent || (arrowsIndex && i <= arrowsIndex)"
ref="items"
:key="'IZ-item-' + i"
:class="{
'IZ-select__item': true,
'IZ-select__item--selected': isItemSelected(item)
}"
@click="onClickSelectItem(item)"
>
<slot
:item="item"
name="item"
>
<span>
{{ getItemText(item) }}
</span>
</slot>
</div>

<div
v-if="!itemsComputed.length && !loading"
class="IZ-select__no-data"
<slot
:item="item"
name="item"
>
<slot name="no-data">
{{ $coolSelect.options.text.noData }}
</slot>
</div>
<span>
{{ getItemText(item) }}
</span>
</slot>
</div>

<slot name="after-items">
<div style="height: 8px;" />
<div
v-if="!itemsComputed.length && !loading"
class="IZ-select__no-data"
>
<slot name="no-data">
{{ $coolSelect.options.text.noData }}
</slot>
</div>

<slot name="after-items-fixed" />
<slot name="after-items">
<div style="height: 8px;" />
</slot>
</div>

<div style="position: absolute; top: 0; left: 0; right: 0;">
<slot name="before-items-fixed-absolute" />
</div>
<div style="position: absolute; bottom: 0; left: 0; right: 0;">
<slot name="after-items-fixed-absolute" />
</div>
<slot name="after-items-fixed" />

<div style="position: absolute; top: 0; left: 0; right: 0;">
<slot name="before-items-fixed-absolute" />
</div>
</transition>
<div style="position: absolute; bottom: 0; left: 0; right: 0;">
<slot name="after-items-fixed-absolute" />
</div>
</div>

<transition name="fade">
<div
Expand All @@ -171,7 +173,8 @@ import { isObject, getOffsetSum } from './helpers'
import eventsListeners from './eventsListeners'
import props from './props'
import computed from './computed'
import { SIZES } from '~/constants'
import { SIZES, MENU_POSITIONS } from '~/constants'
import { outOfViewportGetFreePosition } from '~/helpers'
export default {
name: 'VueSelect',
Expand All @@ -183,6 +186,7 @@ export default {
props,
data () {
return {
MENU_POSITIONS,
SIZES,
wishShowMenu: false,
arrowsIndex: null,
Expand All @@ -193,7 +197,19 @@ export default {
searchData: '',
scrollItemsLimitCurrent: this.scrollItemsLimit,
// addEventListener identifier
mousedownListener: null
listeners: {
mousedown: ({ target }) => {
const select = this.$refs['IZ-select']
if (this.focused && select && !select.contains(target)) {
this.setBlured()
}
},
scroll: this.menuCalculatePos,
resize: this.menuCalculatePos
},
menuCurrentPosition: this.menuDefaultPosition,
lastMenuDynamicStyles: null
}
},
computed,
Expand Down Expand Up @@ -226,19 +242,71 @@ export default {
},
mounted () {
// listener for window (see removeEventListener on beforeDestroy hook)
this.mousedownListener = window.addEventListener('mousedown', ({ target }) => {
const select = this.$refs['IZ-select']
window.addEventListener('mousedown', this.listeners.mousedown)
if (this.focused && select && !select.contains(target)) {
this.setBlured()
}
})
if (this.menuDynamicPosition) {
window.addEventListener('scroll', this.listeners.scroll)
window.addEventListener('resize', this.listeners.resize)
}
},
beforeDestroy () {
window.removeEventListener('mousedown', this.mousedownListener)
window.removeEventListener('mousedown', this.listeners.mousedown)
if (this.menuDynamicPosition) {
window.removeEventListener('scroll', this.listeners.scroll)
window.removeEventListener('resize', this.listeners.resize)
}
},
methods: {
...eventsListeners,
getMenuDynamicStyles (menuPos) {
const isCurrentMenu = this.menuCurrentPosition === menuPos && this.hasMenu
const obj = {
visibility: isCurrentMenu ? 'visible' : 'hidden',
opacity: +isCurrentMenu
}
// возвращать старую позицию если нет меню, чтобы стили не дёргались
if (!this.hasMenu) return { ...this.lastMenuDynamicStyles, ...obj }
const input = this.$refs['IZ-select__input']
// ширина и смещение слева такие же как и у поля ввода
obj.width = input.offsetWidth + 'px'
obj.left = input.offsetLeft + 'px'
if (menuPos === MENU_POSITIONS.BOTTOM) {
obj.top = input.offsetTop + input.offsetHeight + 'px'
if (this.disableSearch) {
obj.top = input.offsetTop + 'px'
}
} else if (menuPos === MENU_POSITIONS.TOP) {
obj.bottom = '100%'
if (this.disableSearch) {
obj.bottom = 0
}
}
this.lastMenuDynamicStyles = obj
return obj
},
// динамически позиционирует меню чтобы не выходило за границу viewport
menuCalculatePos () {
if (!this.menuDynamicPosition) return
if (this.hasMenu) {
const newPosTop = outOfViewportGetFreePosition(this.$refs['IZ-select__menu-' + MENU_POSITIONS.TOP][0])
const newPosBottom = outOfViewportGetFreePosition(this.$refs['IZ-select__menu-' + MENU_POSITIONS.BOTTOM][0])
if (!newPosTop && !newPosBottom) {
this.menuCurrentPosition = this.menuDefaultPosition
} else {
this.menuCurrentPosition = newPosTop || newPosBottom
}
}
},
getSearchData () {
return this.searchData
},
Expand Down Expand Up @@ -361,10 +429,12 @@ export default {
isItemSelected (item) {
return item === this.selectedItemByArrows || (item === this.selectedItem && !this.selectedItemByArrows)
},
showMenu () {
async showMenu () {
if (this.hasMenu) return
this.wishShowMenu = true
this.menuCalculatePos()
},
hideMenu () {
if (!this.hasMenu) return
Expand Down
17 changes: 2 additions & 15 deletions src/computed.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MENU_POSITIONS } from './constants'

export default {
itemsComputed () {
let items = this.items
Expand Down Expand Up @@ -46,21 +48,6 @@ export default {
// return window.innerWidth + window.innerHeight <= 1800
return window.innerWidth <= 900 && window.innerHeight <= 900
},
menuDynamicStyles () {
const input = this.$refs['IZ-select__input']
const obj = {
// ширина и смещение слева такие же как и у поля ввода
width: input.offsetWidth + 'px',
left: input.offsetLeft + 'px',
'pointer-events': this.hasMenu ? 'auto' : 'none'
}

if (this.disableSearch) {
obj.top = input.offsetTop + 'px'
}

return obj
},
// get item index from arr
selectedItemIndex () {
for (let itemKey in this.itemsComputed) {
Expand Down
5 changes: 5 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ export const SIZES = {
SMALL: 'sm',
LARGE: 'lg'
}

export const MENU_POSITIONS = {
TOP: 'top',
BOTTOM: 'bottom'
}

0 comments on commit 6a30ed3

Please sign in to comment.