UNPKG

isu-element

Version:

Polymer components for building web apps.

457 lines (419 loc) 13.5 kB
import {html, PolymerElement} from "@polymer/polymer"; import {mixinBehaviors} from "@polymer/polymer/lib/legacy/class"; import '@polymer/iron-icon'; import '@polymer/iron-icons'; import '@polymer/paper-dialog'; import {BaseBehavior} from "./behaviors/base-behavior"; import './behaviors/isu-elements-shared-styles.js'; /** * `isu-cascading` * * ```html * * ``` * * @customElement * @polymer * @demo demo/isu-cascading/index.html */ class IsuCascading extends mixinBehaviors([BaseBehavior], PolymerElement) { static get template() { return html` <style include="isu-elements-shared-styles"> :host { font-family: var(--isu-ui-font-family), sans-serif; font-size: var(--isu-ui-font-size); display: flex; height: 34px; line-height: 34px; min-width: 200px; } :host .cascade { position: relative; } .cascading__container { flex: 1; display: flex; align-items: center; line-height: inherit; min-width: 200px; border: 1px solid #ccc; border-radius: 4px; padding: 2px 5px; position: relative; cursor: pointer; } :host([readonly]) .cascading__container { pointer-events: none; opacity: 0.5; z-index: 10; cursor: no-drop; } #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; } #targetDialog { position: absolute; bottom: -5px; left: 0; width: inherit; } :host([opened]) .caret { transform: rotate(180deg); transition: transform .2s ease-in-out; } .caret { transition: transform .2s ease-in-out; color: var(--isu-ui-color_skyblue); position: absolute; right: 0; top: 0; height: 34px; line-height: 34px; } #boxDialog { position: absolute!important; height: 206px; margin: 0; max-width: initial!important; } .dialog-container { margin: 0; padding: 0; display: flex; } .view-container { flex: 1; height: 206px; min-width: 160px; margin: 0; padding: 8px 0; box-sizing: border-box; border-right: 1px solid #ccc; background: #fff; @apply --isu-view-container; } .view-container:last-of-type { border-right: none; } .view-list { height: 190px; overflow-y: auto; } .view-item { position: relative; padding: 0 12px; height: 30px; line-height: 30px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } .view-item:hover, .view-item-active { color: var(--isu-ui-color_skyblue); font-weight: bold; } .chevron-iron { position: absolute; right: 0; height: 30px; line-height: 30px; } :host([required]) .cascading__container::before { content: "*"; color: red; position: absolute; left: -10px; line-height: inherit; } :host([data-invalid]) .cascading__container { border-color: var(--isu-ui-color_pink); } .icon-clear { position: absolute; right: 5px; width: 12px; height: 12px; line-height: 34px; border: 1px solid #ccc; border-radius: 50%; color: #ccc; display: none; } .cascading__container:hover .icon-clear { display: inline-block; } .cascading__container:hover .caret { display: none; } :host([data-invalid]) #innerInput { border-color: var(--isu-ui-color_pink); } </style> <template is="dom-if" if="[[ toBoolean(label) ]]"> <div class="isu-label">[[label]]</div> </template> <div class="cascading__container" on-click="_onInputClick"> <div id="placeholder">[[placeholder]]</div> <div class="box-value">[[showLabel]]</div> <iron-icon class="caret" icon="icons:expand-more"></iron-icon> <iron-icon class="icon-clear" icon=icons:clear on-click="clear"></iron-icon> <div id="targetDialog"> </div> </div> <div class="cascade"> <paper-dialog id="boxDialog" no-overlap horizontal-align="auto" vertical-align="auto" on-iron-overlay-closed="__cancelClick"> <div class="dialog-container"> <template is="dom-repeat" items="{{treeItems}}" as="tree" index-as="treeIndex"> <div class="view-container"> <div class="view-list"> <template is="dom-repeat" items="[[tree]]"> <template is="dom-if" if="[[__isHover(expandTrigger)]]"> <div class$="view-item [[__setViewClass(item.__select)]]" on-mouseover="__viewItemClick"> [[getValueByKey(item, attrForLabel)]] <template is="dom-if" if="[[item.children]]"> <iron-icon class="chevron-iron" icon="icons:chevron-right"></iron-icon> </template> </div> </template> <template is="dom-if" if="[[!__isHover(expandTrigger)]]"> <div class$="view-item [[__setViewClass(item.__select)]]" on-click="__viewItemClick"> [[getValueByKey(item, attrForLabel)]] <template is="dom-if" if="[[item.children]]"> <iron-icon class="chevron-iron" icon="icons:chevron-right"></iron-icon> </template> </div> </template> </template> </div> </div> </template> </div> </paper-dialog> <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> </div> `; } static get properties() { return { label: { type: String }, placeholder: { type: String, value: '请选择' }, opened: { type: Boolean, value: false, reflectToAttribute: true }, items: { type: Array, observer: '__itemsChanged', value: [] }, treeItems: { type: Array, notify: true, observer: '__treeItemsChanged', value: [] }, value: { type: Array, notify: true, value: [], observer: '__valueChanged' }, selectedValues: { type: Array, notify: true, value: [] }, valueLabel: { type: String, notify: true }, lazy: Boolean, /** * Attribute name for value. * @type {string} * @default 'value' */ attrForValue: { type: String, value: "value" }, prompt: { type: String }, /** * * Attribute name for label. * * @type {string} * @default 'label' */ attrForLabel: { type: String, value: "label" }, separator: { type: String, value: '/' }, required: { type: Boolean, value: false }, readonly: { type: Boolean, value: false }, /** * 次级菜单的展开方式(click/hover) * */ expandTrigger: { type: String, value: 'click' }, /** * 输入框中是否显示选中值的完整路径, 如果为false则显示最后一层路径 * */ showAllLevels: { type: Boolean, value: false }, /** * 显示在输入框中的值 * */ showLabel: { type: String, notify: true } }; } __itemsChanged() { if (this.items.length) this.set('treeItems', [this.items]); } __valueChanged(value) { this.getInvalidAttribute() if (this.treeItems && this.treeItems.length && !this.lazy && value) { let treeItems = [].concat(this.treeItems), selectedValues = [] value.forEach((item, index) => { const findIndex = treeItems[index].findIndex(itm => itm[this.attrForValue] === item); treeItems[index][findIndex].__select = true; selectedValues.push(treeItems[index][findIndex]); if (treeItems[index][findIndex].children) treeItems.push(treeItems[index][findIndex].children); }); this.set('selectedValues', selectedValues); this.set('valueLabel', selectedValues.map(itm => itm[this.attrForLabel]).join(this.separator)); this.set('treeItems', treeItems); let lastLevelValue = (selectedValues.length > 0 && selectedValues[selectedValues.length-1][this.attrForLabel]) || '' this.showLabel = this.showAllLevels ? this.valueLabel : lastLevelValue this.$.placeholder.hidden = lastLevelValue } } __treeItemsChanged(treeItems) { if (treeItems && treeItems.length && this.lazy && this.value) { let selectedValues = []; this.value.forEach((item, index) => { const findIndex = treeItems[index].findIndex(itm => itm[this.attrForValue] === item); if (treeItems[index] && treeItems[index].length && findIndex >= 0) { treeItems[index][findIndex].__select = true; selectedValues.push(treeItems[index][findIndex]); } }); this.set('selectedValues', selectedValues); this.set('valueLabel', selectedValues.map(itm => itm[this.attrForLabel]).join(this.separator)); this.set('treeItems', treeItems); let lastLevelValue = selectedValues.length && selectedValues[selectedValues.length-1][this.attrForLabel] this.$.placeholder.hidden = lastLevelValue this.showLabel = this.showAllLevels ? this.valueLabel : lastLevelValue } } __setViewClass(select) { return select ? 'view-item-active' : '' } _onInputClick() { this.opened = !this.opened; this.$.boxDialog.positionTarget = this.$.targetDialog; this.opened ? this.$.boxDialog.open() : this.$.boxDialog.close(); } __cancelClick() { this.opened = !this.opened; } __viewItemClick({model}) { const {index, item, parentModel} = model; let treeItems = this.treeItems.slice(0, parentModel.treeIndex + 1); const treeItem = parentModel.tree.map((itm, idx) => Object.assign({}, itm, {__select: idx === index})); treeItems[parentModel.treeIndex] = treeItem; if (item.children) { parentModel.treeIndex + 1 >= this.treeItems.length ? treeItems.push(item.children) : treeItems.splice(parentModel.treeIndex + 1, 1, item.children); } let selectedValues = this.selectedValues.slice(0, parentModel.treeIndex + 1); selectedValues[parentModel.treeIndex] = item; this.set('selectedValues', selectedValues); if (!item.children) { this.set('valueLabel', selectedValues.map(itm => itm[this.attrForLabel]).join(this.separator)); this.set('value', selectedValues.map(itm => itm[this.attrForValue])); } this.set('treeItems', treeItems); let lastLevelValue = selectedValues.length && selectedValues[selectedValues.length-1][this.attrForLabel] this.$.placeholder.hidden = lastLevelValue this.showLabel = this.showAllLevels ? this.valueLabel : lastLevelValue } close() { this.$.boxDialog.close() } clear(e) { e.stopPropagation(); let treeItems = [].concat(this.treeItems) const chosedStrs = this.valueLabel.split(this.separator) if (chosedStrs) { chosedStrs.forEach((str, index)=> { const findIndex = treeItems[index].findIndex(item=> item[this.attrForLabel] === str) delete treeItems[index][findIndex].__select }) } // 解决清空选项之后层级表里面还有选中的选项的样式的问题 Array.prototype.forEach.call(this.root.querySelectorAll('.view-item'), function (item) { item.classList.remove('view-item-active') }) this.set('value', []); this.set('valueLabel', null); this.set('treeItems', treeItems); } /** * Validate, true if the select is set to be required and this.selectedValues.length > 0, or else false. * @returns {boolean} */ validate() { super.validate() return this.required ? !!(this.value && this.value.length) : true; } __isHover(expandTrigger) { return expandTrigger === 'hover' } static get is() { return "isu-cascading"; } } window.customElements.define(IsuCascading.is, IsuCascading);