feat(data-table): selection.options

This commit is contained in:
07akioni 2021-04-20 22:37:06 +08:00
parent 8d8b4c764f
commit 3581c3570f
10 changed files with 190 additions and 35 deletions

View File

@ -19,9 +19,12 @@ Rows can be selectable by making first column's type as `selection`.
``` ```
```js ```js
import { DataTableCheckOption } from 'naive-ui'
const columns = [ const columns = [
{ {
type: 'selection', type: 'selection',
options: [DataTableCheckOption.CHECK_ALL, DataTableCheckOption.UNCHECK_ALL],
disabled (row, index) { disabled (row, index) {
return row.name === 'Edward King 3' return row.name === 'Edward King 3'
} }

View File

@ -16,9 +16,12 @@
``` ```
```js ```js
import { DataTableCheckOption } from 'naive-ui'
const columns = [ const columns = [
{ {
type: 'selection', type: 'selection',
options: [DataTableCheckOption.CHECK_ALL, DataTableCheckOption.UNCHECK_ALL],
disabled (row, index) { disabled (row, index) {
return row.name === 'Edward King 3' return row.name === 'Edward King 3'
} }

View File

@ -1,4 +1,5 @@
export { default as NDataTable } from './src/DataTable' export { default as NDataTable } from './src/DataTable'
export { DataTableCheckOption } from './src/TableParts/CheckMenu'
export type { DataTableProps } from './src/DataTable' export type { DataTableProps } from './src/DataTable'
export type { export type {
RenderFilter as DataTableRenderFilter, RenderFilter as DataTableRenderFilter,
@ -6,7 +7,7 @@ export type {
ColumnKey as DataTableColumnKey, ColumnKey as DataTableColumnKey,
TableColumn as DataTableColumn, TableColumn as DataTableColumn,
TableBaseColumn as DataTableBaseColumn, TableBaseColumn as DataTableBaseColumn,
TableSelectionColumn as DataTableSelectionColumn, TableSelectionColumn as DataTableCheckOptionColumn,
TableExpandColumn as DataTableExpandColumn, TableExpandColumn as DataTableExpandColumn,
DataTableInst DataTableInst
} from './src/interface' } from './src/interface'

View File

@ -225,6 +225,7 @@ export default defineComponent({
treeMate: treeMateRef, treeMate: treeMateRef,
mergedCurrentPage: mergedCurrentPageRef, mergedCurrentPage: mergedCurrentPageRef,
paginatedData: paginatedDataRef, paginatedData: paginatedDataRef,
selectionColumn: selectionColumnRef,
hoverKey, hoverKey,
currentPage, currentPage,
mergedPagination, mergedPagination,
@ -247,8 +248,9 @@ export default defineComponent({
allRowsChecked, allRowsChecked,
mergedCheckedRowKeys mergedCheckedRowKeys
} = useCheck(props, { } = useCheck(props, {
paginatedDataRef, selectionColumnRef,
treeMateRef treeMateRef,
paginatedDataRef
}) })
const { const {
mergedExpandedRowKeys, mergedExpandedRowKeys,
@ -299,6 +301,10 @@ export default defineComponent({
mergedExpandedRowKeys, mergedExpandedRowKeys,
locale, locale,
rowKey: toRef(props, 'rowKey'), rowKey: toRef(props, 'rowKey'),
checkOptions: computed(() => {
const { value: selectionColumn } = selectionColumnRef
return selectionColumn?.options
}),
filterMenuCssVars: computed(() => { filterMenuCssVars: computed(() => {
const { const {
self: { actionDividerColor, actionPadding, actionButtonMargin } self: { actionDividerColor, actionPadding, actionButtonMargin }

View File

@ -0,0 +1,99 @@
import { h, defineComponent, inject, computed } from 'vue'
import { NDropdown } from '../../../dropdown'
import { NBaseIcon } from '../../../_internal'
import { ChevronDownIcon } from '../../../_internal/icons'
import { dataTableInjectionKey } from '../interface'
export enum DataTableCheckOption {
CHECK_ALL,
UNCHECK_ALL
}
function createSelectHandler (
options:
| Array<
| DataTableCheckOption
| { label: string, key: string | number, onSelect: () => void }
>
| undefined,
doCheckAll: (checkWholeTable?: boolean) => void,
doUncheckAll: (checkWholeTable?: boolean) => void
): (key: string | number) => void {
if (!options) return () => {}
return (key: string | number) => {
for (const option of options) {
switch (key) {
case DataTableCheckOption.CHECK_ALL:
doCheckAll(true)
return
case DataTableCheckOption.UNCHECK_ALL:
doUncheckAll(true)
return
default:
if (typeof option === 'object' && option.key === key) {
option.onSelect()
return
}
}
}
}
}
function createDropdownOptions (
options:
| Array<
| DataTableCheckOption
| { label: string, key: string | number, onSelect: () => void }
>
| undefined
): Array<{ label: string, key: string | number }> {
if (!options) return []
return options.map((option) => {
switch (option) {
case DataTableCheckOption.CHECK_ALL:
return {
label: '选择表格全部数据',
key: DataTableCheckOption.CHECK_ALL
}
case DataTableCheckOption.UNCHECK_ALL:
return {
label: '选择表格全部数据取消',
key: DataTableCheckOption.UNCHECK_ALL
}
default:
return option
}
})
}
export default defineComponent({
name: 'DataTableCheckMenu',
setup () {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const NDataTable = inject(dataTableInjectionKey)!
const { doCheckAll, doUncheckAll } = NDataTable
return {
handleSelect: createSelectHandler(
NDataTable.checkOptions,
doCheckAll,
doUncheckAll
),
options: computed(() => createDropdownOptions(NDataTable.checkOptions))
}
},
render () {
return (
<NDropdown options={this.options} onSelect={this.handleSelect}>
{{
default: () => (
<NBaseIcon clsPrefix="n" class="n-data-table-check-extra">
{{
default: () => <ChevronDownIcon />
}}
</NBaseIcon>
)
}}
</NDropdown>
)
}
})

View File

@ -1,4 +1,4 @@
import { h, defineComponent, inject, PropType, VNodeChild } from 'vue' import { h, defineComponent, inject, PropType, VNodeChild, Fragment } from 'vue'
import { happensIn, pxfy } from 'seemly' import { happensIn, pxfy } from 'seemly'
import { formatLength } from '../../../_utils' import { formatLength } from '../../../_utils'
import { NCheckbox } from '../../../checkbox' import { NCheckbox } from '../../../checkbox'
@ -13,11 +13,11 @@ import {
} from '../utils' } from '../utils'
import { import {
TableExpandColumn, TableExpandColumn,
TableSelectionColumn,
TableColumnGroup, TableColumnGroup,
TableBaseColumn, TableBaseColumn,
dataTableInjectionKey dataTableInjectionKey
} from '../interface' } from '../interface'
import CheckMenu from './CheckMenu'
function renderTitle ( function renderTitle (
column: TableExpandColumn | TableBaseColumn | TableColumnGroup column: TableExpandColumn | TableBaseColumn | TableColumnGroup
@ -35,11 +35,11 @@ export default defineComponent({
setup () { setup () {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const NDataTable = inject(dataTableInjectionKey)! const NDataTable = inject(dataTableInjectionKey)!
function handleCheckboxUpdateChecked (column: TableSelectionColumn): void { function handleCheckboxUpdateChecked (): void {
if (NDataTable.someRowsChecked || NDataTable.allRowsChecked) { if (NDataTable.someRowsChecked || NDataTable.allRowsChecked) {
NDataTable.doUncheckAll(column) NDataTable.doUncheckAll()
} else { } else {
NDataTable.doCheckAll(column) NDataTable.doCheckAll()
} }
} }
function handleColHeaderClick ( function handleColHeaderClick (
@ -75,7 +75,8 @@ export default defineComponent({
rightActiveFixedColKey, rightActiveFixedColKey,
rows, rows,
cols, cols,
mergedTheme mergedTheme,
checkOptions
}, },
headerStyle, headerStyle,
handleColHeaderClick, handleColHeaderClick,
@ -145,15 +146,16 @@ export default defineComponent({
} }
> >
{column.type === 'selection' ? ( {column.type === 'selection' ? (
<NCheckbox <>
key={currentPage} <NCheckbox
tableHeader key={currentPage}
checked={allRowsChecked} tableHeader
indeterminate={someRowsChecked} checked={allRowsChecked}
onUpdateChecked={() => indeterminate={someRowsChecked}
handleCheckboxUpdateChecked(column) onUpdateChecked={handleCheckboxUpdateChecked}
} />
/> {checkOptions ? <CheckMenu /> : null}
</>
) : column.ellipsis === true || ) : column.ellipsis === true ||
(column.ellipsis && !column.ellipsis.tooltip) ? ( (column.ellipsis && !column.ellipsis.tooltip) ? (
<div <div

View File

@ -5,6 +5,7 @@ import { NLocale } from '../../locales'
import { MergedTheme } from '../../_mixins' import { MergedTheme } from '../../_mixins'
import { DataTableTheme } from '../styles' import { DataTableTheme } from '../styles'
import { RowItem, ColItem } from './use-group-header' import { RowItem, ColItem } from './use-group-header'
import { DataTableCheckOption } from './TableParts/CheckMenu'
export type FilterOptionValue = string | number export type FilterOptionValue = string | number
export type ColumnKey = string | number export type ColumnKey = string | number
@ -100,6 +101,7 @@ export type TableBaseColumn = {
export type TableSelectionColumn = { export type TableSelectionColumn = {
type: 'selection' type: 'selection'
disabled?: (row: RowData) => boolean disabled?: (row: RowData) => boolean
options?: DataTableCheckOptions
// to suppress type error in utils // to suppress type error in utils
sorter?: never sorter?: never
@ -127,11 +129,16 @@ export type TableColumn =
| TableExpandColumn | TableExpandColumn
export type TableColumns = TableColumn[] export type TableColumns = TableColumn[]
export type DataTableCheckOptions = Array<
| DataTableCheckOption
| { label: string, key: string | number, onSelect: () => void }
>
export interface DataTableInjection { export interface DataTableInjection {
checkOptions: DataTableCheckOptions | undefined
hoverKey: RowKey | null hoverKey: RowKey | null
mergedClsPrefix: string mergedClsPrefix: string
mergedTheme: MergedTheme<DataTableTheme> mergedTheme: MergedTheme<DataTableTheme>
scrollX?: string | number scrollX: string | number | undefined
rows: RowItem[][] rows: RowItem[][]
cols: ColItem[] cols: ColItem[]
treeMate: TreeMate<RowData> treeMate: TreeMate<RowData>
@ -148,7 +155,7 @@ export interface DataTableInjection {
mergedSortState: SortState | null mergedSortState: SortState | null
mergedFilterState: FilterState mergedFilterState: FilterState
loading: boolean loading: boolean
rowClassName?: string | CreateRowClassName rowClassName: string | CreateRowClassName | undefined
mergedCheckedRowKeys: RowKey[] mergedCheckedRowKeys: RowKey[]
locale: NLocale['DataTable'] locale: NLocale['DataTable']
filterMenuCssVars: CSSProperties filterMenuCssVars: CSSProperties
@ -162,8 +169,8 @@ export interface DataTableInjection {
) => void ) => void
doUpdateSorter: (sorter: SortState | null) => void doUpdateSorter: (sorter: SortState | null) => void
doUpdateCheckedRowKeys: (keys: RowKey[]) => void doUpdateCheckedRowKeys: (keys: RowKey[]) => void
doUncheckAll: (column: TableSelectionColumn) => void doUncheckAll: (checkWholeTable?: boolean) => void
doCheckAll: (column: TableSelectionColumn) => void doCheckAll: (checkWholeTable?: boolean) => void
handleTableHeaderScroll: (e: Event) => void handleTableHeaderScroll: (e: Event) => void
handleTableBodyScroll: (e: Event) => void handleTableBodyScroll: (e: Event) => void
deriveActiveRightFixedColumn: ( deriveActiveRightFixedColumn: (

View File

@ -214,10 +214,12 @@ export default c([
cM('filterable', { cM('filterable', {
paddingRight: '36px' paddingRight: '36px'
}), }),
fixedColumnStyle,
cM('selection', ` cM('selection', `
padding: 0; padding: 0;
text-align: center; text-align: center;
line-height: 0; line-height: 0;
z-index: 3;
`), `),
cE('ellipsis', ` cE('ellipsis', `
display: inline-block; display: inline-block;
@ -236,8 +238,7 @@ export default c([
c('&:hover', { c('&:hover', {
backgroundColor: 'var(--merged-th-color-hover)' backgroundColor: 'var(--merged-th-color-hover)'
}) })
]), ])
fixedColumnStyle
]), ]),
cB('data-table-td', ` cB('data-table-td', `
text-align: start; text-align: start;
@ -329,7 +330,17 @@ export default c([
`) `)
]) ])
]) ])
]) ]),
cB('data-table-check-extra', `
transition: color .3s var(--bezier);
color: var(--th-icon-color);
position: absolute;
font-size: 14px;
right: -4px;
top: 50%;
transform: translateY(-50%);
z-index: 1;
`)
]), ]),
cB('data-table-filter-menu', [ cB('data-table-filter-menu', [
cB('scrollbar', { cB('scrollbar', {
@ -387,11 +398,11 @@ export default c([
function createFixedColumnStyle (): CNode[] { function createFixedColumnStyle (): CNode[] {
return [ return [
cM('fixed-left', { cM('fixed-left', `
left: 0, left: 0;
position: 'sticky', position: sticky;
zIndex: 2 z-index: 2;
}, [ `, [
c('&::after', ` c('&::after', `
pointer-events: none; pointer-events: none;
content: ""; content: "";

View File

@ -9,11 +9,12 @@ import { TreeMate } from 'treemate'
export function useCheck ( export function useCheck (
props: DataTableSetupProps, props: DataTableSetupProps,
data: { data: {
selectionColumnRef: ComputedRef<TableSelectionColumn | null>
paginatedDataRef: ComputedRef<TmNode[]> paginatedDataRef: ComputedRef<TmNode[]>
treeMateRef: ComputedRef<TreeMate<RowData>> treeMateRef: ComputedRef<TreeMate<RowData>>
} }
) { ) {
const { paginatedDataRef, treeMateRef } = data const { paginatedDataRef, treeMateRef, selectionColumnRef } = data
const uncontrolledCheckedRowKeysRef = ref(props.defaultCheckedRowKeys) const uncontrolledCheckedRowKeysRef = ref(props.defaultCheckedRowKeys)
const controlledCheckedRowKeysRef = toRef(props, 'checkedRowKeys') const controlledCheckedRowKeysRef = toRef(props, 'checkedRowKeys')
const mergedCheckedRowKeysRef = useMergedState( const mergedCheckedRowKeysRef = useMergedState(
@ -47,9 +48,14 @@ export function useCheck (
if (onCheckedRowKeysChange) call(onCheckedRowKeysChange, keys) if (onCheckedRowKeysChange) call(onCheckedRowKeysChange, keys)
uncontrolledCheckedRowKeysRef.value = keys uncontrolledCheckedRowKeysRef.value = keys
} }
function doCheckAll (column: TableSelectionColumn): void { function doCheckAll (checkWholeTable: boolean = false): void {
const { value: column } = selectionColumnRef
if (!column) return
const rowKeysToCheck: RowKey[] = [] const rowKeysToCheck: RowKey[] = []
paginatedDataRef.value.forEach((tmNode) => { ;(checkWholeTable
? treeMateRef.value.treeNodes
: paginatedDataRef.value
).forEach((tmNode) => {
if (column.disabled?.(tmNode.rawNode)) { if (column.disabled?.(tmNode.rawNode)) {
return return
} }
@ -62,9 +68,14 @@ export function useCheck (
.checkedKeys .checkedKeys
) )
} }
function doUncheckAll (column: TableSelectionColumn): void { function doUncheckAll (checkWholeTable: boolean = false): void {
const { value: column } = selectionColumnRef
if (!column) return
const rowKeysToUncheck: RowKey[] = [] const rowKeysToUncheck: RowKey[] = []
paginatedDataRef.value.forEach((tmNode) => { ;(checkWholeTable
? treeMateRef.value.treeNodes
: paginatedDataRef.value
).forEach((tmNode) => {
const { rawNode: row } = tmNode const { rawNode: row } = tmNode
if (column.disabled?.(row)) { if (column.disabled?.(row)) {
return return

View File

@ -301,6 +301,17 @@ export function useTableData (
} }
}) })
const selectionColumnRef = computed<TableSelectionColumn | null>(() => {
return (
(props.columns.find((col) => {
if (col.type === 'selection') {
return true
}
return false
}) as TableSelectionColumn | undefined) || null
)
})
function doUpdatePage (page: number): void { function doUpdatePage (page: number): void {
const { 'onUpdate:page': onUpdatePage, onPageChange } = props const { 'onUpdate:page': onUpdatePage, onPageChange } = props
if (onUpdatePage) call(onUpdatePage, page) if (onUpdatePage) call(onUpdatePage, page)
@ -380,6 +391,7 @@ export function useTableData (
mergedFilterState: mergedFilterStateRef, mergedFilterState: mergedFilterStateRef,
mergedSortState: mergedSortStateRef, mergedSortState: mergedSortStateRef,
hoverKey: ref<RowKey | null>(null), hoverKey: ref<RowKey | null>(null),
selectionColumn: selectionColumnRef,
doUpdateFilters, doUpdateFilters,
doUpdateSorter, doUpdateSorter,
doUpdatePageSize, doUpdatePageSize,