From cf5847263f5e8a8d391d7868bc278e0968641b73 Mon Sep 17 00:00:00 2001 From: Zhongxiang Wang Date: Thu, 22 Dec 2022 12:20:07 +0800 Subject: [PATCH] feat(components): [table] overflow tooltip supports `enterable` and customization (#10984) * feat(components): [table] overflow tooltip supports `enterable` * fix(components): [table] fix modifiers merge * fix(components): [table] refine logic & fix popperClass isn't respected * docs(components): [table] add docs for `tooltip-options` & `show-overflow-tooltip` * docs(components): [table] * docs(components): [table] update version tag Co-authored-by: Xc <124118265@qq.com> --- docs/en-US/component/table.md | 135 +++++++++--------- .../table/__tests__/table-column.test.ts | 8 -- .../table/src/table-body/defaults.ts | 7 +- .../table/src/table-body/events-helper.ts | 13 +- .../table/src/table-body/render-helper.ts | 14 +- .../table/src/table-column/defaults.ts | 9 +- .../table/src/table-column/index.ts | 3 +- packages/components/table/src/table.vue | 1 + .../components/table/src/table/defaults.ts | 3 + packages/components/table/src/util.ts | 120 +++++++++++----- 10 files changed, 186 insertions(+), 127 deletions(-) diff --git a/docs/en-US/component/table.md b/docs/en-US/component/table.md index 6d0234958f..d2ac2ed8c4 100644 --- a/docs/en-US/component/table.md +++ b/docs/en-US/component/table.md @@ -231,44 +231,45 @@ table/table-layout ## Table Attributes -| Name | Description | Type | Accepted Values | Default | -| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------ | -| data | Table data | array | — | — | -| height | Table's height. By default it has an `auto` height. If its value is a number, the height is measured in pixels; if its value is a string, the value will be assigned to element's style.height, the height is affected by external styles | string / number | — | — | -| max-height | Table's max-height. The legal value is a number or the height in px. | string / number | — | — | -| stripe | whether Table is striped | boolean | — | false | -| border | whether Table has vertical border | boolean | — | false | -| size | size of Table | string | large / default /small | — | -| fit | whether width of column automatically fits its container | boolean | — | true | -| show-header | whether Table header is visible | boolean | — | true | -| highlight-current-row | whether current row is highlighted | boolean | — | false | -| current-row-key | key of current row, a set only prop | string / number | — | — | -| row-class-name | function that returns custom class names for a row, or a string assigning class names for every row | function(\{ row, rowIndex \}) / string | — | — | -| row-style | function that returns custom style for a row, or an object assigning custom style for every row | function(\{ row, rowIndex \}) / object | — | — | -| cell-class-name | function that returns custom class names for a cell, or a string assigning class names for every cell | function(\{ row, column, rowIndex, columnIndex \}) / string | — | — | -| cell-style | function that returns custom style for a cell, or an object assigning custom style for every cell | function(\{ row, column, rowIndex, columnIndex \}) / object | — | — | -| header-row-class-name | function that returns custom class names for a row in table header, or a string assigning class names for every row in table header | function(\{ row, rowIndex }\) / string | — | — | -| header-row-style | function that returns custom style for a row in table header, or an object assigning custom style for every row in table header | function(\{ row, rowIndex \}) / object | — | — | -| header-cell-class-name | function that returns custom class names for a cell in table header, or a string assigning class names for every cell in table header | function(\{ row, column, rowIndex, columnIndex \}) / string | — | — | -| header-cell-style | function that returns custom style for a cell in table header, or an object assigning custom style for every cell in table header | function(\{ row, column, rowIndex, columnIndex \}) / object | — | — | -| row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on or display tree data. When its type is String, multi-level access is supported, e.g. `user.info.id`, but `user.info[0].id` is not supported, in which case `Function` should be used. | function(row) / string | — | — | -| empty-text | Displayed text when data is empty. You can customize this area with `#empty` | string | — | No Data | -| default-expand-all | whether expand all rows by default, works when the table has a column type="expand" or contains tree structure data | boolean | — | false | -| expand-row-keys | set expanded rows by this prop, prop's value is the keys of expand rows, you should set row-key before using this prop | array | — | — | -| default-sort | set the default sort column and order. property `prop` is used to set default sort column, property `order` is used to set default sort order | object | (order: 'ascending' \| 'descending') | if `prop` is set, and `order` is not set, then `order` is default to ascending | -| tooltip-effect | tooltip `effect` property | string | dark / light | dark | -| show-summary | whether to display a summary row | boolean | — | false | -| sum-text | displayed text for the first column of summary row | string | — | Sum | -| summary-method | custom summary method | function(\{ columns, data \}) | — | — | -| span-method | method that returns rowspan and colspan | function(\{ row, column, rowIndex, columnIndex \}) | — | — | -| select-on-indeterminate | controls the behavior of master checkbox in multi-select tables when only some rows are selected (but not all). If true, all rows will be selected, else deselected. | boolean | — | true | -| indent | horizontal indentation of tree data | number | — | 16 | -| lazy | whether to lazy loading data | boolean | — | — | -| load | method for loading child row data, only works when `lazy` is true | function(row, treeNode, resolve) | — | — | -| tree-props | configuration for rendering nested data | object | — | `{ hasChildren: 'hasChildren', children: 'children' }` | -| table-layout | Sets the algorithm used to lay out table cells, rows, and columns | string | fixed / auto | fixed | -| scrollbar-always-on | always show scrollbar | boolean | — | false | -| flexible | ensure main axis minimum-size doesn't follow the content | boolean | — | false | +| Name | Description | Type | Accepted Values | Default | +| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| data | Table data | array | — | — | +| height | Table's height. By default it has an `auto` height. If its value is a number, the height is measured in pixels; if its value is a string, the value will be assigned to element's style.height, the height is affected by external styles | string / number | — | — | +| max-height | Table's max-height. The legal value is a number or the height in px. | string / number | — | — | +| stripe | whether Table is striped | boolean | — | false | +| border | whether Table has vertical border | boolean | — | false | +| size | size of Table | string | large / default /small | — | +| fit | whether width of column automatically fits its container | boolean | — | true | +| show-header | whether Table header is visible | boolean | — | true | +| highlight-current-row | whether current row is highlighted | boolean | — | false | +| current-row-key | key of current row, a set only prop | string / number | — | — | +| row-class-name | function that returns custom class names for a row, or a string assigning class names for every row | function(\{ row, rowIndex \}) / string | — | — | +| row-style | function that returns custom style for a row, or an object assigning custom style for every row | function(\{ row, rowIndex \}) / object | — | — | +| cell-class-name | function that returns custom class names for a cell, or a string assigning class names for every cell | function(\{ row, column, rowIndex, columnIndex \}) / string | — | — | +| cell-style | function that returns custom style for a cell, or an object assigning custom style for every cell | function(\{ row, column, rowIndex, columnIndex \}) / object | — | — | +| header-row-class-name | function that returns custom class names for a row in table header, or a string assigning class names for every row in table header | function(\{ row, rowIndex }\) / string | — | — | +| header-row-style | function that returns custom style for a row in table header, or an object assigning custom style for every row in table header | function(\{ row, rowIndex \}) / object | — | — | +| header-cell-class-name | function that returns custom class names for a cell in table header, or a string assigning class names for every cell in table header | function(\{ row, column, rowIndex, columnIndex \}) / string | — | — | +| header-cell-style | function that returns custom style for a cell in table header, or an object assigning custom style for every cell in table header | function(\{ row, column, rowIndex, columnIndex \}) / object | — | — | +| row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on or display tree data. When its type is String, multi-level access is supported, e.g. `user.info.id`, but `user.info[0].id` is not supported, in which case `Function` should be used. | function(row) / string | — | — | +| empty-text | Displayed text when data is empty. You can customize this area with `#empty` | string | — | No Data | +| default-expand-all | whether expand all rows by default, works when the table has a column type="expand" or contains tree structure data | boolean | — | false | +| expand-row-keys | set expanded rows by this prop, prop's value is the keys of expand rows, you should set row-key before using this prop | array | — | — | +| default-sort | set the default sort column and order. property `prop` is used to set default sort column, property `order` is used to set default sort order | object | (order: 'ascending' \| 'descending') | if `prop` is set, and `order` is not set, then `order` is default to ascending | +| tooltip-effect | the `effect` of the overflow tooltip | string | dark / light | dark | +| tooltip-options | the options for the overflow tooltip, [see the following tooltip component](tooltip.html#attributes) | ^[object]`Pick` | [refer to tooltip](tooltip.html#attributes) | ^[object]`{ enterable: true, placement: 'top', showArrow: true, hideAfter: 200, popperOptions: { strategy: 'fixed' } }` | +| show-summary | whether to display a summary row | boolean | — | false | +| sum-text | displayed text for the first column of summary row | string | — | Sum | +| summary-method | custom summary method | function(\{ columns, data \}) | — | — | +| span-method | method that returns rowspan and colspan | function(\{ row, column, rowIndex, columnIndex \}) | — | — | +| select-on-indeterminate | controls the behavior of master checkbox in multi-select tables when only some rows are selected (but not all). If true, all rows will be selected, else deselected. | boolean | — | true | +| indent | horizontal indentation of tree data | number | — | 16 | +| lazy | whether to lazy loading data | boolean | — | — | +| load | method for loading child row data, only works when `lazy` is true | function(row, treeNode, resolve) | — | — | +| tree-props | configuration for rendering nested data | object | — | `{ hasChildren: 'hasChildren', children: 'children' }` | +| table-layout | Sets the algorithm used to lay out table cells, rows, and columns | string | fixed / auto | fixed | +| scrollbar-always-on | always show scrollbar | boolean | — | false | +| flexible | ensure main axis minimum-size doesn't follow the content | boolean | — | false | ## Table Events @@ -321,35 +322,35 @@ table/table-layout ## Table-column Attributes -| Name | Description | Type | Accepted Values | Default | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| type | type of the column. If set to `selection`, the column will display checkbox. If set to `index`, the column will display index of the row (staring from 1). If set to `expand`, the column will display expand icon. | string | selection / index / expand | — | -| index | customize indices for each row, works on columns with `type=index` | number / function(index) | — | — | -| label | column label | string | — | — | -| column-key | column's key. If you need to use the filter-change event, you need this attribute to identify which column is being filtered | string | — | — | -| prop | field name. You can also use its alias: `property` | string | — | — | -| width | column width | string / number | — | — | -| min-width | column minimum width. Columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — | -| fixed | whether column is fixed at left / right. Will be fixed at left if `true` | string / boolean | true / 'left' / 'right' | — | -| render-header | render function for table header of this column | function(\{ column, $index \}) | — | — | -| sortable | whether column can be sorted. Remote sorting can be done by setting this attribute to 'custom' and listening to the `sort-change` event of Table | boolean / string | custom | false | -| sort-method | sorting method, works when `sortable` is `true`. Should return a number, just like Array.sort | function(a, b) | — | — | -| sort-by | specify which property to sort by, works when `sortable` is `true` and `sort-method` is `undefined`. If set to an Array, the column will sequentially sort by the next property if the previous one is equal | function(row, index) / string / array | — | — | -| sort-orders | the order of the sorting strategies used when sorting the data, works when `sortable` is `true`. Accepts an array, as the user clicks on the header, the column is sorted in order of the elements in the array | array | (the elements in the array need to be one of the following: `ascending`, `descending` and `null` restores to the original order) | ['ascending', 'descending', null] | -| resizable | whether column width can be resized, works when `border` of `el-table` is `true` | boolean | — | true | -| formatter | function that formats cell content | function(row, column, cellValue, index) | — | — | -| show-overflow-tooltip | whether to hide extra content and show them in a tooltip when hovering on the cell | boolean | — | false | -| align | alignment | string | left / center / right | left | -| header-align | alignment of the table header. If omitted, the value of the above `align` attribute will be applied | string | left / center / right | — | -| class-name | class name of cells in the column | string | — | — | -| label-class-name | class name of the label of this column | string | — | — | -| selectable | function that determines if a certain row can be selected, works when `type` is 'selection' | function(row, index) | — | — | -| reserve-selection | whether to reserve selection after data refreshing, works when `type` is 'selection'. Note that `row-key` is required for this to work | boolean | — | false | -| filters | an array of data filtering options. For each element in this array, `text` and `value` are required | `Array<{text: string, value: string}>` | — | — | -| filter-placement | placement for the filter dropdown | string | top / top-start / top-end / bottom / bottom-start / bottom-end / left / left-start / left-end / right / right-start / right-end | — | -| filter-multiple | whether data filtering supports multiple options | boolean | — | true | -| filter-method | data filtering method. If `filter-multiple` is on, this method will be called multiple times for each row, and a row will display if one of the calls returns `true` | function(value, row, column) | — | — | -| filtered-value | filter value for selected data, might be useful when table header is rendered with `render-header` | array | — | — | +| Name | Description | Type | Accepted Values | Default | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| type | type of the column. If set to `selection`, the column will display checkbox. If set to `index`, the column will display index of the row (staring from 1). If set to `expand`, the column will display expand icon. | string | selection / index / expand | — | +| index | customize indices for each row, works on columns with `type=index` | number / function(index) | — | — | +| label | column label | string | — | — | +| column-key | column's key. If you need to use the filter-change event, you need this attribute to identify which column is being filtered | string | — | — | +| prop | field name. You can also use its alias: `property` | string | — | — | +| width | column width | string / number | — | — | +| min-width | column minimum width. Columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — | +| fixed | whether column is fixed at left / right. Will be fixed at left if `true` | string / boolean | true / 'left' / 'right' | — | +| render-header | render function for table header of this column | function(\{ column, $index \}) | — | — | +| sortable | whether column can be sorted. Remote sorting can be done by setting this attribute to 'custom' and listening to the `sort-change` event of Table | boolean / string | custom | false | +| sort-method | sorting method, works when `sortable` is `true`. Should return a number, just like Array.sort | function(a, b) | — | — | +| sort-by | specify which property to sort by, works when `sortable` is `true` and `sort-method` is `undefined`. If set to an Array, the column will sequentially sort by the next property if the previous one is equal | function(row, index) / string / array | — | — | +| sort-orders | the order of the sorting strategies used when sorting the data, works when `sortable` is `true`. Accepts an array, as the user clicks on the header, the column is sorted in order of the elements in the array | array | (the elements in the array need to be one of the following: `ascending`, `descending` and `null` restores to the original order) | ['ascending', 'descending', null] | +| resizable | whether column width can be resized, works when `border` of `el-table` is `true` | boolean | — | true | +| formatter | function that formats cell content | function(row, column, cellValue, index) | — | — | +| show-overflow-tooltip | whether to hide extra content and show them in a tooltip when hovering on the cell | boolean \| [`object`](#table-attributes) | Refer to table [tooltip-options](#table-attributes) | — | +| align | alignment | string | left / center / right | left | +| header-align | alignment of the table header. If omitted, the value of the above `align` attribute will be applied | string | left / center / right | — | +| class-name | class name of cells in the column | string | — | — | +| label-class-name | class name of the label of this column | string | — | — | +| selectable | function that determines if a certain row can be selected, works when `type` is 'selection' | function(row, index) | — | — | +| reserve-selection | whether to reserve selection after data refreshing, works when `type` is 'selection'. Note that `row-key` is required for this to work | boolean | — | false | +| filters | an array of data filtering options. For each element in this array, `text` and `value` are required | `Array<{text: string, value: string}>` | — | — | +| filter-placement | placement for the filter dropdown | string | top / top-start / top-end / bottom / bottom-start / bottom-end / left / left-start / left-end / right / right-start / right-end | — | +| filter-multiple | whether data filtering supports multiple options | boolean | — | true | +| filter-method | data filtering method. If `filter-multiple` is on, this method will be called multiple times for each row, and a row will display if one of the calls returns `true` | function(value, row, column) | — | — | +| filtered-value | filter value for selected data, might be useful when table header is rendered with `render-header` | array | — | — | ## Table-column Slots diff --git a/packages/components/table/__tests__/table-column.test.ts b/packages/components/table/__tests__/table-column.test.ts index 69354fd513..1383a2c65d 100644 --- a/packages/components/table/__tests__/table-column.test.ts +++ b/packages/components/table/__tests__/table-column.test.ts @@ -160,14 +160,6 @@ describe('table column', () => { wrapper.unmount() }) - it('show-tooltip-when-overflow', async () => { - // old version prop name - const wrapper = createTable('show-tooltip-when-overflow') - await doubleWait() - expect(wrapper.findAll('.el-tooltip').length).toEqual(5) - wrapper.unmount() - }) - it('align', async () => { const wrapper = createTable( 'align="left"', diff --git a/packages/components/table/src/table-body/defaults.ts b/packages/components/table/src/table-body/defaults.ts index 167143d787..e343d97c60 100644 --- a/packages/components/table/src/table-body/defaults.ts +++ b/packages/components/table/src/table-body/defaults.ts @@ -7,6 +7,7 @@ import type { DefaultRow, Table, } from '../table/defaults' +import type { TableOverflowTooltipOptions } from '../util' interface TableBodyProps { store: Store @@ -16,7 +17,8 @@ interface TableBodyProps { rowStyle: ColumnStyle fixed: string highlight: boolean - tooltipEffect: string + tooltipEffect?: string + tooltipOptions?: TableOverflowTooltipOptions } const defaultProps = { @@ -26,6 +28,9 @@ const defaultProps = { }, stripe: Boolean, tooltipEffect: String, + tooltipOptions: { + type: Object as PropType['tooltipOptions']>, + }, context: { default: () => ({}), type: Object as PropType['context']>, diff --git a/packages/components/table/src/table-body/events-helper.ts b/packages/components/table/src/table-body/events-helper.ts index 0fe97c2d81..759a90c839 100644 --- a/packages/components/table/src/table-body/events-helper.ts +++ b/packages/components/table/src/table-body/events-helper.ts @@ -6,6 +6,7 @@ import { createTablePopper, getCell, getColumnByCell } from '../util' import { TABLE_INJECTION_KEY } from '../tokens' import type { TableColumnCtx } from '../table-column/defaults' import type { TableBodyProps } from './defaults' +import type { TableOverflowTooltipOptions } from '../util' function useEvents(props: Partial>) { const parent = inject(TABLE_INJECTION_KEY) @@ -49,7 +50,7 @@ function useEvents(props: Partial>) { const handleCellMouseEnter = ( event: MouseEvent, row: T, - tooltipEffect: string + tooltipOptions: TableOverflowTooltipOptions ) => { const table = parent const cell = getCell(event) @@ -72,6 +73,10 @@ function useEvents(props: Partial>) { ) } + if (!tooltipOptions) { + return + } + // 判断是否text-overflow, 如果是就显示tooltip const cellChild = (event.target as HTMLElement).querySelector( '.cell' @@ -107,11 +112,7 @@ function useEvents(props: Partial>) { parent?.refs.tableWrapper, cell, cell.innerText || cell.textContent, - { - placement: 'top', - strategy: 'fixed', - }, - tooltipEffect + tooltipOptions ) } } diff --git a/packages/components/table/src/table-body/render-helper.ts b/packages/components/table/src/table-body/render-helper.ts index 9531a7c774..dc388632de 100644 --- a/packages/components/table/src/table-body/render-helper.ts +++ b/packages/components/table/src/table-body/render-helper.ts @@ -1,5 +1,6 @@ // @ts-nocheck import { computed, h, inject } from 'vue' +import { merge } from 'lodash-unified' import { useNamespace } from '@element-plus/hooks' import { getRowIdentity } from '../util' import { TABLE_INJECTION_KEY } from '../tokens' @@ -48,7 +49,7 @@ function useRender(props: Partial>) { treeRowData?: TreeNode, expanded = false ) => { - const { tooltipEffect, store } = props + const { tooltipEffect, tooltipOptions, store } = props const { indent, columns } = store.states const rowClasses = getRowClass(row, $index) let display = true @@ -112,6 +113,15 @@ function useRender(props: Partial>) { const baseKey = `${$index},${cellIndex}` const patchKey = columnData.columnKey || columnData.rawColumnKey || '' const tdChildren = cellChildren(cellIndex, column, data) + const mergedTooltipOptions = + column.showOverflowTooltip && + merge( + { + effect: tooltipEffect, + }, + tooltipOptions, + column.showOverflowTooltip + ) return h( 'td', { @@ -121,7 +131,7 @@ function useRender(props: Partial>) { rowspan, colspan, onMouseenter: ($event) => - handleCellMouseEnter($event, row, tooltipEffect), + handleCellMouseEnter($event, row, mergedTooltipOptions), onMouseleave: handleCellMouseLeave, }, [tdChildren] diff --git a/packages/components/table/src/table-column/defaults.ts b/packages/components/table/src/table-column/defaults.ts index bcc3b5ebaf..0a8e2c4af2 100644 --- a/packages/components/table/src/table-column/defaults.ts +++ b/packages/components/table/src/table-column/defaults.ts @@ -1,6 +1,7 @@ // @ts-nocheck import type { ComponentInternalInstance, PropType, Ref, VNode } from 'vue' import type { DefaultRow, Table } from '../table/defaults' +import type { TableOverflowTooltipOptions } from '../util' type CI = { column: TableColumnCtx; $index: number } @@ -33,8 +34,7 @@ interface TableColumnCtx { rawColumnKey: string align: string headerAlign: string - showTooltipWhenOverflow: boolean - showOverflowTooltip: boolean + showOverflowTooltip?: boolean | TableOverflowTooltipOptions fixed: boolean | string formatter: ( row: T, @@ -113,8 +113,9 @@ export default { columnKey: String, align: String, headerAlign: String, - showTooltipWhenOverflow: Boolean, - showOverflowTooltip: Boolean, + showOverflowTooltip: [Boolean, Object] as PropType< + TableColumnCtx['showOverflowTooltip'] + >, fixed: [Boolean, String], formatter: Function as PropType['formatter']>, selectable: Function as PropType['selectable']>, diff --git a/packages/components/table/src/table-column/index.ts b/packages/components/table/src/table-column/index.ts index fb161a23c4..e4a5918fe5 100644 --- a/packages/components/table/src/table-column/index.ts +++ b/packages/components/table/src/table-column/index.ts @@ -73,8 +73,7 @@ export default defineComponent({ property: props.prop || props.property, align: realAlign, headerAlign: realHeaderAlign, - showOverflowTooltip: - props.showOverflowTooltip || props.showTooltipWhenOverflow, + showOverflowTooltip: props.showOverflowTooltip, // filter 相关属性 filterable: props.filters || props.filterMethod, filteredValue: [], diff --git a/packages/components/table/src/table.vue b/packages/components/table/src/table.vue index e335067915..08f602d94a 100644 --- a/packages/components/table/src/table.vue +++ b/packages/components/table/src/table.vue @@ -92,6 +92,7 @@ :highlight="highlightCurrentRow" :row-class-name="rowClassName" :tooltip-effect="tooltipEffect" + :tooltip-options="tooltipOptions" :row-style="rowStyle" :store="store" :stripe="stripe" diff --git a/packages/components/table/src/table/defaults.ts b/packages/components/table/src/table/defaults.ts index b16b3a9bd5..642460ff05 100644 --- a/packages/components/table/src/table/defaults.ts +++ b/packages/components/table/src/table/defaults.ts @@ -10,6 +10,7 @@ import type { Nullable } from '@element-plus/utils' import type { Store } from '../store' import type { TableColumnCtx } from '../table-column/defaults' import type TableLayout from '../table-layout' +import type { TableOverflowTooltipOptions } from '../util' export type DefaultRow = any @@ -115,6 +116,7 @@ interface TableProps { defaultExpandAll?: boolean defaultSort?: Sort tooltipEffect?: string + tooltipOptions?: TableOverflowTooltipOptions spanMethod?: (data: { row: T rowIndex: number @@ -226,6 +228,7 @@ export default { defaultExpandAll: Boolean, defaultSort: Object as PropType['defaultSort']>, tooltipEffect: String, + tooltipOptions: Object as PropType['tooltipOptions']>, spanMethod: Function as PropType['spanMethod']>, selectOnIndeterminate: { type: Boolean, diff --git a/packages/components/table/src/util.ts b/packages/components/table/src/util.ts index 2ac80c578e..041618f8f5 100644 --- a/packages/components/table/src/util.ts +++ b/packages/components/table/src/util.ts @@ -1,24 +1,40 @@ // @ts-nocheck import { createPopper } from '@popperjs/core' -import { flatMap, get } from 'lodash-unified' +import { flatMap, get, merge } from 'lodash-unified' import escapeHtml from 'escape-html' -import { hasOwn, isArray, isBoolean, throwError } from '@element-plus/utils' -import { useZIndex } from '@element-plus/hooks' -import type { - IPopperOptions, - PopperInstance, -} from '@element-plus/components/popper' +import { + hasOwn, + isArray, + isBoolean, + isObject, + throwError, +} from '@element-plus/utils' +import { useDelayedToggle, useZIndex } from '@element-plus/hooks' +import type { PopperInstance } from '@element-plus/components/popper' import type { Nullable } from '@element-plus/utils' import type { TableColumnCtx } from './table-column/defaults' +import type { ElTooltipProps } from '@element-plus/components/tooltip' + +export type TableOverflowTooltipOptions = Partial< + Pick< + ElTooltipProps, + | 'effect' + | 'enterable' + | 'hideAfter' + | 'offset' + | 'placement' + | 'popperClass' + | 'popperOptions' + | 'showAfter' + | 'showArrow' + // | 'transition' + > +> export const getCell = function (event: Event) { return (event.target as HTMLElement)?.closest('td') } -const isObject = function (obj: unknown): boolean { - return obj !== null && typeof obj === 'object' -} - export const orderBy = function ( array: T[], sortKey: string, @@ -315,16 +331,27 @@ export function createTablePopper( parentNode: HTMLElement | undefined, trigger: HTMLElement, popperContent: string, - popperOptions: Partial, - tooltipEffect: string + tooltipOptions?: TableOverflowTooltipOptions ) { + // TODO transition + tooltipOptions = merge( + { + enterable: true, + showArrow: true, + } as TableOverflowTooltipOptions, + tooltipOptions + ) const { nextZIndex } = useZIndex() const ns = parentNode?.dataset.prefix const scrollContainer = parentNode?.querySelector(`.${ns}-scrollbar__wrap`) function renderContent(): HTMLDivElement { - const isLight = tooltipEffect === 'light' + const isLight = tooltipOptions.effect === 'light' const content = document.createElement('div') - content.className = `${ns}-popper ${isLight ? 'is-light' : 'is-dark'}` + content.className = [ + `${ns}-popper`, + isLight ? 'is-light' : 'is-dark', + tooltipOptions.popperClass || '', + ].join(' ') popperContent = escapeHtml(popperContent) content.innerHTML = popperContent content.style.zIndex = String(nextZIndex()) @@ -345,37 +372,56 @@ export function createTablePopper( try { popperInstance && popperInstance.destroy() content && parentNode?.removeChild(content) - trigger.removeEventListener('mouseenter', showPopper) - trigger.removeEventListener('mouseleave', removePopper) + trigger.removeEventListener('mouseenter', onOpen) + trigger.removeEventListener('mouseleave', onClose) scrollContainer?.removeEventListener('scroll', removePopper) removePopper = undefined } catch {} } let popperInstance: Nullable = null + let onOpen = showPopper + let onClose = removePopper + if (tooltipOptions.enterable) { + ;({ onOpen, onClose } = useDelayedToggle({ + showAfter: tooltipOptions.showAfter, + hideAfter: tooltipOptions.hideAfter, + open: showPopper, + close: removePopper, + })) + } const content = renderContent() - const arrow = renderArrow() - content.appendChild(arrow) + content.onmouseenter = onOpen + content.onmouseleave = onClose + const modifiers = [] + if (tooltipOptions.offset) { + modifiers.push({ + name: 'offset', + options: { + offset: [0, tooltipOptions.offset], + }, + }) + } + if (tooltipOptions.showArrow) { + const arrow = content.appendChild(renderArrow()) + modifiers.push({ + name: 'arrow', + options: { + element: arrow, + padding: 10, + }, + }) + } + const popperOptions = tooltipOptions.popperOptions || {} popperInstance = createPopper(trigger, content, { - strategy: 'absolute', - modifiers: [ - { - name: 'offset', - options: { - offset: [0, 8], - }, - }, - { - name: 'arrow', - options: { - element: arrow, - padding: 10, - }, - }, - ], + placement: tooltipOptions.placement || 'top', + strategy: 'fixed', ...popperOptions, + modifiers: popperOptions.modifiers + ? modifiers.concat(popperOptions.modifiers) + : modifiers, }) - trigger.addEventListener('mouseenter', showPopper) - trigger.addEventListener('mouseleave', removePopper) + trigger.addEventListener('mouseenter', onOpen) + trigger.addEventListener('mouseleave', onClose) scrollContainer?.addEventListener('scroll', removePopper) return popperInstance }