diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..af5adff9 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged \ No newline at end of file diff --git a/package.json b/package.json index 5efa36bb..1c544296 100644 --- a/package.json +++ b/package.json @@ -34,49 +34,54 @@ "compile": "father build", "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js", "now-build": "npm run build", - "prepare": "dumi setup", + "prepare": "husky", "prepublishOnly": "npm run compile && np --yolo --no-publish --any-branch", "prettier": "prettier '{src,tests}/**/*.{ts,tsx}' 'tests/**/*.js' --write", "start": "dumi dev", "test": "rc-test" }, + "lint-staged": { + "*": "prettier --write --ignore-unknown" + }, "dependencies": { "@babel/runtime": "^7.25.7", - "classnames": "2.x", - "rc-select": "~14.15.0", - "rc-tree": "~5.9.0", + "classnames": "^2.5.1", + "rc-select": "~14.15.2", + "rc-tree": "~5.10.0", "rc-util": "^5.43.0" }, "devDependencies": { - "@rc-component/father-plugin": "^1.0.0", - "@rc-component/trigger": "^1.5.0", - "@testing-library/react": "^12.0.0", - "@types/jest": "^29.5.12", + "@rc-component/father-plugin": "^1.1.0", + "@rc-component/trigger": "^1.18.3", + "@testing-library/react": "^12.1.5", + "@types/jest": "^29.5.13", "@types/node": "^22.7.5", "@types/react": "^18.3.11", - "@types/react-dom": "^18.2.19", - "@types/warning": "^3.0.0", + "@types/react-dom": "^18.3.1", + "@types/warning": "^3.0.3", "@umijs/fabric": "^4.0.1", + "cheerio": "1.0.0-rc.12", "cross-env": "^7.0.3", "dumi": "^2.4.12", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.8", "enzyme-to-json": "^3.6.2", - "eslint": "^8.56.0", - "eslint-plugin-jest": "^27.6.0", + "eslint": "^8.57.1", + "eslint-plugin-jest": "^27.9.0", "eslint-plugin-unicorn": "^56.0.0", "father": "^4.5.0", "glob": "^11.0.0", + "husky": "^9.1.6", + "lint-staged": "^15.2.10", "np": "^10.0.7", "prettier": "^3.3.3", "rc-dialog": "^9.6.0", "rc-field-form": "^2.4.0", "rc-test": "^7.1.0", "rc-virtual-list": "^3.14.8", - "react": "16.x", - "react-dom": "16.x", - "typescript": "^5.6.3", - "cheerio": "1.0.0-rc.12" + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.6.3" }, "peerDependencies": { "react": "*", diff --git a/src/LegacyContext.tsx b/src/LegacyContext.tsx index 61347e6f..c90ada70 100644 --- a/src/LegacyContext.tsx +++ b/src/LegacyContext.tsx @@ -1,26 +1,26 @@ import * as React from 'react'; import type { DataEntity, IconType } from 'rc-tree/lib/interface'; -import type { Key, LegacyDataNode, RawValueType } from './interface'; +import type { LegacyDataNode, SafeKey } from './interface'; interface LegacyContextProps { checkable: boolean | React.ReactNode; - checkedKeys: Key[]; - halfCheckedKeys: Key[]; - treeExpandedKeys: Key[]; - treeDefaultExpandedKeys: Key[]; - onTreeExpand: (keys: Key[]) => void; + checkedKeys: SafeKey[]; + halfCheckedKeys: SafeKey[]; + treeExpandedKeys: SafeKey[]; + treeDefaultExpandedKeys: SafeKey[]; + onTreeExpand: (keys: SafeKey[]) => void; treeDefaultExpandAll: boolean; treeIcon: IconType; showTreeIcon: boolean; switcherIcon: IconType; treeLine: boolean; treeNodeFilterProp: string; - treeLoadedKeys: Key[]; + treeLoadedKeys: SafeKey[]; treeMotion: any; loadData: (treeNode: LegacyDataNode) => Promise; - onTreeLoad: (loadedKeys: Key[]) => void; + onTreeLoad: (loadedKeys: SafeKey[]) => void; - keyEntities: Record>; + keyEntities: Record>; } const LegacySelectContext = React.createContext(null); diff --git a/src/OptionList.tsx b/src/OptionList.tsx index 2d627c1d..4e87be23 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -8,7 +8,7 @@ import useMemo from 'rc-util/lib/hooks/useMemo'; import * as React from 'react'; import LegacyContext from './LegacyContext'; import TreeSelectContext from './TreeSelectContext'; -import type { Key, TreeDataNode } from './interface'; +import type { SafeKey, TreeDataNode } from './interface'; import { getAllKeys, isCheckDisabled } from './utils/valueUtil'; const HIDDEN_STYLE = { @@ -23,7 +23,7 @@ const HIDDEN_STYLE = { }; interface TreeEventInfo { - node: { key: Key }; + node: { key: SafeKey }; selected?: boolean; checked?: boolean; } @@ -77,7 +77,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, ); // ========================== Active ========================== - const [activeKey, setActiveKey] = React.useState(null); + const [activeKey, setActiveKey] = React.useState(null); const activeEntity = keyEntities[activeKey]; // ========================== Values ========================== @@ -112,8 +112,8 @@ const OptionList: React.ForwardRefRenderFunction = (_, }; // =========================== Keys =========================== - const [expandedKeys, setExpandedKeys] = React.useState(treeDefaultExpandedKeys); - const [searchExpandedKeys, setSearchExpandedKeys] = React.useState(null); + const [expandedKeys, setExpandedKeys] = React.useState(treeDefaultExpandedKeys); + const [searchExpandedKeys, setSearchExpandedKeys] = React.useState(null); const mergedExpandedKeys = React.useMemo(() => { if (treeExpandedKeys) { @@ -129,7 +129,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchValue]); - const onInternalExpand = (keys: Key[]) => { + const onInternalExpand = (keys: SafeKey[]) => { setExpandedKeys(keys); setSearchExpandedKeys(keys); @@ -143,7 +143,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, event.preventDefault(); }; - const onInternalSelect = (__: React.Key[], info: TreeEventInfo) => { + const onInternalSelect = (__: SafeKey[], info: TreeEventInfo) => { const { node } = info; if (checkable && isCheckDisabled(node)) { diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx index 53f30341..3afbb596 100644 --- a/src/TreeNode.tsx +++ b/src/TreeNode.tsx @@ -1,9 +1,9 @@ /* istanbul ignore file */ import type * as React from 'react'; -import type { DataNode, Key } from './interface'; +import type { DataNode, SafeKey } from './interface'; export interface TreeNodeProps extends Omit { - value: Key; + value: SafeKey; children?: React.ReactNode; } diff --git a/src/TreeSelect.tsx b/src/TreeSelect.tsx index 0843e358..3847e14f 100644 --- a/src/TreeSelect.tsx +++ b/src/TreeSelect.tsx @@ -28,22 +28,9 @@ import type { CheckedStrategy } from './utils/strategyUtil'; import { formatStrategyValues, SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil'; import { fillFieldNames, isNil, toArray } from './utils/valueUtil'; import warningProps from './utils/warningPropsUtil'; +import type { LabeledValueType, SafeKey, SelectSource, DefaultValueType } from './interface'; -export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void; - -export type RawValueType = string | number; - -export interface LabeledValueType { - key?: React.Key; - value?: RawValueType; - label?: React.ReactNode; - /** Only works on `treeCheckStrictly` */ - halfChecked?: boolean; -} - -export type SelectSource = 'option' | 'selection' | 'input' | 'clear'; - -export type DraftValueType = RawValueType | LabeledValueType | (RawValueType | LabeledValueType)[]; +export type OnInternalSelect = (value: SafeKey, info: { selected: boolean }) => void; /** @deprecated This is only used for legacy compatible. Not works on new code. */ export interface LegacyCheckedNode { @@ -55,7 +42,7 @@ export interface LegacyCheckedNode { export interface ChangeEventExtra { /** @deprecated Please save prev value by control logic instead */ preValue: LabeledValueType[]; - triggerValue: RawValueType; + triggerValue: SafeKey; /** @deprecated Use `onSelect` or `onDeselect` instead. */ selected?: boolean; /** @deprecated Use `onSelect` or `onDeselect` instead. */ @@ -79,9 +66,9 @@ export interface InternalFieldName extends Omit { } export interface SimpleModeConfig { - id?: React.Key; - pId?: React.Key; - rootPId?: React.Key; + id?: SafeKey; + pId?: SafeKey; + rootPId?: SafeKey; } export interface BaseOptionType { @@ -93,10 +80,10 @@ export interface BaseOptionType { } export interface DefaultOptionType extends BaseOptionType { - value?: RawValueType; + value?: SafeKey; title?: React.ReactNode; label?: React.ReactNode; - key?: React.Key; + key?: SafeKey; children?: DefaultOptionType[]; } @@ -145,14 +132,14 @@ export interface TreeSelectProps< treeData?: OptionType[]; treeDataSimpleMode?: boolean | SimpleModeConfig; loadData?: (dataNode: LegacyDataNode) => Promise; - treeLoadedKeys?: React.Key[]; - onTreeLoad?: (loadedKeys: React.Key[]) => void; + treeLoadedKeys?: SafeKey[]; + onTreeLoad?: (loadedKeys: SafeKey[]) => void; // >>> Expanded treeDefaultExpandAll?: boolean; - treeExpandedKeys?: React.Key[]; - treeDefaultExpandedKeys?: React.Key[]; - onTreeExpand?: (expandedKeys: React.Key[]) => void; + treeExpandedKeys?: SafeKey[]; + treeDefaultExpandedKeys?: SafeKey[]; + onTreeExpand?: (expandedKeys: SafeKey[]) => void; treeExpandAction?: ExpandAction; // >>> Options @@ -171,7 +158,7 @@ export interface TreeSelectProps< treeMotion?: any; } -function isRawValue(value: RawValueType | LabeledValueType): value is RawValueType { +function isRawValue(value: SafeKey | LabeledValueType): value is SafeKey { return !value || typeof value !== 'object'; } @@ -295,7 +282,7 @@ const TreeSelect = React.forwardRef((props, ref) /** Get `missingRawValues` which not exist in the tree yet */ const splitRawValues = React.useCallback( - (newRawValues: RawValueType[]) => { + (newRawValues: SafeKey[]) => { const missingRawValues = []; const existRawValues = []; @@ -343,7 +330,7 @@ const TreeSelect = React.forwardRef((props, ref) ); // ========================= Wrap Value ========================= - const toLabeledValues = React.useCallback((draftValues: DraftValueType) => { + const toLabeledValues = React.useCallback((draftValues: DefaultValueType) => { const values = toArray(draftValues); return values.map(val => { @@ -355,7 +342,7 @@ const TreeSelect = React.forwardRef((props, ref) }, []); const convert2LabelValues = React.useCallback( - (draftValues: DraftValueType) => { + (draftValues: DefaultValueType) => { const values = toLabeledValues(draftValues); return values.map(item => { @@ -368,7 +355,9 @@ const TreeSelect = React.forwardRef((props, ref) // Fill missing label & status if (entity) { - rawLabel = treeTitleRender ? treeTitleRender(entity.node) : rawLabel ?? getLabel(entity.node); + rawLabel = treeTitleRender + ? treeTitleRender(entity.node) + : (rawLabel ?? getLabel(entity.node)); rawDisabled = entity.node.disabled; } else if (rawLabel === undefined) { // We try to find in current `labelInValue` value @@ -475,8 +464,8 @@ const TreeSelect = React.forwardRef((props, ref) // =========================== Change =========================== const triggerChange = useRefFunc( ( - newRawValues: RawValueType[], - extra: { triggerValue?: RawValueType; selected?: boolean }, + newRawValues: SafeKey[], + extra: { triggerValue?: SafeKey; selected?: boolean }, source: SelectSource, ) => { const labeledValues = convert2LabelValues(newRawValues); @@ -489,7 +478,7 @@ const TreeSelect = React.forwardRef((props, ref) // Generate rest parameters is costly, so only do it when necessary if (onChange) { - let eventValues: RawValueType[] = newRawValues; + let eventValues: SafeKey[] = newRawValues; if (treeConduction) { const formattedKeyList = formatStrategyValues( newRawValues, @@ -508,7 +497,7 @@ const TreeSelect = React.forwardRef((props, ref) selected: undefined, }; - let returnRawValues: (LabeledValueType | RawValueType)[] = eventValues; + let returnRawValues: (LabeledValueType | SafeKey)[] = eventValues; // We need fill half check back if (treeCheckStrictly) { @@ -563,7 +552,7 @@ const TreeSelect = React.forwardRef((props, ref) // ========================== Options =========================== /** Trigger by option list */ const onOptionSelect = React.useCallback( - (selectedKey: React.Key, { selected, source }: { selected: boolean; source: SelectSource }) => { + (selectedKey: SafeKey, { selected, source }: { selected: boolean; source: SelectSource }) => { const entity = keyEntities[selectedKey]; const node = entity?.node; const selectedValue = node?.[mergedFieldNames.value] ?? selectedKey; @@ -584,7 +573,7 @@ const TreeSelect = React.forwardRef((props, ref) const keyList = existRawValues.map(val => valueEntities.get(val).key); // Conduction by selected or not - let checkedKeys: React.Key[]; + let checkedKeys: SafeKey[]; if (selected) { ({ checkedKeys } = conductCheck(keyList, true, keyEntities)); } else { diff --git a/src/hooks/useCache.ts b/src/hooks/useCache.ts index db939e5f..ae730e28 100644 --- a/src/hooks/useCache.ts +++ b/src/hooks/useCache.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import type { LabeledValueType, RawValueType } from '../TreeSelect'; +import type { LabeledValueType, SafeKey } from '../interface'; /** * This function will try to call requestIdleCallback if available to save performance. @@ -7,12 +7,12 @@ import type { LabeledValueType, RawValueType } from '../TreeSelect'; */ export default (values: LabeledValueType[]): [LabeledValueType[]] => { const cacheRef = React.useRef({ - valueLabels: new Map(), + valueLabels: new Map(), }); return React.useMemo(() => { const { valueLabels } = cacheRef.current; - const valueLabelsCache = new Map(); + const valueLabelsCache = new Map(); const filledValues = values.map(item => { const { value } = item; diff --git a/src/hooks/useCheckedKeys.ts b/src/hooks/useCheckedKeys.ts index de9a97d1..d5d1e938 100644 --- a/src/hooks/useCheckedKeys.ts +++ b/src/hooks/useCheckedKeys.ts @@ -1,17 +1,17 @@ import * as React from 'react'; import type { DataEntity } from 'rc-tree/lib/interface'; import { conductCheck } from 'rc-tree/lib/utils/conductUtil'; -import type { LabeledValueType, RawValueType } from '../TreeSelect'; +import type { LabeledValueType, SafeKey } from '../interface'; export default ( rawLabeledValues: LabeledValueType[], rawHalfCheckedValues: LabeledValueType[], treeConduction: boolean, - keyEntities: Record, + keyEntities: Record, ) => React.useMemo(() => { - let checkedKeys: RawValueType[] = rawLabeledValues.map(({ value }) => value); - let halfCheckedKeys: RawValueType[] = rawHalfCheckedValues.map(({ value }) => value); + let checkedKeys: SafeKey[] = rawLabeledValues.map(({ value }) => value); + let halfCheckedKeys: SafeKey[] = rawHalfCheckedValues.map(({ value }) => value); const missingValues = checkedKeys.filter(key => !keyEntities[key]); @@ -23,5 +23,6 @@ export default ( // Checked keys should fill with missing keys which should de-duplicated Array.from(new Set([...missingValues, ...checkedKeys])), // Half checked keys - halfCheckedKeys]; + halfCheckedKeys, + ]; }, [rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities]); diff --git a/src/hooks/useDataEntities.ts b/src/hooks/useDataEntities.ts index d015033c..8fe48bc8 100644 --- a/src/hooks/useDataEntities.ts +++ b/src/hooks/useDataEntities.ts @@ -1,13 +1,14 @@ import * as React from 'react'; import { convertDataToEntities } from 'rc-tree/lib/utils/treeUtil'; import type { DataEntity } from 'rc-tree/lib/interface'; -import type { FieldNames, RawValueType } from '../TreeSelect'; +import type { FieldNames } from '../TreeSelect'; +import type { SafeKey } from '../interface'; import warning from 'rc-util/lib/warning'; import { isNil } from '../utils/valueUtil'; export default (treeData: any, fieldNames: FieldNames) => React.useMemo<{ - valueEntities: Map; + valueEntities: Map; keyEntities: Record; }>(() => { const collection = convertDataToEntities(treeData, { diff --git a/src/interface.ts b/src/interface.ts index 9f491eda..13309456 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,26 +1,25 @@ import type * as React from 'react'; +import type { SafeKey } from 'rc-tree/lib/interface'; -export type SelectSource = 'option' | 'selection' | 'input' | 'clear'; - -export type Key = string | number; +export type { SafeKey }; -export type RawValueType = string | number; +export type SelectSource = 'option' | 'selection' | 'input' | 'clear'; -export interface LabelValueType { - key?: Key; - value?: RawValueType; +export interface LabeledValueType { + key?: SafeKey; + value?: SafeKey; label?: React.ReactNode; /** Only works on `treeCheckStrictly` */ halfChecked?: boolean; } -export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[]; +export type DefaultValueType = SafeKey | SafeKey[] | LabeledValueType | LabeledValueType[]; export interface DataNode { - value?: RawValueType; + value?: SafeKey; title?: React.ReactNode; label?: React.ReactNode; - key?: Key; + key?: SafeKey; disabled?: boolean; disableCheckbox?: boolean; checkable?: boolean; @@ -31,8 +30,8 @@ export interface DataNode { } export interface InternalDataEntity { - key: Key; - value: RawValueType; + key: SafeKey; + value: SafeKey; title?: React.ReactNode; disableCheckbox?: boolean; disabled?: boolean; @@ -47,22 +46,22 @@ export interface LegacyDataNode extends DataNode { } export interface TreeDataNode extends DataNode { - key: Key; + key: SafeKey; children?: TreeDataNode[]; } export interface FlattenDataNode { data: InternalDataEntity; - key: Key; - value: RawValueType; + key: SafeKey; + value: SafeKey; level: number; parent?: FlattenDataNode; } export interface SimpleModeConfig { - id?: Key; - pId?: Key; - rootPId?: Key; + id?: SafeKey; + pId?: SafeKey; + rootPId?: SafeKey; } /** @deprecated This is only used for legacy compatible. Not works on new code. */ @@ -74,8 +73,8 @@ export interface LegacyCheckedNode { export interface ChangeEventExtra { /** @deprecated Please save prev value by control logic instead */ - preValue: LabelValueType[]; - triggerValue: RawValueType; + preValue: LabeledValueType[]; + triggerValue: SafeKey; /** @deprecated Use `onSelect` or `onDeselect` instead. */ selected?: boolean; /** @deprecated Use `onSelect` or `onDeselect` instead. */ diff --git a/src/utils/legacyUtil.tsx b/src/utils/legacyUtil.tsx index c300f5d8..1a6229ff 100644 --- a/src/utils/legacyUtil.tsx +++ b/src/utils/legacyUtil.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import toArray from 'rc-util/lib/Children/toArray'; import warning from 'rc-util/lib/warning'; -import type { DataNode, ChangeEventExtra, RawValueType, LegacyCheckedNode } from '../interface'; +import type { DataNode, ChangeEventExtra, SafeKey, LegacyCheckedNode } from '../interface'; import TreeNode from '../TreeNode'; import type { DefaultOptionType, FieldNames } from '../TreeSelect'; @@ -57,8 +57,8 @@ export function fillLegacyProps(dataNode: DataNode): any { export function fillAdditionalInfo( extra: ChangeEventExtra, - triggerValue: RawValueType, - checkedValues: RawValueType[], + triggerValue: SafeKey, + checkedValues: SafeKey[], treeData: DefaultOptionType[], showPosition: boolean, fieldNames: FieldNames, diff --git a/src/utils/strategyUtil.ts b/src/utils/strategyUtil.ts index cd08960a..0bf09ccf 100644 --- a/src/utils/strategyUtil.ts +++ b/src/utils/strategyUtil.ts @@ -1,7 +1,7 @@ import type * as React from 'react'; import type { InternalFieldName } from '../TreeSelect'; import type { DataEntity } from 'rc-tree/lib/interface'; -import type { RawValueType, Key } from '../interface'; +import type { SafeKey } from '../interface'; import { isCheckDisabled } from './valueUtil'; export const SHOW_ALL = 'SHOW_ALL'; @@ -11,11 +11,11 @@ export const SHOW_CHILD = 'SHOW_CHILD'; export type CheckedStrategy = typeof SHOW_ALL | typeof SHOW_PARENT | typeof SHOW_CHILD; export function formatStrategyValues( - values: React.Key[], + values: SafeKey[], strategy: CheckedStrategy, - keyEntities: Record, + keyEntities: Record, fieldNames: InternalFieldName, -): RawValueType[] { +): SafeKey[] { const valueSet = new Set(values); if (strategy === SHOW_CHILD) { diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index 0063ce82..a1d12593 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -1,5 +1,5 @@ import type * as React from 'react'; -import type { DataNode, FieldNames } from '../interface'; +import type { DataNode, FieldNames, SafeKey } from '../interface'; import type { DefaultOptionType, InternalFieldName } from '../TreeSelect'; export function toArray(value: T | T[]): T[] { @@ -28,7 +28,7 @@ export function isCheckDisabled(node: DataNode) { /** Loop fetch all the keys exist in the tree */ export function getAllKeys(treeData: DefaultOptionType[], fieldNames: InternalFieldName) { - const keys: React.Key[] = []; + const keys: SafeKey[] = []; function dig(list: DefaultOptionType[]) { list.forEach(item => {