perf: table use sticky for fixed column
							parent
							
								
									4fed700e96
								
							
						
					
					
						commit
						38569c28c7
					
				|  | @ -0,0 +1,41 @@ | |||
| import type { Ref } from 'vue'; | ||||
| import { onBeforeUnmount, ref } from 'vue'; | ||||
| import wrapperRaf from '../raf'; | ||||
| 
 | ||||
| export type Updater<State> = (prev: State) => State; | ||||
| /** | ||||
|  * Execute code before next frame but async | ||||
|  */ | ||||
| export function useLayoutState<State>( | ||||
|   defaultState: State, | ||||
| ): [Ref<State>, (updater: Updater<State>) => void] { | ||||
|   const stateRef = ref(defaultState); | ||||
|   let tempState = stateRef.value; | ||||
| 
 | ||||
|   let updateBatchRef = []; | ||||
|   const rafRef = ref(); | ||||
|   function setFrameState(updater: Updater<State>) { | ||||
|     wrapperRaf.cancel(rafRef.value); | ||||
|     updateBatchRef.push(updater); | ||||
| 
 | ||||
|     rafRef.value = wrapperRaf(() => { | ||||
|       const prevBatch = updateBatchRef; | ||||
|       // const prevState = stateRef.value;
 | ||||
|       updateBatchRef = []; | ||||
| 
 | ||||
|       prevBatch.forEach(batchUpdater => { | ||||
|         tempState = batchUpdater(tempState); | ||||
|       }); | ||||
| 
 | ||||
|       // if (tempState !== stateRef.value) {
 | ||||
|       stateRef.value = tempState; | ||||
|       // }
 | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onBeforeUnmount(() => { | ||||
|     wrapperRaf.cancel(rafRef.value); | ||||
|   }); | ||||
| 
 | ||||
|   return [stateRef as Ref<State>, setFrameState]; | ||||
| } | ||||
|  | @ -615,15 +615,6 @@ | |||
|     overflow-x: hidden; | ||||
|     table { | ||||
|       min-width: 100%; | ||||
| 
 | ||||
|       // https://github.com/ant-design/ant-design/issues/14545 | ||||
|       // https://github.com/ant-design/ant-design/issues/19491 | ||||
|       .@{table-prefix-cls}-fixed-columns-in-body:not([colspan]) { | ||||
|         color: transparent; | ||||
|         & > * { | ||||
|           visibility: hidden; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -776,6 +767,96 @@ | |||
|   &-row[class*='@{table-prefix-cls}-row-level-0'] .@{table-prefix-cls}-selection-column > span { | ||||
|     display: inline-block; | ||||
|   } | ||||
| 
 | ||||
|   // ============================ Fixed ============================= | ||||
|   &-cell-fix-left, | ||||
|   &-cell-fix-right { | ||||
|     position: -webkit-sticky !important; | ||||
|     position: sticky !important; | ||||
|     z-index: @zindex-table-fixed; | ||||
|     background: @table-bg; | ||||
|   } | ||||
| 
 | ||||
|   &-cell-fix-left-first::after, | ||||
|   &-cell-fix-left-last::after { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     bottom: -1px; | ||||
|     width: 30px; | ||||
|     transform: translateX(100%); | ||||
|     transition: box-shadow 0.3s; | ||||
|     content: ''; | ||||
|     pointer-events: none; | ||||
|   } | ||||
| 
 | ||||
|   &-cell-fix-right-first::after, | ||||
|   &-cell-fix-right-last::after { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     bottom: -1px; | ||||
|     left: 0; | ||||
|     width: 30px; | ||||
|     transform: translateX(-100%); | ||||
|     transition: box-shadow 0.3s; | ||||
|     content: ''; | ||||
|     pointer-events: none; | ||||
|   } | ||||
| 
 | ||||
|   .@{table-prefix-cls}-container { | ||||
|     &::before, | ||||
|     &::after { | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       bottom: 0; | ||||
|       z-index: 1; | ||||
|       width: 30px; | ||||
|       transition: box-shadow 0.3s; | ||||
|       content: ''; | ||||
|       pointer-events: none; | ||||
|     } | ||||
| 
 | ||||
|     &::before { | ||||
|       left: 0; | ||||
|     } | ||||
|     &::after { | ||||
|       right: 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &-ping-left { | ||||
|     &:not(.@{table-prefix-cls}-has-fix-left) .@{table-prefix-cls}-container { | ||||
|       position: relative; | ||||
| 
 | ||||
|       &::before { | ||||
|         box-shadow: inset 10px 0 8px -8px darken(@shadow-color, 5%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .@{table-prefix-cls}-cell-fix-left-first::after, | ||||
|     .@{table-prefix-cls}-cell-fix-left-last::after { | ||||
|       box-shadow: inset 10px 0 8px -8px darken(@shadow-color, 5%); | ||||
|     } | ||||
| 
 | ||||
|     .@{table-prefix-cls}-cell-fix-left-last::before { | ||||
|       background-color: transparent !important; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &-ping-right { | ||||
|     &:not(.@{table-prefix-cls}-has-fix-right) .@{table-prefix-cls}-container { | ||||
|       position: relative; | ||||
| 
 | ||||
|       &::after { | ||||
|         box-shadow: inset -10px 0 8px -8px darken(@shadow-color, 5%); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .@{table-prefix-cls}-cell-fix-right-first::after, | ||||
|     .@{table-prefix-cls}-cell-fix-right-last::after { | ||||
|       box-shadow: inset -10px 0 8px -8px darken(@shadow-color, 5%); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .@{table-prefix-cls}-filter-dropdown, | ||||
|  |  | |||
|  | @ -27,15 +27,10 @@ const BaseTable = { | |||
|   }, | ||||
|   methods: { | ||||
|     getColumns(cols) { | ||||
|       const { columns = [], fixed } = this.$props; | ||||
|       const { table } = this; | ||||
|       const { prefixCls } = table.$props; | ||||
|       const { columns = [] } = this.$props; | ||||
|       return (cols || columns).map(column => ({ | ||||
|         ...column, | ||||
|         className: | ||||
|           !!column.fixed && !fixed | ||||
|             ? classNames(`${prefixCls}-fixed-columns-in-body`, column.className, column.class) | ||||
|             : classNames(column.className, column.class), | ||||
|         className: classNames(column.className, column.class), | ||||
|       })); | ||||
|     }, | ||||
|     handleRowHover(isHover, key) { | ||||
|  | @ -44,7 +39,6 @@ const BaseTable = { | |||
| 
 | ||||
|     renderRows(renderData, indent, ancestorKeys = []) { | ||||
|       const { | ||||
|         columnManager, | ||||
|         sComponents: components, | ||||
|         prefixCls, | ||||
|         childrenColumnName, | ||||
|  | @ -57,6 +51,7 @@ const BaseTable = { | |||
|         onRowMouseLeave = noop, | ||||
|         rowRef, | ||||
|       } = { ...this.table.$attrs, ...this.table.$props, ...this.table.$data }; | ||||
|       const { columnManager } = this.store; | ||||
|       const { getRowKey, fixed, expander, isAnyColumnsFixed } = this; | ||||
| 
 | ||||
|       const rows = []; | ||||
|  | @ -68,17 +63,17 @@ const BaseTable = { | |||
|           typeof rowClassName === 'string' ? rowClassName : rowClassName(record, i, indent); | ||||
| 
 | ||||
|         const onHoverProps = {}; | ||||
|         if (columnManager.isAnyColumnsFixed()) { | ||||
|         if (columnManager.isAnyColumnsFixed) { | ||||
|           onHoverProps.onHover = this.handleRowHover; | ||||
|         } | ||||
| 
 | ||||
|         let leafColumns; | ||||
|         if (fixed === 'left') { | ||||
|           leafColumns = columnManager.leftLeafColumns(); | ||||
|           leafColumns = columnManager.leftLeafColumns; | ||||
|         } else if (fixed === 'right') { | ||||
|           leafColumns = columnManager.rightLeafColumns(); | ||||
|           leafColumns = columnManager.rightLeafColumns; | ||||
|         } else { | ||||
|           leafColumns = this.getColumns(columnManager.leafColumns()); | ||||
|           leafColumns = this.getColumns(columnManager.leafColumns); | ||||
|         } | ||||
| 
 | ||||
|         const rowPrefixCls = `${prefixCls}-row`; | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { inject } from 'vue'; | ||||
| import PropTypes from '../../_util/vue-types'; | ||||
| import { INTERNAL_COL_DEFINE } from './utils'; | ||||
| import ResizeObserver from '../../vc-resize-observer'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'ColGroup', | ||||
|  | @ -12,11 +13,12 @@ export default { | |||
|   setup() { | ||||
|     return { | ||||
|       table: inject('table', {}), | ||||
|       store: inject('table-store', () => ({})), | ||||
|     }; | ||||
|   }, | ||||
|   render() { | ||||
|     const { fixed, table } = this; | ||||
|     const { prefixCls, expandIconAsCell, columnManager } = table; | ||||
|     const { prefixCls, expandIconAsCell, onColumnResize } = table; | ||||
| 
 | ||||
|     let cols = []; | ||||
| 
 | ||||
|  | @ -25,19 +27,32 @@ export default { | |||
|     } | ||||
| 
 | ||||
|     let leafColumns; | ||||
| 
 | ||||
|     const { columnManager } = this.store; | ||||
|     if (fixed === 'left') { | ||||
|       leafColumns = columnManager.leftLeafColumns(); | ||||
|       leafColumns = columnManager.leftLeafColumns; | ||||
|     } else if (fixed === 'right') { | ||||
|       leafColumns = columnManager.rightLeafColumns(); | ||||
|       leafColumns = columnManager.rightLeafColumns; | ||||
|     } else { | ||||
|       leafColumns = columnManager.leafColumns(); | ||||
|       leafColumns = columnManager.leafColumns; | ||||
|     } | ||||
|     cols = cols.concat( | ||||
|       leafColumns.map(({ key, dataIndex, width, [INTERNAL_COL_DEFINE]: additionalProps }) => { | ||||
|         const mergedKey = key !== undefined ? key : dataIndex; | ||||
|         const w = typeof width === 'number' ? `${width}px` : width; | ||||
|         return <col key={mergedKey} style={{ width: w, minWidth: w }} {...additionalProps} />; | ||||
|         return ( | ||||
|           <ResizeObserver | ||||
|             onResize={({ offsetWidth }) => { | ||||
|               onColumnResize(mergedKey, offsetWidth); | ||||
|             }} | ||||
|           > | ||||
|             <col | ||||
|               data-key={mergedKey} | ||||
|               key={mergedKey} | ||||
|               style={{ width: w, minWidth: w }} | ||||
|               {...additionalProps} | ||||
|             /> | ||||
|           </ResizeObserver> | ||||
|         ); | ||||
|       }), | ||||
|     ); | ||||
|     return <colgroup>{cols}</colgroup>; | ||||
|  |  | |||
|  | @ -1,116 +0,0 @@ | |||
| import { toRaw } from 'vue'; | ||||
| export default class ColumnManager { | ||||
|   constructor(columns) { | ||||
|     this.columns = toRaw(columns); | ||||
|     this._cached = {}; | ||||
|   } | ||||
| 
 | ||||
|   isAnyColumnsFixed() { | ||||
|     return this._cache('isAnyColumnsFixed', () => this.columns.some(column => !!column.fixed)); | ||||
|   } | ||||
| 
 | ||||
|   isAnyColumnsLeftFixed() { | ||||
|     return this._cache('isAnyColumnsLeftFixed', () => | ||||
|       this.columns.some(column => column.fixed === 'left' || column.fixed === true), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   isAnyColumnsRightFixed() { | ||||
|     return this._cache('isAnyColumnsRightFixed', () => | ||||
|       this.columns.some(column => column.fixed === 'right'), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   leftColumns() { | ||||
|     return this._cache('leftColumns', () => | ||||
|       this.groupedColumns().filter(column => column.fixed === 'left' || column.fixed === true), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   rightColumns() { | ||||
|     return this._cache('rightColumns', () => | ||||
|       this.groupedColumns().filter(column => column.fixed === 'right'), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   leafColumns() { | ||||
|     return this._cache('leafColumns', () => this._leafColumns(this.columns)); | ||||
|   } | ||||
| 
 | ||||
|   leftLeafColumns() { | ||||
|     return this._cache('leftLeafColumns', () => this._leafColumns(this.leftColumns())); | ||||
|   } | ||||
| 
 | ||||
|   rightLeafColumns() { | ||||
|     return this._cache('rightLeafColumns', () => this._leafColumns(this.rightColumns())); | ||||
|   } | ||||
| 
 | ||||
|   // add appropriate rowspan and colspan to column | ||||
|   groupedColumns() { | ||||
|     return this._cache('groupedColumns', () => { | ||||
|       const _groupColumns = (columns, currentRow = 0, parentColumn = {}, rows = []) => { | ||||
|         // track how many rows we got | ||||
|         rows[currentRow] = rows[currentRow] || []; | ||||
|         const grouped = []; | ||||
|         const setRowSpan = column => { | ||||
|           const rowSpan = rows.length - currentRow; | ||||
|           if ( | ||||
|             column && | ||||
|             !column.children && // parent columns are supposed to be one row | ||||
|             rowSpan > 1 && | ||||
|             (!column.rowSpan || column.rowSpan < rowSpan) | ||||
|           ) { | ||||
|             column.rowSpan = rowSpan; | ||||
|           } | ||||
|         }; | ||||
|         columns.forEach((column, index) => { | ||||
|           const newColumn = { ...column }; | ||||
|           rows[currentRow].push(newColumn); | ||||
|           parentColumn.colSpan = parentColumn.colSpan || 0; | ||||
|           if (newColumn.children && newColumn.children.length > 0) { | ||||
|             newColumn.children = _groupColumns(newColumn.children, currentRow + 1, newColumn, rows); | ||||
|             parentColumn.colSpan += newColumn.colSpan; | ||||
|           } else { | ||||
|             parentColumn.colSpan += 1; | ||||
|           } | ||||
|           // update rowspan to all same row columns | ||||
|           for (let i = 0; i < rows[currentRow].length - 1; i += 1) { | ||||
|             setRowSpan(rows[currentRow][i]); | ||||
|           } | ||||
|           // last column, update rowspan immediately | ||||
|           if (index + 1 === columns.length) { | ||||
|             setRowSpan(newColumn); | ||||
|           } | ||||
|           grouped.push(newColumn); | ||||
|         }); | ||||
|         return grouped; | ||||
|       }; | ||||
|       return _groupColumns(this.columns); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   reset(columns) { | ||||
|     this.columns = toRaw(columns); | ||||
|     this._cached = {}; | ||||
|   } | ||||
| 
 | ||||
|   _cache(name, fn) { | ||||
|     if (name in this._cached) { | ||||
|       return this._cached[name]; | ||||
|     } | ||||
|     this._cached[name] = fn(); | ||||
|     return this._cached[name]; | ||||
|   } | ||||
| 
 | ||||
|   _leafColumns(columns) { | ||||
|     const leafColumns = []; | ||||
|     columns.forEach(column => { | ||||
|       if (!column.children) { | ||||
|         leafColumns.push(column); | ||||
|       } else { | ||||
|         leafColumns.push(...this._leafColumns(column.children)); | ||||
|       } | ||||
|     }); | ||||
|     return leafColumns; | ||||
|   } | ||||
| } | ||||
|  | @ -147,11 +147,11 @@ const ExpandableTable = { | |||
|       }; | ||||
|       let colCount; | ||||
|       if (fixed === 'left') { | ||||
|         colCount = this.columnManager.leftLeafColumns().length; | ||||
|         colCount = this.columnManager.leftLeafColumns.value.length; | ||||
|       } else if (fixed === 'right') { | ||||
|         colCount = this.columnManager.rightLeafColumns().length; | ||||
|         colCount = this.columnManager.rightLeafColumns.value.length; | ||||
|       } else { | ||||
|         colCount = this.columnManager.leafColumns().length; | ||||
|         colCount = this.columnManager.leafColumns.value.length; | ||||
|       } | ||||
|       const columns = [ | ||||
|         { | ||||
|  |  | |||
|  | @ -1,19 +1,33 @@ | |||
| /* eslint-disable camelcase */ | ||||
| import { provide, markRaw, defineComponent, nextTick, reactive } from 'vue'; | ||||
| import { | ||||
|   provide, | ||||
|   markRaw, | ||||
|   defineComponent, | ||||
|   nextTick, | ||||
|   reactive, | ||||
|   computed, | ||||
|   ref, | ||||
|   onUpdated, | ||||
|   onMounted, | ||||
| } from 'vue'; | ||||
| import shallowequal from '../../_util/shallowequal'; | ||||
| import merge from 'lodash-es/merge'; | ||||
| import classes from '../../_util/component-classes'; | ||||
| import classNames from '../../_util/classNames'; | ||||
| import PropTypes from '../../_util/vue-types'; | ||||
| import { debounce, getDataAndAriaProps } from './utils'; | ||||
| import { debounce, getColumnsKey, getDataAndAriaProps, validateValue } from './utils'; | ||||
| import warning from '../../_util/warning'; | ||||
| import addEventListener from '../../vc-util/Dom/addEventListener'; | ||||
| import ColumnManager from './ColumnManager'; | ||||
| import HeadTable from './HeadTable'; | ||||
| import BodyTable from './BodyTable'; | ||||
| import ExpandableTable from './ExpandableTable'; | ||||
| import { initDefaultProps, getOptionProps } from '../../_util/props-util'; | ||||
| import BaseMixin from '../../_util/BaseMixin'; | ||||
| import { useLayoutState } from '../../_util/hooks/useLayoutState'; | ||||
| import useColumnManager from './useColumnManager'; | ||||
| import useStickyOffsets from './useStickyOffsets'; | ||||
| import { getCellFixedInfo } from './fixUtil'; | ||||
| import ResizeObserver from '../../vc-resize-observer'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'Table', | ||||
|  | @ -84,23 +98,76 @@ export default defineComponent({ | |||
|       customHeaderRow: () => {}, | ||||
|     }, | ||||
|   ), | ||||
|   setup() { | ||||
|   setup(props) { | ||||
|     const columnManager = useColumnManager(props.columns); | ||||
|     const colsKeys = computed(() => getColumnsKey(columnManager.leafColumns.value)); | ||||
|     const [colsWidths, updateColsWidths] = useLayoutState(new Map()); | ||||
|     const pureColWidths = computed(() => | ||||
|       colsKeys.value.map(columnKey => colsWidths.value.get(columnKey)), | ||||
|     ); | ||||
|     const stickyOffsets = useStickyOffsets(pureColWidths, columnManager.leafColumns); | ||||
|     const onColumnResize = (columnKey, width) => { | ||||
|       updateColsWidths(widths => { | ||||
|         if (widths.get(columnKey) !== width) { | ||||
|           const newWidths = new Map(widths); | ||||
|           newWidths.set(columnKey, width); | ||||
|           return newWidths; | ||||
|         } | ||||
|         return widths; | ||||
|       }); | ||||
|     }; | ||||
|     const fixedInfoList = computed(() => | ||||
|       columnManager.leafColumns.value.map((_, colIndex) => | ||||
|         getCellFixedInfo(colIndex, colIndex, columnManager.leafColumns.value, stickyOffsets.value), | ||||
|       ), | ||||
|     ); | ||||
|     const store = reactive({ | ||||
|       currentHoverKey: null, | ||||
|       fixedColumnsHeadRowsHeight: [], | ||||
|       fixedColumnsBodyRowsHeight: {}, | ||||
|       expandedRowsHeight: {}, | ||||
|       expandedRowKeys: [], | ||||
|       columnManager, | ||||
|       fixedInfoList, | ||||
|       stickyOffsets, | ||||
|     }); | ||||
|     provide('table-store', store); | ||||
|     const bodyRef = ref(); | ||||
|     const pingedLeft = ref(false); | ||||
|     const pingedRight = ref(false); | ||||
|     const horizonScroll = computed(() => props.scroll && validateValue(props.scroll.x)); | ||||
|     const onScroll = currentTarget => { | ||||
|       const { scrollWidth, clientWidth, scrollLeft } = currentTarget; | ||||
|       pingedLeft.value = scrollLeft > 0; | ||||
|       pingedRight.value = scrollLeft < scrollWidth - clientWidth; | ||||
|     }; | ||||
|     onUpdated(() => { | ||||
|       nextTick(() => { | ||||
|         horizonScroll.value && onScroll(bodyRef.value.$el); | ||||
|       }); | ||||
|     }); | ||||
|     onMounted(() => { | ||||
|       nextTick(() => { | ||||
|         horizonScroll.value && onScroll(bodyRef.value.$el); | ||||
|       }); | ||||
|     }); | ||||
|     const onFullTableResize = () => { | ||||
|       horizonScroll.value && onScroll(bodyRef.value.$el); | ||||
|     }; | ||||
|     return { | ||||
|       bodyRef, | ||||
|       store, | ||||
|       onColumnResize, | ||||
|       columnManager, | ||||
|       onScroll, | ||||
|       pingedLeft, | ||||
|       pingedRight, | ||||
|       onFullTableResize, | ||||
|     }; | ||||
|   }, | ||||
|   data() { | ||||
|     this.preData = [...this.data]; | ||||
|     return { | ||||
|       columnManager: markRaw(new ColumnManager(this.columns)), | ||||
|       sComponents: markRaw( | ||||
|         merge( | ||||
|           { | ||||
|  | @ -145,11 +212,6 @@ export default defineComponent({ | |||
|         this.components, | ||||
|       ); | ||||
|     }, | ||||
|     columns(val) { | ||||
|       if (val) { | ||||
|         this.columnManager.reset(val); | ||||
|       } | ||||
|     }, | ||||
|     dataLen(val, preVal) { | ||||
|       if ((val === 0 || preVal === 0) && this.hasScrollX()) { | ||||
|         nextTick(() => { | ||||
|  | @ -160,21 +222,6 @@ export default defineComponent({ | |||
|   }, | ||||
|   created() { | ||||
|     provide('table', this); | ||||
|     // ['rowClick', 'rowDoubleclick', 'rowContextmenu', 'rowMouseenter', 'rowMouseleave'].forEach( | ||||
|     //   name => { | ||||
|     //     warning( | ||||
|     //       getListeners(this)[name] === undefined, | ||||
|     //       `${name} is deprecated, please use customRow instead.`, | ||||
|     //     ); | ||||
|     //   }, | ||||
|     // ); | ||||
| 
 | ||||
|     // warning( | ||||
|     //   this.getBodyWrapper === undefined, | ||||
|     //   'getBodyWrapper is deprecated, please use custom components instead.', | ||||
|     // ); | ||||
| 
 | ||||
|     // this.columnManager = new ColumnManager(this.columns, this.$slots.default) | ||||
| 
 | ||||
|     this.setScrollPosition('left'); | ||||
| 
 | ||||
|  | @ -183,7 +230,7 @@ export default defineComponent({ | |||
| 
 | ||||
|   mounted() { | ||||
|     this.$nextTick(() => { | ||||
|       if (this.columnManager.isAnyColumnsFixed()) { | ||||
|       if (this.columnManager.isAnyColumnsFixed.value) { | ||||
|         this.handleWindowResize(); | ||||
|         this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize); | ||||
|       } | ||||
|  | @ -199,7 +246,7 @@ export default defineComponent({ | |||
| 
 | ||||
|   updated() { | ||||
|     this.$nextTick(() => { | ||||
|       if (this.columnManager.isAnyColumnsFixed()) { | ||||
|       if (this.columnManager.isAnyColumnsFixed.value) { | ||||
|         this.handleWindowResize(); | ||||
|         if (!this.resizeEvent) { | ||||
|           this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize); | ||||
|  | @ -383,8 +430,8 @@ export default defineComponent({ | |||
|       // Remember last scrollTop for scroll direction detecting. | ||||
|       this.lastScrollTop = target.scrollTop; | ||||
|     }, | ||||
| 
 | ||||
|     handleBodyScroll(e) { | ||||
|       this.onScroll(e.target); | ||||
|       this.handleBodyScrollLeft(e); | ||||
|       this.handleBodyScrollTop(e); | ||||
|     }, | ||||
|  | @ -432,28 +479,34 @@ export default defineComponent({ | |||
|     }, | ||||
|     renderMainTable() { | ||||
|       const { scroll, prefixCls } = this; | ||||
|       const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed(); | ||||
|       const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed.value; | ||||
|       const scrollable = isAnyColumnsFixed || scroll.x || scroll.y; | ||||
| 
 | ||||
|       const table = [ | ||||
|         this.renderTable({ | ||||
|           columns: this.columnManager.groupedColumns(), | ||||
|           columns: this.columnManager.groupedColumns.value, | ||||
|           isAnyColumnsFixed, | ||||
|         }), | ||||
|         this.renderEmptyText(), | ||||
|         this.renderFooter(), | ||||
|       ]; | ||||
| 
 | ||||
|       return scrollable ? <div class={`${prefixCls}-scroll`}>{table}</div> : table; | ||||
|       return scrollable ? ( | ||||
|         <ResizeObserver onResize={this.onFullTableResize}> | ||||
|           <div class={`${prefixCls}-scroll`}>{table}</div> | ||||
|         </ResizeObserver> | ||||
|       ) : ( | ||||
|         table | ||||
|       ); | ||||
|     }, | ||||
| 
 | ||||
|     renderLeftFixedTable() { | ||||
|       const { prefixCls } = this; | ||||
| 
 | ||||
|       return ( | ||||
|         <div class={`${prefixCls}-fixed-left`}> | ||||
|         <div key="left" class={`${prefixCls}-fixed-left`}> | ||||
|           {this.renderTable({ | ||||
|             columns: this.columnManager.leftColumns(), | ||||
|             columns: this.columnManager.leftColumns.value, | ||||
|             fixed: 'left', | ||||
|           })} | ||||
|         </div> | ||||
|  | @ -465,7 +518,7 @@ export default defineComponent({ | |||
|       return ( | ||||
|         <div class={`${prefixCls}-fixed-right`}> | ||||
|           {this.renderTable({ | ||||
|             columns: this.columnManager.rightColumns(), | ||||
|             columns: this.columnManager.rightColumns.value, | ||||
|             fixed: 'right', | ||||
|           })} | ||||
|         </div> | ||||
|  | @ -499,6 +552,7 @@ export default defineComponent({ | |||
|           handleBodyScroll={this.handleBodyScroll} | ||||
|           expander={this.expander} | ||||
|           isAnyColumnsFixed={isAnyColumnsFixed} | ||||
|           ref="bodyRef" | ||||
|         /> | ||||
|       ); | ||||
| 
 | ||||
|  | @ -548,10 +602,9 @@ export default defineComponent({ | |||
|         this.scrollPosition === 'both', | ||||
|       [`${prefixCls}-scroll-position-${this.scrollPosition}`]: this.scrollPosition !== 'both', | ||||
|       [`${prefixCls}-layout-fixed`]: this.isTableLayoutFixed(), | ||||
|       [`${prefixCls}-ping-left`]: this.pingedLeft, | ||||
|       [`${prefixCls}-ping-right`]: this.pingedRight, | ||||
|     }); | ||||
| 
 | ||||
|     const hasLeftFixed = columnManager.isAnyColumnsLeftFixed(); | ||||
|     const hasRightFixed = columnManager.isAnyColumnsRightFixed(); | ||||
|     const dataAndAriaProps = getDataAndAriaProps(props); | ||||
|     const expandableTableProps = { | ||||
|       ...props, | ||||
|  | @ -573,11 +626,7 @@ export default defineComponent({ | |||
|                 {...dataAndAriaProps} | ||||
|               > | ||||
|                 {this.renderTitle()} | ||||
|                 <div class={`${prefixCls}-content`}> | ||||
|                   {this.renderMainTable()} | ||||
|                   {hasLeftFixed && this.renderLeftFixedTable()} | ||||
|                   {hasRightFixed && this.renderRightFixedTable()} | ||||
|                 </div> | ||||
|                 <div class={`${prefixCls}-content`}>{this.renderMainTable()}</div> | ||||
|               </div> | ||||
|             ); | ||||
|           }, | ||||
|  |  | |||
|  | @ -22,10 +22,12 @@ export default { | |||
|     column: PropTypes.object, | ||||
|     expandIcon: PropTypes.any, | ||||
|     component: PropTypes.any, | ||||
|     colIndex: PropTypes.number, | ||||
|   }, | ||||
|   setup() { | ||||
|     return { | ||||
|       table: inject('table', {}), | ||||
|       store: inject('table-store', {}), | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|  | @ -51,8 +53,24 @@ export default { | |||
|       column, | ||||
|       component: BodyCell, | ||||
|     } = this; | ||||
|     const fixedInfoList = this.store.fixedInfoList || []; | ||||
|     const fixedInfo = fixedInfoList[this.colIndex]; | ||||
|     const { fixLeft, fixRight, firstFixLeft, lastFixLeft, firstFixRight, lastFixRight } = fixedInfo; | ||||
|     // ====================== Fixed ======================= | ||||
|     const fixedStyle = {}; | ||||
|     const isFixLeft = typeof fixLeft === 'number'; | ||||
|     const isFixRight = typeof fixRight === 'number'; | ||||
| 
 | ||||
|     if (isFixLeft) { | ||||
|       fixedStyle.position = 'sticky'; | ||||
|       fixedStyle.left = `${fixLeft}px`; | ||||
|     } | ||||
|     if (isFixRight) { | ||||
|       fixedStyle.position = 'sticky'; | ||||
|       fixedStyle.right = `${fixRight}px`; | ||||
|     } | ||||
|     const { dataIndex, customRender, className = '' } = column; | ||||
|     const { transformCellText } = this.table; | ||||
|     const { transformCellText, prefixCls: rootPrefixCls } = this.table; | ||||
|     // We should return undefined if no dataIndex is specified, but in order to | ||||
|     // be compatible with object-path's behavior, we return the record object instead. | ||||
|     let text; | ||||
|  | @ -110,6 +128,12 @@ export default { | |||
|       // 如果有宽度,增加断行处理 | ||||
|       // https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241 | ||||
|       [`${prefixCls}-cell-break-word`]: !!column.width, | ||||
|       [`${rootPrefixCls}-cell-fix-left`]: isFixLeft, | ||||
|       [`${rootPrefixCls}-cell-fix-left-first`]: firstFixLeft, | ||||
|       [`${rootPrefixCls}-cell-fix-left-last`]: lastFixLeft, | ||||
|       [`${rootPrefixCls}-cell-fix-right`]: isFixRight, | ||||
|       [`${rootPrefixCls}-cell-fix-right-first`]: firstFixRight, | ||||
|       [`${rootPrefixCls}-cell-fix-right-last`]: lastFixRight, | ||||
|     }); | ||||
| 
 | ||||
|     if (column.ellipsis) { | ||||
|  | @ -124,7 +148,11 @@ export default { | |||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <BodyCell class={cellClassName} {...tdProps}> | ||||
|       <BodyCell | ||||
|         class={cellClassName} | ||||
|         {...tdProps} | ||||
|         {...{ style: { ...(tdProps.style || {}), ...fixedStyle } }} | ||||
|       > | ||||
|         {indentText} | ||||
|         {expandIcon} | ||||
|         {toRaw(text)} | ||||
|  |  | |||
|  | @ -1,44 +1,70 @@ | |||
| import { inject } from 'vue'; | ||||
| import { computed, inject } from 'vue'; | ||||
| import PropTypes from '../../_util/vue-types'; | ||||
| import TableHeaderRow from './TableHeaderRow'; | ||||
| 
 | ||||
| function getHeaderRows({ columns = [], currentRow = 0, rows = [], isLast = true }) { | ||||
|   rows = rows || []; | ||||
|   rows[currentRow] = rows[currentRow] || []; | ||||
| function parseHeaderRows(rootColumns) { | ||||
|   const rows = []; | ||||
| 
 | ||||
|   columns.forEach((column, i) => { | ||||
|     if (column.rowSpan && rows.length < column.rowSpan) { | ||||
|       while (rows.length < column.rowSpan) { | ||||
|         rows.push([]); | ||||
|   function fillRowCells(columns, colIndex, rowIndex = 0) { | ||||
|     // Init rows | ||||
|     rows[rowIndex] = rows[rowIndex] || []; | ||||
| 
 | ||||
|     let currentColIndex = colIndex; | ||||
|     const colSpans = columns.filter(Boolean).map(column => { | ||||
|       const cell = { | ||||
|         key: column.key, | ||||
|         className: column.className || column.class || '', | ||||
|         children: column.title, | ||||
|         column, | ||||
|         colStart: currentColIndex, | ||||
|       }; | ||||
| 
 | ||||
|       let colSpan = 1; | ||||
| 
 | ||||
|       const subColumns = column.children; | ||||
|       if (subColumns && subColumns.length > 0) { | ||||
|         colSpan = fillRowCells(subColumns, currentColIndex, rowIndex + 1).reduce( | ||||
|           (total, count) => total + count, | ||||
|           0, | ||||
|         ); | ||||
|         cell.hasSubColumns = true; | ||||
|       } | ||||
|     } | ||||
|     const cellIsLast = isLast && i === columns.length - 1; | ||||
|     const cell = { | ||||
|       key: column.key, | ||||
|       className: column.className || column.class || '', | ||||
|       children: column.title, | ||||
|       isLast: cellIsLast, | ||||
|       column, | ||||
|     }; | ||||
|     if (column.children) { | ||||
|       getHeaderRows({ | ||||
|         columns: column.children, | ||||
|         currentRow: currentRow + 1, | ||||
|         rows, | ||||
|         isLast: cellIsLast, | ||||
|       }); | ||||
|     } | ||||
|     if ('colSpan' in column) { | ||||
|       cell.colSpan = column.colSpan; | ||||
|     } | ||||
|     if ('rowSpan' in column) { | ||||
|       cell.rowSpan = column.rowSpan; | ||||
|     } | ||||
|     if (cell.colSpan !== 0) { | ||||
|       rows[currentRow].push(cell); | ||||
|     } | ||||
|   }); | ||||
|   return rows.filter(row => row.length > 0); | ||||
| 
 | ||||
|       if ('colSpan' in column) { | ||||
|         ({ colSpan } = column); | ||||
|       } | ||||
| 
 | ||||
|       if ('rowSpan' in column) { | ||||
|         cell.rowSpan = column.rowSpan; | ||||
|       } | ||||
| 
 | ||||
|       cell.colSpan = colSpan; | ||||
|       cell.colEnd = cell.colStart + colSpan - 1; | ||||
|       rows[rowIndex].push(cell); | ||||
| 
 | ||||
|       currentColIndex += colSpan; | ||||
| 
 | ||||
|       return colSpan; | ||||
|     }); | ||||
| 
 | ||||
|     return colSpans; | ||||
|   } | ||||
| 
 | ||||
|   // Generate `rows` cell data | ||||
|   fillRowCells(rootColumns, 0); | ||||
| 
 | ||||
|   // Handle `rowSpan` | ||||
|   const rowCount = rows.length; | ||||
|   for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) { | ||||
|     rows[rowIndex].forEach(cell => { | ||||
|       if (!('rowSpan' in cell) && !cell.hasSubColumns) { | ||||
|         // eslint-disable-next-line no-param-reassign | ||||
|         cell.rowSpan = rowCount - rowIndex; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return rows; | ||||
| } | ||||
| 
 | ||||
| export default { | ||||
|  | @ -49,22 +75,21 @@ export default { | |||
|     columns: PropTypes.array.isRequired, | ||||
|     expander: PropTypes.object.isRequired, | ||||
|   }, | ||||
|   setup() { | ||||
|   setup(props) { | ||||
|     return { | ||||
|       table: inject('table', {}), | ||||
|       rows: computed(() => parseHeaderRows(props.columns)), | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   render() { | ||||
|     const { sComponents: components, prefixCls, showHeader, customHeaderRow } = this.table; | ||||
|     const { expander, columns, fixed } = this; | ||||
|     const { expander, columns, fixed, rows } = this; | ||||
| 
 | ||||
|     if (!showHeader) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const rows = getHeaderRows({ columns }); | ||||
| 
 | ||||
|     expander.renderExpandIndentCell(rows, fixed); | ||||
| 
 | ||||
|     const HeaderWrapper = components.header.wrapper; | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import classNames from '../../_util/classNames'; | ||||
| import PropTypes from '../../_util/vue-types'; | ||||
| import { computed, inject } from 'vue'; | ||||
| import { getCellFixedInfo } from './fixUtil'; | ||||
| 
 | ||||
| const TableHeaderRow = { | ||||
|   name: 'TableHeaderRow', | ||||
|  | @ -35,6 +36,7 @@ const TableHeaderRow = { | |||
|         } | ||||
|         return null; | ||||
|       }), | ||||
|       store, | ||||
|     }; | ||||
|   }, | ||||
|   render() { | ||||
|  | @ -50,21 +52,44 @@ const TableHeaderRow = { | |||
|     if (style.height === null) { | ||||
|       delete style.height; | ||||
|     } | ||||
|     const { stickyOffsets, columnManager } = this.store; | ||||
|     return ( | ||||
|       <HeaderRow {...rowProps} style={style}> | ||||
|         {row.map((cell, i) => { | ||||
|           const { column, isLast, children, className, ...cellProps } = cell; | ||||
|           const fixedInfo = getCellFixedInfo( | ||||
|             cell.colStart, | ||||
|             cell.colEnd, | ||||
|             columnManager.leafColumns, | ||||
|             stickyOffsets, | ||||
|           ); | ||||
|           const customProps = column.customHeaderCell ? column.customHeaderCell(column) : {}; | ||||
|           const headerCellProps = { | ||||
|             ...cellProps, | ||||
|             ...customProps, | ||||
|             key: column.key || column.dataIndex || i, | ||||
|           }; | ||||
| 
 | ||||
|           if (headerCellProps.colSpan === 0) { | ||||
|             return null; | ||||
|           } | ||||
|           if (column.align) { | ||||
|             headerCellProps.style = { ...customProps.style, textAlign: column.align }; | ||||
|           } | ||||
|           // ====================== Fixed ======================= | ||||
|           const { fixLeft, fixRight, firstFixLeft, lastFixLeft, firstFixRight, lastFixRight } = | ||||
|             fixedInfo; | ||||
|           const fixedStyle = {}; | ||||
|           const isFixLeft = typeof fixLeft === 'number'; | ||||
|           const isFixRight = typeof fixRight === 'number'; | ||||
| 
 | ||||
|           if (isFixLeft) { | ||||
|             fixedStyle.position = 'sticky'; | ||||
|             fixedStyle.left = `${fixLeft}px`; | ||||
|           } | ||||
|           if (isFixRight) { | ||||
|             fixedStyle.position = 'sticky'; | ||||
|             fixedStyle.right = `${fixRight}px`; | ||||
|           } | ||||
|           headerCellProps.class = classNames( | ||||
|             customProps.class, | ||||
|             customProps.className, | ||||
|  | @ -75,9 +100,15 @@ const TableHeaderRow = { | |||
|               [`${prefixCls}-row-cell-ellipsis`]: !!column.ellipsis, | ||||
|               [`${prefixCls}-row-cell-break-word`]: !!column.width, | ||||
|               [`${prefixCls}-row-cell-last`]: isLast, | ||||
|               [`${prefixCls}-cell-fix-left`]: isFixLeft, | ||||
|               [`${prefixCls}-cell-fix-left-first`]: firstFixLeft, | ||||
|               [`${prefixCls}-cell-fix-left-last`]: lastFixLeft, | ||||
|               [`${prefixCls}-cell-fix-right`]: isFixRight, | ||||
|               [`${prefixCls}-cell-fix-right-first`]: firstFixRight, | ||||
|               [`${prefixCls}-cell-fix-right-last`]: lastFixRight, | ||||
|             }, | ||||
|           ); | ||||
| 
 | ||||
|           headerCellProps.style = { ...(headerCellProps.style || {}), ...fixedStyle }; | ||||
|           if (typeof HeaderCell === 'function') { | ||||
|             return HeaderCell(headerCellProps, children); | ||||
|           } | ||||
|  |  | |||
|  | @ -250,6 +250,7 @@ const TableRow = { | |||
|           indentSize={indentSize} | ||||
|           indent={indent} | ||||
|           index={index} | ||||
|           colIndex={i} | ||||
|           column={column} | ||||
|           key={column.key || column.dataIndex} | ||||
|           expandIcon={hasExpandIcon(i) && renderExpandIcon()} | ||||
|  |  | |||
|  | @ -0,0 +1,73 @@ | |||
| export interface StickyOffsets { | ||||
|   left: readonly number[]; | ||||
|   right: readonly number[]; | ||||
|   isSticky?: boolean; | ||||
| } | ||||
| export type FixedType = 'left' | 'right' | boolean; | ||||
| export interface FixedInfo { | ||||
|   fixLeft: number | false; | ||||
|   fixRight: number | false; | ||||
|   lastFixLeft: boolean; | ||||
|   firstFixRight: boolean; | ||||
| 
 | ||||
|   // For Rtl Direction
 | ||||
|   lastFixRight: boolean; | ||||
|   firstFixLeft: boolean; | ||||
| 
 | ||||
|   isSticky: boolean; | ||||
| } | ||||
| 
 | ||||
| export function getCellFixedInfo( | ||||
|   colStart: number, | ||||
|   colEnd: number, | ||||
|   columns: readonly { fixed?: FixedType }[], | ||||
|   stickyOffsets: StickyOffsets, | ||||
|   direction: 'ltr' | 'rtl', | ||||
| ): FixedInfo { | ||||
|   const startColumn = columns[colStart] || {}; | ||||
|   const endColumn = columns[colEnd] || {}; | ||||
| 
 | ||||
|   let fixLeft: number; | ||||
|   let fixRight: number; | ||||
| 
 | ||||
|   if (startColumn.fixed === 'left') { | ||||
|     fixLeft = stickyOffsets.left[colStart]; | ||||
|   } else if (endColumn.fixed === 'right') { | ||||
|     fixRight = stickyOffsets.right[colEnd]; | ||||
|   } | ||||
| 
 | ||||
|   let lastFixLeft = false; | ||||
|   let firstFixRight = false; | ||||
| 
 | ||||
|   let lastFixRight = false; | ||||
|   let firstFixLeft = false; | ||||
| 
 | ||||
|   const nextColumn = columns[colEnd + 1]; | ||||
|   const prevColumn = columns[colStart - 1]; | ||||
| 
 | ||||
|   if (direction === 'rtl') { | ||||
|     if (fixLeft !== undefined) { | ||||
|       const prevFixLeft = prevColumn && prevColumn.fixed === 'left'; | ||||
|       firstFixLeft = !prevFixLeft; | ||||
|     } else if (fixRight !== undefined) { | ||||
|       const nextFixRight = nextColumn && nextColumn.fixed === 'right'; | ||||
|       lastFixRight = !nextFixRight; | ||||
|     } | ||||
|   } else if (fixLeft !== undefined) { | ||||
|     const nextFixLeft = nextColumn && nextColumn.fixed === 'left'; | ||||
|     lastFixLeft = !nextFixLeft; | ||||
|   } else if (fixRight !== undefined) { | ||||
|     const prevFixRight = prevColumn && prevColumn.fixed === 'right'; | ||||
|     firstFixRight = !prevFixRight; | ||||
|   } | ||||
| 
 | ||||
|   return { | ||||
|     fixLeft, | ||||
|     fixRight, | ||||
|     lastFixLeft, | ||||
|     firstFixRight, | ||||
|     lastFixRight, | ||||
|     firstFixLeft, | ||||
|     isSticky: stickyOffsets.isSticky, | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,97 @@ | |||
| import { computed } from 'vue'; | ||||
| export default function useColumnManager(columns) { | ||||
|   const _leafColumns = (cls, fixed = false) => { | ||||
|     const leafColumns = []; | ||||
|     cls.forEach(column => { | ||||
|       column.fixed = fixed || column.fixed; | ||||
|       if (!column.children) { | ||||
|         leafColumns.push(column); | ||||
|       } else { | ||||
|         leafColumns.push(..._leafColumns(column.children, column.fixed)); | ||||
|       } | ||||
|     }); | ||||
|     return leafColumns; | ||||
|   }; | ||||
| 
 | ||||
|   // add appropriate rowspan and colspan to column | ||||
|   const groupedColumns = computed(() => { | ||||
|     const _groupColumns = (cls, currentRow = 0, parentColumn = {}, rows = [], fixed = false) => { | ||||
|       // track how many rows we got | ||||
|       rows[currentRow] = rows[currentRow] || []; | ||||
|       const grouped = []; | ||||
|       const setRowSpan = column => { | ||||
|         const rowSpan = rows.length - currentRow; | ||||
|         if ( | ||||
|           column && | ||||
|           !column.children && // parent columns are supposed to be one row | ||||
|           rowSpan > 1 && | ||||
|           (!column.rowSpan || column.rowSpan < rowSpan) | ||||
|         ) { | ||||
|           column.rowSpan = rowSpan; | ||||
|         } | ||||
|       }; | ||||
|       cls.forEach((column, index) => { | ||||
|         const newColumn = { ...column }; | ||||
|         newColumn.fixed = fixed || column.fixed; | ||||
|         rows[currentRow].push(newColumn); | ||||
|         parentColumn.colSpan = parentColumn.colSpan || 0; | ||||
|         if (newColumn.children && newColumn.children.length > 0) { | ||||
|           newColumn.children = _groupColumns( | ||||
|             newColumn.children, | ||||
|             currentRow + 1, | ||||
|             newColumn, | ||||
|             rows, | ||||
|             newColumn.fixed, | ||||
|           ); | ||||
|           parentColumn.colSpan += newColumn.colSpan; | ||||
|         } else { | ||||
|           parentColumn.colSpan += 1; | ||||
|         } | ||||
|         // update rowspan to all same row columns | ||||
|         for (let i = 0; i < rows[currentRow].length - 1; i += 1) { | ||||
|           setRowSpan(rows[currentRow][i]); | ||||
|         } | ||||
|         // last column, update rowspan immediately | ||||
|         if (index + 1 === cls.length) { | ||||
|           setRowSpan(newColumn); | ||||
|         } | ||||
|         grouped.push(newColumn); | ||||
|       }); | ||||
|       return grouped; | ||||
|     }; | ||||
|     return _groupColumns(columns); | ||||
|   }); | ||||
| 
 | ||||
|   const isAnyColumnsFixed = computed(() => columns.some(column => !!column.fixed)); | ||||
| 
 | ||||
|   const isAnyColumnsLeftFixed = computed(() => | ||||
|     columns.some(column => column.fixed === 'left' || column.fixed === true), | ||||
|   ); | ||||
| 
 | ||||
|   const isAnyColumnsRightFixed = computed(() => columns.some(column => column.fixed === 'right')); | ||||
| 
 | ||||
|   const leftColumns = computed(() => | ||||
|     groupedColumns.value.filter(column => column.fixed === 'left' || column.fixed === true), | ||||
|   ); | ||||
| 
 | ||||
|   const rightColumns = computed(() => { | ||||
|     return groupedColumns.value.filter(column => column.fixed === 'right'); | ||||
|   }); | ||||
| 
 | ||||
|   const leafColumns = computed(() => _leafColumns(columns)); | ||||
| 
 | ||||
|   const leftLeafColumns = computed(() => _leafColumns(leftColumns.value)); | ||||
| 
 | ||||
|   const rightLeafColumns = computed(() => _leafColumns(rightColumns.value)); | ||||
|   return { | ||||
|     groupedColumns, | ||||
|     isAnyColumnsFixed, | ||||
|     isAnyColumnsLeftFixed, | ||||
|     isAnyColumnsRightFixed, | ||||
|     leftColumns, | ||||
|     rightColumns, | ||||
|     leafColumns, | ||||
|     leftLeafColumns, | ||||
|     rightLeafColumns, | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,43 @@ | |||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| /** | ||||
|  * Get sticky column offset width | ||||
|  */ | ||||
| function useStickyOffsets(colWidths, columns) { | ||||
|   const stickyOffsets = ref({ | ||||
|     left: [], | ||||
|     right: [], | ||||
|   }); | ||||
|   const columnCount = ref(); | ||||
|   watch( | ||||
|     columns, | ||||
|     () => { | ||||
|       columnCount.value = columns.value.length; | ||||
|     }, | ||||
|     { immediate: true }, | ||||
|   ); | ||||
|   watch([colWidths, columnCount], () => { | ||||
|     const leftOffsets = []; | ||||
|     const rightOffsets = []; | ||||
|     let left = 0; | ||||
|     let right = 0; | ||||
| 
 | ||||
|     for (let start = 0; start < columnCount.value; start += 1) { | ||||
|       // Left offset
 | ||||
|       leftOffsets[start] = left; | ||||
|       left += colWidths.value[start] || 0; | ||||
| 
 | ||||
|       // Right offset
 | ||||
|       const end = columnCount.value - start - 1; | ||||
|       rightOffsets[end] = right; | ||||
|       right += colWidths.value[end] || 0; | ||||
|     } | ||||
|     stickyOffsets.value = { | ||||
|       left: leftOffsets, | ||||
|       right: rightOffsets, | ||||
|     }; | ||||
|   }); | ||||
|   return stickyOffsets; | ||||
| } | ||||
| 
 | ||||
| export default useStickyOffsets; | ||||
|  | @ -98,3 +98,19 @@ export function getDataAndAriaProps(props) { | |||
|     return memo; | ||||
|   }, {}); | ||||
| } | ||||
| 
 | ||||
| export function getColumnsKey(columns) { | ||||
|   const columnKeys = []; | ||||
| 
 | ||||
|   columns.forEach(column => { | ||||
|     const { key, dataIndex } = column || {}; | ||||
| 
 | ||||
|     columnKeys.push(key !== undefined ? key : dataIndex); | ||||
|   }); | ||||
| 
 | ||||
|   return columnKeys; | ||||
| } | ||||
| 
 | ||||
| export function validateValue(val) { | ||||
|   return val !== null && val !== undefined; | ||||
| } | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ export default function toArray(children: any[], option: Option = {}): any[] { | |||
|     if ((child === undefined || child === null) && !option.keepEmpty) { | ||||
|       return; | ||||
|     } | ||||
|     debugger; | ||||
|     if (Array.isArray(child)) { | ||||
|       ret = ret.concat(toArray(child)); | ||||
|     } else if (isFragment(child) && child.props) { | ||||
|  |  | |||
|  | @ -185,7 +185,7 @@ | |||
|     "vue-clipboard2": "0.3.1", | ||||
|     "vue-draggable-resizable": "^2.1.0", | ||||
|     "vue-eslint-parser": "^7.0.0", | ||||
|     "vue-i18n": "^9.0.0-alpha.11", | ||||
|     "vue-i18n": "^9.1.7", | ||||
|     "vue-infinite-scroll": "^2.0.2", | ||||
|     "vue-jest": "^5.0.0-alpha.3", | ||||
|     "vue-loader": "^16.1.1", | ||||
|  |  | |||
							
								
								
									
										2
									
								
								v2-doc
								
								
								
								
							
							
								
								
								
								
								
								
							
						
						
									
										2
									
								
								v2-doc
								
								
								
								
							|  | @ -1 +1 @@ | |||
| Subproject commit e67d63729715291f481029fe9497f0109f328504 | ||||
| Subproject commit 1882ee4e30b707551a08561f090e72a3f261f6cf | ||||
		Loading…
	
		Reference in New Issue
	
	 tangjinzhou
						tangjinzhou