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