UNPKG

vue-fantable

Version:
526 lines (474 loc) 13.6 kB
import { clsName, getFixedTotalWidthByColumnKey } from '../util/index' import { INSTANCE_METHODS } from './constant' import { COMPS_NAME, HOOKS_NAME } from '../util/constant' import focus from '@P/src/directives/focus.js' import { autoResize } from '@P/src/utils/auto-resize' import { isEmptyValue, debounce } from '@P/src/utils/index.js' import { getCaretPosition, setCaretPosition } from '@P/src/utils/dom' export default { name: COMPS_NAME.FAN_TABLE_EDIT_INPUT, directives: { focus, }, props: { parentRendered: { type: Boolean, required: true, }, hooks: { type: Object, required: true, }, // start input value every time inputStartValue: { type: [String, Number], required: true, }, rowKeyFieldName: { type: String, default: null, }, // table data tableData: { type: Array, required: true, }, colgroups: { type: Array, required: true, }, // cell selection option cellSelectionData: { type: Object, required: true, }, // editing cell editingCell: { type: Object, required: true, }, // is editing cell isCellEditing: { type: Boolean, required: true, }, // has horizontal scroll bar hasXScrollBar: { type: Boolean, required: true, }, // has vertical scroll bar hasYScrollBar: { type: Boolean, required: true, }, // has right fixed column hasRightFixedColumn: { type: Boolean, required: true, }, scrollBarWidth: { type: Number, required: true, }, }, emits: ['editInputValueChange', 'editInputClick', 'editInputCopy', 'editInputPaste', 'editInputCut'], data() { return { textareaInputRef: 'textareaInputRef', // raw cell value rawCellValue: '', // display textarea displayTextarea: false, // virtual scroll overflowViewport overflowViewport: false, // textarea element rect textareaRect: { left: 0, top: 0, }, // table element tableEl: null, // cell element cellEl: null, // auto resize autoResize: null, // is edit cell focus isEditCellFocus: false, } }, computed: { // current column currentColumn() { let result = null const { colgroups, cellSelectionData } = this const { currentCell } = cellSelectionData if ( !isEmptyValue(currentCell.rowKey) && !isEmptyValue(currentCell.colKey) ) { result = colgroups.find((x) => x.key === currentCell.colKey) } return result }, // container class containerClass() { let result = null const { displayTextarea, overflowViewport } = this result = { [clsName('edit-input-container')]: true, [clsName('edit-input-container-show')]: displayTextarea && !overflowViewport, } return result }, // container style containerStyle() { let result = {} const { displayTextarea, overflowViewport, textareaRect, currentColumn: column, } = this const { top, left } = textareaRect if (displayTextarea && !overflowViewport) { result = { top: top + 'px', left: left + 'px', height: null, // because @ve-fixed-body-cell-index: 10; 'z-index': column.fixed ? 10 : 0, opacity: 1, } } else { result = { top: top + 'px', left: left + 'px', height: '1px', 'z-index': -1, opacity: 0, } } return result }, // textarea class textareaClass() { let result = null result = { [clsName('edit-input')]: true, } return result }, }, watch: { parentRendered: { handler(val) { if (val) { // fixed #471 this.setTableEl() // add table container scroll hook this.hooks.addHook( HOOKS_NAME.TABLE_CONTAINER_SCROLL, () => { if (this.displayTextarea) { if (!this.cellEl) { this.setCellEl() } } this.debounceSetCellEl() this.setTextareaPosition() this.debounceSetTextareaPosition() }, ) // add table size change hook this.hooks.addHook(HOOKS_NAME.TABLE_SIZE_CHANGE, () => { this.setTextareaPosition() }) } }, immediate: true, }, // cell selection key data 'cellSelectionData.currentCell': { handler(val) { this.isEditCellFocus = false const { rowKey, colKey } = val if (!isEmptyValue(rowKey) && !isEmptyValue(colKey)) { this.setCellEl() // wait for selection cell rendered this.$nextTick(() => { this.setTextareaPosition() setTimeout(() => { this.isEditCellFocus = true }) }) } }, deep: true, immediate: true, }, // watch normal end cell 'cellSelectionData.normalEndCell': { handler: function (val) { /* trigger editor(textarea) element select 解决通过点击的区域选择,无法复制的问题 */ if (!isEmptyValue(val.colKey)) { this[INSTANCE_METHODS.TEXTAREA_SELECT]() } }, deep: true, immediate: true, }, // is editing cell isCellEditing: { handler: function (val) { if (val) { this.showTextarea() } else { this.hideTextarea() } }, deep: true, immediate: true, }, inputStartValue: { handler: function () { this.setRawCellValue() }, immediate: true, }, }, created() { // debounce set textarea position this.debounceSetTextareaPosition = debounce( this.setTextareaPosition, 210, ) // debounce set cell el this.debounceSetCellEl = debounce(() => { if (this.displayTextarea) { if (!this.cellEl) { this.setCellEl() } } }, 200) }, mounted() { this.autoResize = autoResize() }, unmounted() { this.textareaUnObserve() }, methods: { // set table element setTableEl() { this.$nextTick(() => { const tableEl = this.$el.previousElementSibling this.tableEl = tableEl }) }, // set cell element setCellEl() { const { cellSelectionData, tableEl } = this const { rowKey, colKey } = cellSelectionData.currentCell if (tableEl) { const cellEl = tableEl.querySelector( `tbody.fan-table-body tr[row-key="${rowKey}"] td[col-key="${colKey}"]`, ) if (cellEl) { this.cellEl = cellEl this.overflowViewport = false } } }, // set textarea position setTextareaPosition() { const { hasXScrollBar, hasYScrollBar, scrollBarWidth, colgroups, hasRightFixedColumn, currentColumn: column, cellEl, tableEl, } = this if (cellEl && tableEl) { const { left: tableLeft, top: tableTop, right: tableRight, bottom: tableBottom, } = tableEl.getBoundingClientRect() const { left: cellLeft, top: cellTop, height: cellHeight, width: cellWidth, right: cellRight, bottom: cellBottom, } = cellEl.getBoundingClientRect() if (cellHeight && cellWidth) { let maxHeight = cellHeight + tableBottom - cellBottom let maxWidth = cellWidth + tableRight - cellRight // has horizontal scroll bar if (hasXScrollBar) { maxHeight -= scrollBarWidth } // has vertical scroll bar if (hasYScrollBar) { maxWidth -= scrollBarWidth } /* If the right fixed column is included, the max width of the textarea needs to be subtracted from the sum of the right fixed columns 如果包含右固定列,编辑框最大宽度需要去减去右固定列之和的宽度 */ if (hasRightFixedColumn) { if (column && !column.fixed) { const rightFixedTotalWidth = getFixedTotalWidthByColumnKey({ colgroups, colKey: column.key, fixed: 'right', }) if (rightFixedTotalWidth) { maxWidth -= rightFixedTotalWidth } } } this.autoResize.init( this.$refs[this.textareaInputRef], { minHeight: Math.min(cellHeight, maxHeight), maxHeight, // TEXTAREA should never be higher than visible part of the viewport (should not cover the scrollbar) minWidth: Math.min(cellWidth, maxWidth), maxWidth, // TEXTAREA should never be wider than visible part of the viewport (should not cover the scrollbar) }, true, // observe textarea change\cut\paste etc. ) this.textareaRect = { left: cellLeft - tableLeft, top: cellTop - tableTop, } } else { /* 存在以下可能: 1、虚拟滚动超出viewport 2、单元格被删除(通过右键菜单等方式) */ // fixed #477 this.textareaRect = { left: 0, top: 0, } this.cellEl = null this.overflowViewport = true } } }, // show textarea showTextarea() { this.setRawCellValue() this.displayTextarea = true }, // hide textarea hideTextarea() { this.displayTextarea = false this.textareaUnObserve() }, // textarea unObserve textareaUnObserve() { if (this.autoResize) { this.autoResize.unObserve() } }, // set raw cell value setRawCellValue() { this.rawCellValue = this.inputStartValue }, // textarea value change textareaValueChange(val) { // this.$emit(EMIT_EVENTS.EDIT_INPUT_VALUE_CHANGE, val) this.$emit('editInputValueChange', val) }, // textarea select [INSTANCE_METHODS.TEXTAREA_SELECT]() { const textareaInputEl = this.$refs[this.textareaInputRef] if (textareaInputEl) { textareaInputEl.select() } }, // textarea add new line [INSTANCE_METHODS.TEXTAREA_ADD_NEW_LINE]() { const { isCellEditing, editingCell } = this if (isCellEditing) { const textareaInputEl = this.$refs[this.textareaInputRef] const caretPosition = getCaretPosition(textareaInputEl) let value = editingCell.row[editingCell.colKey] // solve error of number slice method value += '' const newValue = `${value.slice( 0, caretPosition, )}\n${value.slice(caretPosition)}` // 直接更新 textarea 值 textareaInputEl.value = newValue // 手动赋值不会触发textarea 文本变化事件,需要手动更新 editingCell 值 this.textareaValueChange(newValue) setCaretPosition(textareaInputEl, caretPosition + 1) } }, }, render() { const { containerClass, containerStyle, textareaClass, rawCellValue, isCellEditing, isEditCellFocus, } = this const containerProps = { style: containerStyle, class: containerClass, } const textareaProps = { ref: this.textareaInputRef, class: textareaClass, value: rawCellValue, tabindex: -1, onInput: (e) => { if (isCellEditing) { this.textareaValueChange(e.target.value) this.rawCellValue = e.target.value } }, onClick: () => { // this.$emit(EMIT_EVENTS.EDIT_INPUT_CLICK) this.$emit('editInputClick') // this.$emit(EMIT_EVENTS.EDIT_INPUT_CLICK) }, onCopy: (e) => { this.$emit('editInputCopy', e) // this.$emit(EMIT_EVENTS.EDIT_INPUT_COPY, e) }, onPaste: (e) => { this.$emit('editInputPaste', e) // this.$emit(EMIT_EVENTS.EDIT_INPUT_PASTE, e) }, onCut: (e) => { this.$emit('editInputCut', e) // this.$emit(EMIT_EVENTS.EDIT_INPUT_CUT, e) }, } return ( <div {...containerProps}> <textarea {...textareaProps} v-focus={isEditCellFocus} ></textarea> </div> ) }, }