diff --git a/packages/components/table/src/Table.tsx b/packages/components/table/src/Table.tsx index 949410149..02bcfe99c 100644 --- a/packages/components/table/src/Table.tsx +++ b/packages/components/table/src/Table.tsx @@ -6,18 +6,20 @@ */ import type { VirtualScrollEnabled } from '@idux/cdk/scroll' -import type { VKey } from '@idux/cdk/utils' import { type VNode, computed, defineComponent, normalizeClass, provide, watch } from 'vue' import { isBoolean } from 'lodash-es' +import { type VKey, useState } from '@idux/cdk/utils' import { ɵHeader } from '@idux/components/_private/header' import { useGlobalConfig } from '@idux/components/config' import { IxSpin, type SpinProps } from '@idux/components/spin' import { useThemeToken } from '@idux/components/theme' import { useGetKey } from '@idux/components/utils' +import { useColumnOffsets } from './composables/useColumnOffsets' +import { useColumnWidthMeasure } from './composables/useColumnWidthMeasure' import { type TableColumnMerged, useColumns } from './composables/useColumns' import { useDataSource } from './composables/useDataSource' import { useExpandable } from './composables/useExpandable' @@ -51,6 +53,8 @@ export default defineComponent({ const locale = useGlobalConfig('locale') const config = useGlobalConfig('table') + const [clientWidth, setClientWidth] = useState(0) + const mergedAutoHeight = computed(() => props.autoHeight ?? config.autoHeight) const mergedChildrenKey = computed(() => props.childrenKey ?? config.childrenKey) const mergedGetKey = useGetKey(props, config, 'components/table') @@ -70,9 +74,18 @@ export default defineComponent({ const stickyContext = useSticky(props) const scrollContext = useScroll(props, stickyContext) const columnsContext = useColumns(props, slots, config, scrollContext.scrollBarSizeOnFixedHolder) - const sortableContext = useSortable(columnsContext.flattedColumns) - const filterableContext = useFilterable(columnsContext.flattedColumns) - const expandableContext = useExpandable(props, columnsContext.flattedColumns) + + const { flattedColumns, flattedColumnsWithScrollBar, fixedColumns } = columnsContext + + const columnMeasureContext = useColumnWidthMeasure(flattedColumns) + const { measuredColumnWidthMap } = columnMeasureContext + + const columnCount = computed(() => flattedColumnsWithScrollBar.value.length) + + const columnOffsetsContext = useColumnOffsets(fixedColumns, measuredColumnWidthMap, columnCount) + const sortableContext = useSortable(flattedColumns) + const filterableContext = useFilterable(flattedColumns) + const expandableContext = useExpandable(props, flattedColumns) const tableLayout = useTableLayout( props, columnsContext, @@ -82,7 +95,6 @@ export default defineComponent({ mergedAutoHeight, ) - const { columnWidthMap, flattedColumns } = columnsContext const { activeSorters } = sortableContext const { activeFilters } = filterableContext @@ -117,7 +129,7 @@ export default defineComponent({ useScrollOnChange(props, config, mergedPagination, activeSorters, activeFilters, scrollContext.scrollTo) const getVirtualColWidth = (rowKey: VKey, colKey: VKey) => { - return columnWidthMap.value[colKey] ?? columnMap.get(colKey)?.width + return measuredColumnWidthMap.value[colKey] ?? columnMap.get(colKey)?.width } const context = { @@ -125,6 +137,8 @@ export default defineComponent({ slots, config, locale, + clientWidth, + setClientWidth, mergedPrefixCls, mergedEmptyCell, mergedInsetShadow, @@ -134,6 +148,8 @@ export default defineComponent({ mergedAutoHeight, getVirtualColWidth, ...columnsContext, + ...columnMeasureContext, + ...columnOffsetsContext, ...scrollContext, ...sortableContext, ...filterableContext, diff --git a/packages/components/table/src/composables/useColumnOffsets.ts b/packages/components/table/src/composables/useColumnOffsets.ts new file mode 100644 index 000000000..ff8e23c7a --- /dev/null +++ b/packages/components/table/src/composables/useColumnOffsets.ts @@ -0,0 +1,90 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { VKey } from '@idux/cdk/utils' + +import { type ComputedRef, type Ref, computed } from 'vue' + +import { TableColumnMerged, TableColumnScrollBar } from './useColumns' + +export interface ColumnOffsetsContext { + columnOffsets: ComputedRef<{ + starts: Record + ends: Record + }> + columnOffsetsWithScrollBar: ComputedRef<{ + starts: Record + ends: Record + }> +} + +export function useColumnOffsets( + fixedColumns: ComputedRef<{ + fixedStartColumns: (TableColumnMerged | TableColumnScrollBar)[] + fixedEndColumns: (TableColumnMerged | TableColumnScrollBar)[] + fixedColumnIndexMap: Record + }>, + columnWidthsMap: Ref>, + columnCount: Ref, +): ColumnOffsetsContext { + const columnOffsets = computed(() => { + const { fixedStartColumns, fixedEndColumns, fixedColumnIndexMap } = fixedColumns.value + return calculateOffsets( + fixedStartColumns, + fixedEndColumns.filter(column => column.type !== 'scroll-bar'), + fixedColumnIndexMap, + columnWidthsMap.value, + columnCount.value - 1, + ) + }) + const columnOffsetsWithScrollBar = computed(() => { + const { fixedStartColumns, fixedEndColumns, fixedColumnIndexMap } = fixedColumns.value + return calculateOffsets( + fixedStartColumns, + fixedEndColumns, + fixedColumnIndexMap, + columnWidthsMap.value, + columnCount.value, + ) + }) + return { columnOffsets, columnOffsetsWithScrollBar } +} + +function calculateOffsets( + startColumns: (TableColumnMerged | TableColumnScrollBar)[], + endColumns: (TableColumnMerged | TableColumnScrollBar)[], + columnIndexMap: Record, + columnWidthsMap: Record, + columnCount: number, +) { + const startOffsets: Record = {} + const endOffsets: Record = {} + + let startOffset = 0 + let endOffset = 0 + + for (let index = 0; index < startColumns.length; index++) { + const column = startColumns[index] + const width = columnWidthsMap[column.key] ?? column.width ?? 0 + + startOffsets[column.key] = { index: columnIndexMap[column.key] ?? index, offset: startOffset } + startOffset += width + } + + for (let index = 0; index < endColumns.length; index++) { + const column = endColumns[endColumns.length - index - 1] + const width = columnWidthsMap[column.key] ?? column.width ?? 0 + + endOffsets[column.key] = { index: columnIndexMap[column.key] ?? columnCount - index - 1, offset: endOffset } + endOffset += width + } + + return { + starts: startOffsets, + ends: endOffsets, + } +} diff --git a/packages/components/table/src/composables/useColumnWidthMeasure.ts b/packages/components/table/src/composables/useColumnWidthMeasure.ts new file mode 100644 index 000000000..d1f80027e --- /dev/null +++ b/packages/components/table/src/composables/useColumnWidthMeasure.ts @@ -0,0 +1,42 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { TableColumnMerged } from './useColumns' +import type { VKey } from '@idux/cdk/utils' + +import { type ComputedRef, type Ref, ref, watch } from 'vue' + +export interface ColumnWidthMeasureContext { + measuredColumnWidthMap: Ref> + changeColumnWidth: (key: VKey, width: number | false) => void +} + +export function useColumnWidthMeasure(flattedColumns: ComputedRef): ColumnWidthMeasureContext { + const measuredColumnWidthMap = ref>({}) + + const changeColumnWidth = (key: VKey, width: number | false) => { + if (width === false) { + delete measuredColumnWidthMap.value[key] + } else { + measuredColumnWidthMap.value[key] = width + } + } + + watch(flattedColumns, columns => { + const columnKeySet = new Set(columns.map(column => column.key)) + Object.keys(measuredColumnWidthMap).forEach(key => { + if (!columnKeySet.has(key)) { + delete measuredColumnWidthMap.value[key] + } + }) + }) + + return { + measuredColumnWidthMap, + changeColumnWidth, + } +} diff --git a/packages/components/table/src/composables/useColumns.ts b/packages/components/table/src/composables/useColumns.ts index ad47dab1c..db70b5c2f 100644 --- a/packages/components/table/src/composables/useColumns.ts +++ b/packages/components/table/src/composables/useColumns.ts @@ -5,19 +5,9 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { - type ComputedRef, - type Ref, - type Slots, - type VNode, - type VNodeChild, - computed, - ref, - watch, - watchEffect, -} from 'vue' - -import { debounce, isNil, isString } from 'lodash-es' +import { type ComputedRef, type Slots, type VNode, type VNodeChild, computed } from 'vue' + +import { isNil, isString } from 'lodash-es' import { type VKey, flattenNode } from '@idux/cdk/utils' import { type TableConfig } from '@idux/components/config' @@ -54,11 +44,6 @@ export function useColumns( ) const hasFixed = computed(() => flattedColumns.value.some(column => column.fixed)) - const columnCount = computed(() => flattedColumnsWithScrollBar.value.length) - - const { columnWidthMap, columnWidths, changeColumnWidth } = useColumnWidths(flattedColumns) - const { columnOffsets, columnOffsetsWithScrollBar } = useColumnOffsets(fixedColumns, columnWidthMap, columnCount) - const mergedRows = computed(() => mergeRows(mergedColumns.value, scrollBarColumn.value)) return { @@ -69,11 +54,6 @@ export function useColumns( fixedColumnKeys, hasEllipsis, hasFixed, - columnWidthMap, - columnWidths, - changeColumnWidth, - columnOffsets, - columnOffsetsWithScrollBar, mergedRows, } } @@ -85,6 +65,7 @@ export interface ColumnsContext { fixedColumns: ComputedRef<{ fixedStartColumns: (TableColumnMerged | TableColumnScrollBar)[] fixedEndColumns: (TableColumnMerged | TableColumnScrollBar)[] + fixedColumnIndexMap: Record }> fixedColumnKeys: ComputedRef<{ lastStartKey: VKey | undefined @@ -92,17 +73,6 @@ export interface ColumnsContext { }> hasEllipsis: ComputedRef hasFixed: ComputedRef - columnWidthMap: Ref> - columnWidths: Ref - changeColumnWidth: (key: VKey, width: number | false) => void - columnOffsets: ComputedRef<{ - starts: Record - ends: Record - }> - columnOffsetsWithScrollBar: ComputedRef<{ - starts: Record - ends: Record - }> mergedRows: ComputedRef<{ rows: TableColumnMergedExtra[][] offsetIndexMap: Record @@ -315,101 +285,6 @@ function useFixedColumns(flattedColumnsWithScrollBar: ComputedRef<(TableColumnMe return { fixedColumns, fixedColumnKeys } } -function useColumnWidths(flattedColumns: ComputedRef) { - const widthMap = ref>({}) - const widthString = ref() - const columnWidths = ref([]) - watch( - widthString, - // resizable: 列宽设置百分比的情况下,拖拽会改变多列的宽度,用 debounce 来减少重复渲染次数。 - debounce(widths => { - columnWidths.value = widths ? widths.split('-').filter(Boolean).map(Number) : [] - }, 16), - ) - - watchEffect(() => { - const columns = flattedColumns.value - widthString.value = columns.map(column => widthMap.value[column.key]).join('-') - }) - - const changeColumnWidth = (key: VKey, width: number | false) => { - if (width === false) { - delete widthMap.value[key] - } else { - widthMap.value[key] = width - } - } - - return { columnWidthMap: widthMap, columnWidths, changeColumnWidth } -} - -function useColumnOffsets( - fixedColumns: ComputedRef<{ - fixedStartColumns: (TableColumnMerged | TableColumnScrollBar)[] - fixedEndColumns: (TableColumnMerged | TableColumnScrollBar)[] - fixedColumnIndexMap: Record - }>, - columnWidthsMap: Ref>, - columnCount: Ref, -) { - const columnOffsets = computed(() => { - const { fixedStartColumns, fixedEndColumns, fixedColumnIndexMap } = fixedColumns.value - return calculateOffsets( - fixedStartColumns, - fixedEndColumns.filter(column => column.type !== 'scroll-bar'), - fixedColumnIndexMap, - columnWidthsMap.value, - columnCount.value - 1, - ) - }) - const columnOffsetsWithScrollBar = computed(() => { - const { fixedStartColumns, fixedEndColumns, fixedColumnIndexMap } = fixedColumns.value - return calculateOffsets( - fixedStartColumns, - fixedEndColumns, - fixedColumnIndexMap, - columnWidthsMap.value, - columnCount.value, - ) - }) - return { columnOffsets, columnOffsetsWithScrollBar } -} - -function calculateOffsets( - startColumns: (TableColumnMerged | TableColumnScrollBar)[], - endColumns: (TableColumnMerged | TableColumnScrollBar)[], - columnIndexMap: Record, - columnWidthsMap: Record, - columnCount: number, -) { - const startOffsets: Record = {} - const endOffsets: Record = {} - - let startOffset = 0 - let endOffset = 0 - - for (let index = 0; index < startColumns.length; index++) { - const column = startColumns[index] - const width = columnWidthsMap[column.key] ?? column.width ?? 0 - - startOffsets[column.key] = { index: columnIndexMap[column.key] ?? index, offset: startOffset } - startOffset += width - } - - for (let index = 0; index < endColumns.length; index++) { - const column = endColumns[endColumns.length - index - 1] - const width = columnWidthsMap[column.key] ?? column.width ?? 0 - - endOffsets[column.key] = { index: columnIndexMap[column.key] ?? columnCount - index - 1, offset: endOffset } - endOffset += width - } - - return { - starts: startOffsets, - ends: endOffsets, - } -} - function mergeRows(mergedColumns: TableColumnMerged[], scrollBarColumn: TableColumnScrollBar | undefined) { const rows: TableColumnMergedExtra[][] = [] const offsetIndexMap: Record = {} diff --git a/packages/components/table/src/main/ColGroup.tsx b/packages/components/table/src/main/ColGroup.tsx index a4c074539..bf09f8d7a 100644 --- a/packages/components/table/src/main/ColGroup.tsx +++ b/packages/components/table/src/main/ColGroup.tsx @@ -17,8 +17,13 @@ import { TABLE_TOKEN } from '../token' export default defineComponent({ props: { isFixedHolder: Boolean, columns: Array as PropType }, setup(props) { - const { flattedColumns, flattedColumnsWithScrollBar, columnWidthMap, mergedSelectableMenus, mergedPrefixCls } = - inject(TABLE_TOKEN)! + const { + flattedColumns, + flattedColumnsWithScrollBar, + measuredColumnWidthMap, + mergedSelectableMenus, + mergedPrefixCls, + } = inject(TABLE_TOKEN)! const resolvedColumns = computed(() => { const { isFixedHolder, columns } = props @@ -42,7 +47,7 @@ export default defineComponent({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const { key, type } = column - const mergedWidth = column.width ?? columnWidthMap.value[key] + const mergedWidth = props.isFixedHolder ? measuredColumnWidthMap.value[key] ?? column.width : column.width const className = type ? normalizeClass({ [`${prefixCls}-col-${type}`]: true, diff --git a/packages/components/table/src/main/MainTable.tsx b/packages/components/table/src/main/MainTable.tsx index da8072511..6f6aff39c 100644 --- a/packages/components/table/src/main/MainTable.tsx +++ b/packages/components/table/src/main/MainTable.tsx @@ -21,7 +21,7 @@ import { watch, } from 'vue' -import { debounce, isNumber } from 'lodash-es' +import { isNumber } from 'lodash-es' import { offResize, onResize } from '@idux/cdk/resize' import { @@ -53,6 +53,8 @@ export default defineComponent({ const { props, slots, + clientWidth, + setClientWidth, expandable, mergedPrefixCls, mergedInsetShadow, @@ -60,7 +62,6 @@ export default defineComponent({ mergedVirtualItemHeight, mergedVirtualColWidth, mergedAutoHeight, - columnWidths, changeColumnWidth, flattedData, flattedColumns, @@ -83,7 +84,6 @@ export default defineComponent({ } = inject(TABLE_TOKEN)! const mainTableRef = ref() - const mainTableWidth = ref(0) const showMeasure = computed( () => mergedAutoHeight.value || !!scrollWidth.value || isSticky.value || mergedVirtual.value.horizontal, @@ -95,7 +95,7 @@ export default defineComponent({ } } - provide(tableBodyToken, { mainTableWidth, changeColumnWidth: _changeColumnWidth }) + provide(tableBodyToken, { changeColumnWidth: _changeColumnWidth }) const triggerScroll = () => { const currentTarget = convertElement(scrollBodyRef) @@ -106,8 +106,8 @@ export default defineComponent({ const handleWrapperResize = (evt: ResizeObserverEntry) => { const { offsetWidth } = evt.target as HTMLDivElement - if (offsetWidth !== mainTableWidth.value) { - mainTableWidth.value = offsetWidth + if (offsetWidth !== clientWidth.value) { + setClientWidth(offsetWidth) } } @@ -120,15 +120,6 @@ export default defineComponent({ } }) - // see https://github.com/IDuxFE/idux/issues/1140 - const handlerColumnWidthsChange = () => { - const currScrollLeft = convertElement(scrollBodyRef)?.scrollLeft - if (currScrollLeft === 0) { - triggerScroll() - } - } - watch(columnWidths, debounce(handlerColumnWidthsChange, 16)) - onResize(mainTableRef.value, handleWrapperResize) }) @@ -283,7 +274,7 @@ export default defineComponent({ item, index, rowIndex, - }) => renderBodyCell(item, row, rowIndex, index) + }) => renderBodyCell(item, row, rowIndex, index === row.data.length - 1) const contentRender: VirtualContentRenderFn = (children, { renderedData }) => { const columns = flattedData.value.length diff --git a/packages/components/table/src/main/body/BodyRowSingle.tsx b/packages/components/table/src/main/body/BodyRowSingle.tsx index 9d7bd71f9..6a9cc9548 100644 --- a/packages/components/table/src/main/body/BodyRowSingle.tsx +++ b/packages/components/table/src/main/body/BodyRowSingle.tsx @@ -11,13 +11,12 @@ import { type PropType, type VNodeChild, computed, defineComponent, inject } fro import { convertCssPixel } from '@idux/cdk/utils' -import { TABLE_TOKEN, tableBodyToken } from '../../token' +import { TABLE_TOKEN } from '../../token' export default defineComponent({ props: { columns: Array as PropType, isEmpty: Boolean }, setup(props, { slots }) { - const { mergedPrefixCls, hasFixed, scrollHorizontalOverflowed, scrollBarColumn } = inject(TABLE_TOKEN)! - const { mainTableWidth } = inject(tableBodyToken)! + const { clientWidth, mergedPrefixCls, hasFixed, scrollHorizontalOverflowed, scrollBarColumn } = inject(TABLE_TOKEN)! const columnCount = computed(() => props.columns?.length ?? 1) return () => { let children: VNodeChild = slots.default!() @@ -28,7 +27,7 @@ export default defineComponent({
number | undefined + clientWidth: ComputedRef + setClientWidth: (clientWidth: number) => void mergedPrefixCls: ComputedRef mergedAutoHeight: ComputedRef mergedEmptyCell: ComputedRef VNodeChild) | undefined> @@ -50,7 +56,6 @@ export interface TableContext export const TABLE_TOKEN: InjectionKey = Symbol('TABLE_TOKEN') export interface TableBodyContext { - mainTableWidth: Ref changeColumnWidth: (key: VKey, width: number | false) => void }