isu-elements
Version:
Polymer components for building web apps.
1,168 lines (1,068 loc) • 34.4 kB
JavaScript
import { html, PolymerElement } from '@polymer/polymer'
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class'
import '@webcomponents/shadycss/entrypoints/apply-shim.js'
import '@polymer/iron-icon/iron-icon'
import '@polymer/iron-icons/iron-icons'
import '@polymer/iron-icons/social-icons'
import { BaseBehavior } from './behaviors/base-behavior'
import './behaviors/isu-elements-shared-styles.js'
import { IsuFetch } from './isu-fetch'
import { CacheSearchUtil } from './utils/cacheSearchUtil'
import { PinyinUtil } from './utils/pinyinUtil'
import './isu-iron-fit'
import { dom } from '@polymer/polymer/lib/legacy/polymer.dom.js'
import { TipBehavior } from './behaviors/tip-behavior'
/**
Example:
```html
<isu-picker id="picker" label="单选" value="1" attr-for-value="id" placeholder="请选择" clearable></isu-picker>
<isu-picker id="picker1" label="多选" value="1,2,3,4" attr-for-value="id" multi placeholder="请选择" picker-meta='[{"field": "label", "label": "选项"}, {"field": "business", "label": "业务范围"}]'></isu-picker>
<isu-picker id="pickerAll" label="多选" value="1,2,3,4" attr-for-value="id" show-all
multi placeholder="请选择" picker-meta='[{"field": "label", "label": "选项"}, {"field": "business", "label": "业务范围"}]'></isu-picker>
<isu-picker id="picker3" label="只读" value="1,2,3" attr-for-value="id" multi readonly></isu-picker>
<isu-picker id="picker4" label="必填" value="1,2,3" attr-for-value="id" multi required enable-hotkey></isu-picker>
<isu-picker id="pickerNum" label="限制多选数量" attr-for-value="id" multi-limit="3" multi required prompt="公司不能为空" enable-hotkey></isu-picker>
<isu-picker id="picker5" label="修改组件大小" multi="" attr-for-value="id" value="1,2,3,4,5"></isu-picker>
<isu-picker id="picker6" label="默认" attr-for-value="id"></isu-picker>
<isu-picker id="picker7" label="自定义搜索字段" attr-for-value="id"></isu-picker>
<isu-picker id="picker8" query-by-value-url="/init.do" label="自定义初始数据源" attr-for-value="id"></isu-picker>
<isu-picker id="picker9" label="通过接口搜索数据" query-by-value-url="/init.do" multi="" attr-for-value="id"></isu-picker>
<isu-picker id="picker10" label="键盘快捷键操作" query-by-value-url="/api/listProduct" attr-for-value="id" keyword-path="request.keyword" result-path="success.result"
fetch-param='{"request": {"pageRequest": {"limit": 10, "start": 0}}}'></isu-picker>
```
## Styling
The following custom properties and mixins are available for styling:
|Custom property | Description | Default|
|----------------|-------------|----------|
|`--isu-picker-width` | The width of the picker | 320px
|`--isu-ui-font-family` | The font family of the picker | Microsoft YaHei
|`--isu-ui-font-size` | The font size of the picker | 14px
|`--isu-ui-bg` | The basic color of the selected tags,collapse tr`s color when hover tr | linear-gradient(315deg, var(--isu-ui-color_lightblue) 0%, var(--isu-ui-color_skyblue) 100%)
|`--isu-ui-red` | The color of the selected tag`s delete shape when hover the tag | linear-gradient(315deg, #f9a7c3 0%, var(--isu-ui-color_pink) 100%);
|`--isu-picker-input` | Mixin applied to the keyword input | {}
|`--isu-picker-tag` | Mixin applied to the chosed tags | {}
|`--isu-select-tag-deleter` | Mixin applied to the selected tag's delete tag | {}
|`--isu-picker-dropdown` | Mixin applied to the dropdown table | {}
|`--collapase-table-cell` | Mixin applied to the dropdown table's cell | {}
|`--isu-view-text` | Mixin applied to the text when the readonly and is-view is true | {}
* @customElement
* @polymer
* @demo demo/isu-picker/index.html
*/
class IsuPicker extends mixinBehaviors([BaseBehavior, TipBehavior], PolymerElement) {
static get template () {
return html`
<style include="isu-elements-shared-styles">
:host {
display: flex;
height: var(--isu-picker-height, var(--isu-default-line-height, 34px));
line-height: var(--isu-picker-height,var(--isu-default-line-height, 34px));
width: var(--isu-picker-width, 320px);
font-family: var(--isu-ui-font-family), sans-serif;
font-size: var(--isu-ui-font-size);
position: relative;
box-sizing: border-box;
}
.input-wrap {
flex: 1;
position: relative;
display: flex;
min-width: 0;
}
.input-container {
flex: 1;
display: flex;
width: 100%;
}
#keywordInput {
flex: 1;
min-width: 10px;
height: 22px;
line-height: 22px;
padding: 0;
margin: 2px;
border: none;
outline: none;
@apply --isu-picker-input;
}
/*标签容器*/
.tags-input {
flex: 1;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
background: #FFF;
padding: 2px;
overflow-y: auto;
border: 1px solid #CCC;
border-radius: 4px;
position: relative;
@apply --isu-picker-tags-input;
}
.tags-input::-webkit-scrollbar {
display: none;
}
.tag {
color: #fff;
background: var(--isu-ui-bg);
border-radius: 4px;
margin: 2px;
padding: 0 4px;
min-height: 22px;
line-height: 22px;
/*max-width: calc(var(--isu-picker-width)- 30px);*/
display: flex;
cursor: default;
word-break: break-all;
@apply --isu-picker-tag;
}
.tag-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
@apply --isu-picker-tag-name;
}
.tag-deleter {
margin-left: 6px;
width: 18px;
color: #fff;
cursor: pointer;
@apply --isu-select-tag-deleter;
}
.tag-deleter:hover {
color: var(--isu-ui-red);
}
#picker-collapse {
display: flex;
position: absolute;
/*min-width: 100%;*/
margin-top: 1px;
border-radius: 4px;
font-size: 12px;
z-index: 100;
padding: 0;
background: white;
color: black;
visibility: visible;
opacity: 1;
/*transition: all 150ms ease-in;*/
@apply --isu-picker-dropdown;
}
#picker-collapse[hidden] {
visibility: hidden;
height: 0;
opacity: 0;
}
/*显示下拉面板*/
:host .show {
opacity: 1;
visibility: visible;
}
.collapse-content__table {
width: 100%;
font-size: 12px;
border-collapse: separate;
border-spacing: 0;
text-align: left;
border-radius: 4px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
}
.collapse-table__cell {
white-space: nowrap;
text-align: center;
padding: 6px 10px;
line-height: 1.42857143;
border-bottom: 1px solid #ddd;
@apply --collapase-table-cell
}
th.collapse-table__cell {
padding-top: 12px;
font-size: 1.1em;
}
tbody > tr:hover {
background: var(--isu-ui-bg);
color: #fff;
cursor: pointer;
}
tr.candidate-item--focus {
background: var(--isu-ui-bg) ;
color: #fff;
}
.table-hotkey {
width: 40px;
}
#placeholder[hidden] {
display: none;
}
#placeholder {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
color: #999;
opacity: 1;
padding: 0 6px;
overflow: hidden;
white-space: nowrap;
text-align: left;
}
.red-text {
color: red ;
}
:host([required]) .input-wrap::before {
content: "*";
color: red;
position: absolute;
left: -8px;
line-height: inherit;
@apply --isu-required
}
:host([data-invalid]) .tags-input {
border-color: var(--isu-ui-color_pink);
}
:host([show-all]) {
height: auto
}
.clear {
width: 12px;
padding: 0 5px;
z-index: 1;
position: absolute;
right: 5px;
top: -2px;
}
.icon-clear {
width: 12px;
height: 12px;
border: 1px solid #ccc;
border-radius: 50%;
color: #ccc;
display: none;
}
:host([clearable]) .input-wrap:hover .icon-clear {
display: inline-block;
}
.view-text {
@apply --isu-view-text
}
</style>
<template is="dom-if" if="[[ toBoolean(label) ]]">
<div class="isu-label-div"><span class$="isu-label [[fontSize]]">[[label]]</span><span class="isu-label-after-extension"></span></div>
</template>
<div class$="input-wrap [[fontSize]]" id="select__container">
<div class="input-container">
<div class="tags-input" on-click="__openCollapse" id="tags-input">
<div id="placeholder" class$="[[_isHighlightKeyword(isHighlightKeyword, placeholder)]]" hidden="[[isExistTruthy(value, _userInputKeyword)]]">[[placeholder]]</div>
<template is="dom-repeat" items="[[ selectedValues ]]" index-as="index">
<span class="tag" data-args="[[ __calcTagName(item) ]]" on-contextmenu="_contextMenuHandler">
<span class="tag-name" title="[[ getValueByKey(item, attrForLabel) ]]">
[[ __calcTagName(item) ]]
</span>
<iron-icon class="tag-deleter" icon="icons:clear" on-click="_deleteTag"></iron-icon>
</span>
</template>
<input id="keywordInput" value="{{ _userInputKeyword::input }}" autocomplete="off" on-focus="__inputFocus">
<div class="clear">
<template is="dom-if" if="[[ isExistTruthy(_userInputKeyword) ]]">
<iron-icon class="icon-clear" icon=icons:clear on-click="clear"></iron-icon>
</template>
</div>
</div> <!-- class=tags-input -->
<div class="mask" part="mask"></div>
</div>
<isu-iron-fit id="picker-collapse" hidden auto-fit-on-attach vertical-align="auto" horizontal-align="auto" class="selected" no-overlap dynamic-align>
<table class="collapse-content__table">
<thead>
<tr>
<template is="dom-repeat" items="[[pickerMeta]]">
<th class="collapse-table__cell">[[item.label]]</th>
</template>
<template is="dom-if" if="[[ enableHotkey ]]">
<th class="collapse-table__cell table-hotkey">快捷键</th>
</template>
</tr>
</thead>
<tbody>
<template is="dom-repeat" items="[[_displayItems]]" as="row">
<tr id="candidate-item__[[index]]" on-click="_selectCollapseItem">
<template is="dom-repeat" items="[[pickerMeta]]" as="col">
<td class="collapse-table__cell">[[ getValueByPath(row, col.field, '', col.format) ]]</td>
</template>
<template is="dom-if" if="[[ enableHotkey ]]">
<td class="collapse-table__cell table-hotkey">[[_getHotKey(index)]]</td>
</template>
</tr>
</template>
</tbody>
</table>
<div class="prompt-tip__container" data-prompt$="[[prompt]]">
<div class="prompt-tip">
<iron-icon class="prompt-tip-icon" icon="social:sentiment-very-dissatisfied"></iron-icon>
[[prompt]]
</div>
</div>
</isu-iron-fit>
</div>
<template is="dom-if" if="[[_isView(isView, readonly)]]">
<div class="view-text">
<span>[[getViewLabels(selectedValues, attrForLabel, joinConnector)]]</span>
</div>
</template>
`
}
static get properties () {
return {
/**
* Chinese pinyin plugin
*/
_pinyinUtil: {
type: Object,
readOnly: true,
value: function () {
return new PinyinUtil()
}
},
/**
* Cache search plugin
*/
_cacheSearchUtil: {
type: Object,
readOnly: true,
value: function () {
return new CacheSearchUtil()
}
},
/**
* The component that sends the request and simulates the data
*/
_fetchUtil: {
type: Object,
readOnly: true,
value: function () {
return new IsuFetch()
}
},
/**
* The label of the picker.
* @type {string}
*/
label: {
type: String
},
/**
* The placeholder of the select.
* @type {String}
*/
placeholder: {
type: String
},
/**
*
* The selected value of this select, if `multi` is true,
* the value will join with comma ( `selectedValues.map(selected => selected[this.attrForValue]).join(',')` ).
* @type {string}
*/
value: {
type: String,
notify: true
},
/**
* The selected value objects of this select.
* @type {array}
*/
selectedValues: {
type: Array,
notify: true
},
/**
* The selected item.
* @type {object}
*/
selectedItem: {
type: Object,
notify: true
},
/**
* A url for searching data with user input keywords, the response data of the request should be json.
* @type {string}
*/
queryByKeywordUrl: {
type: String
},
/**
* A url for fetching data by value, the response data of the request should be json.
* @type {string}
*/
queryByValueUrl: {
type: String
},
/**
* The candidate selection of this picker.
* @type {array}
*/
items: {
type: Array
},
/**
* The data set displayed currently in the drop-down panel(The first 10 of the items are displayed by default)
* @type {array}
*/
_displayItems: {
type: Array
},
/**
* The search keywords input by the user
* @type {string}
*
*/
_userInputKeyword: {
type: String
},
/**
* Fields to build index for pinyin plugin.
* @type {array}
*/
fieldsForIndex: {
type: Array
},
/**
* The fields shown in the drop-down panel,default: [{"field": "label", "label": "选项"}]
* @type {array}
* @default [{"field": "label", "label": "选项"}]
*/
pickerMeta: {
type: Array,
value: function () {
return [{ field: 'label', label: '选项' }]
}
},
/**
* Attribute name for value.
* @type {string}
* @default 'value'
*/
attrForValue: {
type: String,
value: 'value'
},
/**
* Attribute name for label.
* @type {object}
* @default 'label'
*/
attrForLabel: {
type: Object,
value: 'label'
},
/**
* Whether to disable pinyin search or not
*
* @type {boolean}
* @default false
*/
disablePinyinSearch: {
type: Boolean,
value: false
},
/**
* Set to true, if the selection is required.
* @type {boolean}
* @default false
*/
required: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* Set to true, if the picker is readonly.
* @type {boolean}
* @default false
*/
readonly: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* If true, multiple selections are allowed.
* @type {boolean}
* @default false
*/
multi: {
type: Boolean,
value: false
},
/**
* The current option focus in the dropdown panel.
*/
__focusIndex: {
type: Number,
value: 0
},
/**
* If true, hotkeys for selecting items are allowed.
* @type {boolean}
* @default false
*/
enableHotkey: {
type: Boolean,
value: false
},
/**
* The limit number that customers can choose if the selection is multiple.
*
* @type {number}
* @default
*/
multiLimit: {
type: Number
},
/**
* url`s params
* @type {Object}
* @default
*/
fetchParam: {
type: Object
},
/**
* queryByKeywordUrl query params name ,default 'keyword',such as `/queryBykeyword?keyword = `
*/
keywordPath: {
type: String,
value: 'keyword'
},
/**
* queryByValueUrl query params name,default 'ids',such as `/queryByValues?ids = `
*/
valuePath: {
type: String,
value: 'ids'
},
resultPath: {
type: String
},
/**
* shortcut key
* @type {string}
* @default 'enter'
*/
shortcutKey: {
type: String,
value: 'Enter'
},
inputChinese: Boolean,
/**
* The prompt tip to show when input is invalid.
* @type String
*/
prompt: {
type: String
},
/**
* The prompt tip's position. top/bottom
* @type String
* @default ''
*/
promptPosition: {
type: String,
value: ''
},
/**
* The mode of the request, eg: POST/GET
* @type String
* @default ''
*/
method: {
type: String,
value: 'GET'
},
/**
* The text mode display requires readonly=true to take effect
* @type {boolean}
* @default false
* */
isView: {
type: Boolean,
value: false
},
/**
* The connector to connect labels when the isView=true, eg: "苹果,香蕉,梨"
* @type {string}
* @default ','
* */
joinConnector: {
type: String,
value: ','
},
/**
* If true hides the component, default false
*/
hidden: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* If you do not have permissions, the component does not display
* @type Boolean
* @default true
*/
permission: {
type: Boolean,
value: true,
observer: '_permissionChange'
},
/**
* Ajax headers
* @type Object
* @default
*/
headers: {
type: Object
},
/**
* 0 是否为有效value值,默认为否
*/
zeroIsValue: {
type: Boolean,
value: false
},
/**
* 选择选项的长度,默认10
*/
displayItemsLength: {
type: Number,
value: 10
},
isHighlightKeyword: {
type: Boolean,
value () {
return false
}
}
}
}
static get is () {
return 'isu-picker'
}
static get observers () {
return [
'_queryByKeywordUrlChanged(queryByKeywordUrl)',
'_itemsChanged(items)',
'_userInputKeywordChanged(_userInputKeyword)',
'_selectedValuesChanged(selectedValues.splices)',
'_valueChanged(value)',
'getInvalidAttribute(required, value)',
'__isViewChanged(isView,readonly)'
]
}
connectedCallback () {
super.connectedCallback()
this.$.keywordInput.addEventListener('keydown', this._keyDownHandler.bind(this))
this.$.keywordInput.addEventListener('compositionstart', () => { this.inputChinese = true })
this.$.keywordInput.addEventListener('compositionend', () => { this.inputChinese = false })
this.addEventListener('blur', e => {
e.stopPropagation()
setTimeout(() => { // 解决blur事件和click事件冲突的问题
if (this.shadowRoot.activeElement && this.shadowRoot.activeElement.id === 'keywordInput') return
this.displayCollapse(false)
this._userInputKeyword = ''
}, 200)
})
const target = dom(this.$['picker-collapse']).rootTarget
const myFit = this.$['picker-collapse']
myFit.positionTarget = target || this.$['tags-input']
}
_contextMenuHandler (e) {
e.preventDefault()
const text = e.currentTarget.dataArgs
const oInput = document.createElement('input')
oInput.value = text
document.body.appendChild(oInput)
oInput.select() // 选择对象
document.execCommand('Copy') // 执行浏览器复制命令
oInput.style.display = 'none'
this.isuTip.success('复制成功', 1000)
}
getViewLabels (items = [], attrForLabel, connector) {
const labels = items.map(item => this.__calcTagName(item))
return labels.join(connector)
}
__calcTagName (item) {
const attrForLabel = this.attrForLabel
if (this.isFunction(attrForLabel)) {
return attrForLabel.call(this, item)
}
return this.getValueByKey(item, attrForLabel)
}
_mkRequest (url, data) {
if (this.method === 'GET') {
return {
url: url,
method: 'GET',
headers: {
'Cache-Control': 'no-cache',
...this.headers
},
credentials: 'include',
body: JSON.stringify(data)
}
}
return {
url: url,
method: 'POST',
headers: {
'content-type': 'application/json;charset=utf-8',
'Cache-Control': 'no-cache',
...this.headers
},
credentials: 'include',
body: JSON.stringify(data)
}
}
async _queryByKeywordUrlChanged (queryByKeywordUrl) {
if (!queryByKeywordUrl) return
// this.debounceFetchByKeyword()
}
async _getSelectedForItems (itemsArr) {
try {
const selectedValues = await this.fetchSelectedValues()
const items = itemsArr || []
// 判断是否有交集
const flag = items.filter(d => selectedValues.find(i => `${i[this.attrForValue]}` === `${d[this.attrForValue]}`)).length > 0
const addItems = items.filter(d => !selectedValues.find(i => `${i[this.attrForValue]}` === `${d[this.attrForValue]}`))
this.items = flag || items.length === 0 ? selectedValues.concat(addItems) : items
return selectedValues
} catch (e) {
console.error(e)
}
}
async _itemsChanged (items = []) {
this._displayItems = items.slice(0, this.displayItemsLength || 10)
// 初始化一次选中项
if (this.value !== undefined && this.value !== null && this.value !== '') {
if (!this.zeroIsValue && this.value === 0) return
await this._valueChanged(this.value)
}
// 清空缓存插件的缓存
this._cacheSearchUtil.resetCache()
items.forEach(item => this._cacheSearchUtil.addCacheItem(item, this._loadPinyinKeys(item, this.fieldsForIndex)))
}
_userInputKeywordChanged (_userInputKeyword) {
if (this._userInputKeyword.length > 0) {
this.displayCollapse(true)
}
this.debounceFetchByKeyword()
}
debounceFetchByKeyword() {
this.debounce('__debounceFetchByKeyword', this.fetchByKeyword, 200)
}
/**
* query by Keyword
* @return {Promise<void>}
* @private
*/
async fetchByKeyword () {
try {
if (this.queryByKeywordUrl) {
const requestObj = this.fetchParam
const req = this.setValueByPath(this.mkObject(this.keywordPath, requestObj), this.keywordPath, this._userInputKeyword || '')
const request = this._mkRequest(this.queryByKeywordUrl, req)
const data = await this._fetchUtil.fetchIt(request).then(res => {
return res.json().catch(err => {
console.warn(`'${err}' happened, but no big deal!`)
return []
})
})
const candidateItems = this.resultPath ? this.getValueByPath(data, this.resultPath, []) : data || []
const _displayItems = candidateItems
this._displayItems = _displayItems.slice(0, this.displayItemsLength || 10)
this.items = candidateItems
} else {
const matched = this._cacheSearchUtil.search(this._userInputKeyword, ' ')
this._displayItems = matched.slice(0, this.displayItemsLength || 10)
// this._switchFocusItemAt(0)
}
setTimeout(this.$['picker-collapse'].fixPosition.bind(this.$['picker-collapse']), 0)
} catch (err) {
console.error(err)
}
}
async fetchSelectedValues () {
const { queryByValueUrl, valuePath, value, resultPath, fetchParam } = this
if (valuePath && value) {
const req = this.setValueByPath(this.mkObject(valuePath, fetchParam), valuePath, value ? `${value}` : '')
const request = this._mkRequest(queryByValueUrl, req)
const selectedValues = await this._fetchUtil.fetchIt(request).then(res => {
return res.text().then(text => {
return text ? JSON.parse(text) : []
})
}).then(data => {
return resultPath ? this.getValueByPath(data, resultPath, []) : data
})
return selectedValues
}
return []
}
_selectedValuesChanged () {
const { attrForValue } = this
if (this.selectedValues.length > 0) {
// value去重
this.value = Array.from(new Set(this.selectedValues.map(selected => selected[attrForValue]).filter(item => !this.isEmptyObject(item)))).join(',')
this.selectedItem = this.selectedValues[this.selectedValues.length - 1]
} else {
this.value = ''
this.selectedItem = ''
}
this.displayCollapse(false)
}
/**
* value属性变化监听函数
*/
async _valueChanged (value) {
const { attrForValue } = this
// 本地模式,或远程数据已经就位
let itemsTemp = Array.isArray(this.items) ? this.items : []
const flatValues = [...(new Set(String(value).split(',').filter(item => !!item)))]
const selectedValues = this.selectedValues || []
const dirty = selectedValues.map(selected => selected[attrForValue]).join(',')
if (dirty !== value) {
// 判断是否有交集
const addSelectedItemTemp = selectedValues.filter(selectedItem => !itemsTemp.find(item => `${item[attrForValue]}` === `${selectedItem[attrForValue]}`))
itemsTemp = Array.from(new Set([...addSelectedItemTemp, ...itemsTemp]))
const selectedValuesTemp = flatValues.map(val => itemsTemp.find(item => `${item[attrForValue]}` === `${val}`))
.filter(selected => !!selected)
if (selectedValuesTemp.length !== flatValues.length) {
const newSelectedValues = await this._getSelectedForItems([...itemsTemp])
this.selectedValues = newSelectedValues
} else {
this.selectedValues = selectedValuesTemp
}
}
this.getInvalidAttribute(value)
}
_selectItemAt (index) {
if (index >= 0 && index < this._displayItems.length) {
this._switchFocusItemAt(index)
this._selectItem(this._displayItems[index])
}
}
/**
* 选择选项
* @param item
*/
_selectItem (item) {
// not yet selected
if (!~(this.selectedValues || []).findIndex(selected => `${selected[this.attrForValue]}` === `${item[this.attrForValue]}`)) {
if (this.multi && this.selectedValues) {
this.push('selectedValues', item)
} else {
this.selectedValues = [item]
}
}
this.displayCollapse(false)
if (this.multi) this.__focusOnKeywordInput()
this._userInputKeyword = ''
}
/**
* 切换焦点到第n个元素,从0开始
* @param index
* @private
*/
_switchFocusItemAt (index) {
// setTimeout(() => {
// const maxIndex = (this._displayItems || []).length
// const newIndex = (maxIndex + index) % maxIndex
// this.root.querySelectorAll("tr.candidate-item--focus")
// .forEach(e => e.classList.remove('candidate-item--focus'));
//
// const newFocusItem = this.root.querySelector(`#candidate-item__${newIndex}`);
// if (newFocusItem != null) {
// newFocusItem.classList.add('candidate-item--focus');
// this.__focusIndex = newIndex;
// }
// }, 0)
}
_isPickerCollapseHidden () {
return this.$['picker-collapse'].hidden
}
__openCollapse ({ target: { classList } }) {
if (classList.contains('tag-deleter')) return
this.__focusOnKeywordInput()
}
async __inputFocus () {
if (this.multiLimit && this.selectedValues && this.multiLimit <= this.selectedValues.length) return
this.debounceFetchByKeyword()
// await this.fetchByKeyword()
if (this.isHighlightKeyword) {
this.set('_userInputKeyword', this.placeholder)
this.set('placeholder', '')
}
this.displayCollapse(true)
// this._switchFocusItemAt(0);
}
_isHighlightKeyword(isHighlightKeyword, placeholder) {
if (isHighlightKeyword && !!placeholder) {
return 'red-text'
}
}
__getElemPos (obj) {
const { x, y } = obj.getBoundingClientRect()
return {
left: x,
top: y + 2
}
}
__focusOnKeywordInput () {
this.$.keywordInput.focus()
}
_selectCollapseItem (event) {
event.stopPropagation()
this._selectItem(event.model.row)
this.dispatchEvent(new CustomEvent('picker-selected-item', { detail: event.model.row, bubbles: true, composed: true }))
this._fireValueSelectedChanged()
this.displayCollapse(false)
this.blur()
}
/**
* 人为选择修改value触发
* @private
*/
_fireValueSelectedChanged () {
this.debounce('valuesSelectedChanged', () => {
this.dispatchEvent(new CustomEvent('value-selected-changed', { detail: this.value, bubbles: true, composed: true }))
}, 10)
}
/**
* 输入框键盘按键事件
* @param event
* @private
*/
_keyDownHandler (event) {
if (this.inputChinese) return
if (this.shortcutKey !== event.key && !this.$['picker-collapse'].hidden) event.stopPropagation()
const key = event.key
if (event.altKey || key === this.shortcutKey) {
event.preventDefault()
}
const collapseOpend = !this._isPickerCollapseHidden()
if (collapseOpend && this.enableHotkey && event.altKey) {
const ind = event.code.replace(/[A-Za-z]*/g, '') - 1
this._selectItemAt(ind)
} else {
switch (key) {
case 'ArrowUp':
collapseOpend && this._switchFocusItemAt(this.__focusIndex - 1)
break
case 'ArrowDown':
if (collapseOpend) {
this._switchFocusItemAt(this.__focusIndex + 1)
} else {
this._switchFocusItemAt(0)
this.displayCollapse(true)
}
break
case this.shortcutKey:
if (collapseOpend && this._displayItems.length > 0 && this.__focusIndex < this._displayItems.length) {
this._selectItemAt(this.__focusIndex)
}
break
case 'Backspace':
if (this._userInputKeyword === undefined || this._userInputKeyword.length === 0) {
this.deleteLastTag()
}
break
}
}
}
/**
* 给对象根据fieldsForIndex给对应的字段做拼音缓存(字段值,字段值全拼和拼音首字母)
*/
_loadPinyinKeys (item, fieldsForIndex = []) {
let keys = []; let values = fieldsForIndex.map(sf => item[sf])
values = values.length === 0 ? Object.values(item) : values
if (this.disablePinyinSearch) {
keys = values.map(value => String(value))
} else {
values.forEach(
value => {
keys = keys.concat(
String(value),
this._pinyinUtil.convert2CompletePinyin(value),
this._pinyinUtil.convert2PinyinAbbreviation(value)
)
}
)
}
return keys
}
/**
* Delete the last selected tag.
*/
deleteLastTag () {
if (this.selectedValues && this.selectedValues.length > 0) {
this.pop('selectedValues')
this._fireValueSelectedChanged()
}
}
/**
* 删除Tag项,事件处理函数
*/
_deleteTag (e) {
const item = e.model.item
const value = this.getValueByKey(item, this.attrForValue)
const ind = this.selectedValues.findIndex(selected => selected[this.attrForValue] === value)
this.splice('selectedValues', ind, 1)
this._fireValueSelectedChanged()
if (!this.multi || (this.multi && this.selectedValues.length === 0)) this._userInputKeyword = ''
}
_getHotKey (index) {
return 'Alt+' + (index + 1)
}
/**
* Open or close the collapse
* @param {boolean} display true to open the collapse.
*/
displayCollapse (display) {
this.$['picker-collapse'].hidden = !display
}
/**
* Toggle collapse. Side effect: the picker input will get a focus.
*/
toggleCollapse () {
const hidden = this.$['picker-collapse'].hidden
this.$['picker-collapse'].hidden = !hidden
this.__focusOnKeywordInput()
}
/**
* Set focus to picker.
*/
doFocus () {
this.__focusOnKeywordInput()
}
/**
* Validate, true if the select is set to be required and this.selectedValues.length > 0, or else false.
* @return {boolean}
*/
validate () {
return this.required ? !!this.value : true
}
__isViewChanged (isView, readonly) {
this.$.select__container.style.display = (this.readonly && isView) ? 'none' : 'flex'
}
_isView (isView, readonly) {
return isView && readonly
}
_permissionChange (permission) {
this.set('hidden', !permission)
}
clear (e) {
e.stopPropagation()
this._userInputKeyword = ''
}
}
window.customElements.define(IsuPicker.is, IsuPicker)