feat: table add tree filter
parent
cc387b9bbd
commit
79ff7ac2db
|
|
@ -6,5 +6,10 @@ export type ChangeEvent = Event & {
|
||||||
value?: string | undefined;
|
value?: string | undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export type CheckboxChangeEvent = Event & {
|
||||||
|
target: {
|
||||||
|
checked?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type EventHandler = (...args: any[]) => void;
|
export type EventHandler = (...args: any[]) => void;
|
||||||
|
|
|
||||||
|
|
@ -632,6 +632,8 @@
|
||||||
// Sorter
|
// Sorter
|
||||||
// Legacy: `table-header-sort-active-bg` is used for hover not real active
|
// Legacy: `table-header-sort-active-bg` is used for hover not real active
|
||||||
@table-header-sort-active-bg: rgba(0, 0, 0, 0.04);
|
@table-header-sort-active-bg: rgba(0, 0, 0, 0.04);
|
||||||
|
@table-fixed-header-sort-active-bg: hsv(0, 0, 96%);
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
@table-header-filter-active-bg: rgba(0, 0, 0, 0.04);
|
@table-header-filter-active-bg: rgba(0, 0, 0, 0.04);
|
||||||
@table-filter-btns-bg: inherit;
|
@table-filter-btns-bg: inherit;
|
||||||
|
|
|
||||||
|
|
@ -520,7 +520,6 @@ const InteralTable = defineComponent<
|
||||||
expandType,
|
expandType,
|
||||||
childrenColumnName,
|
childrenColumnName,
|
||||||
locale: tableLocale,
|
locale: tableLocale,
|
||||||
expandIconColumnIndex,
|
|
||||||
getPopupContainer: computed(() => props.getPopupContainer),
|
getPopupContainer: computed(() => props.getPopupContainer),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -581,8 +580,11 @@ const InteralTable = defineComponent<
|
||||||
|
|
||||||
const renderPagination = (position: string) => (
|
const renderPagination = (position: string) => (
|
||||||
<Pagination
|
<Pagination
|
||||||
class={`${prefixCls.value}-pagination ${prefixCls.value}-pagination-${position}`}
|
|
||||||
{...mergedPagination.value}
|
{...mergedPagination.value}
|
||||||
|
class={[
|
||||||
|
`${prefixCls.value}-pagination ${prefixCls.value}-pagination-${position}`,
|
||||||
|
mergedPagination.value.class,
|
||||||
|
]}
|
||||||
size={paginationSize}
|
size={paginationSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,15 @@ import type {
|
||||||
} from '../../interface';
|
} from '../../interface';
|
||||||
import FilterDropdownMenuWrapper from './FilterWrapper';
|
import FilterDropdownMenuWrapper from './FilterWrapper';
|
||||||
import type { FilterState } from '.';
|
import type { FilterState } from '.';
|
||||||
|
import { flattenKeys } from '.';
|
||||||
import { computed, defineComponent, onBeforeUnmount, ref, shallowRef, watch } from 'vue';
|
import { computed, defineComponent, onBeforeUnmount, ref, shallowRef, watch } from 'vue';
|
||||||
import classNames from '../../../_util/classNames';
|
import classNames from '../../../_util/classNames';
|
||||||
import useConfigInject from '../../../_util/hooks/useConfigInject';
|
import useConfigInject from '../../../_util/hooks/useConfigInject';
|
||||||
import { useInjectSlots } from '../../context';
|
import { useInjectSlots } from '../../context';
|
||||||
|
import type { DataNode, EventDataNode } from '../../../tree';
|
||||||
|
import type { CheckboxChangeEvent, EventHandler } from '../../../_util/EventInterface';
|
||||||
|
import FilterSearch from './FilterSearch';
|
||||||
|
import Tree from '../../../tree';
|
||||||
|
|
||||||
const { SubMenu, Item: MenuItem } = Menu;
|
const { SubMenu, Item: MenuItem } = Menu;
|
||||||
|
|
||||||
|
|
@ -26,40 +31,26 @@ function hasSubMenu(filters: ColumnFilterItem[]) {
|
||||||
return filters.some(({ children }) => children && children.length > 0);
|
return filters.some(({ children }) => children && children.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchValueMatched(searchValue: string, text: any) {
|
||||||
|
if (typeof text === 'string' || typeof text === 'number') {
|
||||||
|
return text?.toString().toLowerCase().includes(searchValue.trim().toLowerCase());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function renderFilterItems({
|
function renderFilterItems({
|
||||||
filters,
|
filters,
|
||||||
prefixCls,
|
prefixCls,
|
||||||
filteredKeys,
|
filteredKeys,
|
||||||
filterMultiple,
|
filterMultiple,
|
||||||
locale,
|
searchValue,
|
||||||
}: {
|
}: {
|
||||||
filters: ColumnFilterItem[];
|
filters: ColumnFilterItem[];
|
||||||
prefixCls: string;
|
prefixCls: string;
|
||||||
filteredKeys: Key[];
|
filteredKeys: Key[];
|
||||||
filterMultiple: boolean;
|
filterMultiple: boolean;
|
||||||
locale: TableLocale;
|
searchValue: string;
|
||||||
}) {
|
}) {
|
||||||
if (filters.length === 0) {
|
|
||||||
// wrapped with <div /> to avoid react warning
|
|
||||||
// https://github.com/ant-design/ant-design/issues/25979
|
|
||||||
return (
|
|
||||||
<MenuItem key="empty">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
margin: '16px 0',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Empty
|
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
||||||
description={locale.filterEmptyText}
|
|
||||||
imageStyle={{
|
|
||||||
height: 24,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return filters.map((filter, index) => {
|
return filters.map((filter, index) => {
|
||||||
const key = String(filter.value);
|
const key = String(filter.value);
|
||||||
|
|
||||||
|
|
@ -75,7 +66,7 @@ function renderFilterItems({
|
||||||
prefixCls,
|
prefixCls,
|
||||||
filteredKeys,
|
filteredKeys,
|
||||||
filterMultiple,
|
filterMultiple,
|
||||||
locale,
|
searchValue,
|
||||||
})}
|
})}
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
);
|
);
|
||||||
|
|
@ -83,12 +74,16 @@ function renderFilterItems({
|
||||||
|
|
||||||
const Component = filterMultiple ? Checkbox : Radio;
|
const Component = filterMultiple ? Checkbox : Radio;
|
||||||
|
|
||||||
return (
|
const item = (
|
||||||
<MenuItem key={filter.value !== undefined ? key : index}>
|
<MenuItem key={filter.value !== undefined ? key : index}>
|
||||||
<Component checked={filteredKeys.includes(key)} />
|
<Component checked={filteredKeys.includes(key)} />
|
||||||
<span>{filter.text}</span>
|
<span>{filter.text}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
|
if (searchValue.trim()) {
|
||||||
|
return searchValueMatched(searchValue, filter.text) ? item : undefined;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +94,8 @@ export interface FilterDropdownProps<RecordType> {
|
||||||
column: ColumnType<RecordType>;
|
column: ColumnType<RecordType>;
|
||||||
filterState?: FilterState<RecordType>;
|
filterState?: FilterState<RecordType>;
|
||||||
filterMultiple: boolean;
|
filterMultiple: boolean;
|
||||||
|
filterMode?: 'menu' | 'tree';
|
||||||
|
filterSearch?: boolean;
|
||||||
columnKey: Key;
|
columnKey: Key;
|
||||||
triggerFilter: (filterState: FilterState<RecordType>) => void;
|
triggerFilter: (filterState: FilterState<RecordType>) => void;
|
||||||
locale: TableLocale;
|
locale: TableLocale;
|
||||||
|
|
@ -114,6 +111,8 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
'column',
|
'column',
|
||||||
'filterState',
|
'filterState',
|
||||||
'filterMultiple',
|
'filterMultiple',
|
||||||
|
'filterMode',
|
||||||
|
'filterSearch',
|
||||||
'columnKey',
|
'columnKey',
|
||||||
'triggerFilter',
|
'triggerFilter',
|
||||||
'locale',
|
'locale',
|
||||||
|
|
@ -121,6 +120,8 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
] as any,
|
] as any,
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const contextSlots = useInjectSlots();
|
const contextSlots = useInjectSlots();
|
||||||
|
const filterMode = computed(() => props.filterMode ?? 'menu');
|
||||||
|
const filterSearch = computed(() => props.filterSearch ?? false);
|
||||||
const filterDropdownVisible = computed(() => props.column.filterDropdownVisible);
|
const filterDropdownVisible = computed(() => props.column.filterDropdownVisible);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const filtered = computed(
|
const filtered = computed(
|
||||||
|
|
@ -168,9 +169,20 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
filteredKeys.value = selectedKeys;
|
filteredKeys.value = selectedKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onCheck = (keys: Key[], { node, checked }: { node: EventDataNode; checked: boolean }) => {
|
||||||
|
if (!props.filterMultiple) {
|
||||||
|
onSelectKeys({ selectedKeys: checked && node.key ? [node.key] : [] });
|
||||||
|
} else {
|
||||||
|
onSelectKeys({ selectedKeys: keys as Key[] });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
propFilteredKeys,
|
propFilteredKeys,
|
||||||
() => {
|
() => {
|
||||||
|
if (!visible.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onSelectKeys({ selectedKeys: propFilteredKeys.value || [] });
|
onSelectKeys({ selectedKeys: propFilteredKeys.value || [] });
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
|
|
@ -193,6 +205,18 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
clearTimeout(openRef.value);
|
clearTimeout(openRef.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const searchValue = ref('');
|
||||||
|
const onSearch: EventHandler = e => {
|
||||||
|
const { value } = e.target;
|
||||||
|
searchValue.value = value;
|
||||||
|
};
|
||||||
|
// clear search value after close filter dropdown
|
||||||
|
watch(visible, () => {
|
||||||
|
if (!visible.value) {
|
||||||
|
searchValue.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ======================= Submit ========================
|
// ======================= Submit ========================
|
||||||
const internalTriggerFilter = (keys: Key[] | undefined | null) => {
|
const internalTriggerFilter = (keys: Key[] | undefined | null) => {
|
||||||
const { column, columnKey, filterState } = props;
|
const { column, columnKey, filterState } = props;
|
||||||
|
|
@ -218,9 +242,8 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
|
searchValue.value = '';
|
||||||
filteredKeys.value = [];
|
filteredKeys.value = [];
|
||||||
triggerVisible(false);
|
|
||||||
internalTriggerFilter([]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
|
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
|
||||||
|
|
@ -244,20 +267,143 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
};
|
};
|
||||||
|
|
||||||
const { direction } = useConfigInject('', props);
|
const { direction } = useConfigInject('', props);
|
||||||
return () => {
|
|
||||||
const {
|
const onCheckAll = (e: CheckboxChangeEvent) => {
|
||||||
tablePrefixCls,
|
if (e.target.checked) {
|
||||||
prefixCls,
|
const allFilterKeys = flattenKeys(props.column?.filters).map(key => String(key));
|
||||||
column,
|
filteredKeys.value = allFilterKeys;
|
||||||
dropdownPrefixCls,
|
} else {
|
||||||
filterMultiple,
|
filteredKeys.value = [];
|
||||||
locale,
|
}
|
||||||
getPopupContainer,
|
};
|
||||||
} = props;
|
|
||||||
// ======================== Style ========================
|
const getTreeData = ({ filters }: { filters?: ColumnFilterItem[] }) =>
|
||||||
const dropdownMenuClass = classNames({
|
(filters || []).map((filter, index) => {
|
||||||
[`${dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(column.filters || []),
|
const key = String(filter.value);
|
||||||
|
const item: DataNode = {
|
||||||
|
title: filter.text,
|
||||||
|
key: filter.value !== undefined ? key : index,
|
||||||
|
};
|
||||||
|
if (filter.children) {
|
||||||
|
item.children = getTreeData({ filters: filter.children });
|
||||||
|
}
|
||||||
|
return item;
|
||||||
});
|
});
|
||||||
|
// ======================== Style ========================
|
||||||
|
const dropdownMenuClass = computed(() =>
|
||||||
|
classNames({
|
||||||
|
[`${props.dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(
|
||||||
|
props.column.filters || [],
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const getFilterComponent = () => {
|
||||||
|
const selectedKeys = filteredKeys.value;
|
||||||
|
const {
|
||||||
|
column,
|
||||||
|
locale,
|
||||||
|
tablePrefixCls,
|
||||||
|
filterMultiple,
|
||||||
|
dropdownPrefixCls,
|
||||||
|
getPopupContainer,
|
||||||
|
prefixCls,
|
||||||
|
} = props;
|
||||||
|
if ((column.filters || []).length === 0) {
|
||||||
|
return (
|
||||||
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description={locale.filterEmptyText}
|
||||||
|
imageStyle={{
|
||||||
|
height: 24,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: '16px 0',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filterMode.value === 'tree') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterSearch
|
||||||
|
filterSearch={filterSearch.value}
|
||||||
|
value={searchValue.value}
|
||||||
|
onChange={onSearch}
|
||||||
|
tablePrefixCls={tablePrefixCls}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
<div class={`${tablePrefixCls}-filter-dropdown-tree`}>
|
||||||
|
{filterMultiple ? (
|
||||||
|
<Checkbox
|
||||||
|
class={`${tablePrefixCls}-filter-dropdown-checkall`}
|
||||||
|
onChange={onCheckAll}
|
||||||
|
>
|
||||||
|
{locale.filterCheckall}
|
||||||
|
</Checkbox>
|
||||||
|
) : null}
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
selectable={false}
|
||||||
|
blockNode
|
||||||
|
multiple={filterMultiple}
|
||||||
|
checkStrictly={!filterMultiple}
|
||||||
|
class={`${dropdownPrefixCls}-menu`}
|
||||||
|
onCheck={onCheck}
|
||||||
|
checkedKeys={selectedKeys}
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
showIcon={false}
|
||||||
|
treeData={getTreeData({ filters: column.filters })}
|
||||||
|
autoExpandParent
|
||||||
|
defaultExpandAll
|
||||||
|
filterTreeNode={
|
||||||
|
searchValue.value.trim()
|
||||||
|
? node => searchValueMatched(searchValue.value, node.title)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterSearch
|
||||||
|
filterSearch={filterSearch.value}
|
||||||
|
value={searchValue.value}
|
||||||
|
onChange={onSearch}
|
||||||
|
tablePrefixCls={tablePrefixCls}
|
||||||
|
locale={locale}
|
||||||
|
/>
|
||||||
|
<Menu
|
||||||
|
multiple={filterMultiple}
|
||||||
|
prefixCls={`${dropdownPrefixCls}-menu`}
|
||||||
|
class={dropdownMenuClass.value}
|
||||||
|
onClick={onMenuClick}
|
||||||
|
onSelect={onSelectKeys}
|
||||||
|
onDeselect={onSelectKeys}
|
||||||
|
selectedKeys={selectedKeys}
|
||||||
|
getPopupContainer={getPopupContainer}
|
||||||
|
openKeys={openKeys.value}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
v-slots={{
|
||||||
|
default: () =>
|
||||||
|
renderFilterItems({
|
||||||
|
filters: column.filters || [],
|
||||||
|
prefixCls,
|
||||||
|
filteredKeys: filteredKeys.value,
|
||||||
|
filterMultiple,
|
||||||
|
searchValue: searchValue.value,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
></Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const { tablePrefixCls, prefixCls, column, dropdownPrefixCls, locale, getPopupContainer } =
|
||||||
|
props;
|
||||||
|
|
||||||
let dropdownContent;
|
let dropdownContent;
|
||||||
|
|
||||||
|
|
@ -278,28 +424,7 @@ export default defineComponent<FilterDropdownProps<any>>({
|
||||||
const selectedKeys = filteredKeys.value as any;
|
const selectedKeys = filteredKeys.value as any;
|
||||||
dropdownContent = (
|
dropdownContent = (
|
||||||
<>
|
<>
|
||||||
<Menu
|
{getFilterComponent()}
|
||||||
multiple={filterMultiple}
|
|
||||||
prefixCls={`${dropdownPrefixCls}-menu`}
|
|
||||||
class={dropdownMenuClass}
|
|
||||||
onClick={onMenuClick}
|
|
||||||
onSelect={onSelectKeys}
|
|
||||||
onDeselect={onSelectKeys}
|
|
||||||
selectedKeys={selectedKeys}
|
|
||||||
getPopupContainer={getPopupContainer}
|
|
||||||
openKeys={openKeys.value}
|
|
||||||
onOpenChange={onOpenChange}
|
|
||||||
v-slots={{
|
|
||||||
default: () =>
|
|
||||||
renderFilterItems({
|
|
||||||
filters: column.filters || [],
|
|
||||||
prefixCls,
|
|
||||||
filteredKeys: filteredKeys.value,
|
|
||||||
filterMultiple,
|
|
||||||
locale,
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
></Menu>
|
|
||||||
<div class={`${prefixCls}-dropdown-btns`}>
|
<div class={`${prefixCls}-dropdown-btns`}>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
|
||||||
|
import type { TableLocale } from '../../interface';
|
||||||
|
import Input from '../../../input';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FilterSearch',
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
value: String,
|
||||||
|
onChange: Function as PropType<(e: InputEvent) => void>,
|
||||||
|
filterSearch: Boolean,
|
||||||
|
tablePrefixCls: String,
|
||||||
|
locale: { type: Object as PropType<TableLocale>, default: undefined as TableLocale },
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => {
|
||||||
|
const { value, onChange, filterSearch, tablePrefixCls, locale } = props;
|
||||||
|
if (!filterSearch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class={`${tablePrefixCls}-filter-dropdown-search`}>
|
||||||
|
<Input
|
||||||
|
v-slots={{ prefix: () => <SearchOutlined /> }}
|
||||||
|
placeholder={locale.filterSearchPlaceholder}
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
// for skip min-width of input
|
||||||
|
htmlSize={1}
|
||||||
|
class={`${tablePrefixCls}-filter-dropdown-search-input`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -81,7 +81,7 @@ function injectFilter<RecordType>(
|
||||||
): ColumnsType<RecordType> {
|
): ColumnsType<RecordType> {
|
||||||
return columns.map((column, index) => {
|
return columns.map((column, index) => {
|
||||||
const columnPos = getColumnPos(index, pos);
|
const columnPos = getColumnPos(index, pos);
|
||||||
const { filterMultiple = true } = column as ColumnType<RecordType>;
|
const { filterMultiple = true, filterMode, filterSearch } = column as ColumnType<RecordType>;
|
||||||
|
|
||||||
let newColumn: ColumnsType<RecordType>[number] = column;
|
let newColumn: ColumnsType<RecordType>[number] = column;
|
||||||
const hasFilterDropdown =
|
const hasFilterDropdown =
|
||||||
|
|
@ -101,6 +101,8 @@ function injectFilter<RecordType>(
|
||||||
columnKey={columnKey}
|
columnKey={columnKey}
|
||||||
filterState={filterState}
|
filterState={filterState}
|
||||||
filterMultiple={filterMultiple}
|
filterMultiple={filterMultiple}
|
||||||
|
filterMode={filterMode}
|
||||||
|
filterSearch={filterSearch}
|
||||||
triggerFilter={triggerFilter}
|
triggerFilter={triggerFilter}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
getPopupContainer={getPopupContainer}
|
getPopupContainer={getPopupContainer}
|
||||||
|
|
@ -131,7 +133,7 @@ function injectFilter<RecordType>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenKeys(filters?: ColumnFilterItem[]) {
|
export function flattenKeys(filters?: ColumnFilterItem[]) {
|
||||||
let keys: FilterValue = [];
|
let keys: FilterValue = [];
|
||||||
(filters || []).forEach(({ value, children }) => {
|
(filters || []).forEach(({ value, children }) => {
|
||||||
keys.push(value);
|
keys.push(value);
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,10 @@ export default function usePagination(
|
||||||
return mP;
|
return mP;
|
||||||
});
|
});
|
||||||
|
|
||||||
const refreshPagination = (current = 1, pageSize?: number) => {
|
const refreshPagination = (current?: number, pageSize?: number) => {
|
||||||
if (pagination.value === false) return;
|
if (pagination.value === false) return;
|
||||||
setInnerPagination({
|
setInnerPagination({
|
||||||
current,
|
current: current ?? 1,
|
||||||
pageSize: pageSize || mergedPagination.value.pageSize,
|
pageSize: pageSize || mergedPagination.value.pageSize,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
|
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
|
||||||
import type { DataNode } from '../../tree';
|
import type { DataNode } from '../../tree';
|
||||||
import { INTERNAL_COL_DEFINE } from '../../vc-table';
|
import { INTERNAL_COL_DEFINE } from '../../vc-table';
|
||||||
import type { ColumnType, FixedType } from '../../vc-table/interface';
|
import type { FixedType } from '../../vc-table/interface';
|
||||||
import type { GetCheckDisabled } from '../../vc-tree/interface';
|
import type { GetCheckDisabled } from '../../vc-tree/interface';
|
||||||
import { arrAdd, arrDel } from '../../vc-tree/util';
|
import { arrAdd, arrDel } from '../../vc-tree/util';
|
||||||
import { conductCheck } from '../../vc-tree/utils/conductUtil';
|
import { conductCheck } from '../../vc-tree/utils/conductUtil';
|
||||||
|
|
@ -10,7 +10,7 @@ import devWarning from '../../vc-util/devWarning';
|
||||||
import useMergedState from '../../_util/hooks/useMergedState';
|
import useMergedState from '../../_util/hooks/useMergedState';
|
||||||
import useState from '../../_util/hooks/useState';
|
import useState from '../../_util/hooks/useState';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { computed, shallowRef } from 'vue';
|
import { watchEffect, computed, shallowRef } from 'vue';
|
||||||
import type { CheckboxProps } from '../../checkbox';
|
import type { CheckboxProps } from '../../checkbox';
|
||||||
import Checkbox from '../../checkbox';
|
import Checkbox from '../../checkbox';
|
||||||
import Dropdown from '../../dropdown';
|
import Dropdown from '../../dropdown';
|
||||||
|
|
@ -20,6 +20,7 @@ import type {
|
||||||
TableRowSelection,
|
TableRowSelection,
|
||||||
Key,
|
Key,
|
||||||
ColumnsType,
|
ColumnsType,
|
||||||
|
ColumnType,
|
||||||
GetRowKey,
|
GetRowKey,
|
||||||
TableLocale,
|
TableLocale,
|
||||||
SelectionItem,
|
SelectionItem,
|
||||||
|
|
@ -29,14 +30,12 @@ import type {
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
|
|
||||||
// TODO: warning if use ajax!!!
|
// TODO: warning if use ajax!!!
|
||||||
|
|
||||||
|
export const SELECTION_COLUMN = {} as const;
|
||||||
export const SELECTION_ALL = 'SELECT_ALL' as const;
|
export const SELECTION_ALL = 'SELECT_ALL' as const;
|
||||||
export const SELECTION_INVERT = 'SELECT_INVERT' as const;
|
export const SELECTION_INVERT = 'SELECT_INVERT' as const;
|
||||||
export const SELECTION_NONE = 'SELECT_NONE' as const;
|
export const SELECTION_NONE = 'SELECT_NONE' as const;
|
||||||
|
|
||||||
function getFixedType<RecordType>(column: ColumnsType<RecordType>[number]): FixedType | undefined {
|
|
||||||
return (column && column.fixed) as FixedType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseSelectionConfig<RecordType> {
|
interface UseSelectionConfig<RecordType> {
|
||||||
prefixCls: Ref<string>;
|
prefixCls: Ref<string>;
|
||||||
pageData: Ref<RecordType[]>;
|
pageData: Ref<RecordType[]>;
|
||||||
|
|
@ -45,7 +44,6 @@ interface UseSelectionConfig<RecordType> {
|
||||||
getRecordByKey: (key: Key) => RecordType;
|
getRecordByKey: (key: Key) => RecordType;
|
||||||
expandType: Ref<ExpandType>;
|
expandType: Ref<ExpandType>;
|
||||||
childrenColumnName: Ref<string>;
|
childrenColumnName: Ref<string>;
|
||||||
expandIconColumnIndex?: Ref<number>;
|
|
||||||
locale: Ref<TableLocale>;
|
locale: Ref<TableLocale>;
|
||||||
getPopupContainer?: Ref<GetPopupContainer>;
|
getPopupContainer?: Ref<GetPopupContainer>;
|
||||||
}
|
}
|
||||||
|
|
@ -79,13 +77,12 @@ export default function useSelection<RecordType>(
|
||||||
rowSelectionRef: Ref<TableRowSelection<RecordType> | undefined>,
|
rowSelectionRef: Ref<TableRowSelection<RecordType> | undefined>,
|
||||||
configRef: UseSelectionConfig<RecordType>,
|
configRef: UseSelectionConfig<RecordType>,
|
||||||
): [TransformColumns<RecordType>, Ref<Set<Key>>] {
|
): [TransformColumns<RecordType>, Ref<Set<Key>>] {
|
||||||
// ======================== Caches ========================
|
|
||||||
const preserveRecordsRef = shallowRef(new Map<Key, RecordType>());
|
|
||||||
const mergedRowSelection = computed(() => {
|
const mergedRowSelection = computed(() => {
|
||||||
const temp = rowSelectionRef.value || {};
|
const temp = rowSelectionRef.value || {};
|
||||||
const { checkStrictly = true } = temp;
|
const { checkStrictly = true } = temp;
|
||||||
return { ...temp, checkStrictly };
|
return { ...temp, checkStrictly };
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========================= Keys =========================
|
// ========================= Keys =========================
|
||||||
const [mergedSelectedKeys, setMergedSelectedKeys] = useMergedState(
|
const [mergedSelectedKeys, setMergedSelectedKeys] = useMergedState(
|
||||||
mergedRowSelection.value.selectedRowKeys ||
|
mergedRowSelection.value.selectedRowKeys ||
|
||||||
|
|
@ -96,6 +93,31 @@ export default function useSelection<RecordType>(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ======================== Caches ========================
|
||||||
|
const preserveRecordsRef = shallowRef(new Map<Key, RecordType>());
|
||||||
|
|
||||||
|
const updatePreserveRecordsCache = (keys: Key[]) => {
|
||||||
|
if (mergedRowSelection.value.preserveSelectedRowKeys) {
|
||||||
|
const newCache = new Map<Key, RecordType>();
|
||||||
|
// Keep key if mark as preserveSelectedRowKeys
|
||||||
|
keys.forEach(key => {
|
||||||
|
let record = configRef.getRecordByKey(key);
|
||||||
|
|
||||||
|
if (!record && preserveRecordsRef.value.has(key)) {
|
||||||
|
record = preserveRecordsRef.value.get(key)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
newCache.set(key, record);
|
||||||
|
});
|
||||||
|
// Refresh to new cache
|
||||||
|
preserveRecordsRef.value = newCache;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
updatePreserveRecordsCache(mergedSelectedKeys.value);
|
||||||
|
});
|
||||||
|
|
||||||
const keyEntities = computed(() =>
|
const keyEntities = computed(() =>
|
||||||
mergedRowSelection.value.checkStrictly
|
mergedRowSelection.value.checkStrictly
|
||||||
? { keyEntities: null }
|
? { keyEntities: null }
|
||||||
|
|
@ -179,26 +201,12 @@ export default function useSelection<RecordType>(
|
||||||
const setSelectedKeys = (keys: Key[]) => {
|
const setSelectedKeys = (keys: Key[]) => {
|
||||||
let availableKeys: Key[];
|
let availableKeys: Key[];
|
||||||
let records: RecordType[];
|
let records: RecordType[];
|
||||||
|
updatePreserveRecordsCache(keys);
|
||||||
const { preserveSelectedRowKeys, onChange: onSelectionChange } = mergedRowSelection.value;
|
const { preserveSelectedRowKeys, onChange: onSelectionChange } = mergedRowSelection.value;
|
||||||
const { getRecordByKey } = configRef;
|
const { getRecordByKey } = configRef;
|
||||||
if (preserveSelectedRowKeys) {
|
if (preserveSelectedRowKeys) {
|
||||||
// Keep key if mark as preserveSelectedRowKeys
|
|
||||||
const newCache = new Map<Key, RecordType>();
|
|
||||||
availableKeys = keys;
|
availableKeys = keys;
|
||||||
records = keys.map(key => {
|
records = keys.map(key => preserveRecordsRef.value.get(key)!);
|
||||||
let record = getRecordByKey(key);
|
|
||||||
|
|
||||||
if (!record && preserveRecordsRef.value.has(key)) {
|
|
||||||
record = preserveRecordsRef.value.get(key)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
newCache.set(key, record);
|
|
||||||
|
|
||||||
return record;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refresh to new cache
|
|
||||||
preserveRecordsRef.value = newCache;
|
|
||||||
} else {
|
} else {
|
||||||
// Filter key which not exist in the `dataSource`
|
// Filter key which not exist in the `dataSource`
|
||||||
availableKeys = [];
|
availableKeys = [];
|
||||||
|
|
@ -249,7 +257,14 @@ export default function useSelection<RecordType>(
|
||||||
key: 'all',
|
key: 'all',
|
||||||
text: tableLocale.value.selectionAll,
|
text: tableLocale.value.selectionAll,
|
||||||
onSelect() {
|
onSelect() {
|
||||||
setSelectedKeys(data.value.map((record, index) => getRowKey.value(record, index)));
|
setSelectedKeys(
|
||||||
|
data.value
|
||||||
|
.map((record, index) => getRowKey.value(record, index))
|
||||||
|
.filter(key => {
|
||||||
|
const checkProps = checkboxPropsMap.value.get(key);
|
||||||
|
return !checkProps?.disabled || derivedSelectedKeySet.value.has(key);
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -261,11 +276,13 @@ export default function useSelection<RecordType>(
|
||||||
const keySet = new Set(derivedSelectedKeySet.value);
|
const keySet = new Set(derivedSelectedKeySet.value);
|
||||||
pageData.value.forEach((record, index) => {
|
pageData.value.forEach((record, index) => {
|
||||||
const key = getRowKey.value(record, index);
|
const key = getRowKey.value(record, index);
|
||||||
|
const checkProps = checkboxPropsMap.value.get(key);
|
||||||
if (keySet.has(key)) {
|
if (!checkProps?.disabled) {
|
||||||
keySet.delete(key);
|
if (keySet.has(key)) {
|
||||||
} else {
|
keySet.delete(key);
|
||||||
keySet.add(key);
|
} else {
|
||||||
|
keySet.add(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -289,7 +306,12 @@ export default function useSelection<RecordType>(
|
||||||
text: tableLocale.value.selectNone,
|
text: tableLocale.value.selectNone,
|
||||||
onSelect() {
|
onSelect() {
|
||||||
onSelectNone?.();
|
onSelectNone?.();
|
||||||
setSelectedKeys([]);
|
setSelectedKeys(
|
||||||
|
Array.from(derivedSelectedKeySet.value).filter(key => {
|
||||||
|
const checkProps = checkboxPropsMap.value.get(key);
|
||||||
|
return checkProps?.disabled;
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -310,19 +332,21 @@ export default function useSelection<RecordType>(
|
||||||
checkStrictly,
|
checkStrictly,
|
||||||
} = mergedRowSelection.value;
|
} = mergedRowSelection.value;
|
||||||
|
|
||||||
const {
|
const { prefixCls, getRecordByKey, getRowKey, expandType, getPopupContainer } = configRef;
|
||||||
prefixCls,
|
|
||||||
getRecordByKey,
|
|
||||||
getRowKey,
|
|
||||||
expandType,
|
|
||||||
expandIconColumnIndex,
|
|
||||||
getPopupContainer,
|
|
||||||
} = configRef;
|
|
||||||
if (!rowSelectionRef.value) {
|
if (!rowSelectionRef.value) {
|
||||||
return columns;
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
devWarning(
|
||||||
|
!columns.includes(SELECTION_COLUMN),
|
||||||
|
'Table',
|
||||||
|
'`rowSelection` is not config but `SELECTION_COLUMN` exists in the `columns`.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns.filter(col => col !== SELECTION_COLUMN);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support selection
|
// Support selection
|
||||||
|
let cloneColumns = columns.slice();
|
||||||
const keySet = new Set(derivedSelectedKeySet.value);
|
const keySet = new Set(derivedSelectedKeySet.value);
|
||||||
|
|
||||||
// Record key only need check with enabled
|
// Record key only need check with enabled
|
||||||
|
|
@ -587,8 +611,62 @@ export default function useSelection<RecordType>(
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Columns
|
// Insert selection column if not exist
|
||||||
|
if (!cloneColumns.includes(SELECTION_COLUMN)) {
|
||||||
|
// Always after expand icon
|
||||||
|
if (
|
||||||
|
cloneColumns.findIndex(
|
||||||
|
(col: any) => col[INTERNAL_COL_DEFINE]?.columnType === 'EXPAND_COLUMN',
|
||||||
|
) === 0
|
||||||
|
) {
|
||||||
|
const [expandColumn, ...restColumns] = cloneColumns;
|
||||||
|
cloneColumns = [expandColumn, SELECTION_COLUMN, ...restColumns];
|
||||||
|
} else {
|
||||||
|
// Normal insert at first column
|
||||||
|
cloneColumns = [SELECTION_COLUMN, ...cloneColumns];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate selection column
|
||||||
|
const selectionColumnIndex = cloneColumns.indexOf(SELECTION_COLUMN);
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
cloneColumns.filter(col => col === SELECTION_COLUMN).length > 1
|
||||||
|
) {
|
||||||
|
devWarning(false, 'Table', 'Multiple `SELECTION_COLUMN` exist in `columns`.');
|
||||||
|
}
|
||||||
|
cloneColumns = cloneColumns.filter(
|
||||||
|
(column, index) => column !== SELECTION_COLUMN || index === selectionColumnIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fixed column logic
|
||||||
|
const prevCol: ColumnType<RecordType> & Record<string, any> =
|
||||||
|
cloneColumns[selectionColumnIndex - 1];
|
||||||
|
const nextCol: ColumnType<RecordType> & Record<string, any> =
|
||||||
|
cloneColumns[selectionColumnIndex + 1];
|
||||||
|
|
||||||
|
let mergedFixed: FixedType | undefined = fixed;
|
||||||
|
|
||||||
|
if (mergedFixed === undefined) {
|
||||||
|
if (nextCol?.fixed !== undefined) {
|
||||||
|
mergedFixed = nextCol.fixed;
|
||||||
|
} else if (prevCol?.fixed !== undefined) {
|
||||||
|
mergedFixed = prevCol.fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mergedFixed &&
|
||||||
|
prevCol &&
|
||||||
|
prevCol[INTERNAL_COL_DEFINE]?.columnType === 'EXPAND_COLUMN' &&
|
||||||
|
prevCol.fixed === undefined
|
||||||
|
) {
|
||||||
|
prevCol.fixed = mergedFixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace with real selection column
|
||||||
const selectionColumn = {
|
const selectionColumn = {
|
||||||
|
fixed: mergedFixed,
|
||||||
width: selectionColWidth,
|
width: selectionColWidth,
|
||||||
className: `${prefixCls.value}-selection-column`,
|
className: `${prefixCls.value}-selection-column`,
|
||||||
title: mergedRowSelection.value.columnTitle || title,
|
title: mergedRowSelection.value.columnTitle || title,
|
||||||
|
|
@ -598,15 +676,7 @@ export default function useSelection<RecordType>(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (expandType.value === 'row' && columns.length && !expandIconColumnIndex.value) {
|
return cloneColumns.map(col => (col === SELECTION_COLUMN ? selectionColumn : col));
|
||||||
const [expandColumn, ...restColumns] = columns;
|
|
||||||
const selectionFixed = fixed || getFixedType(restColumns[0]);
|
|
||||||
if (selectionFixed) {
|
|
||||||
expandColumn.fixed = selectionFixed;
|
|
||||||
}
|
|
||||||
return [expandColumn, { ...selectionColumn, fixed: selectionFixed }, ...restColumns];
|
|
||||||
}
|
|
||||||
return [{ ...selectionColumn, fixed: fixed || getFixedType(columns[0]) }, ...columns];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return [transformColumns, derivedSelectedKeySet];
|
return [transformColumns, derivedSelectedKeySet];
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,13 @@ import ColumnGroup from './ColumnGroup';
|
||||||
import type { TableProps, TablePaginationConfig } from './Table';
|
import type { TableProps, TablePaginationConfig } from './Table';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { Summary, SummaryCell, SummaryRow } from '../vc-table';
|
import { EXPAND_COLUMN, Summary, SummaryCell, SummaryRow } from '../vc-table';
|
||||||
import { SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE } from './hooks/useSelection';
|
import {
|
||||||
|
SELECTION_ALL,
|
||||||
|
SELECTION_INVERT,
|
||||||
|
SELECTION_NONE,
|
||||||
|
SELECTION_COLUMN,
|
||||||
|
} from './hooks/useSelection';
|
||||||
|
|
||||||
export type { ColumnProps } from './Column';
|
export type { ColumnProps } from './Column';
|
||||||
export type { ColumnsType, ColumnType, ColumnGroupType } from './interface';
|
export type { ColumnsType, ColumnType, ColumnGroupType } from './interface';
|
||||||
|
|
@ -34,6 +39,8 @@ export default Object.assign(Table, {
|
||||||
SELECTION_ALL,
|
SELECTION_ALL,
|
||||||
SELECTION_INVERT,
|
SELECTION_INVERT,
|
||||||
SELECTION_NONE,
|
SELECTION_NONE,
|
||||||
|
SELECTION_COLUMN,
|
||||||
|
EXPAND_COLUMN,
|
||||||
Column,
|
Column,
|
||||||
ColumnGroup,
|
ColumnGroup,
|
||||||
Summary: TableSummary,
|
Summary: TableSummary,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import type {
|
||||||
RenderedCell as RcRenderedCell,
|
RenderedCell as RcRenderedCell,
|
||||||
ExpandableConfig,
|
ExpandableConfig,
|
||||||
DefaultRecordType,
|
DefaultRecordType,
|
||||||
|
FixedType,
|
||||||
} from '../vc-table/interface';
|
} from '../vc-table/interface';
|
||||||
import type { TooltipProps } from '../tooltip';
|
import type { TooltipProps } from '../tooltip';
|
||||||
import type { CheckboxProps } from '../checkbox';
|
import type { CheckboxProps } from '../checkbox';
|
||||||
|
|
@ -111,6 +112,8 @@ export interface ColumnType<RecordType = DefaultRecordType> extends RcColumnType
|
||||||
filteredValue?: FilterValue | null;
|
filteredValue?: FilterValue | null;
|
||||||
defaultFilteredValue?: FilterValue | null;
|
defaultFilteredValue?: FilterValue | null;
|
||||||
filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode);
|
filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode);
|
||||||
|
filterMode?: 'menu' | 'tree';
|
||||||
|
filterSearch?: boolean;
|
||||||
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
|
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
|
||||||
filterDropdownVisible?: boolean;
|
filterDropdownVisible?: boolean;
|
||||||
onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
||||||
|
|
@ -158,7 +161,7 @@ export interface TableRowSelection<T = DefaultRecordType> {
|
||||||
onSelectNone?: () => void;
|
onSelectNone?: () => void;
|
||||||
selections?: INTERNAL_SELECTION_ITEM[] | boolean;
|
selections?: INTERNAL_SELECTION_ITEM[] | boolean;
|
||||||
hideSelectAll?: boolean;
|
hideSelectAll?: boolean;
|
||||||
fixed?: boolean;
|
fixed?: FixedType;
|
||||||
columnWidth?: string | number;
|
columnWidth?: string | number;
|
||||||
columnTitle?: string | VueNode;
|
columnTitle?: string | VueNode;
|
||||||
checkStrictly?: boolean;
|
checkStrictly?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
@import './index';
|
|
||||||
@import './size';
|
@import './size';
|
||||||
|
@import (reference) '../../style/themes/index';
|
||||||
|
@table-prefix-cls: ~'@{ant-prefix}-table';
|
||||||
|
|
||||||
@table-border: @border-width-base @border-style-base @table-border-color;
|
@table-border: @border-width-base @border-style-base @table-border-color;
|
||||||
|
|
||||||
|
|
@ -12,9 +13,7 @@
|
||||||
|
|
||||||
> .@{table-prefix-cls}-container {
|
> .@{table-prefix-cls}-container {
|
||||||
// ============================ Content ============================
|
// ============================ Content ============================
|
||||||
border: @table-border;
|
border-left: @table-border;
|
||||||
border-right: 0;
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
> .@{table-prefix-cls}-content,
|
> .@{table-prefix-cls}-content,
|
||||||
> .@{table-prefix-cls}-header,
|
> .@{table-prefix-cls}-header,
|
||||||
|
|
@ -67,6 +66,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .@{table-prefix-cls}-content,
|
||||||
|
> .@{table-prefix-cls}-header {
|
||||||
|
> table {
|
||||||
|
border-top: @table-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.@{table-prefix-cls}-scroll-horizontal {
|
&.@{table-prefix-cls}-scroll-horizontal {
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
@import '../../style/mixins/index';
|
@import '../../style/mixins/index';
|
||||||
@import './size';
|
@import './size';
|
||||||
@import './bordered';
|
@import './bordered';
|
||||||
@import './resize.less';
|
|
||||||
|
|
||||||
@table-prefix-cls: ~'@{ant-prefix}-table';
|
@table-prefix-cls: ~'@{ant-prefix}-table';
|
||||||
|
@tree-prefix-cls: ~'@{ant-prefix}-tree';
|
||||||
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
|
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
|
||||||
@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions';
|
@descriptions-prefix-cls: ~'@{ant-prefix}-descriptions';
|
||||||
@table-header-icon-color: #bfbfbf;
|
@table-header-icon-color: #bfbfbf;
|
||||||
@table-header-icon-color-hover: darken(@table-header-icon-color, 10%);
|
@table-header-icon-color-hover: darken(@table-header-icon-color, 10%);
|
||||||
@table-sticky-zindex: (@zindex-table-fixed + 1);
|
@table-sticky-zindex: calc(@zindex-table-fixed + 1);
|
||||||
@table-sticky-scroll-bar-active-bg: fade(@table-sticky-scroll-bar-bg, 80%);
|
@table-sticky-scroll-bar-active-bg: fade(@table-sticky-scroll-bar-bg, 80%);
|
||||||
|
@table-filter-dropdown-max-height: 264px;
|
||||||
|
|
||||||
.@{table-prefix-cls}-wrapper {
|
.@{table-prefix-cls}-wrapper {
|
||||||
clear: both;
|
clear: both;
|
||||||
|
|
@ -144,10 +145,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.@{table-prefix-cls}-row:hover {
|
&.@{table-prefix-cls}-row:hover > td,
|
||||||
> td {
|
> td.@{table-prefix-cls}-cell-row-hover {
|
||||||
background: @table-row-hover-bg;
|
background: @table-row-hover-bg;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.@{table-prefix-cls}-row-selected {
|
&.@{table-prefix-cls}-row-selected {
|
||||||
|
|
@ -167,6 +167,8 @@
|
||||||
|
|
||||||
// =========================== Summary ============================
|
// =========================== Summary ============================
|
||||||
&-summary {
|
&-summary {
|
||||||
|
position: relative;
|
||||||
|
z-index: @zindex-table-fixed;
|
||||||
background: @table-bg;
|
background: @table-bg;
|
||||||
|
|
||||||
div& {
|
div& {
|
||||||
|
|
@ -228,7 +230,7 @@
|
||||||
// https://github.com/ant-design/ant-design/issues/30969
|
// https://github.com/ant-design/ant-design/issues/30969
|
||||||
&.@{table-prefix-cls}-cell-fix-left:hover,
|
&.@{table-prefix-cls}-cell-fix-left:hover,
|
||||||
&.@{table-prefix-cls}-cell-fix-right:hover {
|
&.@{table-prefix-cls}-cell-fix-right:hover {
|
||||||
background: lighten(@black, 96%);
|
background: @table-fixed-header-sort-active-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,6 +271,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-column-sorter {
|
&-column-sorter {
|
||||||
|
margin-left: 4px;
|
||||||
color: @table-header-icon-color;
|
color: @table-header-icon-color;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
transition: color 0.3s;
|
transition: color 0.3s;
|
||||||
|
|
@ -329,21 +332,64 @@
|
||||||
&-filter-dropdown {
|
&-filter-dropdown {
|
||||||
.reset-component();
|
.reset-component();
|
||||||
|
|
||||||
|
min-width: 120px;
|
||||||
|
background-color: @table-filter-dropdown-bg;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
box-shadow: @box-shadow-base;
|
||||||
|
|
||||||
// Reset menu
|
// Reset menu
|
||||||
.@{dropdown-prefix-cls}-menu {
|
.@{dropdown-prefix-cls}-menu {
|
||||||
// https://github.com/ant-design/ant-design/issues/4916
|
// https://github.com/ant-design/ant-design/issues/4916
|
||||||
// https://github.com/ant-design/ant-design/issues/19542
|
// https://github.com/ant-design/ant-design/issues/19542
|
||||||
max-height: 264px;
|
max-height: @table-filter-dropdown-max-height;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
border: 0;
|
border: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:empty::after {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 0;
|
||||||
|
color: @disabled-color;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
text-align: center;
|
||||||
|
content: 'Not Found';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
min-width: 120px;
|
&-tree {
|
||||||
background-color: @table-filter-dropdown-bg;
|
padding: 8px 8px 0;
|
||||||
|
|
||||||
border-radius: @border-radius-base;
|
.@{tree-prefix-cls}-treenode .@{tree-prefix-cls}-node-content-wrapper:hover {
|
||||||
box-shadow: @box-shadow-base;
|
background-color: @tree-node-hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{tree-prefix-cls}-treenode-checkbox-checked .@{tree-prefix-cls}-node-content-wrapper {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
background-color: @tree-node-selected-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-search {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: @border-width-base @border-color-split @border-style-base;
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
input {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
.@{iconfont-css-prefix} {
|
||||||
|
color: @disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-checkall {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&-submenu > ul {
|
&-submenu > ul {
|
||||||
max-height: calc(100vh - 130px);
|
max-height: calc(100vh - 130px);
|
||||||
|
|
@ -363,7 +409,7 @@
|
||||||
&-btns {
|
&-btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 7px 8px 7px 3px;
|
padding: 7px 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: @table-filter-btns-bg;
|
background-color: @table-filter-btns-bg;
|
||||||
border-top: @border-width-base @border-style-base @table-border-color;
|
border-top: @border-width-base @border-style-base @table-border-color;
|
||||||
|
|
@ -390,6 +436,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table tr th&-selection-column&-cell-fix-left {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
table tr th&-selection-column::after {
|
table tr th&-selection-column::after {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
@ -488,6 +538,7 @@
|
||||||
&-collapsed::before {
|
&-collapsed::before {
|
||||||
transform: rotate(-180deg);
|
transform: rotate(-180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-collapsed::after {
|
&-collapsed::after {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
@ -542,6 +593,7 @@
|
||||||
.@{table-prefix-cls}-empty & {
|
.@{table-prefix-cls}-empty & {
|
||||||
color: @disabled-color;
|
color: @disabled-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
> td {
|
> td {
|
||||||
background: @component-background;
|
background: @component-background;
|
||||||
|
|
@ -552,7 +604,6 @@
|
||||||
// ============================ Fixed =============================
|
// ============================ Fixed =============================
|
||||||
&-cell-fix-left,
|
&-cell-fix-left,
|
||||||
&-cell-fix-right {
|
&-cell-fix-right {
|
||||||
position: -webkit-sticky !important;
|
|
||||||
position: sticky !important;
|
position: sticky !important;
|
||||||
z-index: @zindex-table-fixed;
|
z-index: @zindex-table-fixed;
|
||||||
background: @table-bg;
|
background: @table-bg;
|
||||||
|
|
@ -600,6 +651,7 @@
|
||||||
&::before {
|
&::before {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -638,11 +690,14 @@
|
||||||
box-shadow: inset -10px 0 8px -8px darken(@shadow-color, 5%);
|
box-shadow: inset -10px 0 8px -8px darken(@shadow-color, 5%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-sticky {
|
&-sticky {
|
||||||
&-holder {
|
&-holder {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
z-index: @table-sticky-zindex;
|
z-index: @table-sticky-zindex;
|
||||||
|
background: @component-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-scroll {
|
&-scroll {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
@ -652,16 +707,20 @@
|
||||||
background: lighten(@table-border-color, 80%);
|
background: lighten(@table-border-color, 80%);
|
||||||
border-top: 1px solid @table-border-color;
|
border-top: 1px solid @table-border-color;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform-origin: center bottom;
|
transform-origin: center bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-bar {
|
&-bar {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background-color: @table-sticky-scroll-bar-bg;
|
background-color: @table-sticky-scroll-bar-bg;
|
||||||
border-radius: @table-sticky-scroll-bar-radius;
|
border-radius: @table-sticky-scroll-bar-radius;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @table-sticky-scroll-bar-active-bg;
|
background-color: @table-sticky-scroll-bar-active-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-active {
|
&-active {
|
||||||
background-color: @table-sticky-scroll-bar-active-bg;
|
background-color: @table-sticky-scroll-bar-active-bg;
|
||||||
}
|
}
|
||||||
|
|
@ -677,6 +736,7 @@
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-ping-right {
|
&-ping-right {
|
||||||
.@{table-prefix-cls}-cell-fix-right-first::after {
|
.@{table-prefix-cls}-cell-fix-right-first::after {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,5 @@ import '../../dropdown/style';
|
||||||
import '../../spin/style';
|
import '../../spin/style';
|
||||||
import '../../pagination/style';
|
import '../../pagination/style';
|
||||||
import '../../tooltip/style';
|
import '../../tooltip/style';
|
||||||
|
import '../../input/style';
|
||||||
|
import '../../tree/style';
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
.@{table-prefix-cls}-resize-handle {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
height: 100% !important;
|
|
||||||
bottom: 0;
|
|
||||||
left: auto !important;
|
|
||||||
right: -8px;
|
|
||||||
cursor: col-resize;
|
|
||||||
touch-action: none;
|
|
||||||
user-select: auto;
|
|
||||||
width: 16px;
|
|
||||||
z-index: 1;
|
|
||||||
&-line {
|
|
||||||
display: block;
|
|
||||||
width: 1px;
|
|
||||||
margin-left: 7px;
|
|
||||||
height: 100% !important;
|
|
||||||
background-color: @primary-color;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
&:hover &-line {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragging .@{table-prefix-cls}-resize-handle-line {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
@ -32,6 +32,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(:last-child):not(.@{table-prefix-cls}-selection-column):not(.@{table-prefix-cls}-row-expand-icon-cell):not([colspan])::before {
|
||||||
|
.@{table-wrapepr-rtl-cls} & {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.@{table-wrapepr-rtl-cls} & {
|
.@{table-wrapepr-rtl-cls} & {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +80,7 @@
|
||||||
// ============================ Sorter ============================
|
// ============================ Sorter ============================
|
||||||
&-column-sorter {
|
&-column-sorter {
|
||||||
.@{table-wrapepr-rtl-cls} & {
|
.@{table-wrapepr-rtl-cls} & {
|
||||||
margin-right: @padding-xs;
|
margin-right: 4px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,10 +100,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-filter-trigger-container {
|
&-filter-trigger {
|
||||||
.@{table-wrapepr-rtl-cls} & {
|
.@{table-wrapepr-rtl-cls} & {
|
||||||
right: auto;
|
margin: -4px 4px -4px (-@table-padding-horizontal / 2);
|
||||||
left: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
@import './index';
|
@import (reference) '../../style/themes/index';
|
||||||
|
|
||||||
|
@table-prefix-cls: ~'@{ant-prefix}-table';
|
||||||
|
|
||||||
.table-size(@size, @padding-vertical, @padding-horizontal, @font-size) {
|
.table-size(@size, @padding-vertical, @padding-horizontal, @font-size) {
|
||||||
.@{table-prefix-cls}.@{table-prefix-cls}-@{size} {
|
.@{table-prefix-cls}.@{table-prefix-cls}-@{size} {
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ import { useInjectTable } from '../context/TableContext';
|
||||||
import { useInjectBody } from '../context/BodyContext';
|
import { useInjectBody } from '../context/BodyContext';
|
||||||
import classNames from '../../_util/classNames';
|
import classNames from '../../_util/classNames';
|
||||||
import { parseStyleText } from '../../_util/props-util';
|
import { parseStyleText } from '../../_util/props-util';
|
||||||
|
import type { MouseEventHandler } from '../../_util/EventInterface';
|
||||||
|
|
||||||
export interface BodyRowProps<RecordType> {
|
export interface BodyRowProps<RecordType> {
|
||||||
record: RecordType;
|
record: RecordType;
|
||||||
index: number;
|
index: number;
|
||||||
|
renderIndex: number;
|
||||||
recordKey: Key;
|
recordKey: Key;
|
||||||
expandedKeys: Set<Key>;
|
expandedKeys: Set<Key>;
|
||||||
rowComponent: CustomizeComponent;
|
rowComponent: CustomizeComponent;
|
||||||
|
|
@ -29,6 +31,7 @@ export default defineComponent<BodyRowProps<unknown>>({
|
||||||
props: [
|
props: [
|
||||||
'record',
|
'record',
|
||||||
'index',
|
'index',
|
||||||
|
'renderIndex',
|
||||||
'recordKey',
|
'recordKey',
|
||||||
'expandedKeys',
|
'expandedKeys',
|
||||||
'rowComponent',
|
'rowComponent',
|
||||||
|
|
@ -74,14 +77,12 @@ export default defineComponent<BodyRowProps<unknown>>({
|
||||||
() => props.customRow?.(props.record, props.index) || {},
|
() => props.customRow?.(props.record, props.index) || {},
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClick = (event, ...args) => {
|
const onClick: MouseEventHandler = (event, ...args) => {
|
||||||
if (bodyContext.expandRowByClick && mergedExpandable.value) {
|
if (bodyContext.expandRowByClick && mergedExpandable.value) {
|
||||||
onInternalTriggerExpand(props.record, event);
|
onInternalTriggerExpand(props.record, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalProps.value?.onClick) {
|
additionalProps.value?.onClick?.(event, ...args);
|
||||||
additionalProps.value.onClick(event, ...args);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeRowClassName = computed(() => {
|
const computeRowClassName = computed(() => {
|
||||||
|
|
@ -109,10 +110,6 @@ export default defineComponent<BodyRowProps<unknown>>({
|
||||||
} = props;
|
} = props;
|
||||||
const { prefixCls, fixedInfoList, transformCellText } = tableContext;
|
const { prefixCls, fixedInfoList, transformCellText } = tableContext;
|
||||||
const {
|
const {
|
||||||
fixHeader,
|
|
||||||
fixColumn,
|
|
||||||
horizonScroll,
|
|
||||||
componentWidth,
|
|
||||||
flattenColumns,
|
flattenColumns,
|
||||||
expandedRowClassName,
|
expandedRowClassName,
|
||||||
indentSize,
|
indentSize,
|
||||||
|
|
@ -175,6 +172,7 @@ export default defineComponent<BodyRowProps<unknown>>({
|
||||||
key={key}
|
key={key}
|
||||||
record={record}
|
record={record}
|
||||||
index={index}
|
index={index}
|
||||||
|
renderIndex={props.renderIndex}
|
||||||
dataIndex={dataIndex}
|
dataIndex={dataIndex}
|
||||||
customRender={customRender}
|
customRender={customRender}
|
||||||
{...fixedInfo}
|
{...fixedInfo}
|
||||||
|
|
@ -208,13 +206,10 @@ export default defineComponent<BodyRowProps<unknown>>({
|
||||||
computedExpandedRowClassName,
|
computedExpandedRowClassName,
|
||||||
)}
|
)}
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
fixHeader={fixHeader}
|
|
||||||
fixColumn={fixColumn}
|
|
||||||
horizonScroll={horizonScroll}
|
|
||||||
component={RowComponent}
|
component={RowComponent}
|
||||||
componentWidth={componentWidth}
|
|
||||||
cellComponent={cellComponent}
|
cellComponent={cellComponent}
|
||||||
colSpan={flattenColumns.length}
|
colSpan={flattenColumns.length}
|
||||||
|
isEmpty={false}
|
||||||
>
|
>
|
||||||
{expandContent}
|
{expandContent}
|
||||||
</ExpandedRow>
|
</ExpandedRow>
|
||||||
|
|
|
||||||
|
|
@ -2,46 +2,27 @@ import type { CustomizeComponent } from '../interface';
|
||||||
import Cell from '../Cell';
|
import Cell from '../Cell';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useInjectTable } from '../context/TableContext';
|
import { useInjectTable } from '../context/TableContext';
|
||||||
|
import { useInjectExpandedRow } from '../context/ExpandedRowContext';
|
||||||
|
|
||||||
export interface ExpandedRowProps {
|
export interface ExpandedRowProps {
|
||||||
prefixCls: string;
|
prefixCls: string;
|
||||||
component: CustomizeComponent;
|
component: CustomizeComponent;
|
||||||
cellComponent: CustomizeComponent;
|
cellComponent: CustomizeComponent;
|
||||||
fixHeader: boolean;
|
|
||||||
fixColumn: boolean;
|
|
||||||
horizonScroll: boolean;
|
|
||||||
componentWidth: number;
|
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
colSpan: number;
|
colSpan: number;
|
||||||
|
isEmpty: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent<ExpandedRowProps>({
|
export default defineComponent<ExpandedRowProps>({
|
||||||
name: 'ExpandedRow',
|
name: 'ExpandedRow',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: [
|
props: ['prefixCls', 'component', 'cellComponent', 'expanded', 'colSpan', 'isEmpty'] as any,
|
||||||
'prefixCls',
|
|
||||||
'component',
|
|
||||||
'cellComponent',
|
|
||||||
'fixHeader',
|
|
||||||
'fixColumn',
|
|
||||||
'horizonScroll',
|
|
||||||
'componentWidth',
|
|
||||||
'expanded',
|
|
||||||
'colSpan',
|
|
||||||
] as any,
|
|
||||||
setup(props, { slots, attrs }) {
|
setup(props, { slots, attrs }) {
|
||||||
const tableContext = useInjectTable();
|
const tableContext = useInjectTable();
|
||||||
|
const expandedRowContext = useInjectExpandedRow();
|
||||||
|
const { fixHeader, fixColumn, componentWidth, horizonScroll } = expandedRowContext;
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const { prefixCls, component: Component, cellComponent, expanded, colSpan, isEmpty } = props;
|
||||||
prefixCls,
|
|
||||||
component: Component,
|
|
||||||
cellComponent,
|
|
||||||
fixHeader,
|
|
||||||
fixColumn,
|
|
||||||
expanded,
|
|
||||||
componentWidth,
|
|
||||||
colSpan,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
|
|
@ -58,11 +39,13 @@ export default defineComponent<ExpandedRowProps>({
|
||||||
default: () => {
|
default: () => {
|
||||||
let contentNode: any = slots.default?.();
|
let contentNode: any = slots.default?.();
|
||||||
|
|
||||||
if (fixColumn) {
|
if (isEmpty ? horizonScroll.value : fixColumn.value) {
|
||||||
contentNode = (
|
contentNode = (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: `${componentWidth - (fixHeader ? tableContext.scrollbarSize : 0)}px`,
|
width: `${
|
||||||
|
componentWidth.value - (fixHeader.value ? tableContext.scrollbarSize : 0)
|
||||||
|
}px`,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
left: 0,
|
left: 0,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default defineComponent<MeasureCellProps>({
|
||||||
name: 'MeasureCell',
|
name: 'MeasureCell',
|
||||||
props: ['columnKey'] as any,
|
props: ['columnKey'] as any,
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const tdRef = ref();
|
const tdRef = ref<HTMLTableCellElement>();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (tdRef.value) {
|
if (tdRef.value) {
|
||||||
emit('columnResize', props.columnKey, tdRef.value.offsetWidth);
|
emit('columnResize', props.columnKey, tdRef.value.offsetWidth);
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ import { getColumnsKey } from '../utils/valueUtil';
|
||||||
import MeasureCell from './MeasureCell';
|
import MeasureCell from './MeasureCell';
|
||||||
import BodyRow from './BodyRow';
|
import BodyRow from './BodyRow';
|
||||||
import useFlattenRecords from '../hooks/useFlattenRecords';
|
import useFlattenRecords from '../hooks/useFlattenRecords';
|
||||||
import { defineComponent, toRef } from 'vue';
|
import { defineComponent, ref, toRef } from 'vue';
|
||||||
import { useInjectResize } from '../context/ResizeContext';
|
import { useInjectResize } from '../context/ResizeContext';
|
||||||
import { useInjectTable } from '../context/TableContext';
|
import { useInjectTable } from '../context/TableContext';
|
||||||
import { useInjectBody } from '../context/BodyContext';
|
import { useInjectBody } from '../context/BodyContext';
|
||||||
|
import { useProvideHover } from '../context/HoverContext';
|
||||||
|
|
||||||
export interface BodyProps<RecordType> {
|
export interface BodyProps<RecordType> {
|
||||||
data: RecordType[];
|
data: RecordType[];
|
||||||
|
|
@ -43,7 +44,16 @@ export default defineComponent<BodyProps<any>>({
|
||||||
toRef(props, 'expandedKeys'),
|
toRef(props, 'expandedKeys'),
|
||||||
toRef(props, 'getRowKey'),
|
toRef(props, 'getRowKey'),
|
||||||
);
|
);
|
||||||
|
const startRow = ref(-1);
|
||||||
|
const endRow = ref(-1);
|
||||||
|
useProvideHover({
|
||||||
|
startRow,
|
||||||
|
endRow,
|
||||||
|
onHover: (start, end) => {
|
||||||
|
startRow.value = start;
|
||||||
|
endRow.value = end;
|
||||||
|
},
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
|
@ -56,17 +66,17 @@ export default defineComponent<BodyProps<any>>({
|
||||||
} = props;
|
} = props;
|
||||||
const { onColumnResize } = resizeContext;
|
const { onColumnResize } = resizeContext;
|
||||||
const { prefixCls, getComponent } = tableContext;
|
const { prefixCls, getComponent } = tableContext;
|
||||||
const { fixHeader, horizonScroll, flattenColumns, componentWidth } = bodyContext;
|
const { flattenColumns } = bodyContext;
|
||||||
const WrapperComponent = getComponent(['body', 'wrapper'], 'tbody');
|
const WrapperComponent = getComponent(['body', 'wrapper'], 'tbody');
|
||||||
const trComponent = getComponent(['body', 'row'], 'tr');
|
const trComponent = getComponent(['body', 'row'], 'tr');
|
||||||
const tdComponent = getComponent(['body', 'cell'], 'td');
|
const tdComponent = getComponent(['body', 'cell'], 'td');
|
||||||
|
|
||||||
let rows;
|
let rows;
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
rows = flattenData.value.map((item, index) => {
|
rows = flattenData.value.map((item, idx) => {
|
||||||
const { record, indent } = item;
|
const { record, indent, index: renderIndex } = item;
|
||||||
|
|
||||||
const key = getRowKey(record, index);
|
const key = getRowKey(record, idx);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BodyRow
|
<BodyRow
|
||||||
|
|
@ -74,7 +84,8 @@ export default defineComponent<BodyProps<any>>({
|
||||||
rowKey={key}
|
rowKey={key}
|
||||||
record={record}
|
record={record}
|
||||||
recordKey={key}
|
recordKey={key}
|
||||||
index={index}
|
index={idx}
|
||||||
|
renderIndex={renderIndex}
|
||||||
rowComponent={trComponent}
|
rowComponent={trComponent}
|
||||||
cellComponent={tdComponent}
|
cellComponent={tdComponent}
|
||||||
expandedKeys={expandedKeys}
|
expandedKeys={expandedKeys}
|
||||||
|
|
@ -92,13 +103,10 @@ export default defineComponent<BodyProps<any>>({
|
||||||
expanded
|
expanded
|
||||||
class={`${prefixCls}-placeholder`}
|
class={`${prefixCls}-placeholder`}
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
fixHeader={fixHeader}
|
|
||||||
fixColumn={horizonScroll}
|
|
||||||
horizonScroll={horizonScroll}
|
|
||||||
component={trComponent}
|
component={trComponent}
|
||||||
componentWidth={componentWidth}
|
|
||||||
cellComponent={tdComponent}
|
cellComponent={tdComponent}
|
||||||
colSpan={flattenColumns.length}
|
colSpan={flattenColumns.length}
|
||||||
|
isEmpty
|
||||||
>
|
>
|
||||||
{slots.emptyNode?.()}
|
{slots.emptyNode?.()}
|
||||||
</ExpandedRow>
|
</ExpandedRow>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import classNames from '../../_util/classNames';
|
import classNames from '../../_util/classNames';
|
||||||
import { flattenChildren, isValidElement, parseStyleText } from '../../_util/props-util';
|
import { flattenChildren, isValidElement, parseStyleText } from '../../_util/props-util';
|
||||||
import type { CSSProperties, HTMLAttributes } from 'vue';
|
import type { CSSProperties, TdHTMLAttributes } from 'vue';
|
||||||
import { defineComponent, isVNode, renderSlot } from 'vue';
|
import { computed, defineComponent, isVNode, renderSlot } from 'vue';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
DataIndex,
|
DataIndex,
|
||||||
|
|
@ -17,6 +17,16 @@ import type {
|
||||||
import { getPathValue, validateValue } from '../utils/valueUtil';
|
import { getPathValue, validateValue } from '../utils/valueUtil';
|
||||||
import { useInjectSlots } from '../../table/context';
|
import { useInjectSlots } from '../../table/context';
|
||||||
import { INTERNAL_COL_DEFINE } from '../utils/legacyUtil';
|
import { INTERNAL_COL_DEFINE } from '../utils/legacyUtil';
|
||||||
|
import { useInjectHover } from '../context/HoverContext';
|
||||||
|
import { useInjectSticky } from '../context/StickyContext';
|
||||||
|
import { warning } from '../../vc-util/warning';
|
||||||
|
import type { MouseEventHandler } from '../../_util/EventInterface';
|
||||||
|
|
||||||
|
/** Check if cell is in hover range */
|
||||||
|
function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) {
|
||||||
|
const cellEndRow = cellStartRow + cellRowSpan - 1;
|
||||||
|
return cellStartRow <= endRow && cellEndRow >= startRow;
|
||||||
|
}
|
||||||
|
|
||||||
function isRenderCell<RecordType = DefaultRecordType>(
|
function isRenderCell<RecordType = DefaultRecordType>(
|
||||||
data: RenderedCell<RecordType>,
|
data: RenderedCell<RecordType>,
|
||||||
|
|
@ -27,8 +37,10 @@ function isRenderCell<RecordType = DefaultRecordType>(
|
||||||
export interface CellProps<RecordType = DefaultRecordType> {
|
export interface CellProps<RecordType = DefaultRecordType> {
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
record?: RecordType;
|
record?: RecordType;
|
||||||
/** `record` index. Not `column` index. */
|
/** `column` index is the real show rowIndex */
|
||||||
index?: number;
|
index?: number;
|
||||||
|
/** the index of the record. For the render(value, record, renderIndex) */
|
||||||
|
renderIndex?: number;
|
||||||
dataIndex?: DataIndex;
|
dataIndex?: DataIndex;
|
||||||
customRender?: ColumnType<RecordType>['customRender'];
|
customRender?: ColumnType<RecordType>['customRender'];
|
||||||
component?: CustomizeComponent;
|
component?: CustomizeComponent;
|
||||||
|
|
@ -49,7 +61,7 @@ export interface CellProps<RecordType = DefaultRecordType> {
|
||||||
/** @private Used for `expandable` with nest tree */
|
/** @private Used for `expandable` with nest tree */
|
||||||
appendNode?: any;
|
appendNode?: any;
|
||||||
|
|
||||||
additionalProps?: HTMLAttributes;
|
additionalProps?: TdHTMLAttributes;
|
||||||
|
|
||||||
rowType?: 'header' | 'body' | 'footer';
|
rowType?: 'header' | 'body' | 'footer';
|
||||||
|
|
||||||
|
|
@ -67,6 +79,7 @@ export default defineComponent<CellProps>({
|
||||||
'prefixCls',
|
'prefixCls',
|
||||||
'record',
|
'record',
|
||||||
'index',
|
'index',
|
||||||
|
'renderIndex',
|
||||||
'dataIndex',
|
'dataIndex',
|
||||||
'customRender',
|
'customRender',
|
||||||
'component',
|
'component',
|
||||||
|
|
@ -91,16 +104,46 @@ export default defineComponent<CellProps>({
|
||||||
slots: ['appendNode'],
|
slots: ['appendNode'],
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const contextSlots = useInjectSlots();
|
const contextSlots = useInjectSlots();
|
||||||
|
const { onHover, startRow, endRow } = useInjectHover();
|
||||||
|
const colSpan = computed(() => {
|
||||||
|
return props.colSpan ?? (props.additionalProps?.colspan as number);
|
||||||
|
});
|
||||||
|
const rowSpan = computed(() => {
|
||||||
|
return props.rowSpan ?? (props.additionalProps?.rowspan as number);
|
||||||
|
});
|
||||||
|
const hovering = computed(() => {
|
||||||
|
const { index } = props;
|
||||||
|
return inHoverRange(index, rowSpan.value || 1, startRow.value, endRow.value);
|
||||||
|
});
|
||||||
|
const supportSticky = useInjectSticky();
|
||||||
|
|
||||||
|
// ====================== Hover =======================
|
||||||
|
const onMouseenter = (event: MouseEvent, mergedRowSpan: number) => {
|
||||||
|
const { record, index, additionalProps } = props;
|
||||||
|
if (record) {
|
||||||
|
onHover(index, index + mergedRowSpan - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalProps?.onMouseenter?.(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseleave: MouseEventHandler = event => {
|
||||||
|
const { record, additionalProps } = props;
|
||||||
|
if (record) {
|
||||||
|
onHover(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalProps?.onMouseleave?.(event);
|
||||||
|
};
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
record,
|
record,
|
||||||
index,
|
index,
|
||||||
|
renderIndex,
|
||||||
dataIndex,
|
dataIndex,
|
||||||
customRender,
|
customRender,
|
||||||
component: Component = 'td',
|
component: Component = 'td',
|
||||||
colSpan,
|
|
||||||
rowSpan,
|
|
||||||
fixLeft,
|
fixLeft,
|
||||||
fixRight,
|
fixRight,
|
||||||
firstFixLeft,
|
firstFixLeft,
|
||||||
|
|
@ -135,10 +178,17 @@ export default defineComponent<CellProps>({
|
||||||
value,
|
value,
|
||||||
record,
|
record,
|
||||||
index,
|
index,
|
||||||
|
renderIndex,
|
||||||
column: column.__originColumn__,
|
column: column.__originColumn__,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isRenderCell(renderData)) {
|
if (isRenderCell(renderData)) {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'`columns.customRender` return cell props is deprecated with perf issue, please use `customCell` instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
childNode = renderData.children;
|
childNode = renderData.children;
|
||||||
cellProps = renderData.props;
|
cellProps = renderData.props;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -208,8 +258,8 @@ export default defineComponent<CellProps>({
|
||||||
class: cellClassName,
|
class: cellClassName,
|
||||||
...restCellProps
|
...restCellProps
|
||||||
} = cellProps || {};
|
} = cellProps || {};
|
||||||
const mergedColSpan = cellColSpan !== undefined ? cellColSpan : colSpan;
|
const mergedColSpan = (cellColSpan !== undefined ? cellColSpan : colSpan.value) ?? 1;
|
||||||
const mergedRowSpan = cellRowSpan !== undefined ? cellRowSpan : rowSpan;
|
const mergedRowSpan = (cellRowSpan !== undefined ? cellRowSpan : rowSpan.value) ?? 1;
|
||||||
|
|
||||||
if (mergedColSpan === 0 || mergedRowSpan === 0) {
|
if (mergedColSpan === 0 || mergedRowSpan === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -217,8 +267,8 @@ export default defineComponent<CellProps>({
|
||||||
|
|
||||||
// ====================== Fixed =======================
|
// ====================== Fixed =======================
|
||||||
const fixedStyle: CSSProperties = {};
|
const fixedStyle: CSSProperties = {};
|
||||||
const isFixLeft = typeof fixLeft === 'number';
|
const isFixLeft = typeof fixLeft === 'number' && supportSticky.value;
|
||||||
const isFixRight = typeof fixRight === 'number';
|
const isFixRight = typeof fixRight === 'number' && supportSticky.value;
|
||||||
|
|
||||||
if (isFixLeft) {
|
if (isFixLeft) {
|
||||||
fixedStyle.position = 'sticky';
|
fixedStyle.position = 'sticky';
|
||||||
|
|
@ -251,24 +301,30 @@ export default defineComponent<CellProps>({
|
||||||
title,
|
title,
|
||||||
...restCellProps,
|
...restCellProps,
|
||||||
...additionalProps,
|
...additionalProps,
|
||||||
colSpan: mergedColSpan && mergedColSpan !== 1 ? mergedColSpan : null,
|
colSpan: mergedColSpan !== 1 ? mergedColSpan : null,
|
||||||
rowSpan: mergedRowSpan && mergedRowSpan !== 1 ? mergedRowSpan : null,
|
rowSpan: mergedRowSpan !== 1 ? mergedRowSpan : null,
|
||||||
class: classNames(
|
class: classNames(
|
||||||
cellPrefixCls,
|
cellPrefixCls,
|
||||||
{
|
{
|
||||||
[`${cellPrefixCls}-fix-left`]: isFixLeft,
|
[`${cellPrefixCls}-fix-left`]: isFixLeft && supportSticky.value,
|
||||||
[`${cellPrefixCls}-fix-left-first`]: firstFixLeft,
|
[`${cellPrefixCls}-fix-left-first`]: firstFixLeft && supportSticky.value,
|
||||||
[`${cellPrefixCls}-fix-left-last`]: lastFixLeft,
|
[`${cellPrefixCls}-fix-left-last`]: lastFixLeft && supportSticky.value,
|
||||||
[`${cellPrefixCls}-fix-right`]: isFixRight,
|
[`${cellPrefixCls}-fix-right`]: isFixRight && supportSticky.value,
|
||||||
[`${cellPrefixCls}-fix-right-first`]: firstFixRight,
|
[`${cellPrefixCls}-fix-right-first`]: firstFixRight && supportSticky.value,
|
||||||
[`${cellPrefixCls}-fix-right-last`]: lastFixRight,
|
[`${cellPrefixCls}-fix-right-last`]: lastFixRight && supportSticky.value,
|
||||||
[`${cellPrefixCls}-ellipsis`]: ellipsis,
|
[`${cellPrefixCls}-ellipsis`]: ellipsis,
|
||||||
[`${cellPrefixCls}-with-append`]: appendNode,
|
[`${cellPrefixCls}-with-append`]: appendNode,
|
||||||
[`${cellPrefixCls}-fix-sticky`]: (isFixLeft || isFixRight) && isSticky,
|
[`${cellPrefixCls}-fix-sticky`]:
|
||||||
|
(isFixLeft || isFixRight) && isSticky && supportSticky.value,
|
||||||
|
[`${cellPrefixCls}-row-hover`]: !cellProps && hovering.value,
|
||||||
},
|
},
|
||||||
additionalProps.class,
|
additionalProps.class,
|
||||||
cellClassName,
|
cellClassName,
|
||||||
),
|
),
|
||||||
|
onMouseenter: (e: MouseEvent) => {
|
||||||
|
onMouseenter(e, mergedRowSpan);
|
||||||
|
},
|
||||||
|
onMouseleave,
|
||||||
style: {
|
style: {
|
||||||
...parseStyleText(additionalProps.style as any),
|
...parseStyleText(additionalProps.style as any),
|
||||||
...alignStyle,
|
...alignStyle,
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,12 @@ function ColGroup<RecordType>({ colWidths, columns, columCount }: ColGroupProps<
|
||||||
const additionalProps = column && column[INTERNAL_COL_DEFINE];
|
const additionalProps = column && column[INTERNAL_COL_DEFINE];
|
||||||
|
|
||||||
if (width || additionalProps || mustInsert) {
|
if (width || additionalProps || mustInsert) {
|
||||||
|
const { columnType, ...restAdditionalProps } = additionalProps || {};
|
||||||
cols.unshift(
|
cols.unshift(
|
||||||
<col
|
<col
|
||||||
key={i}
|
key={i}
|
||||||
style={{ width: typeof width === 'number' ? `${width}px` : width }}
|
style={{ width: typeof width === 'number' ? `${width}px` : width }}
|
||||||
{...additionalProps}
|
{...restAdditionalProps}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
mustInsert = true;
|
mustInsert = true;
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,10 @@ export default defineComponent<SummaryCellProps>({
|
||||||
record={null}
|
record={null}
|
||||||
dataIndex={null}
|
dataIndex={null}
|
||||||
align={align}
|
align={align}
|
||||||
|
colSpan={mergedColSpan}
|
||||||
|
rowSpan={rowSpan}
|
||||||
customRender={() => ({
|
customRender={() => ({
|
||||||
children: slots.default?.(),
|
children: slots.default?.(),
|
||||||
props: {
|
|
||||||
colSpan: mergedColSpan,
|
|
||||||
rowSpan,
|
|
||||||
},
|
|
||||||
})}
|
})}
|
||||||
{...fixedInfo}
|
{...fixedInfo}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,9 @@ import VCResizeObserver from '../vc-resize-observer';
|
||||||
import { useProvideTable } from './context/TableContext';
|
import { useProvideTable } from './context/TableContext';
|
||||||
import { useProvideBody } from './context/BodyContext';
|
import { useProvideBody } from './context/BodyContext';
|
||||||
import { useProvideResize } from './context/ResizeContext';
|
import { useProvideResize } from './context/ResizeContext';
|
||||||
import { getDataAndAriaProps } from './utils/legacyUtil';
|
import { useProvideSticky } from './context/StickyContext';
|
||||||
|
import pickAttrs from '../_util/pickAttrs';
|
||||||
|
import { useProvideExpandedRow } from './context/ExpandedRowContext';
|
||||||
|
|
||||||
// Used for conditions cache
|
// Used for conditions cache
|
||||||
const EMPTY_DATA = [];
|
const EMPTY_DATA = [];
|
||||||
|
|
@ -288,6 +290,17 @@ export default defineComponent<TableProps<DefaultRecordType>>({
|
||||||
emit('expandedRowsChange', newExpandedKeys);
|
emit('expandedRowsChange', newExpandedKeys);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Warning if use `expandedRowRender` and nest children in the same time
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
props.expandedRowRender &&
|
||||||
|
mergedData.value.some(record => {
|
||||||
|
return Array.isArray(record?.[mergedChildrenColumnName.value]);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
warning(false, '`expandedRowRender` should not use with nested Table');
|
||||||
|
}
|
||||||
|
|
||||||
const componentWidth = ref(0);
|
const componentWidth = ref(0);
|
||||||
|
|
||||||
const [columns, flattenColumns] = useColumns(
|
const [columns, flattenColumns] = useColumns(
|
||||||
|
|
@ -444,8 +457,11 @@ export default defineComponent<TableProps<DefaultRecordType>>({
|
||||||
};
|
};
|
||||||
|
|
||||||
const triggerOnScroll = () => {
|
const triggerOnScroll = () => {
|
||||||
if (scrollBodyRef.value) {
|
if (horizonScroll.value && scrollBodyRef.value) {
|
||||||
onScroll({ currentTarget: scrollBodyRef.value });
|
onScroll({ currentTarget: scrollBodyRef.value });
|
||||||
|
} else {
|
||||||
|
setPingedLeft(false);
|
||||||
|
setPingedRight(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let timtout;
|
let timtout;
|
||||||
|
|
@ -473,7 +489,7 @@ export default defineComponent<TableProps<DefaultRecordType>>({
|
||||||
});
|
});
|
||||||
|
|
||||||
const [scrollbarSize, setScrollbarSize] = useState(0);
|
const [scrollbarSize, setScrollbarSize] = useState(0);
|
||||||
|
useProvideSticky();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
triggerOnScroll();
|
triggerOnScroll();
|
||||||
|
|
@ -554,10 +570,6 @@ export default defineComponent<TableProps<DefaultRecordType>>({
|
||||||
columns,
|
columns,
|
||||||
flattenColumns,
|
flattenColumns,
|
||||||
tableLayout: mergedTableLayout,
|
tableLayout: mergedTableLayout,
|
||||||
componentWidth,
|
|
||||||
fixHeader,
|
|
||||||
fixColumn,
|
|
||||||
horizonScroll,
|
|
||||||
expandIcon: mergedExpandIcon,
|
expandIcon: mergedExpandIcon,
|
||||||
expandableType,
|
expandableType,
|
||||||
onTriggerExpand,
|
onTriggerExpand,
|
||||||
|
|
@ -568,6 +580,13 @@ export default defineComponent<TableProps<DefaultRecordType>>({
|
||||||
onColumnResize,
|
onColumnResize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useProvideExpandedRow({
|
||||||
|
componentWidth,
|
||||||
|
fixHeader,
|
||||||
|
fixColumn,
|
||||||
|
horizonScroll,
|
||||||
|
});
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
const bodyTable = () => (
|
const bodyTable = () => (
|
||||||
<Body
|
<Body
|
||||||
|
|
@ -773,7 +792,7 @@ export default defineComponent<TableProps<DefaultRecordType>>({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const ariaProps = getDataAndAriaProps(attrs);
|
const ariaProps = pickAttrs(attrs, { aria: true, data: true });
|
||||||
const fullTable = () => (
|
const fullTable = () => (
|
||||||
<div
|
<div
|
||||||
{...ariaProps}
|
{...ariaProps}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const EXPAND_COLUMN = {} as const;
|
||||||
|
|
@ -19,11 +19,7 @@ export interface BodyContextProps<RecordType = DefaultRecordType> {
|
||||||
columns: ColumnsType<RecordType>;
|
columns: ColumnsType<RecordType>;
|
||||||
flattenColumns: readonly ColumnType<RecordType>[];
|
flattenColumns: readonly ColumnType<RecordType>[];
|
||||||
|
|
||||||
componentWidth: number;
|
|
||||||
tableLayout: TableLayout;
|
tableLayout: TableLayout;
|
||||||
fixHeader: boolean;
|
|
||||||
fixColumn: boolean;
|
|
||||||
horizonScroll: boolean;
|
|
||||||
|
|
||||||
indentSize: number;
|
indentSize: number;
|
||||||
expandableType: ExpandableType;
|
expandableType: ExpandableType;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { InjectionKey, Ref } from 'vue';
|
||||||
|
import { inject, provide } from 'vue';
|
||||||
|
|
||||||
|
export interface ExpandedRowProps {
|
||||||
|
componentWidth: Ref<number>;
|
||||||
|
fixHeader: Ref<boolean>;
|
||||||
|
fixColumn: Ref<boolean>;
|
||||||
|
horizonScroll: Ref<boolean>;
|
||||||
|
}
|
||||||
|
export const ExpandedRowContextKey: InjectionKey<ExpandedRowProps> = Symbol('ExpandedRowProps');
|
||||||
|
|
||||||
|
export const useProvideExpandedRow = (props: ExpandedRowProps) => {
|
||||||
|
provide(ExpandedRowContextKey, props);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useInjectExpandedRow = () => {
|
||||||
|
return inject(ExpandedRowContextKey, {} as ExpandedRowProps);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import type { InjectionKey, Ref } from 'vue';
|
||||||
|
import { ref, inject, provide } from 'vue';
|
||||||
|
|
||||||
|
export interface HoverContextProps {
|
||||||
|
startRow: Ref<number>;
|
||||||
|
endRow: Ref<number>;
|
||||||
|
onHover: (start: number, end: number) => void;
|
||||||
|
}
|
||||||
|
export const HoverContextKey: InjectionKey<HoverContextProps> = Symbol('HoverContextProps');
|
||||||
|
|
||||||
|
export const useProvideHover = (props: HoverContextProps) => {
|
||||||
|
provide(HoverContextKey, props);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useInjectHover = () => {
|
||||||
|
return inject(HoverContextKey, {
|
||||||
|
startRow: ref(-1),
|
||||||
|
endRow: ref(-1),
|
||||||
|
onHover() {},
|
||||||
|
} as HoverContextProps);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import isStyleSupport from '../../_util/styleChecker';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const supportSticky = ref(false);
|
||||||
|
export const useProvideSticky = () => {
|
||||||
|
onMounted(() => {
|
||||||
|
supportSticky.value = supportSticky.value || isStyleSupport('position', 'sticky');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useInjectSticky = () => {
|
||||||
|
return supportSticky;
|
||||||
|
};
|
||||||
|
|
@ -12,6 +12,7 @@ import type {
|
||||||
ColumnGroupType,
|
ColumnGroupType,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
import { INTERNAL_COL_DEFINE } from '../utils/legacyUtil';
|
import { INTERNAL_COL_DEFINE } from '../utils/legacyUtil';
|
||||||
|
import { EXPAND_COLUMN } from '../constant';
|
||||||
|
|
||||||
function flatColumns<RecordType>(columns: ColumnsType<RecordType>): ColumnType<RecordType>[] {
|
function flatColumns<RecordType>(columns: ColumnsType<RecordType>): ColumnType<RecordType>[] {
|
||||||
return columns.reduce((list, column) => {
|
return columns.reduce((list, column) => {
|
||||||
|
|
@ -121,8 +122,38 @@ function useColumns<RecordType>(
|
||||||
// Add expand column
|
// Add expand column
|
||||||
const withExpandColumns = computed<ColumnsType<RecordType>>(() => {
|
const withExpandColumns = computed<ColumnsType<RecordType>>(() => {
|
||||||
if (expandable.value) {
|
if (expandable.value) {
|
||||||
const expandColIndex = expandIconColumnIndex.value || 0;
|
let cloneColumns = baseColumns.value.slice();
|
||||||
const prevColumn = baseColumns.value[expandColIndex];
|
|
||||||
|
// >>> Warning if use `expandIconColumnIndex`
|
||||||
|
if (process.env.NODE_ENV !== 'production' && expandIconColumnIndex.value >= 0) {
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'`expandIconColumnIndex` is deprecated. Please use `Table.EXPAND_COLUMN` in `columns` instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// >>> Insert expand column if not exist
|
||||||
|
if (!cloneColumns.includes(EXPAND_COLUMN)) {
|
||||||
|
const expandColIndex = expandIconColumnIndex.value || 0;
|
||||||
|
if (expandColIndex >= 0) {
|
||||||
|
cloneColumns.splice(expandColIndex, 0, EXPAND_COLUMN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// >>> Deduplicate additional expand column
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
cloneColumns.filter(c => c === EXPAND_COLUMN).length > 1
|
||||||
|
) {
|
||||||
|
warning(false, 'There exist more than one `EXPAND_COLUMN` in `columns`.');
|
||||||
|
}
|
||||||
|
const expandColumnIndex = cloneColumns.indexOf(EXPAND_COLUMN);
|
||||||
|
cloneColumns = cloneColumns.filter(
|
||||||
|
(column, index) => column !== EXPAND_COLUMN || index === expandColumnIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
// >>> Check if expand column need to fixed
|
||||||
|
const prevColumn = baseColumns.value[expandColumnIndex];
|
||||||
|
|
||||||
let fixedColumn: FixedType | null;
|
let fixedColumn: FixedType | null;
|
||||||
if ((expandFixed.value === 'left' || expandFixed.value) && !expandIconColumnIndex.value) {
|
if ((expandFixed.value === 'left' || expandFixed.value) && !expandIconColumnIndex.value) {
|
||||||
|
|
@ -140,9 +171,11 @@ function useColumns<RecordType>(
|
||||||
const expandIconValue = expandIcon.value;
|
const expandIconValue = expandIcon.value;
|
||||||
const prefixClsValue = prefixCls.value;
|
const prefixClsValue = prefixCls.value;
|
||||||
const expandRowByClickValue = expandRowByClick.value;
|
const expandRowByClickValue = expandRowByClick.value;
|
||||||
|
// >>> Create expandable column
|
||||||
const expandColumn = {
|
const expandColumn = {
|
||||||
[INTERNAL_COL_DEFINE]: {
|
[INTERNAL_COL_DEFINE]: {
|
||||||
class: `${prefixCls.value}-expand-icon-col`,
|
class: `${prefixCls.value}-expand-icon-col`,
|
||||||
|
columnType: 'EXPAND_COLUMN',
|
||||||
},
|
},
|
||||||
title: '',
|
title: '',
|
||||||
fixed: fixedColumn,
|
fixed: fixedColumn,
|
||||||
|
|
@ -168,14 +201,13 @@ function useColumns<RecordType>(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert expand column in the target position
|
return cloneColumns.map(col => (col === EXPAND_COLUMN ? expandColumn : col));
|
||||||
const cloneColumns = baseColumns.value.slice();
|
|
||||||
if (expandColIndex >= 0) {
|
|
||||||
cloneColumns.splice(expandColIndex, 0, expandColumn);
|
|
||||||
}
|
|
||||||
return cloneColumns;
|
|
||||||
}
|
}
|
||||||
return baseColumns.value;
|
if (process.env.NODE_ENV !== 'production' && baseColumns.value.includes(EXPAND_COLUMN)) {
|
||||||
|
warning(false, '`expandable` is not config but there exist `EXPAND_COLUMN` in `columns`.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseColumns.value.filter(col => col !== EXPAND_COLUMN);
|
||||||
});
|
});
|
||||||
|
|
||||||
const mergedColumns = computed(() => {
|
const mergedColumns = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,14 @@ function flatRecord<T>(
|
||||||
childrenColumnName: string,
|
childrenColumnName: string,
|
||||||
expandedKeys: Set<Key>,
|
expandedKeys: Set<Key>,
|
||||||
getRowKey: GetRowKey<T>,
|
getRowKey: GetRowKey<T>,
|
||||||
|
index: number,
|
||||||
) {
|
) {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
|
|
||||||
arr.push({
|
arr.push({
|
||||||
record,
|
record,
|
||||||
indent,
|
indent,
|
||||||
|
index,
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = getRowKey(record);
|
const key = getRowKey(record);
|
||||||
|
|
@ -30,6 +32,7 @@ function flatRecord<T>(
|
||||||
childrenColumnName,
|
childrenColumnName,
|
||||||
expandedKeys,
|
expandedKeys,
|
||||||
getRowKey,
|
getRowKey,
|
||||||
|
i,
|
||||||
);
|
);
|
||||||
|
|
||||||
arr.push(...tempArr);
|
arr.push(...tempArr);
|
||||||
|
|
@ -56,27 +59,30 @@ export default function useFlattenRecords<T = unknown>(
|
||||||
expandedKeysRef: Ref<Set<Key>>,
|
expandedKeysRef: Ref<Set<Key>>,
|
||||||
getRowKey: Ref<GetRowKey<T>>,
|
getRowKey: Ref<GetRowKey<T>>,
|
||||||
) {
|
) {
|
||||||
const arr: Ref<{ record: T; indent: number }[]> = computed(() => {
|
const arr: Ref<{ record: T; indent: number; index: number }[]> = computed(() => {
|
||||||
const childrenColumnName = childrenColumnNameRef.value;
|
const childrenColumnName = childrenColumnNameRef.value;
|
||||||
const expandedKeys = expandedKeysRef.value;
|
const expandedKeys = expandedKeysRef.value;
|
||||||
const data = dataRef.value;
|
const data = dataRef.value;
|
||||||
if (expandedKeys?.size) {
|
if (expandedKeys?.size) {
|
||||||
const temp: { record: T; indent: number }[] = [];
|
const temp: { record: T; indent: number; index: number }[] = [];
|
||||||
|
|
||||||
// collect flattened record
|
// collect flattened record
|
||||||
for (let i = 0; i < data?.length; i += 1) {
|
for (let i = 0; i < data?.length; i += 1) {
|
||||||
const record = data[i];
|
const record = data[i];
|
||||||
|
|
||||||
temp.push(...flatRecord<T>(record, 0, childrenColumnName, expandedKeys, getRowKey.value));
|
temp.push(
|
||||||
|
...flatRecord<T>(record, 0, childrenColumnName, expandedKeys, getRowKey.value, i),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data?.map(item => {
|
return data?.map((item, index) => {
|
||||||
return {
|
return {
|
||||||
record: item,
|
record: item,
|
||||||
indent: 0,
|
indent: 0,
|
||||||
|
index,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,19 @@
|
||||||
// base rc-table@7.17.2
|
// base rc-table@7.22.2
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import { FooterComponents as Summary, SummaryCell, SummaryRow } from './Footer';
|
import { FooterComponents as Summary, SummaryCell, SummaryRow } from './Footer';
|
||||||
import Column from './sugar/Column';
|
import Column from './sugar/Column';
|
||||||
import ColumnGroup from './sugar/ColumnGroup';
|
import ColumnGroup from './sugar/ColumnGroup';
|
||||||
import { INTERNAL_COL_DEFINE } from './utils/legacyUtil';
|
import { INTERNAL_COL_DEFINE } from './utils/legacyUtil';
|
||||||
|
import { EXPAND_COLUMN } from './constant';
|
||||||
|
|
||||||
export { Summary, Column, ColumnGroup, SummaryCell, SummaryRow, INTERNAL_COL_DEFINE };
|
export {
|
||||||
|
Summary,
|
||||||
|
Column,
|
||||||
|
ColumnGroup,
|
||||||
|
SummaryCell,
|
||||||
|
SummaryRow,
|
||||||
|
INTERNAL_COL_DEFINE,
|
||||||
|
EXPAND_COLUMN,
|
||||||
|
};
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* - onFilterDropdownVisibleChange
|
* - onFilterDropdownVisibleChange
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CSSProperties, HTMLAttributes, Ref } from 'vue';
|
import type { CSSProperties, HTMLAttributes, Ref, TdHTMLAttributes } from 'vue';
|
||||||
|
|
||||||
export type Key = number | string;
|
export type Key = number | string;
|
||||||
|
|
||||||
|
|
@ -106,6 +106,7 @@ export interface ColumnType<RecordType> extends ColumnSharedType<RecordType> {
|
||||||
text: any; // 兼容 V2
|
text: any; // 兼容 V2
|
||||||
record: RecordType;
|
record: RecordType;
|
||||||
index: number;
|
index: number;
|
||||||
|
renderIndex: number;
|
||||||
column: ColumnType<RecordType>;
|
column: ColumnType<RecordType>;
|
||||||
}) => any | RenderedCell<RecordType>;
|
}) => any | RenderedCell<RecordType>;
|
||||||
rowSpan?: number;
|
rowSpan?: number;
|
||||||
|
|
@ -114,7 +115,7 @@ export interface ColumnType<RecordType> extends ColumnSharedType<RecordType> {
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
resizable?: boolean;
|
resizable?: boolean;
|
||||||
customCell?: GetComponentProps<RecordType>;
|
customCell?: GetComponentProps<RecordType>;
|
||||||
/** @deprecated Please use `onCell` instead */
|
/** @deprecated Please use `customCell` instead */
|
||||||
onCellClick?: (record: RecordType, e: MouseEvent) => void;
|
onCellClick?: (record: RecordType, e: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +138,7 @@ export type GetComponentProps<DataType> = (
|
||||||
data: DataType,
|
data: DataType,
|
||||||
index?: number,
|
index?: number,
|
||||||
column?: ColumnType<any>,
|
column?: ColumnType<any>,
|
||||||
) => Omit<HTMLAttributes, 'style'> & { style?: CSSProperties };
|
) => Omit<HTMLAttributes | TdHTMLAttributes, 'style'> & { style?: CSSProperties };
|
||||||
|
|
||||||
// type Component<P> = DefineComponent<P> | FunctionalComponent<P> | string;
|
// type Component<P> = DefineComponent<P> | FunctionalComponent<P> | string;
|
||||||
|
|
||||||
|
|
@ -231,7 +232,9 @@ export interface ExpandableConfig<RecordType> {
|
||||||
onExpandedRowsChange?: (expandedKeys: readonly Key[]) => void;
|
onExpandedRowsChange?: (expandedKeys: readonly Key[]) => void;
|
||||||
defaultExpandAllRows?: boolean;
|
defaultExpandAllRows?: boolean;
|
||||||
indentSize?: number;
|
indentSize?: number;
|
||||||
|
/** @deprecated Please use `EXPAND_COLUMN` in `columns` directly */
|
||||||
expandIconColumnIndex?: number;
|
expandIconColumnIndex?: number;
|
||||||
|
showExpandColumn?: boolean;
|
||||||
expandedRowClassName?: RowClassName<RecordType>;
|
expandedRowClassName?: RowClassName<RecordType>;
|
||||||
childrenColumnName?: string;
|
childrenColumnName?: string;
|
||||||
rowExpandable?: (record: RecordType) => boolean;
|
rowExpandable?: (record: RecordType) => boolean;
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ export default defineComponent<StickyScrollBarProps>({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onContainerScroll = () => {
|
const onContainerScroll = () => {
|
||||||
|
if (!props.scrollBodyRef.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const tableOffsetTop = getOffset(props.scrollBodyRef.value).top;
|
const tableOffsetTop = getOffset(props.scrollBodyRef.value).top;
|
||||||
const tableBottomOffset = tableOffsetTop + props.scrollBodyRef.value.offsetHeight;
|
const tableBottomOffset = tableOffsetTop + props.scrollBodyRef.value.offsetHeight;
|
||||||
const currentClientOffset =
|
const currentClientOffset =
|
||||||
|
|
|
||||||
|
|
@ -9,34 +9,40 @@ export function getExpandableProps<RecordType>(
|
||||||
},
|
},
|
||||||
): ExpandableConfig<RecordType> {
|
): ExpandableConfig<RecordType> {
|
||||||
const { expandable, ...legacyExpandableConfig } = props;
|
const { expandable, ...legacyExpandableConfig } = props;
|
||||||
|
let config: ExpandableConfig<RecordType>;
|
||||||
if (props.expandable !== undefined) {
|
if (props.expandable !== undefined) {
|
||||||
return {
|
config = {
|
||||||
...legacyExpandableConfig,
|
...legacyExpandableConfig,
|
||||||
...expandable,
|
...expandable,
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
[
|
||||||
|
'indentSize',
|
||||||
|
'expandedRowKeys',
|
||||||
|
'defaultExpandedRowKeys',
|
||||||
|
'defaultExpandAllRows',
|
||||||
|
'expandedRowRender',
|
||||||
|
'expandRowByClick',
|
||||||
|
'expandIcon',
|
||||||
|
'onExpand',
|
||||||
|
'onExpandedRowsChange',
|
||||||
|
'expandedRowClassName',
|
||||||
|
'expandIconColumnIndex',
|
||||||
|
'showExpandColumn',
|
||||||
|
].some(prop => prop in props)
|
||||||
|
) {
|
||||||
|
warning(false, 'expanded related props have been moved into `expandable`.');
|
||||||
|
}
|
||||||
|
|
||||||
|
config = legacyExpandableConfig;
|
||||||
|
}
|
||||||
|
if (config.showExpandColumn === false) {
|
||||||
|
config.expandIconColumnIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
return config;
|
||||||
process.env.NODE_ENV !== 'production' &&
|
|
||||||
[
|
|
||||||
'indentSize',
|
|
||||||
'expandedRowKeys',
|
|
||||||
'defaultExpandedRowKeys',
|
|
||||||
'defaultExpandAllRows',
|
|
||||||
'expandedRowRender',
|
|
||||||
'expandRowByClick',
|
|
||||||
'expandIcon',
|
|
||||||
'onExpand',
|
|
||||||
'onExpandedRowsChange',
|
|
||||||
'expandedRowClassName',
|
|
||||||
'expandIconColumnIndex',
|
|
||||||
].some(prop => prop in props)
|
|
||||||
) {
|
|
||||||
warning(false, 'expanded related props have been moved into `expandable`.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return legacyExpandableConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue