diff --git a/packages/cdk/resize/demo/ResizableBasic.vue b/packages/cdk/resize/demo/ResizableBasic.vue index 8c3ddd944..1d4e7e5e4 100644 --- a/packages/cdk/resize/demo/ResizableBasic.vue +++ b/packages/cdk/resize/demo/ResizableBasic.vue @@ -88,31 +88,27 @@ } } - &:not(.cdk-resizable-resizing) { - .cdk-resizable-handler { - &-top, - &-bottom { - cursor: ns-resize; - } - &-start, - &-end { - cursor: ew-resize; - } - &-topStart, - &-bottomEnd { - cursor: nwse-resize; - } - &-topEnd, - &-bottomStart { - cursor: nesw-resize; - } + &:not(&-resizing) &-handler { + &-top, + &-bottom { + cursor: ns-resize; + } + &-start, + &-end { + cursor: ew-resize; + } + &-topStart, + &-bottomEnd { + cursor: nwse-resize; + } + &-topEnd, + &-bottomStart { + cursor: nesw-resize; } } - &-disabled { - .cdk-resizable-handler { - pointer-events: none; - } + &-disabled &-handler { + pointer-events: none; } } diff --git a/packages/cdk/resize/demo/ResizableHandler.vue b/packages/cdk/resize/demo/ResizableHandler.vue index ae82014b0..0207130da 100644 --- a/packages/cdk/resize/demo/ResizableHandler.vue +++ b/packages/cdk/resize/demo/ResizableHandler.vue @@ -117,31 +117,27 @@ } } - &:not(.cdk-resizable-resizing) { - .cdk-resizable-handler { - &-top, - &-bottom { - cursor: ns-resize; - } - &-start, - &-end { - cursor: ew-resize; - } - &-topStart, - &-bottomEnd { - cursor: nwse-resize; - } - &-topEnd, - &-bottomStart { - cursor: nesw-resize; - } + &:not(&-resizing) &-handler { + &-top, + &-bottom { + cursor: ns-resize; + } + &-start, + &-end { + cursor: ew-resize; + } + &-topStart, + &-bottomEnd { + cursor: nwse-resize; + } + &-topEnd, + &-bottomStart { + cursor: nesw-resize; } } - &-disabled { - .cdk-resizable-handler { - pointer-events: none; - } + &-disabled &-handler { + pointer-events: none; } } diff --git a/packages/cdk/resize/src/resizable/types.ts b/packages/cdk/resize/src/resizable/types.ts index a8ab0c8c8..c0a248b68 100644 --- a/packages/cdk/resize/src/resizable/types.ts +++ b/packages/cdk/resize/src/resizable/types.ts @@ -76,7 +76,7 @@ export type ResizableHandlerComponent = DefineComponent< > export type ResizableHandlerInstance = InstanceType> -export type ResizableOptions = Omit +export type ResizableOptions = Omit export type ResizableHandlerPlacement = typeof allHandlerPlacements[number] diff --git a/packages/cdk/resize/src/resizable/useResizable.ts b/packages/cdk/resize/src/resizable/useResizable.ts index 712e84ccf..eb1bb1325 100644 --- a/packages/cdk/resize/src/resizable/useResizable.ts +++ b/packages/cdk/resize/src/resizable/useResizable.ts @@ -136,7 +136,7 @@ export function useResizable( if (!startPosition.value) { return } - + setBodyCursor(_placement) resizing.value = true const { width: currWidth, height: currHeight } = calcSizeByEvent(_placement, evt) @@ -158,7 +158,7 @@ export function useResizable( if (!startPosition.value) { return } - + clearBodyCursor() resizing.value = false startPosition.value = undefined @@ -174,6 +174,35 @@ export function useResizable( } } -function ensureInBounds(value: number, boundValue: number): number { +function ensureInBounds(value: number, boundValue: number) { return value < boundValue ? value : boundValue } + +function setBodyCursor(placement: ResizableHandlerPlacement) { + let cursor = '' + switch (placement) { + case 'top': + case 'bottom': + cursor = 'ns-resize' + break + case 'start': + case 'end': + cursor = 'ew-resize' + break + case 'topStart': + case 'bottomEnd': + cursor = 'nwse-resize' + break + case 'topEnd': + case 'bottomStart': + cursor = 'nesw-resize' + break + } + document.body.style.cursor = cursor + document.body.style.userSelect = 'none' +} + +function clearBodyCursor() { + document.body.style.cursor = '' + document.body.style.userSelect = '' +} diff --git a/packages/components/table/index.ts b/packages/components/table/index.ts index 3eb47d351..5aa2c05b3 100644 --- a/packages/components/table/index.ts +++ b/packages/components/table/index.ts @@ -26,6 +26,7 @@ export type { TableColumnExpandable, TableColumnSelectable, TableCustomAdditional, + TableCustomTag, TablePagination, TablePaginationPosition, TableScroll, diff --git a/packages/components/table/src/main/ColGroup.tsx b/packages/components/table/src/main/ColGroup.tsx index 17d5c32a2..fbf9a14db 100644 --- a/packages/components/table/src/main/ColGroup.tsx +++ b/packages/components/table/src/main/ColGroup.tsx @@ -59,7 +59,7 @@ function renderCol( [`${prefixCls}-selectable-with-dropdown`]: type === 'selectable' && mergedSelectableMenus.value.length > 0, }) - const mergedWidth = width ?? column.width + const mergedWidth = column.width ?? width const style = mergedWidth ? { width: convertCssPixel(mergedWidth) } : undefined return } diff --git a/packages/components/table/src/main/body/BodyCell.tsx b/packages/components/table/src/main/body/BodyCell.tsx index ec7c233a4..1989e895d 100644 --- a/packages/components/table/src/main/body/BodyCell.tsx +++ b/packages/components/table/src/main/body/BodyCell.tsx @@ -106,12 +106,13 @@ export default defineComponent({ const customAdditional = customAdditionalFn ? customAdditionalFn({ column, record: props.record, rowIndex: props.rowIndex }) : undefined - + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Tag = (tableProps.customTag?.bodyCell ?? 'td') as any return ( - + {type === 'expandable' && renderExpandableChildren(props, slots, expandable, mergedPrefixCls.value)} {children} - + ) } }, diff --git a/packages/components/table/src/main/body/BodyRow.tsx b/packages/components/table/src/main/body/BodyRow.tsx index b23bb99e6..04b5a51bd 100644 --- a/packages/components/table/src/main/body/BodyRow.tsx +++ b/packages/components/table/src/main/body/BodyRow.tsx @@ -69,9 +69,10 @@ export default defineComponent({ const customAdditional = customAdditionalFn ? customAdditionalFn({ record: props.record, rowIndex: props.rowIndex }) : undefined - + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Tag = (tableProps.customTag?.bodyRow ?? 'tr') as any return ( - + {renderChildren( props, flattedColumns, @@ -82,7 +83,7 @@ export default defineComponent({ selectDisabled, handleSelect, )} - + ) } }, diff --git a/packages/components/table/src/main/head/HeadCell.tsx b/packages/components/table/src/main/head/HeadCell.tsx index 1c9f97278..8a3eba712 100644 --- a/packages/components/table/src/main/head/HeadCell.tsx +++ b/packages/components/table/src/main/head/HeadCell.tsx @@ -143,9 +143,10 @@ export default defineComponent({ const customAdditionalFn = tableProps.customAdditional?.headCell const customAdditional = customAdditionalFn ? customAdditionalFn({ column }) : undefined - + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Tag = (tableProps.customTag?.headCell ?? 'th') as any return ( - {children} - + ) } }, diff --git a/packages/components/table/src/main/head/HeadRow.tsx b/packages/components/table/src/main/head/HeadRow.tsx index 5e9aeddf0..3da20ce40 100644 --- a/packages/components/table/src/main/head/HeadRow.tsx +++ b/packages/components/table/src/main/head/HeadRow.tsx @@ -20,14 +20,16 @@ export default defineComponent({ const { columns } = props const customAdditionalFn = tableProps.customAdditional?.headRow const customAdditional = customAdditionalFn ? customAdditionalFn({ columns }) : undefined + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const Tag = (tableProps.customTag?.headRow ?? 'tr') as any return ( - + {columns .filter(column => column.titleColSpan !== 0) .map(column => ( ))} - + ) } }, diff --git a/packages/components/table/src/types.ts b/packages/components/table/src/types.ts index 80bf72140..56367cb5c 100644 --- a/packages/components/table/src/types.ts +++ b/packages/components/table/src/types.ts @@ -7,21 +7,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { type DefineComponent, FunctionalComponent, type HTMLAttributes, type PropType, type VNodeChild } from 'vue' - -import { type BreakpointKey } from '@idux/cdk/breakpoint' -import { type VirtualScrollToFn } from '@idux/cdk/scroll' -import { type ExtractInnerPropTypes, type ExtractPublicPropTypes, type MaybeArray, type VKey } from '@idux/cdk/utils' -import { type EmptyProps } from '@idux/components/empty' -import { type HeaderProps } from '@idux/components/header' -import { type MenuClickOptions, type MenuData } from '@idux/components/menu' -import { type PaginationProps } from '@idux/components/pagination' -import { type SpinProps } from '@idux/components/spin' - -import { type TableColumnMerged, type TableColumnMergedExtra } from './composables/useColumns' -import { type FlattedData } from './composables/useDataSource' -import { type ActiveFilter } from './composables/useFilterable' -import { type ActiveSorter } from './composables/useSortable' +import type { TableColumnMerged, TableColumnMergedExtra } from './composables/useColumns' +import type { FlattedData } from './composables/useDataSource' +import type { ActiveFilter } from './composables/useFilterable' +import type { ActiveSorter } from './composables/useSortable' +import type { BreakpointKey } from '@idux/cdk/breakpoint' +import type { VirtualScrollToFn } from '@idux/cdk/scroll' +import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' +import type { EmptyProps } from '@idux/components/empty' +import type { HeaderProps } from '@idux/components/header' +import type { MenuClickOptions, MenuData } from '@idux/components/menu' +import type { PaginationProps } from '@idux/components/pagination' +import type { SpinProps } from '@idux/components/spin' +import type { Component, DefineComponent, FunctionalComponent, HTMLAttributes, PropType, VNodeChild } from 'vue' export const tableProps = { expandedRowKeys: { type: Array as PropType, default: undefined }, @@ -32,6 +30,7 @@ export const tableProps = { childrenKey: { type: String, default: undefined }, columns: { type: Array as PropType, default: () => [] }, customAdditional: { type: Object as PropType, default: undefined }, + customTag: { type: Object as PropType, default: undefined }, dataSource: { type: Array as PropType, default: () => [] }, ellipsis: { type: Boolean, default: false }, empty: { type: [String, Object] as PropType, default: undefined }, @@ -161,6 +160,13 @@ export interface TableCustomAdditional { headRow?: (data: { columns: TableColumn[] }) => Record | undefined } +export interface TableCustomTag { + bodyCell?: string | Component + bodyRow?: string | Component + headCell?: string | Component + headRow?: string | Component +} + export interface TablePagination extends PaginationProps { position?: TablePaginationPosition } diff --git a/packages/pro/table/demo/Basic.vue b/packages/pro/table/demo/Basic.vue index 1a1e1ce59..02f82487b 100644 --- a/packages/pro/table/demo/Basic.vue +++ b/packages/pro/table/demo/Basic.vue @@ -13,9 +13,6 @@ Invite {{ record.name }} Delete - @@ -46,11 +43,7 @@ const onColumnsChange = console.log const columns: ProTableColumn[] = [ { type: 'indexable', - }, - { - type: 'expandable', changeVisible: false, - customExpand: 'expand', }, { title: 'Name', diff --git a/packages/pro/table/demo/HeadGroup.vue b/packages/pro/table/demo/HeadGroup.vue index 9964ec599..a81354201 100644 --- a/packages/pro/table/demo/HeadGroup.vue +++ b/packages/pro/table/demo/HeadGroup.vue @@ -7,9 +7,6 @@ Invite {{ record.name }} Delete - @@ -33,13 +30,7 @@ const columns: ProTableColumn[] = [ { type: 'indexable', fixed: 'start', - }, - { - type: 'expandable', - fixed: 'start', changeVisible: false, - onChange: expendedRowKeys => console.log(expendedRowKeys), - customExpand: 'expand', }, { title: 'Name', diff --git a/packages/pro/table/demo/Resizable.md b/packages/pro/table/demo/Resizable.md new file mode 100644 index 000000000..b466a9e73 --- /dev/null +++ b/packages/pro/table/demo/Resizable.md @@ -0,0 +1,14 @@ +--- +order: 10 +title: + zh: 可调整列宽 + en: Resizable column +--- + +## zh + +可以通过配置 `resizable`,开启拖拽调整列宽,还可以配置 `maxWidth` `minWidth` 来限制列宽的范围。 + +## en + +Column width can be adjusted by configuring `resizable`, enabling drag and drop, and column width can be limited by configuring `maxWidth` and `minWidth`. diff --git a/packages/pro/table/demo/Resizable.vue b/packages/pro/table/demo/Resizable.vue new file mode 100644 index 000000000..8d4065d26 --- /dev/null +++ b/packages/pro/table/demo/Resizable.vue @@ -0,0 +1,104 @@ + + + diff --git a/packages/pro/table/src/ProTable.tsx b/packages/pro/table/src/ProTable.tsx index 921cc58a4..5f661442d 100644 --- a/packages/pro/table/src/ProTable.tsx +++ b/packages/pro/table/src/ProTable.tsx @@ -5,6 +5,8 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ +/* eslint-disable indent */ + import { cloneVNode, computed, defineComponent, provide } from 'vue' import { isString } from 'lodash-es' @@ -12,11 +14,13 @@ import { isString } from 'lodash-es' import { IxHeader } from '@idux/components/header' import { IxIcon } from '@idux/components/icon' import { IxSpace } from '@idux/components/space' -import { IxTable } from '@idux/components/table' +import { IxTable, type TableCustomAdditional, type TableCustomTag } from '@idux/components/table' import { useGlobalConfig } from '@idux/pro/config' import ProTableLayoutTool from './ProTableLayoutTool' import { useColumns } from './composables/useColumns' +import { useResizable } from './composables/useResizable' +import ResizableHeadCell from './contents/ResizableHeadCell' import { proTableToken } from './token' import { proTableProps } from './types' @@ -31,6 +35,7 @@ export default defineComponent({ const mergedPrefixCls = computed(() => `${common.prefixCls}-table`) const mergedToolbar = computed(() => props.toolbar ?? config.toolbar) const columnsContext = useColumns(props, config) + const { hasResizable, onResizeEnd } = useResizable(columnsContext) provide(proTableToken, { props, @@ -67,12 +72,31 @@ export default defineComponent({ } return () => { + const { editable, toolbar, customAdditional, customTag, ...restProps } = props + + const mergedCustomAdditional: TableCustomAdditional = hasResizable.value + ? { + ...customAdditional, + headCell: ({ column }) => { + const additionalProps = customAdditional?.headCell ? customAdditional.headCell({ column }) : undefined + return { ...additionalProps, column, onResizeEnd } + }, + } + : customAdditional + const mergedCustomTag: TableCustomTag = hasResizable.value + ? { + headCell: ResizableHeadCell, + ...customAdditional, + } + : customTag return ( ) } diff --git a/packages/pro/table/src/composables/useResizable.ts b/packages/pro/table/src/composables/useResizable.ts new file mode 100644 index 000000000..745f7a6d2 --- /dev/null +++ b/packages/pro/table/src/composables/useResizable.ts @@ -0,0 +1,30 @@ +/** + * @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 ComputedRef, computed } from 'vue' + +import { type VKey } from '@idux/cdk/utils' + +import { type ColumnsContext } from './useColumns' + +export interface ResizableContext { + hasResizable: ComputedRef + onResizeEnd: (key: VKey, width: number) => void +} + +export function useResizable({ mergedColumns, mergedColumnMap }: ColumnsContext): ResizableContext { + const hasResizable = computed(() => mergedColumns.value.some(column => column.resizable)) + + const onResizeEnd = (key: VKey, width: number) => { + const targetColumn = mergedColumnMap.value.get(key) + if (targetColumn) { + targetColumn.width = width + } + } + + return { hasResizable, onResizeEnd } +} diff --git a/packages/pro/table/src/contents/LayoutToolTree.tsx b/packages/pro/table/src/contents/LayoutToolTree.tsx index 25a11b146..0c5639505 100644 --- a/packages/pro/table/src/contents/LayoutToolTree.tsx +++ b/packages/pro/table/src/contents/LayoutToolTree.tsx @@ -148,8 +148,8 @@ export default defineComponent({ return (
- {title && {title}} - + {title && {title}} +
) } diff --git a/packages/pro/table/src/contents/ResizableHeadCell.tsx b/packages/pro/table/src/contents/ResizableHeadCell.tsx new file mode 100644 index 000000000..f8e611a68 --- /dev/null +++ b/packages/pro/table/src/contents/ResizableHeadCell.tsx @@ -0,0 +1,46 @@ +/** + * @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 { defineComponent, h, reactive, ref } from 'vue' + +import { CdkResizableHandler, type ResizableEvent, type ResizableOptions, useResizable } from '@idux/cdk/resize' +import { callEmit } from '@idux/cdk/utils' + +export default defineComponent({ + // eslint-disable-next-line vue/require-prop-types + props: ['column', 'onResizeEnd'], + setup(props, { slots }) { + const elementRef = ref() + const onEnd: ResizableEvent = position => { + callEmit(props.onResizeEnd, props.column.key, position.width) + } + + const resizableOptions = reactive({ + maxWidth: props.column.maxWidth, + minWidth: props.column.minWidth, + onEnd, + }) + + const { resizing, position } = useResizable(elementRef, resizableOptions) + + return () => { + const children = slots.default ? slots.default() : [] + if (!props.column.resizable) { + return h('th', null, children) + } + + children.push() + + if (resizing.value) { + const { width, height } = position.value + children.push(
) + } + + return h('th', { ref: elementRef }, children) + } + }, +}) diff --git a/packages/pro/table/src/types.ts b/packages/pro/table/src/types.ts index f74e4b47b..26b68f763 100644 --- a/packages/pro/table/src/types.ts +++ b/packages/pro/table/src/types.ts @@ -27,6 +27,7 @@ import type { TableColumnExpandable, TableColumnSelectable, TableCustomAdditional, + TableCustomTag, TablePagination, TableScroll, TableSize, @@ -47,6 +48,7 @@ export const proTableProps = { childrenKey: { type: String, default: undefined }, columns: { type: Array as PropType, default: () => [] }, customAdditional: { type: Object as PropType, default: undefined }, + customTag: { type: Object as PropType, default: undefined }, dataSource: { type: Array as PropType, default: () => [] }, editable: { type: Boolean, default: false }, ellipsis: { type: Boolean, default: false }, diff --git a/packages/pro/table/style/index.less b/packages/pro/table/style/index.less index 76570704f..a130768b6 100644 --- a/packages/pro/table/style/index.less +++ b/packages/pro/table/style/index.less @@ -4,7 +4,6 @@ .reset-component(); &-layout-tool { - .@{tree-prefix} { &-node { &-content-suffix { @@ -19,3 +18,32 @@ } } } + +.cdk-resizable { + &-handler { + position: absolute; + user-select: none; + z-index: 9; + + &-end { + width: 10px; + height: 100%; + top: 0; + right: -5px; + } + } + + &-preview { + position: absolute; + top: 0; + left: 0; + z-index: 8; + border-right: 1px dashed #d1d1d1; + } + + &-handler { + &-end { + cursor: ew-resize; + } + } +}