UNPKG

@extclp/vexip-ui

Version:

A Vue 3 UI library, Highly customizability, full TypeScript, performance pretty good

1 lines 99 kB
{"version":3,"file":"store.mjs","sources":["../../../components/table/store.ts"],"sourcesContent":["import { useCascadedChecked } from '@/components/tree/hooks'\r\n\r\nimport { computed, reactive, watchEffect } from 'vue'\r\n\r\nimport {\r\n boundRange,\r\n createBITree,\r\n debounceMinor,\r\n deepClone,\r\n getLast,\r\n isNull,\r\n mapTree,\r\n sortByProps,\r\n toFalse,\r\n toFixed,\r\n toNumber,\r\n walkTree,\r\n} from '@vexip-ui/utils'\r\nimport { DEFAULT_KEY_FIELD, TABLE_FOOT_PREFIX, TABLE_HEAD_PREFIX, columnTypes } from './symbol'\r\n\r\nimport type { ClassType, LocaleConfig, StyleType } from '@vexip-ui/config'\r\nimport type { TooltipTheme } from '@/components/tooltip'\r\nimport type {\r\n CellSpanResult,\r\n ColumnCellSpanFn,\r\n ColumnGroupWithKey,\r\n ColumnRawWithKey,\r\n ColumnWithKey,\r\n Data,\r\n ExpandRenderFn,\r\n Key,\r\n ParsedFilterOptions,\r\n ParsedTableSorterOptions,\r\n StoreOptions,\r\n StoreState,\r\n SummaryCellSpanFn,\r\n SummaryWithKey,\r\n TableCellPropFn,\r\n TableCellSpanFn,\r\n TableColResizeType,\r\n TableColumnOptions,\r\n TableColumnRawOptions,\r\n TableColumnType,\r\n TableDragColumn,\r\n TableExpandColumn,\r\n TableFilterOptions,\r\n TableFootPropFn,\r\n TableHeadPropFn,\r\n TableKeyConfig,\r\n TableRowPropFn,\r\n TableRowState,\r\n TableSelectionColumn,\r\n TableSorterOptions,\r\n TableSummaryData,\r\n TableSummaryOptions,\r\n} from './symbol'\r\n\r\nconst defaultSummaryData = Object.freeze<TableSummaryData>({\r\n sum: NaN,\r\n min: NaN,\r\n max: NaN,\r\n})\r\n\r\nlet indexId = 1\r\n\r\nfunction getIndexId() {\r\n return `__vxp-table-key-${indexId++}`\r\n}\r\n\r\nfunction defaultIndexLabel(index: number) {\r\n return index + 1\r\n}\r\n\r\nconst COLUMN_DEFAULT_WIDTH = 100\r\nconst COLUMN_DEFAULT_MIN_WIDTH = 100\r\n\r\nexport function useStore(options: StoreOptions) {\r\n const state = reactive({\r\n ...options,\r\n columns: [],\r\n normalColumns: [],\r\n allColumns: [],\r\n summaries: [],\r\n data: [],\r\n dataKey: options.dataKey ?? DEFAULT_KEY_FIELD,\r\n rowData: [],\r\n treeRowData: [],\r\n width: 0,\r\n rightFixedColumns: [],\r\n leftFixedColumns: [],\r\n aboveSummaries: [],\r\n belowSummaries: [],\r\n rowMinHeight: options.rowMinHeight || 36,\r\n rowDraggable: !!options.rowDraggable,\r\n columnMap: new Map(),\r\n rowMap: new Map(),\r\n summaryMap: new Map(),\r\n idMaps: new WeakMap(),\r\n checkedAll: false,\r\n partial: false,\r\n widths: new Map(),\r\n sorters: new Map(),\r\n filters: new Map(),\r\n resized: new Set(),\r\n bodyYScroll: 0,\r\n bodyXScroll: 0,\r\n padTop: 0,\r\n startRow: 0,\r\n endRow: 0,\r\n dragging: false,\r\n heightBITree: null!,\r\n virtualData: [],\r\n totalHeight: options.rowMinHeight * options.data.length,\r\n colResizing: false,\r\n resizeLeft: 0,\r\n cellSpanMap: new Map(),\r\n collapseMap: new Map(),\r\n sidePadding: options.sidePadding || [0, 0],\r\n locked: false,\r\n barScrolling: false,\r\n heightTrigger: 0,\r\n hoveredRowKey: null as Key | null,\r\n }) as StoreState\r\n\r\n setColumns(options.columns)\r\n setSummaries(options.summaries)\r\n setData(options.data)\r\n\r\n const userData = computed(() => {\r\n return typeof state.dataFilter === 'function'\r\n ? state.rowData.filter(row => state.dataFilter(row.data))\r\n : state.rowData\r\n })\r\n const filteredData = computed(() => {\r\n return state.customFilter\r\n ? userData.value\r\n : filterData(state.filters, userData.value, state.singleFilter)\r\n })\r\n const sortedData = computed(() => {\r\n const data = state.customSorter\r\n ? filteredData.value\r\n : sortData(state.sorters, filteredData.value, state.columns, state.singleSorter)\r\n\r\n return data\r\n })\r\n const processedData = computed(() => {\r\n const data = pageData(state.currentPage, state.pageSize, sortedData.value)\r\n\r\n for (let i = 0, len = data.length; i < len; ++i) {\r\n data[i].listIndex = i\r\n data[i].last = i === len - 1\r\n }\r\n\r\n return data\r\n })\r\n const visibleKeys = computed(() => new Set(processedData.value.map(row => row.key)))\r\n const disableCheckRows = computed(() => {\r\n const rowData = processedData.value\r\n const selectionColumn = state.columns.find(\r\n item => (item as TableSelectionColumn).type === 'selection',\r\n ) as TableSelectionColumn | undefined\r\n const disableCheckRows = new Set<Key>()\r\n\r\n if (selectionColumn && typeof selectionColumn.disableRow === 'function') {\r\n const isDisabled = selectionColumn.disableRow\r\n\r\n for (let i = 0, len = rowData.length; i < len; ++i) {\r\n const row = rowData[i]\r\n\r\n if (isDisabled(row.data)) {\r\n disableCheckRows.add(row.key)\r\n }\r\n }\r\n }\r\n\r\n return disableCheckRows\r\n })\r\n const disableExpandRows = computed(() => {\r\n const rowData = processedData.value\r\n const expandColumn = state.columns.find(\r\n item => (item as TableExpandColumn).type === 'expand',\r\n ) as TableExpandColumn | undefined\r\n const disableExpandRows = new Set<Key>()\r\n\r\n if (expandColumn && typeof expandColumn.disableRow === 'function') {\r\n const isDisabled = expandColumn.disableRow\r\n\r\n for (let i = 0, len = rowData.length; i < len; ++i) {\r\n const row = rowData[i]\r\n\r\n if (isDisabled(row.data)) {\r\n disableExpandRows.add(row.key)\r\n }\r\n }\r\n }\r\n\r\n return disableExpandRows\r\n })\r\n const disableDragRows = computed(() => {\r\n const rowData = processedData.value\r\n const dragColumn = state.columns.find(item => (item as TableDragColumn).type === 'drag') as\r\n | TableDragColumn\r\n | undefined\r\n const disableDragRows = new Set<Key>()\r\n\r\n if (dragColumn && typeof dragColumn.disableRow === 'function') {\r\n const isDisabled = dragColumn.disableRow\r\n\r\n for (let i = 0, len = rowData.length; i < len; ++i) {\r\n const row = rowData[i]\r\n\r\n if (isDisabled(row.data)) {\r\n disableDragRows.add(row.key)\r\n }\r\n }\r\n }\r\n\r\n return disableDragRows\r\n })\r\n const usingTree = computed(() => {\r\n return !state.disabledTree && state.rowData.some(row => row.children?.length)\r\n })\r\n const hasDragColumn = computed(() => {\r\n return !!state.columns.find(column => 'type' in column && column.type === 'drag')\r\n })\r\n const rowDragging = computed(() => !!processedData.value.find(row => row.dragging))\r\n const totalWidths = computed(() => getColumnsWidths())\r\n const normalWidths = computed(() => getColumnsWidths(state.normalColumns))\r\n const leftFixedWidths = computed(() => getColumnsWidths(state.leftFixedColumns))\r\n const rightFixedWidths = computed(() => getColumnsWidths(state.rightFixedColumns))\r\n const expandColumn = computed(() => {\r\n return state.columns.find(column => (column as TableExpandColumn).type === 'expand') as\r\n | TableExpandColumn\r\n | undefined\r\n })\r\n const summaryData = computed(() => {\r\n const { columns, summaries, data } = state\r\n const map = new Map<Key, TableSummaryData>()\r\n\r\n if (!summaries.length) return map\r\n\r\n for (const column of columns) {\r\n const key = column.key\r\n\r\n if (column.type || column.noSummary) {\r\n map.set(key, defaultSummaryData)\r\n continue\r\n }\r\n\r\n const accessor =\r\n typeof column.accessor === 'function' ? column.accessor : (data: Data) => data[key]\r\n\r\n let sum = 0\r\n let min = Infinity\r\n let max = -Infinity\r\n let valid = false\r\n\r\n for (let i = 0, len = data.length; i < len; ++i) {\r\n const value = accessor(data[i], i)\r\n const number = parseFloat(value as string)\r\n\r\n if (Number.isNaN(number)) continue\r\n\r\n sum += number\r\n min = Math.min(min, number)\r\n max = Math.max(max, number)\r\n valid = true\r\n }\r\n\r\n valid ? map.set(key, { sum, min, max }) : map.set(key, defaultSummaryData)\r\n }\r\n\r\n return map\r\n })\r\n const topFixedHeights = computed(() => getSummariesHeights(state.aboveSummaries))\r\n const bottomFixedHeights = computed(() => getSummariesHeights())\r\n const indentedColumn = computed(() => {\r\n return state.columns.find(column => !column.type && column.indented)\r\n })\r\n const hasFixedColumn = computed(() => {\r\n return !!(state.leftFixedColumns.length || state.rightFixedColumns.length)\r\n })\r\n\r\n const getters = reactive({\r\n filteredData,\r\n sortedData,\r\n processedData,\r\n visibleKeys,\r\n disableCheckRows,\r\n disableExpandRows,\r\n disableDragRows,\r\n usingTree,\r\n hasDragColumn,\r\n rowDragging,\r\n totalWidths,\r\n normalWidths,\r\n leftFixedWidths,\r\n rightFixedWidths,\r\n expandColumn,\r\n summaryData,\r\n topFixedHeights,\r\n bottomFixedHeights,\r\n indentedColumn,\r\n hasFixedColumn,\r\n })\r\n\r\n const mutations = {\r\n // 这几个个方法被 deep watch 回调\r\n // 需要防止在一个微任务内被多次调用\r\n setColumns: debounceMinor(setColumns),\r\n // setColumns,\r\n setSummaries: debounceMinor(setSummaries),\r\n setData: debounceMinor(setData),\r\n\r\n // 这个方法被大量的 watch 回调,需要防抖\r\n updateTotalHeight: debounceMinor(updateTotalHeight),\r\n\r\n isGroupColumn,\r\n buildSummaryKey,\r\n setColumnProp,\r\n setSummaryProp,\r\n setDataKey,\r\n setCurrentPage,\r\n setPageSize,\r\n setRowClass,\r\n setRowStyle,\r\n setRowAttrs,\r\n setCellClass,\r\n setCellStyle,\r\n setCellAttrs,\r\n setHeadClass,\r\n setHeadStyle,\r\n setHeadAttrs,\r\n setFootClass,\r\n setFootStyle,\r\n setFootAttrs,\r\n setTableWidth,\r\n setRowHeight,\r\n setRowMinHeight,\r\n setCellHeight,\r\n setVirtual,\r\n setRowDraggable,\r\n setBodyYScroll,\r\n setBodyXScroll,\r\n setBorder,\r\n setStripe,\r\n setHighlight,\r\n setRowProp,\r\n setLocale,\r\n setTooltipTheme,\r\n setTooltipWidth,\r\n setSingleSorter,\r\n setSingleFilter,\r\n setDragging,\r\n setKeyConfig,\r\n setDisabledTree,\r\n setNoCascaded,\r\n setColResizable,\r\n setCustomSorter,\r\n setCustomFilter,\r\n setColumnResizing,\r\n setResizeLeft,\r\n setExpandRenderer,\r\n setCellSpan,\r\n setSidePadding,\r\n setBorderWidth,\r\n setDataFilter,\r\n setEllipsis,\r\n setLocked,\r\n setBarScrolling,\r\n setHoveredRowKey,\r\n\r\n handleSort,\r\n clearSort,\r\n handleFilter,\r\n clearFilter,\r\n toggleFilterItemActive,\r\n refreshRowIndex,\r\n handleCheck,\r\n handleCheckAll,\r\n clearCheckAll,\r\n setRenderRows,\r\n handleExpand,\r\n handleDrag,\r\n collectUnderRows,\r\n setTreeExpanded,\r\n getParentRow,\r\n handleColumnResize,\r\n getCurrentData,\r\n createMinRowState,\r\n flatTreeRows,\r\n refreshRowDepth,\r\n triggerHeightChange,\r\n queryRow,\r\n }\r\n\r\n watchEffect(() => {\r\n state.heightBITree = createBITree(\r\n processedData.value.length,\r\n state.rowHeight || state.rowMinHeight,\r\n )\r\n\r\n state.totalHeight = -1\r\n updateTotalHeight()\r\n })\r\n watchEffect(computeCellSpan)\r\n\r\n function triggerHeightChange() {\r\n ++state.heightTrigger\r\n\r\n if (state.heightTrigger >= Number.MAX_SAFE_INTEGER) {\r\n state.heightTrigger = 0\r\n }\r\n }\r\n\r\n function getColumnsWidths(columns = state.columns) {\r\n const widths = state.widths\r\n const combinedWidths: number[] = [0]\r\n\r\n let width = 0\r\n\r\n for (let i = 0, len = columns.length; i < len; ++i) {\r\n const column = columns[i]\r\n const key = column.key\r\n const columnWidth = widths.get(key) || 0\r\n\r\n width += columnWidth\r\n combinedWidths.push(width)\r\n }\r\n\r\n return combinedWidths\r\n }\r\n\r\n function getSummariesHeights(summaries = state.belowSummaries) {\r\n const rowMap = state.rowMap\r\n const heights: number[] = [0]\r\n\r\n let height = 0\r\n\r\n for (let i = 0, len = summaries.length; i < len; ++i) {\r\n const summary = summaries[i]\r\n const key = buildSummaryKey(summary.key)\r\n const row = rowMap.get(key)\r\n\r\n if (row) {\r\n height += row.height || 0\r\n }\r\n\r\n heights.push(height)\r\n }\r\n\r\n return heights\r\n }\r\n\r\n function createMinRowState(key: Key) {\r\n return { key, cellHeights: {}, height: state.rowHeight ?? state.rowMinHeight } as TableRowState\r\n }\r\n\r\n function isGroupColumn(column: any): column is ColumnGroupWithKey {\r\n return !!column.children?.length\r\n }\r\n\r\n function buildColumns(columns: TableColumnRawOptions[]) {\r\n const allColumns: ColumnRawWithKey[][] = []\r\n const baseColumns: ColumnWithKey[] = []\r\n const columnMap = new Map<Key, ColumnRawWithKey>()\r\n const existedTypes = new Set<TableColumnType>()\r\n\r\n const getFixedOrder = (fixed?: boolean | 'left' | 'right') => {\r\n return fixed === true || fixed === 'left' ? -1 : fixed === 'right' ? 1 : 0\r\n }\r\n const build = (\r\n _columns: TableColumnRawOptions[],\r\n fixed?: boolean | 'left' | 'right',\r\n row = 0,\r\n result: ColumnRawWithKey[][] = [],\r\n ) => {\r\n _columns = _columns\r\n .filter(column => !('children' in column) || isGroupColumn(column))\r\n .sort((prev, next) => (prev.order || 0) - (next.order || 0))\r\n .sort((prev, next) => getFixedOrder(prev.fixed) - getFixedOrder(next.fixed))\r\n fixed = fixed === true ? 'left' : fixed\r\n\r\n const columns = _columns as ColumnRawWithKey[]\r\n const rowColumns = result[row] ?? (result[row] = [])\r\n\r\n let index = row > 0 ? result[row - 1].length - 1 : 0\r\n\r\n for (const { ...column } of columns) {\r\n if (!isNull(fixed)) {\r\n column.fixed = fixed\r\n }\r\n\r\n rowColumns[index] = column\r\n\r\n if (isGroupColumn(column)) {\r\n const endIndex = build(column.children, column.fixed, row + 1, result)\r\n\r\n column.key = Symbol('TableColumnGroup')\r\n column.headSpan = endIndex - index\r\n index = endIndex\r\n } else {\r\n const validType = column.type && columnTypes.includes(column.type)\r\n\r\n if (validType) {\r\n if (existedTypes.has(column.type)) {\r\n console.warn(`[vexip-ui:Table] Table has duplicate column with type '${column.type}'`)\r\n }\r\n\r\n existedTypes.add(column.type)\r\n }\r\n\r\n let key = column.key\r\n\r\n if (isNull(key)) {\r\n if (validType) {\r\n key = `__vxp_${column.type}`\r\n } else {\r\n console.warn('[vexip-ui:Table] Table column requires key prop, but missing')\r\n\r\n key = getIndexId()\r\n }\r\n }\r\n\r\n column.key = key\r\n baseColumns.push(column)\r\n index += 1\r\n }\r\n\r\n columnMap.set(column.key, column)\r\n }\r\n\r\n return index\r\n }\r\n\r\n build(columns, undefined, 0, allColumns)\r\n\r\n let length = 0\r\n\r\n for (const rowColumns of allColumns) {\r\n length = Math.max(rowColumns.length, length)\r\n }\r\n\r\n for (const rowColumns of allColumns) {\r\n if (rowColumns.length) {\r\n getLast(rowColumns)!.last = true\r\n }\r\n\r\n rowColumns.length = length\r\n }\r\n\r\n for (let i = 0, rowCount = allColumns.length; i < length; ++i) {\r\n let span = 1\r\n\r\n for (let j = rowCount - 1; j >= 0; --j) {\r\n const column = allColumns[j][i]\r\n\r\n if (column) {\r\n column.colIndex = i\r\n column.rowSpan = span\r\n span = 1\r\n } else {\r\n ++span\r\n }\r\n }\r\n }\r\n\r\n return { allColumns, baseColumns, columnMap }\r\n }\r\n\r\n function setColumns(columns: TableColumnRawOptions[]) {\r\n const { widths, sorters, filters } = state\r\n const { allColumns, baseColumns, columnMap } = buildColumns(columns)\r\n\r\n const normalColumns: ColumnWithKey[] = []\r\n const rightFixedColumns: ColumnWithKey[] = []\r\n const leftFixedColumns: ColumnWithKey[] = []\r\n\r\n for (let i = 0, len = baseColumns.length; i < len; ++i) {\r\n const column = baseColumns[i]\r\n\r\n column.first = false\r\n column.last = false\r\n column.index = i\r\n\r\n if (column.type && columnTypes.includes(column.type)) {\r\n switch (column.type) {\r\n case 'order': {\r\n column.truthIndex = !!column.truthIndex\r\n\r\n if (typeof column.orderLabel !== 'function') {\r\n column.orderLabel = defaultIndexLabel\r\n }\r\n\r\n if (isNull(column.width)) {\r\n column.width = 60\r\n column.minWidth = 60\r\n }\r\n\r\n break\r\n }\r\n case 'selection': {\r\n column.selectionSize = column.selectionSize || 'default'\r\n\r\n if (typeof column.disableRow !== 'function') {\r\n column.disableRow = toFalse\r\n }\r\n\r\n if (isNull(column.width)) {\r\n column.width = 40\r\n column.minWidth = 40\r\n }\r\n\r\n break\r\n }\r\n case 'expand': {\r\n if (typeof column.disableRow !== 'function') {\r\n column.disableRow = toFalse\r\n }\r\n\r\n if (isNull(column.width)) {\r\n column.width = 40\r\n column.minWidth = 40\r\n }\r\n\r\n break\r\n }\r\n case 'drag': {\r\n if (typeof column.disableRow !== 'function') {\r\n column.disableRow = toFalse\r\n }\r\n\r\n if (isNull(column.width)) {\r\n column.width = 40\r\n column.minWidth = 40\r\n }\r\n\r\n break\r\n }\r\n }\r\n\r\n if (!column.key) {\r\n column.key = `__vxp_${column.type}-${i}`\r\n }\r\n } else {\r\n column.type = undefined\r\n }\r\n\r\n // 独立属性解析时注意隔断同对象引用\r\n widths.set(\r\n column.key,\r\n typeof column.width === 'string'\r\n ? COLUMN_DEFAULT_MIN_WIDTH\r\n : Math.round(\r\n boundRange(\r\n column.width || COLUMN_DEFAULT_WIDTH,\r\n column.minWidth || COLUMN_DEFAULT_MIN_WIDTH,\r\n column.maxWidth || Infinity,\r\n ),\r\n ),\r\n )\r\n sorters.set(column.key, parseSorter(column.sorter))\r\n filters.set(column.key, parseFilter(column.filter))\r\n\r\n const fixed = column.fixed\r\n\r\n if (fixed === true || fixed === 'left') {\r\n leftFixedColumns.push(column)\r\n } else if (fixed === 'right') {\r\n rightFixedColumns.push(column)\r\n } else {\r\n normalColumns.push(column)\r\n }\r\n }\r\n\r\n if (state.allColumns.length > allColumns.length) {\r\n for (let i = allColumns.length - 1, len = state.allColumns.length; i < len; ++i) {\r\n state.rowMap.delete(`${TABLE_HEAD_PREFIX}${i}`)\r\n }\r\n }\r\n\r\n for (let i = 0, len = allColumns.length; i < len; ++i) {\r\n const rowKey = `${TABLE_HEAD_PREFIX}${i}`\r\n\r\n state.rowMap.set(rowKey, createMinRowState(rowKey))\r\n }\r\n\r\n state.columnMap = columnMap\r\n state.columns = Array.from(leftFixedColumns).concat(normalColumns, rightFixedColumns)\r\n state.normalColumns = normalColumns\r\n state.allColumns = allColumns\r\n\r\n if (state.columns.length) {\r\n for (const column of state.columns) {\r\n if (!column.type) {\r\n column.first = true\r\n break\r\n }\r\n }\r\n\r\n getLast(state.columns)!.last = true\r\n }\r\n\r\n if (leftFixedColumns.length) {\r\n state.leftFixedColumns = leftFixedColumns\r\n }\r\n\r\n if (rightFixedColumns.length) {\r\n state.rightFixedColumns = rightFixedColumns\r\n }\r\n }\r\n\r\n function setColumnProp(key: Key, prop: string, value: any) {\r\n if (state.columnMap.has(key)) {\r\n ;(state.columnMap.get(key) as any)[prop] = value\r\n }\r\n }\r\n\r\n function buildSummaryKey(key: Key) {\r\n return typeof key === 'symbol' ? key : `${TABLE_FOOT_PREFIX}${key}`\r\n }\r\n\r\n function setSummaries(summaries: TableSummaryOptions[]) {\r\n summaries = Array.from(summaries).sort((prev, next) => {\r\n return (prev.order || 0) - (next.order || 0)\r\n })\r\n\r\n const prevKeys = new Set(state.summaries.map(summary => summary.key))\r\n const aboveSummaries: SummaryWithKey[] = []\r\n const belowSummaries: SummaryWithKey[] = []\r\n const summaryMap = new Map<Key, SummaryWithKey>()\r\n\r\n for (let i = 0, len = summaries.length; i < len; ++i) {\r\n const summary = { ...summaries[i] } as SummaryWithKey\r\n\r\n let key = summary.key\r\n\r\n if (isNull(key)) {\r\n console.error('[vexip-ui:Table] Table summary requires key prop, but missing')\r\n\r\n key = getIndexId()\r\n }\r\n\r\n summary.key = key\r\n ;(summary.above ? aboveSummaries : belowSummaries).push(summary)\r\n\r\n if (!prevKeys.has(summary.key)) {\r\n const rowKey = buildSummaryKey(summary.key)\r\n\r\n state.rowMap.set(rowKey, createMinRowState(rowKey))\r\n }\r\n\r\n prevKeys.delete(summary.key)\r\n summaryMap.set(summary.key, summary)\r\n }\r\n\r\n state.summaries = Array.from(aboveSummaries).concat(belowSummaries)\r\n state.summaryMap = summaryMap\r\n\r\n if (aboveSummaries.length) {\r\n state.aboveSummaries = aboveSummaries\r\n }\r\n\r\n if (belowSummaries.length) {\r\n state.belowSummaries = belowSummaries\r\n }\r\n\r\n if (prevKeys.size) {\r\n for (const key of prevKeys) {\r\n state.rowMap.delete(buildSummaryKey(key))\r\n }\r\n }\r\n }\r\n\r\n function setSummaryProp(key: Key, prop: string, value: any) {\r\n if (state.summaryMap.has(key)) {\r\n ;(state.summaryMap.get(key) as any)[prop] = value\r\n }\r\n }\r\n\r\n function setDataKey(field: string) {\r\n const oldDataKey = state.dataKey\r\n\r\n if (!isNull(field) && oldDataKey !== field) {\r\n const { rowData, idMaps } = state\r\n\r\n state.dataKey = field\r\n\r\n rowData.forEach(row => {\r\n let key = row.data[field] as Key\r\n\r\n if (isNull(key)) {\r\n key = getIndexId()\r\n }\r\n\r\n row.key = key\r\n idMaps.set(row.data, key)\r\n })\r\n }\r\n }\r\n\r\n function collectUnderRows(row: TableRowState, result: TableRowState[] = []) {\r\n if (row.treeExpanded && row.children?.length) {\r\n for (const childRow of row.children) {\r\n result.push(childRow)\r\n collectUnderRows(childRow, result)\r\n }\r\n }\r\n\r\n return result\r\n }\r\n\r\n function setData(data: Data[]) {\r\n const clonedData: TableRowState[] = []\r\n const rowMap = new Map<Key, TableRowState>()\r\n const { allColumns, dataKey, keyConfig, idMaps, disabledTree } = state\r\n const oldDataMap = state.rowMap\r\n const hidden = !!state.virtual\r\n\r\n const {\r\n children: childrenKey,\r\n checked: checkedKey,\r\n height: heightKey,\r\n expanded: expandedKey,\r\n treeExpanded: treeExpandedKey,\r\n } = keyConfig\r\n\r\n for (let i = 0, len = allColumns.length; i < len; ++i) {\r\n const key = `${TABLE_HEAD_PREFIX}${i}`\r\n\r\n rowMap.set(key, oldDataMap.get(key) || createMinRowState(key))\r\n }\r\n\r\n for (const summary of state.summaries) {\r\n const key = buildSummaryKey(summary.key)\r\n\r\n rowMap.set(key, oldDataMap.get(key) || createMinRowState(key))\r\n }\r\n\r\n const parseRow = (origin: Data[], result: TableRowState[], parent?: TableRowState) => {\r\n for (let i = 0, len = origin.length; i < len; ++i) {\r\n const item = origin[i]\r\n\r\n let key = item[dataKey] as Key\r\n\r\n if (isNull(key)) {\r\n key = idMaps.get(item)!\r\n\r\n if (isNull(key)) {\r\n key = getIndexId()\r\n }\r\n }\r\n\r\n let row: TableRowState\r\n\r\n if (oldDataMap.has(key)) {\r\n row = oldDataMap.get(key)!\r\n\r\n const {\r\n [checkedKey]: checked,\r\n [heightKey]: height,\r\n [expandedKey]: expanded,\r\n [treeExpandedKey]: treeExpanded,\r\n } = row.data !== item ? Object.assign(row.data, item) : row.data\r\n\r\n row.checked = !isNull(checked) ? !!checked : row.checked\r\n row.height = !isNull(height) ? toNumber(height) : row.height\r\n row.expanded = !isNull(expanded) ? !!expanded : row.expanded\r\n row.treeExpanded = !isNull(treeExpanded) ? !!treeExpanded : row.treeExpanded\r\n } else {\r\n const {\r\n [checkedKey]: checked,\r\n [heightKey]: height,\r\n [expandedKey]: expanded,\r\n [treeExpandedKey]: treeExpanded,\r\n } = item\r\n\r\n row = {\r\n key,\r\n hidden,\r\n checked: !!checked,\r\n height: toNumber(height),\r\n expanded: !!expanded,\r\n hover: false,\r\n expandHeight: 0,\r\n index: -1,\r\n children: [],\r\n depth: 0,\r\n treeExpanded: !!treeExpanded,\r\n partial: false,\r\n dragging: false,\r\n listIndex: 0,\r\n cellHeights: reactive({}),\r\n last: false,\r\n expandAnimate: false,\r\n data: item,\r\n }\r\n\r\n idMaps.set(item, key)\r\n }\r\n\r\n if (parent) {\r\n row.parent = parent.key\r\n row.depth = parent.depth + 1\r\n }\r\n\r\n row.children = []\r\n\r\n const children = row.data[childrenKey]\r\n children?.length && parseRow(children, row.children, row)\r\n\r\n result.push(row)\r\n rowMap.set(key, row)\r\n }\r\n }\r\n\r\n parseRow(data, clonedData)\r\n\r\n state.rowMap = rowMap\r\n state.treeRowData = clonedData\r\n\r\n if (!disabledTree) {\r\n flatTreeRows()\r\n } else {\r\n state.rowData = clonedData\r\n }\r\n\r\n state.data = data\r\n\r\n refreshRowIndex()\r\n computePartial()\r\n }\r\n\r\n function flatTreeRows() {\r\n if (state.disabledTree) return\r\n\r\n const rowData: TableRowState[] = []\r\n\r\n for (const row of state.treeRowData) {\r\n rowData.push(row)\r\n collectUnderRows(row, rowData)\r\n }\r\n\r\n state.rowData = rowData\r\n }\r\n\r\n function refreshRowDepth() {\r\n walkTree(state.treeRowData, (row, depth) => {\r\n row.depth = depth\r\n })\r\n }\r\n\r\n function setCurrentPage(currentPage: number) {\r\n state.currentPage = currentPage ?? 1\r\n }\r\n\r\n function setPageSize(pageSize: number) {\r\n state.pageSize = pageSize || 0\r\n }\r\n\r\n function setRowClass(rowClass: ClassType | TableRowPropFn<ClassType>) {\r\n state.rowClass = rowClass ?? ''\r\n }\r\n\r\n function setRowStyle(rowStyle: StyleType | TableRowPropFn<StyleType>) {\r\n state.rowStyle = rowStyle ?? ''\r\n }\r\n\r\n function setRowAttrs(rowAttrs: Record<string, any> | TableRowPropFn<Record<string, any>>) {\r\n state.rowAttrs = rowAttrs ?? null!\r\n }\r\n\r\n function setCellClass(cellClass: ClassType | TableCellPropFn<ClassType>) {\r\n state.cellClass = cellClass ?? ''\r\n }\r\n\r\n function setCellStyle(cellStyle: StyleType | TableCellPropFn<StyleType>) {\r\n state.cellStyle = cellStyle ?? ''\r\n }\r\n\r\n function setCellAttrs(cellAttrs: Record<string, any> | TableCellPropFn<Record<string, any>>) {\r\n state.cellAttrs = cellAttrs ?? null!\r\n }\r\n\r\n function setHeadClass(headClass: ClassType | TableHeadPropFn<ClassType>) {\r\n state.headClass = headClass ?? ''\r\n }\r\n\r\n function setHeadStyle(headStyle: StyleType | TableHeadPropFn<StyleType>) {\r\n state.headStyle = headStyle ?? ''\r\n }\r\n\r\n function setHeadAttrs(headAttrs: Record<string, any> | TableHeadPropFn<Record<string, any>>) {\r\n state.headAttrs = headAttrs ?? null!\r\n }\r\n\r\n function setFootClass(footClass: ClassType | TableFootPropFn<ClassType>) {\r\n state.footClass = footClass ?? ''\r\n }\r\n\r\n function setFootStyle(footStyle: StyleType | TableFootPropFn<StyleType>) {\r\n state.footStyle = footStyle ?? ''\r\n }\r\n\r\n function setFootAttrs(footAttrs: Record<string, any> | TableFootPropFn<Record<string, any>>) {\r\n state.footAttrs = footAttrs ?? null!\r\n }\r\n\r\n function setTableWidth(width: number) {\r\n width = toNumber(width)\r\n\r\n const { columns, widths, resized } = state\r\n\r\n const hasWidthColumns: ColumnWithKey[] = []\r\n const flexColumns: ColumnWithKey[] = []\r\n\r\n let flexWidth = width\r\n\r\n for (let i = 0, len = columns.length; i < len; ++i) {\r\n const column = columns[i]\r\n const { minWidth, maxWidth } = column\r\n\r\n if (resized.has(column.key)) {\r\n flexWidth -= widths.get(column.key)!\r\n hasWidthColumns.push(column)\r\n } else if (column.width) {\r\n if (typeof column.width === 'string') {\r\n const percent = boundRange(toNumber(column.width), 0, 100)\r\n\r\n if (percent) {\r\n const fixedWidth = Math.round(\r\n boundRange(\r\n (width * percent) / 100,\r\n minWidth || COLUMN_DEFAULT_MIN_WIDTH,\r\n maxWidth || Infinity,\r\n ),\r\n )\r\n\r\n flexWidth -= fixedWidth\r\n widths.set(column.key, fixedWidth)\r\n hasWidthColumns.push(column)\r\n } else {\r\n flexColumns.push(column)\r\n }\r\n } else {\r\n const width = Math.round(\r\n boundRange(\r\n column.width || COLUMN_DEFAULT_WIDTH,\r\n minWidth || COLUMN_DEFAULT_MIN_WIDTH,\r\n maxWidth || Infinity,\r\n ),\r\n )\r\n\r\n flexWidth -= width\r\n widths.set(column.key, width)\r\n hasWidthColumns.push(column)\r\n }\r\n } else {\r\n flexColumns.push(column)\r\n }\r\n }\r\n\r\n const flexColumnCount = flexColumns.length\r\n const flexWidths = distributeWidths(flexColumns, flexWidth)\r\n\r\n let usedWidth = 0\r\n\r\n for (let i = 0; i < flexColumnCount; ++i) {\r\n const column = flexColumns[i]\r\n const width = Math[i % 2 ? 'ceil' : 'floor'](flexWidths[i])\r\n\r\n if (i < flexColumnCount - 1) {\r\n usedWidth += width\r\n }\r\n\r\n widths.set(column.key, width)\r\n }\r\n\r\n if (flexColumnCount && flexWidth >= usedWidth + getLast(flexWidths)!) {\r\n widths.set(getLast(flexColumns)!.key, flexWidth - usedWidth)\r\n }\r\n\r\n state.width = width\r\n }\r\n\r\n function distributeWidths(columns: ColumnWithKey[], totalWidth: number): number[] {\r\n const count = columns.length\r\n const baseWidth = Math.max(totalWidth / count, COLUMN_DEFAULT_WIDTH)\r\n\r\n const widths = columns.map(col => {\r\n let w = baseWidth\r\n if (col.minWidth != null) w = Math.max(w, col.minWidth)\r\n if (col.maxWidth != null) w = Math.min(w, col.maxWidth)\r\n return w\r\n })\r\n\r\n const currentTotal = widths.reduce((a, b) => a + b, 0)\r\n let delta = totalWidth - currentTotal\r\n\r\n const canGrow = (i: number) => columns[i].maxWidth == null || widths[i] < columns[i].maxWidth!\r\n const canShrink = (i: number) => columns[i].minWidth == null || widths[i] > columns[i].minWidth!\r\n\r\n const epsilon = 0.1\r\n let adjusted = false\r\n\r\n while (Math.abs(delta) > epsilon) {\r\n const adjustableIndices = widths\r\n .map((_, i) => {\r\n if (delta > 0 && canGrow(i)) return i\r\n if (delta < 0 && canShrink(i)) return i\r\n return -1\r\n })\r\n .filter(i => i !== -1)\r\n\r\n if (adjustableIndices.length === 0) {\r\n adjusted = false\r\n break\r\n }\r\n\r\n const adjustment = delta / adjustableIndices.length\r\n for (const i of adjustableIndices) {\r\n const old = widths[i]\r\n let next = old + adjustment\r\n\r\n if (columns[i].minWidth != undefined) next = Math.max(next, columns[i].minWidth!)\r\n if (columns[i].maxWidth != undefined) next = Math.min(next, columns[i].maxWidth!)\r\n\r\n delta -= next - old\r\n widths[i] = next\r\n }\r\n\r\n adjusted = true\r\n }\r\n\r\n // delta > 0 且无法再调整时,强行补给最后一列\r\n if (!adjusted && delta > epsilon) {\r\n widths[count - 1] += delta\r\n }\r\n\r\n return widths\r\n }\r\n\r\n function setRowHeight(height: number) {\r\n state.rowHeight = height\r\n }\r\n\r\n function setRowMinHeight(height: number) {\r\n state.rowMinHeight = height\r\n }\r\n\r\n function setCellHeight(rowKey: Key, columnKey: Key, height: number) {\r\n if (!isNull(height) && state.rowMap.has(rowKey)) {\r\n state.rowMap.get(rowKey)!.cellHeights[columnKey] = height\r\n }\r\n }\r\n\r\n function setRowDraggable(draggable: boolean) {\r\n state.rowDraggable = !!draggable\r\n }\r\n\r\n function setBodyYScroll(scroll: number) {\r\n state.bodyYScroll = scroll\r\n }\r\n\r\n function setBodyXScroll(scroll: number) {\r\n state.bodyXScroll = scroll\r\n }\r\n\r\n function setBorder(able: boolean) {\r\n state.border = !!able\r\n }\r\n\r\n function setStripe(able: boolean) {\r\n state.stripe = !!able\r\n }\r\n\r\n function setHighlight(able: boolean) {\r\n state.highlight = !!able\r\n }\r\n\r\n function setVirtual(virtual: boolean) {\r\n state.virtual = !!virtual\r\n }\r\n\r\n function setRowProp(key: Key, prop: Exclude<keyof TableRowState, 'key'>, value: any) {\r\n const row = state.rowMap.get(key)\r\n\r\n if (row && row[prop] !== value) {\r\n ;(row as any)[prop] = value\r\n }\r\n }\r\n\r\n function setLocale(locale: LocaleConfig['table']) {\r\n state.locale = locale\r\n }\r\n\r\n function setTooltipTheme(theme: TooltipTheme) {\r\n state.tooltipTheme = theme\r\n }\r\n\r\n function setTooltipWidth(theme: number | string) {\r\n state.tooltipWidth = theme\r\n }\r\n\r\n function setSingleSorter(able: boolean) {\r\n state.singleSorter = !!able\r\n }\r\n\r\n function setSingleFilter(able: boolean) {\r\n state.singleFilter = !!able\r\n }\r\n\r\n function setDragging(dragging: boolean) {\r\n state.dragging = !!dragging\r\n }\r\n\r\n function setKeyConfig(keyConfig: Required<TableKeyConfig>) {\r\n state.keyConfig = keyConfig\r\n }\r\n\r\n function setDisabledTree(disabled: boolean) {\r\n state.disabledTree = !!disabled\r\n }\r\n\r\n function setNoCascaded(noCascaded: boolean) {\r\n state.noCascaded = !!noCascaded\r\n }\r\n\r\n function setColResizable(resizable: boolean | TableColResizeType) {\r\n state.colResizable = resizable === true ? 'lazy' : resizable\r\n }\r\n\r\n function setCustomSorter(able: boolean) {\r\n state.customSorter = !!able\r\n }\r\n\r\n function setCustomFilter(able: boolean) {\r\n state.customFilter = !!able\r\n }\r\n\r\n function setColumnResizing(resizing: boolean) {\r\n state.colResizing = !!resizing\r\n }\r\n\r\n function setResizeLeft(left: number) {\r\n state.resizeLeft = left\r\n }\r\n\r\n function setExpandRenderer(renderer: ExpandRenderFn | null) {\r\n state.expandRenderer = renderer\r\n }\r\n\r\n function setCellSpan(spanFn: TableCellSpanFn | null) {\r\n state.cellSpan = spanFn\r\n }\r\n\r\n function setSidePadding(padding: number | number[]) {\r\n state.sidePadding = Array.isArray(padding) ? padding : [padding, padding]\r\n }\r\n\r\n function setBorderWidth(width: number) {\r\n state.borderWidth = Math.max(width, 0)\r\n }\r\n\r\n function setDataFilter(filter: (data: Data) => boolean) {\r\n state.dataFilter = filter\r\n }\r\n\r\n function setEllipsis(ellipsis: boolean) {\r\n state.ellipsis = ellipsis\r\n }\r\n\r\n function setLocked(locked: boolean) {\r\n state.locked = locked\r\n }\r\n\r\n function setBarScrolling(scrolling: boolean) {\r\n state.barScrolling = scrolling\r\n }\r\n\r\n function setHoveredRowKey(key: Key | null) {\r\n if (state.hoveredRowKey !== key) {\r\n const prevHoveredRow = state.hoveredRowKey && state.rowMap.get(state.hoveredRowKey)\r\n const newHoveredRow = key && state.rowMap.get(key)\r\n\r\n if (prevHoveredRow) {\r\n prevHoveredRow.hover = false\r\n }\r\n\r\n if (newHoveredRow) {\r\n newHoveredRow.hover = true\r\n }\r\n }\r\n\r\n state.hoveredRowKey = key\r\n }\r\n\r\n function handleSort(key: Key, type: ParsedTableSorterOptions['type']) {\r\n if (state.sorters.has(key)) {\r\n if (state.singleSorter && type) {\r\n clearSort()\r\n }\r\n\r\n state.sorters.get(key)!.type = type\r\n }\r\n }\r\n\r\n function clearSort() {\r\n const sorters = state.sorters\r\n\r\n for (const sorter of sorters.values()) {\r\n sorter.type = null\r\n }\r\n }\r\n\r\n function handleFilter(key: Key, active: ParsedFilterOptions['active']) {\r\n if (state.filters.has(key)) {\r\n if (state.singleFilter && (Array.isArray(active) ? active.length : active)) {\r\n clearFilter()\r\n }\r\n\r\n state.filters.get(key)!.active = Array.isArray(active) ? Array.from(active) : active\r\n }\r\n }\r\n\r\n function clearFilter() {\r\n const filters = state.filters\r\n\r\n for (const filter of filters.values()) {\r\n filter.active = null\r\n\r\n for (const option of filter.options) {\r\n option.active = false\r\n }\r\n }\r\n }\r\n\r\n const { updateCheckedUpward, updateCheckedDown } = useCascadedChecked({\r\n getNode: key => state.rowMap.get(key),\r\n disableNode: row => disableCheckRows.value.has(row.key),\r\n })\r\n\r\n function computeChecked(key: Key) {\r\n const { rowMap, rowData } = state\r\n const { disableCheckRows } = getters\r\n\r\n if (!rowMap.has(key)) return\r\n\r\n const rowList = [rowMap.get(key)!].concat(\r\n // 需要包含被禁用且被勾选的节点\r\n rowData.filter(row => disableCheckRows.has(row.key) && row.checked),\r\n )\r\n\r\n for (let i = 0, len = rowList.length; i < len; ++i) {\r\n updateCheckedUpward(rowList[i].key)\r\n updateCheckedDown(rowList[i].key)\r\n }\r\n }\r\n\r\n function handleCheck(key: Key, checked: boolean, single = false) {\r\n const { rowMap, noCascaded } = state\r\n const { disableCheckRows } = getters\r\n const row = rowMap.get(key)\r\n\r\n if (!row) return\r\n\r\n if (single) {\r\n clearCheckAll(true)\r\n row.checked = !!checked\r\n }\r\n\r\n if (!disableCheckRows.has(key)) {\r\n row.checked = !!checked\r\n row.partial = false\r\n }\r\n\r\n !noCascaded && computeChecked(key)\r\n computePartial()\r\n }\r\n\r\n function handleCheckAll() {\r\n const { rowData, checkedAll } = state\r\n const { disableCheckRows } = getters\r\n\r\n let checked = !checkedAll\r\n\r\n // 阻断 disabled 元素对全选的影响\r\n if (disableCheckRows.size) {\r\n // 由于被禁用的元素不可被操作,如果存在被禁用的元素且该状态为未被选中,则全选时仍然是 partial 状态\r\n // 假设除了禁用的元素,其余元素均为选中状态(此时对于用户来说属于已经全选,点击的期望是取消全选)\r\n let partialCheckedAll = true\r\n\r\n for (const row of rowData) {\r\n // 检查是否存在非禁用的且未被选中的元素(如有则证明现在不是全选,用户点击的期望是进行全选)\r\n if (!disableCheckRows.has(row.key) && !row.checked) {\r\n partialCheckedAll = false\r\n\r\n break\r\n }\r\n }\r\n\r\n checked = !partialCheckedAll\r\n }\r\n\r\n for (const row of rowData) {\r\n if (!disableCheckRows.has(row.key)) {\r\n row.checked = checked\r\n }\r\n }\r\n\r\n state.checkedAll = checked\r\n state.partial = false\r\n\r\n computePartial()\r\n }\r\n\r\n function clearCheckAll(includeDisabled = false) {\r\n const { rowData } = state\r\n const { disableCheckRows } = getters\r\n\r\n for (const row of rowData) {\r\n if (includeDisabled || !disableCheckRows.has(row.key)) {\r\n row.checked = false\r\n }\r\n\r\n if (includeDisabled) {\r\n row.partial = false\r\n }\r\n }\r\n\r\n state.checkedAll = false\r\n state.partial = false\r\n\r\n !includeDisabled && computePartial()\r\n }\r\n\r\n function computePartial() {\r\n const data = state.rowData\r\n\r\n let hasChecked = false\r\n let hasNotChecked = false\r\n let partial = false\r\n\r\n for (let i = 0, len = data.length; i < len; ++i) {\r\n const row = data[i]\r\n\r\n if (row.checked) {\r\n hasChecked = true\r\n } else {\r\n hasNotChecked = true\r\n }\r\n\r\n if (hasChecked && hasNotChecked) {\r\n partial = true\r\n\r\n break\r\n }\r\n }\r\n\r\n if (hasChecked && !partial) {\r\n state.checkedAll = true\r\n } else {\r\n state.checkedAll = false\r\n }\r\n\r\n state.partial = partial\r\n }\r\n\r\n function setRenderRows(start: number, end: number, force = false) {\r\n const { startRow, endRow, heightBITree, virtualData } = state\r\n\r\n if (!force && start === startRow && end === endRow) return\r\n\r\n const { processedData } = getters\r\n\r\n if (!processedData.length) {\r\n virtualData.length = 0\r\n return\r\n }\r\n\r\n const prevData = new Set([...virtualData])\r\n const added: TableRowState[] = []\r\n const removed: TableRowState[] = []\r\n\r\n for (let i = 0, len = processedData.length; i < len; ++i) {\r\n const data = processedData[i]\r\n\r\n data.hidden = !(i >= start && i < end)\r\n\r\n if (data.hidden) {\r\n data.hover = false\r\n\r\n if (prevData.has(data)) {\r\n removed.push(data)\r\n }\r\n } else if (!prevData.has(data)) {\r\n added.push(data)\r\n }\r\n\r\n prevData.delete(data)\r\n }\r\n\r\n removed.push(...prevData)\r\n\r\n const length = Math.min(added.length, removed.length)\r\n\r\n for (let i = 0; i < length; ++i) {\r\n virtualData[virtualData.indexOf(removed[i])] = added[i]\r\n }\r\n\r\n if (added.length > removed.length) {\r\n virtualData.push(...added.slice(length))\r\n } else if (added.length < removed.length) {\r\n state.virtualData = virtualData.filter(data => !removed.includes(data))\r\n }\r\n\r\n state.padTop = heightBITree?.sum(start) ?? 0\r\n state.startRow = start\r\n state.endRow = end\r\n }\r\n\r\n function handleExpand(key: Key, expanded: boolean) {\r\n const { rowMap } = state\r\n const { disableExpandRows } = getters\r\n\r\n if (rowMap.has(key) && !disableExpandRows.has(key)) {\r\n rowMap.get(key)!.expanded = !!expanded\r\n }\r\n }\r\n\r\n function handleDrag(key: Key, dragging: boolean) {\r\n const { rowMap } = state\r\n const { disableDragRows } = getters\r\n\r\n if (rowMap.has(key) && !disableDragRows.has(key)) {\r\n rowMap.get(key)!.dragging = !!dragging\r\n }\r\n }\r\n\r\n function setTreeExpanded(key: Key, expanded: boolean) {\r\n if (!usingTree.value) return\r\n\r\n const { rowMap, rowData, virtual } = state\r\n const row = rowMap.get(key)\r\n\r\n if (!row?.children?.length) return\r\n\r\n const underRows = collectUnderRows({ ...row, treeExpanded: true })\r\n\r\n if (expanded) {\r\n rowData.splice(row.index + 1, 0, ...underRows)\r\n } else {\r\n rowData.splice(row.index + 1, underRows.length)\r\n }\r\n\r\n row.treeExpanded = !!expanded\r\n\r\n refreshRowIndex()\r\n virtual && setRenderRows(state.startRow, state.endRow, true)\r\n }\r\n\r\n function toggleFilterItemActive(options: {\r\n key: Key,\r\n value: number | string | null,\r\n active?: boolean,\r\n disableOthers?: boolean,\r\n }) {\r\n const { key, value, active = false, disableOthers = false } = options\r\n\r\n if (state.filters.has(key)) {\r\n const filterOptions = state.filters.get(key)!.options\r\n\r\n if (disableOthers) {\r\n for (let i = 0, len = filterOptions.length; i < len; ++i) {\r\n filterOptions[i].active = false\r\n }\r\n }\r\n\r\n const item = filterOptions.find(item => item.value === value)\r\n\r\n if (item) {\r\n item.active = active\r\n }\r\n }\r\n }\r\n\r\n function refreshRowIndex() {\r\n const data = state.rowData\r\n\r\n for (let i = 0, len = data.length; i < len; ++i) {\r\n data[i].index = i\r\n }\r\n }\r\n\r\n function updateTotalHeight() {\r\n const { heightBITree } = state\r\n\r\n if (heightBITree) {\r\n state.totalHeight = heightBITree.sum() ?? 0\r\n } else {\r\n state.totalHeight = 0\r\n }\r\n }\r\n\r\n function parseSorter(sorter: boolean | TableSorterOptions = false): ParsedTableSorterOptions {\r\n const raw = typeof sorter === 'boolean' ? { able: sorter } : sorter\r\n const { able = true, type = null, order = 0, method = null } = raw\r\n\r\n return { able, type, order, method }\r\n }\r\n\r\n function parseFilter(filter?: TableFilterOptions | null): ParsedFilterOptions {\r\n filter = filter || { able: false, options: [] }\r\n\r\n const {\r\n able = true,\r\n custom = false,\r\n multiple = false,\r\n active = null,\r\n method = null,\r\n meta,\r\n } = filter\r\n // 防止内部变化触发 deep watch\r\n const options = deepClone(filter.options ?? [])\r\n const formattedOptions = []\r\n\r\n for (let i = 0, len = options.length; i < len; ++i) {\r\n const item = options[i]\r\n const option = typeof item === 'string' ? { value: item } : { ...item }\r\n\r\n option.label = option.label ?? option.value.toString()\r\n\r\n let isActive = false\r\n\r\n if (multiple && Array.isArray(active)) {\r\n isActive = active.includes(option.value)\r\n } else if (!isNull(active)) {\r\n isActive = Object.is(option.value, active)\r\n }\r\n\r\n option.active = isActive\r\n\r\n formattedOptions.push(option as { value: string | number, label: string, active: boolean })\r\n }\r\n\r\n return { able, custom, meta, options: formattedOptions, multiple, active, method }\r\n }\r\n\r\n function filterData(\r\n filters: Map<Key, ParsedFilterOptions>,\r\n data: TableRowState[],\r\n isSingle: boolean,\r\n ) {\r\n const usedFilter: ParsedFilterOptions[] = []\r\n const usedData: TableRowState[] = []\r\n\r\n for (const filter of filters.values()) {\r\n const { able, active, method } = filter\r\n\r\n if (able && active && typeof method === 'function') {\r\n usedFilter.push(filter)\r\n\r\n if (isSingle) break\r\n }\r\n }\r\n\r\n const usedFilterCount = usedFilter.length\r\n\r\n for (let i = 0, len = data.length; i < len; ++i) {\r\n const row = data[i]\r\n\r\n let isFilter = true\r\n\r\n for (let j = 0; j < usedFilterCount; j++) {\r\n const { active, method } = usedFilter[j]\r\n\r\n isFilter = method!(active! as any, row.data)\r\n\r\n if (!isFilter) {\r\n break\r\n }\r\n }\r\n\r\n if (isFilter) {\r\n usedData.push(row)\r\n }\r\n }\r\n\r\n return usedData\r\n }\r\n\r\n function sortData(\r\n sorters: Map<Key, ParsedTableSorterOptions>,\r\n data: TableRowState[],\r\n columns: TableColumnOptions[],\r\n isSingle: boolean,\r\n ) {\r\n const usedSorter = []\r\n\r\n for (const [_key, sorter] of sorters) {\r\n const key = _key as keyof TableRowState\r\n const { able, type, order, method } = sorter\r\n\r\n if (able && type) {\r\n const column = columns.find(item => item.key === key)\r\n const accessor = column?.accessor\r\n\r\n usedSorter.push({\r\n able,\r\n key,\r\n order,\r\n type,\r\n method: method ?? undefined,\r\n accessor(row: TableRowState) {\r\n if (typeof accessor === 'function') {\r\n return accessor(row.data, row.index)\r\n }\r\n\r\n return row.data[key]\r\n },\r\n })\r\n\r\n if (isSingle) break\r\n }\r\n }\r\n\r\n // 多列排序优先级\r\n usedSorter.sort((prev, next) => prev.order - next.order)\r\n\r\n return sortByProps(data, usedSorter)\r\n }\r\n\r\n function pageData(currentPage: number, pageSize: number, data: TableRowState[]) {\r\n return pageSize > 0 ? data.slice((currentPage - 1) * pageSize, currentPage * pageSize) : data\r\n }\r\n\r\n function getParentRow(key: Key) {\r\n const { rowMap } = state\r\n const row = rowMap.get(key)\r\n\r\n if (!isNull(row?.parent)) {\r\n return rowMap.get(row!.parent) ?? null\r\n }\r\n\r\n return null\r\n }\r\n\r\n let lastColumnWidth: number | undefined\r\n\r\n function handleColumnResize(keys: Key[], newWidth: number) {\r\n const { resized, widths, columns, columnMap, width: tableWidth } = state\r\n const length = keys.length\r\n\r\n if (!columns.length || !length) return\r\n\r\n const deltaWidth = newWidth / length\r\n const la