@aloudata/aloudata-design 3.1.7 → 3.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/Dropdown/index.js +22 -1
  2. package/dist/Dropdown/index.js.map +1 -1
  3. package/dist/MemberPicker/components/Panel.d.ts +2 -1
  4. package/dist/MemberPicker/components/Panel.js +4 -1
  5. package/dist/MemberPicker/components/Panel.js.map +1 -1
  6. package/dist/MemberPicker/components/PanelWrapper.d.ts +3 -2
  7. package/dist/MemberPicker/components/PanelWrapper.js +2 -1
  8. package/dist/MemberPicker/components/PanelWrapper.js.map +1 -1
  9. package/dist/MemberPicker/components/UserGroupSelection.d.ts +3 -1
  10. package/dist/MemberPicker/components/UserGroupSelection.js +2 -2
  11. package/dist/MemberPicker/components/UserGroupSelection.js.map +1 -1
  12. package/dist/MemberPicker/dialog/DialogContent.d.ts +1 -1
  13. package/dist/MemberPicker/dialog/DialogContent.js +5 -2
  14. package/dist/MemberPicker/dialog/DialogContent.js.map +1 -1
  15. package/dist/MemberPicker/dialog/MemberPickerDialog.d.ts +1 -1
  16. package/dist/MemberPicker/dialog/MemberPickerDialog.js.map +1 -1
  17. package/dist/MemberPicker/dialog/UserGroupCandidatePanel.d.ts +2 -1
  18. package/dist/MemberPicker/dialog/UserGroupCandidatePanel.js +2 -2
  19. package/dist/MemberPicker/dialog/UserGroupCandidatePanel.js.map +1 -1
  20. package/dist/MemberPicker/index.js +3 -1
  21. package/dist/MemberPicker/index.js.map +1 -1
  22. package/dist/MemberPicker/interface.d.ts +6 -0
  23. package/dist/MemberPicker/interface.js.map +1 -1
  24. package/dist/Menu/index.d.ts +4 -0
  25. package/dist/Menu/index.js +19 -8
  26. package/dist/Menu/index.js.map +1 -1
  27. package/dist/aloudata-design.css +1 -1
  28. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { TUser } from '../../ConfigProvider/getUserList';
2
2
  import { IBaseMemberSelectorProps } from '../interface';
3
3
  /** 独立弹窗公开契约,数据从最近的 ConfigProvider 读取。 */
4
- export interface MemberPickerDialogProps extends Pick<IBaseMemberSelectorProps, 'value' | 'type' | 'lockedIds' | 'filterItem' | 'renderItem' | 'renderSelectedTag' | 'footer' | 'onCancel'> {
4
+ export interface MemberPickerDialogProps extends Pick<IBaseMemberSelectorProps, 'value' | 'type' | 'lockedIds' | 'filterItem' | 'canEnterGroup' | 'renderItem' | 'renderSelectedTag' | 'footer' | 'onCancel'> {
5
5
  open: boolean;
6
6
  onClose: () => void;
7
7
  onChange?: (members: TUser) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MemberPickerDialog.js","names":[],"sources":["../../../src/MemberPicker/dialog/MemberPickerDialog.tsx"],"sourcesContent":["import type { TUser } from '../../ConfigProvider/getUserList';\nimport type { IBaseMemberSelectorProps } from '../interface';\nimport DialogContent from './DialogContent';\n\n/** 独立弹窗公开契约,数据从最近的 ConfigProvider 读取。 */\nexport interface MemberPickerDialogProps\n extends Pick<\n IBaseMemberSelectorProps,\n | 'value'\n | 'type'\n | 'lockedIds'\n | 'filterItem'\n | 'renderItem'\n | 'renderSelectedTag'\n | 'footer'\n | 'onCancel'\n > {\n open: boolean;\n onClose: () => void;\n onChange?: (members: TUser) => void;\n zIndex?: number;\n}\n\n/** 关闭时不挂载内容,避免关闭态请求和选择草稿串入下次打开。 */\nexport default function MemberPickerDialog(props: MemberPickerDialogProps) {\n return props.open ? <DialogContent {...props} /> : null;\n}\n"],"mappings":";;;;AAwBA,SAAwB,mBAAmB,OAAgC;AACzE,QAAO,MAAM,OAAO,oBAAC,eAAD,EAAe,GAAI,OAAS,CAAA,GAAG"}
1
+ {"version":3,"file":"MemberPickerDialog.js","names":[],"sources":["../../../src/MemberPicker/dialog/MemberPickerDialog.tsx"],"sourcesContent":["import type { TUser } from '../../ConfigProvider/getUserList';\nimport type { IBaseMemberSelectorProps } from '../interface';\nimport DialogContent from './DialogContent';\n\n/** 独立弹窗公开契约,数据从最近的 ConfigProvider 读取。 */\nexport interface MemberPickerDialogProps\n extends Pick<\n IBaseMemberSelectorProps,\n | 'value'\n | 'type'\n | 'lockedIds'\n | 'filterItem'\n | 'canEnterGroup'\n | 'renderItem'\n | 'renderSelectedTag'\n | 'footer'\n | 'onCancel'\n > {\n open: boolean;\n onClose: () => void;\n onChange?: (members: TUser) => void;\n zIndex?: number;\n}\n\n/** 关闭时不挂载内容,避免关闭态请求和选择草稿串入下次打开。 */\nexport default function MemberPickerDialog(props: MemberPickerDialogProps) {\n return props.open ? <DialogContent {...props} /> : null;\n}\n"],"mappings":";;;;AAyBA,SAAwB,mBAAmB,OAAgC;AACzE,QAAO,MAAM,OAAO,oBAAC,eAAD,EAAe,GAAI,OAAS,CAAA,GAAG"}
@@ -6,8 +6,9 @@ interface IProps {
6
6
  onToggleAll: (members: readonly TMember[], selected: boolean) => void;
7
7
  lockedIds?: MemberPickerProps['lockedIds'];
8
8
  filterItem?: MemberPickerProps['filterItem'];
9
+ canEnterGroup?: MemberPickerProps['canEnterGroup'];
9
10
  renderItem?: MemberPickerProps['renderItem'];
10
11
  }
11
12
  /** 用户组树与跨层搜索共用选择事实源,但互不重置彼此的浏览状态。 */
12
- export default function UserGroupCandidatePanel({ selectedByKey, onToggle, onToggleAll, lockedIds, filterItem, renderItem, }: IProps): import("react/jsx-runtime").JSX.Element;
13
+ export default function UserGroupCandidatePanel({ selectedByKey, onToggle, onToggleAll, lockedIds, filterItem, canEnterGroup, renderItem, }: IProps): import("react/jsx-runtime").JSX.Element;
13
14
  export {};
@@ -15,7 +15,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
15
15
  var LOAD_MORE_THRESHOLD = 32;
16
16
  var ROOT_BREADCRUMB_INDEX = -1;
17
17
  /** 用户组树与跨层搜索共用选择事实源,但互不重置彼此的浏览状态。 */
18
- function UserGroupCandidatePanel({ selectedByKey, onToggle, onToggleAll, lockedIds = [], filterItem, renderItem }) {
18
+ function UserGroupCandidatePanel({ selectedByKey, onToggle, onToggleAll, lockedIds = [], filterItem, canEnterGroup, renderItem }) {
19
19
  const t = useDialogLocale();
20
20
  const candidates = useUserGroupCandidates();
21
21
  const listRef = useRef(null);
@@ -103,7 +103,7 @@ function UserGroupCandidatePanel({ selectedByKey, onToggle, onToggleAll, lockedI
103
103
  selected: selectedByKey.has(getMemberKey(group)),
104
104
  locked: isMemberLocked(group, lockedIds),
105
105
  onToggle,
106
- onOpenGroup: isSearching ? void 0 : () => {
106
+ onOpenGroup: isSearching || canEnterGroup?.(group) === false ? void 0 : () => {
107
107
  const sourceGroup = candidates.items.find((item) => item.groupId === group.groupId);
108
108
  if (sourceGroup) candidates.enterGroup(sourceGroup, listRef.current?.scrollTop ?? 0);
109
109
  },
@@ -1 +1 @@
1
- {"version":3,"file":"UserGroupCandidatePanel.js","names":[],"sources":["../../../src/MemberPicker/dialog/UserGroupCandidatePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport Breadcrumb from '../../Breadcrumb';\nimport Button from '../../Button';\nimport Checkbox from '../../Checkbox';\nimport Empty from '../../Empty';\nimport InputSearch from '../../InputSearch';\nimport Spin from '../../Spin';\nimport type { IMemberPickerProps as MemberPickerProps } from '../interface';\nimport useDialogLocale from './useDialogLocale';\nimport { getMemberKey, isMemberLocked } from './helper';\nimport SearchFeedback from '../components/SearchFeedback';\nimport MemberOption from './MemberOption';\nimport type { TMember, TMemberKey } from './types';\nimport { useUserGroupCandidates } from './useUserGroupCandidates';\n\nconst LOAD_MORE_THRESHOLD = 32;\nconst ROOT_BREADCRUMB_INDEX = -1;\n\ninterface IProps {\n selectedByKey: ReadonlyMap<TMemberKey, TMember>;\n onToggle: (member: TMember) => void;\n onToggleAll: (members: readonly TMember[], selected: boolean) => void;\n lockedIds?: MemberPickerProps['lockedIds'];\n filterItem?: MemberPickerProps['filterItem'];\n renderItem?: MemberPickerProps['renderItem'];\n}\n\n/** 用户组树与跨层搜索共用选择事实源,但互不重置彼此的浏览状态。 */\nexport default function UserGroupCandidatePanel({\n selectedByKey,\n onToggle,\n onToggleAll,\n lockedIds = [],\n filterItem,\n renderItem,\n}: IProps) {\n const t = useDialogLocale();\n const candidates = useUserGroupCandidates();\n const listRef = useRef<HTMLDivElement>(null);\n const isSearching = candidates.keyword.trim().length > 0;\n const groups = useMemo(\n () =>\n candidates.items.filter((group) =>\n filterItem ? filterItem(group) : true,\n ),\n [candidates.items, filterItem],\n );\n const selectableGroups = groups.filter(\n (group) => !isMemberLocked(group, lockedIds),\n );\n const selectedCount = selectableGroups.filter((group) =>\n selectedByKey.has(getMemberKey(group)),\n ).length;\n const allSelected =\n selectableGroups.length > 0 && selectedCount === selectableGroups.length;\n const partiallySelected = selectedCount > 0 && !allSelected;\n\n useEffect(() => {\n if (!isSearching && listRef.current) {\n listRef.current.scrollTop = candidates.scrollTop;\n }\n }, [candidates.breadcrumbPath, candidates.scrollTop, isSearching]);\n\n return (\n <section\n className=\"tw-flex tw-h-full tw-min-h-0 tw-flex-col\"\n data-testid=\"user-group-candidate-panel\"\n >\n <div className=\"tw-shrink-0 tw-border-b tw-border-solid tw-border-border-default tw-p-sp-150\">\n <div className=\"tw-flex tw-min-w-0 tw-flex-col tw-gap-sp-150\">\n {!isSearching && candidates.breadcrumbPath.length > 0 ? (\n <Breadcrumb\n className=\"tw-min-w-0 tw-flex-wrap\"\n items={[\n {\n title: t.MemberPickerDialog.root,\n onClick: () =>\n candidates.returnToBreadcrumb(\n ROOT_BREADCRUMB_INDEX,\n listRef.current?.scrollTop ?? 0,\n ),\n },\n ...candidates.breadcrumbPath.map((group, index) => ({\n title: group.name,\n onClick: () =>\n candidates.returnToBreadcrumb(\n index,\n listRef.current?.scrollTop ?? 0,\n ),\n })),\n ]}\n />\n ) : null}\n <InputSearch\n inputMode\n size=\"middle\"\n className=\"tw-w-full\"\n debounce={false}\n value={candidates.keyword}\n placeholder={t.MemberPickerDialog.searchGroupsPlaceholder}\n onChange={candidates.setKeyword}\n onSearch={candidates.setKeyword}\n />\n <Checkbox\n size=\"small\"\n className=\"tw-self-start\"\n checked={allSelected}\n indeterminate={partiallySelected}\n disabled={\n candidates.loading ||\n candidates.error ||\n selectableGroups.length === 0\n }\n onChange={(event) =>\n onToggleAll(selectableGroups, event.target.checked)\n }\n >\n {allSelected\n ? t.MemberPickerDialog.deselectCurrent(groups.length)\n : t.MemberPickerDialog.selectCurrent(groups.length)}\n </Checkbox>\n </div>\n </div>\n <SearchFeedback\n pathWarningCode={candidates.pathWarningCode}\n hasMore={candidates.truncated}\n retry={candidates.retry}\n />\n <div\n ref={listRef}\n className=\"tw-min-h-0 tw-flex-1 tw-overflow-auto tw-py-sp-100\"\n onScroll={(event) => {\n const target = event.currentTarget;\n if (!isSearching) {\n candidates.updateScrollTop(target.scrollTop);\n }\n if (\n target.scrollHeight - target.scrollTop - target.clientHeight <=\n LOAD_MORE_THRESHOLD\n ) {\n candidates.loadMore();\n }\n }}\n >\n {candidates.loading ? <Spin /> : null}\n {candidates.error ? (\n <div className=\"tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-center tw-gap-2\">\n <span>{t.MemberPickerDialog.loadFailed}</span>\n <Button type=\"link\" onClick={candidates.retry}>\n {t.MemberPickerDialog.retry}\n </Button>\n </div>\n ) : null}\n {!candidates.loading && !candidates.error && !groups.length ? (\n <Empty description={t.MemberPickerDialog.emptyCandidates} />\n ) : null}\n {!candidates.loading && !candidates.error\n ? groups.map((group) => (\n <MemberOption\n key={group.groupId}\n member={group}\n searchValue={candidates.keyword}\n selected={selectedByKey.has(getMemberKey(group))}\n locked={isMemberLocked(group, lockedIds)}\n onToggle={onToggle}\n onOpenGroup={\n isSearching\n ? undefined\n : () => {\n const sourceGroup = candidates.items.find(\n (item) => item.groupId === group.groupId,\n );\n if (sourceGroup) {\n candidates.enterGroup(\n sourceGroup,\n listRef.current?.scrollTop ?? 0,\n );\n }\n }\n }\n renderItem={renderItem}\n />\n ))\n : null}\n {!candidates.loading &&\n !candidates.error &&\n candidates.hasMore &&\n !candidates.loadMoreError ? (\n <Button\n type=\"link\"\n loading={candidates.loadingMore}\n onClick={candidates.loadMore}\n >\n {t.MemberPickerDialog.loadMore}\n </Button>\n ) : null}\n {candidates.loadMoreError ? (\n <div className=\"tw-flex tw-items-center tw-justify-center tw-gap-2 tw-py-2\">\n <span>{t.MemberPickerDialog.loadMoreFailed}</span>\n <Button type=\"link\" onClick={candidates.retryLoadMore}>\n {t.MemberPickerDialog.retry}\n </Button>\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"UserGroupCandidatePanel.js","names":[],"sources":["../../../src/MemberPicker/dialog/UserGroupCandidatePanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport Breadcrumb from '../../Breadcrumb';\nimport Button from '../../Button';\nimport Checkbox from '../../Checkbox';\nimport Empty from '../../Empty';\nimport InputSearch from '../../InputSearch';\nimport Spin from '../../Spin';\nimport type { IMemberPickerProps as MemberPickerProps } from '../interface';\nimport useDialogLocale from './useDialogLocale';\nimport { getMemberKey, isMemberLocked } from './helper';\nimport SearchFeedback from '../components/SearchFeedback';\nimport MemberOption from './MemberOption';\nimport type { TMember, TMemberKey } from './types';\nimport { useUserGroupCandidates } from './useUserGroupCandidates';\n\nconst LOAD_MORE_THRESHOLD = 32;\nconst ROOT_BREADCRUMB_INDEX = -1;\n\ninterface IProps {\n selectedByKey: ReadonlyMap<TMemberKey, TMember>;\n onToggle: (member: TMember) => void;\n onToggleAll: (members: readonly TMember[], selected: boolean) => void;\n lockedIds?: MemberPickerProps['lockedIds'];\n filterItem?: MemberPickerProps['filterItem'];\n canEnterGroup?: MemberPickerProps['canEnterGroup'];\n renderItem?: MemberPickerProps['renderItem'];\n}\n\n/** 用户组树与跨层搜索共用选择事实源,但互不重置彼此的浏览状态。 */\nexport default function UserGroupCandidatePanel({\n selectedByKey,\n onToggle,\n onToggleAll,\n lockedIds = [],\n filterItem,\n canEnterGroup,\n renderItem,\n}: IProps) {\n const t = useDialogLocale();\n const candidates = useUserGroupCandidates();\n const listRef = useRef<HTMLDivElement>(null);\n const isSearching = candidates.keyword.trim().length > 0;\n const groups = useMemo(\n () =>\n candidates.items.filter((group) =>\n filterItem ? filterItem(group) : true,\n ),\n [candidates.items, filterItem],\n );\n const selectableGroups = groups.filter(\n (group) => !isMemberLocked(group, lockedIds),\n );\n const selectedCount = selectableGroups.filter((group) =>\n selectedByKey.has(getMemberKey(group)),\n ).length;\n const allSelected =\n selectableGroups.length > 0 && selectedCount === selectableGroups.length;\n const partiallySelected = selectedCount > 0 && !allSelected;\n\n useEffect(() => {\n if (!isSearching && listRef.current) {\n listRef.current.scrollTop = candidates.scrollTop;\n }\n }, [candidates.breadcrumbPath, candidates.scrollTop, isSearching]);\n\n return (\n <section\n className=\"tw-flex tw-h-full tw-min-h-0 tw-flex-col\"\n data-testid=\"user-group-candidate-panel\"\n >\n <div className=\"tw-shrink-0 tw-border-b tw-border-solid tw-border-border-default tw-p-sp-150\">\n <div className=\"tw-flex tw-min-w-0 tw-flex-col tw-gap-sp-150\">\n {!isSearching && candidates.breadcrumbPath.length > 0 ? (\n <Breadcrumb\n className=\"tw-min-w-0 tw-flex-wrap\"\n items={[\n {\n title: t.MemberPickerDialog.root,\n onClick: () =>\n candidates.returnToBreadcrumb(\n ROOT_BREADCRUMB_INDEX,\n listRef.current?.scrollTop ?? 0,\n ),\n },\n ...candidates.breadcrumbPath.map((group, index) => ({\n title: group.name,\n onClick: () =>\n candidates.returnToBreadcrumb(\n index,\n listRef.current?.scrollTop ?? 0,\n ),\n })),\n ]}\n />\n ) : null}\n <InputSearch\n inputMode\n size=\"middle\"\n className=\"tw-w-full\"\n debounce={false}\n value={candidates.keyword}\n placeholder={t.MemberPickerDialog.searchGroupsPlaceholder}\n onChange={candidates.setKeyword}\n onSearch={candidates.setKeyword}\n />\n <Checkbox\n size=\"small\"\n className=\"tw-self-start\"\n checked={allSelected}\n indeterminate={partiallySelected}\n disabled={\n candidates.loading ||\n candidates.error ||\n selectableGroups.length === 0\n }\n onChange={(event) =>\n onToggleAll(selectableGroups, event.target.checked)\n }\n >\n {allSelected\n ? t.MemberPickerDialog.deselectCurrent(groups.length)\n : t.MemberPickerDialog.selectCurrent(groups.length)}\n </Checkbox>\n </div>\n </div>\n <SearchFeedback\n pathWarningCode={candidates.pathWarningCode}\n hasMore={candidates.truncated}\n retry={candidates.retry}\n />\n <div\n ref={listRef}\n className=\"tw-min-h-0 tw-flex-1 tw-overflow-auto tw-py-sp-100\"\n onScroll={(event) => {\n const target = event.currentTarget;\n if (!isSearching) {\n candidates.updateScrollTop(target.scrollTop);\n }\n if (\n target.scrollHeight - target.scrollTop - target.clientHeight <=\n LOAD_MORE_THRESHOLD\n ) {\n candidates.loadMore();\n }\n }}\n >\n {candidates.loading ? <Spin /> : null}\n {candidates.error ? (\n <div className=\"tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-center tw-gap-2\">\n <span>{t.MemberPickerDialog.loadFailed}</span>\n <Button type=\"link\" onClick={candidates.retry}>\n {t.MemberPickerDialog.retry}\n </Button>\n </div>\n ) : null}\n {!candidates.loading && !candidates.error && !groups.length ? (\n <Empty description={t.MemberPickerDialog.emptyCandidates} />\n ) : null}\n {!candidates.loading && !candidates.error\n ? groups.map((group) => (\n <MemberOption\n key={group.groupId}\n member={group}\n searchValue={candidates.keyword}\n selected={selectedByKey.has(getMemberKey(group))}\n locked={isMemberLocked(group, lockedIds)}\n onToggle={onToggle}\n onOpenGroup={\n isSearching || canEnterGroup?.(group) === false\n ? undefined\n : () => {\n const sourceGroup = candidates.items.find(\n (item) => item.groupId === group.groupId,\n );\n if (sourceGroup) {\n candidates.enterGroup(\n sourceGroup,\n listRef.current?.scrollTop ?? 0,\n );\n }\n }\n }\n renderItem={renderItem}\n />\n ))\n : null}\n {!candidates.loading &&\n !candidates.error &&\n candidates.hasMore &&\n !candidates.loadMoreError ? (\n <Button\n type=\"link\"\n loading={candidates.loadingMore}\n onClick={candidates.loadMore}\n >\n {t.MemberPickerDialog.loadMore}\n </Button>\n ) : null}\n {candidates.loadMoreError ? (\n <div className=\"tw-flex tw-items-center tw-justify-center tw-gap-2 tw-py-2\">\n <span>{t.MemberPickerDialog.loadMoreFailed}</span>\n <Button type=\"link\" onClick={candidates.retryLoadMore}>\n {t.MemberPickerDialog.retry}\n </Button>\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n"],"mappings":"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"}
@@ -23,7 +23,7 @@ var DEFAULT_MULTIPLE_DROPDOWN_WIDTH = 480;
23
23
  var Component = (props) => {
24
24
  const { locale } = useContext(LocaleContext);
25
25
  const t = getTranslator(locale);
26
- const { value, type = EMemberPicker.USER, onChange, multiple, dropdownWidth, open, onOpenChange, placeholder = t.MemberPicker.select, className, allowClear, disabled, onClear, size, prefix: propPrefix, lockedIds = [], footer, onCancel, filterItem, mode: propsMode, renderItem, renderSelectedTag, "data-testid": dataTestId, "aria-label": ariaLabel, ...restProps } = props;
26
+ const { value, type = EMemberPicker.USER, onChange, multiple, dropdownWidth, open, onOpenChange, placeholder = t.MemberPicker.select, className, allowClear, disabled, onClear, size, prefix: propPrefix, lockedIds = [], footer, onCancel, filterItem, mode: propsMode, canEnterGroup, renderItem, renderSelectedTag, "data-testid": dataTestId, "aria-label": ariaLabel, ...restProps } = props;
27
27
  const allowOverlap = props.allowOverlap ?? false;
28
28
  const { getUsersByIds, getUsersByKeywords, getUsersByKeywordsWithMeta, getNextLevelGroup, getNextLevelGroupWithMeta } = AldCustomConfigProvider.useGetUserList();
29
29
  const inheritedDisabled = useContext(DisabledContext);
@@ -304,6 +304,7 @@ var Component = (props) => {
304
304
  onCancel?.();
305
305
  setDropdownOpen(false);
306
306
  },
307
+ canEnterGroup,
307
308
  renderItem,
308
309
  renderSelectedTag,
309
310
  searchValue: searchString
@@ -316,6 +317,7 @@ var Component = (props) => {
316
317
  type,
317
318
  lockedIds,
318
319
  filterItem,
320
+ canEnterGroup,
319
321
  renderItem,
320
322
  renderSelectedTag,
321
323
  onChange: (members) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/MemberPicker/index.tsx"],"sourcesContent":["import MemberPickerDialog from './dialog/MemberPickerDialog';\nimport DisabledContext from '../ConfigProvider/DisabledContext';\nimport useMemberSearch from './useMemberSearch';\nimport SearchFeedback from './components/SearchFeedback';\nimport { usePrevious } from 'ahooks';\nimport { cn } from '../lib/utils';\nimport _ from 'lodash';\nimport React, {\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport Avatar from '../Avatar';\nimport ConfigProvider from '../ConfigProvider';\nimport './member-picker.css';\nimport {\n EUserType,\n IUser,\n IUserGroup,\n TUser,\n} from '../ConfigProvider/getUserList';\nimport { LocaleContext, getTranslator } from '../locale/default';\nimport Panel from './components/Panel';\nimport MemberPanel from './components/PanelWrapper';\nimport {\n EMemberPicker,\n IMemberPickerProps,\n TUserGroupValue,\n TUserValue,\n} from './interface';\nimport { getId } from './utils';\nimport { isUser, isUserGroup } from './utils/getUsersWithUserId';\nimport Select from '../Select';\nimport { Mode } from '../Select/BaseSelect';\n\nconst DEFAULT_MULTIPLE_DROPDOWN_WIDTH = 480;\n\nconst Component: React.FC<IMemberPickerProps> = (props) => {\n const { locale } = useContext(LocaleContext);\n const t = getTranslator(locale);\n\n const {\n value,\n type = EMemberPicker.USER,\n onChange,\n multiple,\n dropdownWidth,\n open,\n onOpenChange,\n placeholder = t.MemberPicker.select,\n className,\n allowClear,\n disabled,\n onClear,\n size,\n prefix: propPrefix,\n lockedIds = [],\n footer,\n onCancel,\n filterItem,\n mode: propsMode,\n renderItem,\n renderSelectedTag,\n 'data-testid': dataTestId,\n 'aria-label': ariaLabel,\n ...restProps\n } = props;\n const allowOverlap = props.allowOverlap ?? false;\n const {\n getUsersByIds,\n getUsersByKeywords,\n getUsersByKeywordsWithMeta,\n getNextLevelGroup,\n getNextLevelGroupWithMeta,\n } = ConfigProvider.useGetUserList();\n const inheritedDisabled = useContext(DisabledContext);\n const isDisabled = disabled ?? inheritedDisabled;\n // 呈现方式由选择模式唯一决定,多选使用弹窗,单选保持浮窗。\n const usesDialog = !!multiple;\n const usesGroupQuery =\n (type === EMemberPicker.GROUP ||\n (!getUsersByKeywords && !getUsersByKeywordsWithMeta)) &&\n !!(getNextLevelGroup || getNextLevelGroupWithMeta);\n const [selectedUserList, setSelectedUserList] = useState<TUser>([]);\n const prevValue = usePrevious(value);\n const [searchString, setSearchString] = useState<string>('');\n const [dropdownContentWidth, setDropdownContentWidth] =\n useState(dropdownWidth);\n const selectOwnerRef = useRef<HTMLDivElement>(null);\n // 单选默认浮窗跟触发器等宽(true),调用方传 dropdownWidth 时用具体数字;\n // 多选默认 480,单条记录只有 头像+nickname 时触发器太窄装不下选中标签,故固定值。\n const panelWidth: number | boolean =\n dropdownWidth ?? (multiple ? DEFAULT_MULTIPLE_DROPDOWN_WIDTH : true);\n\n const triggerRef = useRef<HTMLDivElement>(null);\n const onSearchValueChange = useCallback((v: string) => {\n setSearchString(v);\n }, []);\n const [dropdownOpen, setDropdownOpen] = useState<boolean>(!!open);\n const queryByKeywords = useMemo(() => {\n if (getUsersByKeywordsWithMeta) return getUsersByKeywordsWithMeta;\n if (getUsersByKeywords) {\n return getUsersByKeywords;\n }\n\n return (_params = {}) =>\n Promise.reject({\n message: t.MemberPicker.pleaseConfigGetUserByKeywordsMethod,\n });\n }, [\n getUsersByKeywords,\n getUsersByKeywordsWithMeta,\n t.MemberPicker.pleaseConfigGetUserByKeywordsMethod,\n ]);\n const queryByIds = useMemo(() => {\n if (getUsersByIds) {\n return getUsersByIds;\n }\n\n return (_params = {}) =>\n Promise.reject({\n message: t.MemberPicker.pleaseConfigGetUserByIdsMethod,\n });\n }, [getUsersByIds, t.MemberPicker.pleaseConfigGetUserByIdsMethod]);\n const {\n result: searchResult,\n loading,\n failed,\n retry,\n } = useMemberSearch<IUser | IUserGroup>(\n queryByKeywords,\n searchString,\n dropdownOpen && !usesGroupQuery && !usesDialog,\n );\n const searchUserList = searchResult.items;\n\n const totalUserList: TUser = useMemo(() => {\n const totalUsers = searchUserList.concat(\n selectedUserList.filter((item) => {\n const name =\n (item.type === EUserType.USER ? item.nickname : item.name) || '';\n\n return name.toUpperCase().includes(searchString.trim().toUpperCase());\n }),\n );\n\n return _.uniqBy(totalUsers, (data: IUser | IUserGroup) => {\n if (data.type === EUserType.USER) return data.userId;\n\n return data.groupId;\n }).filter((item) => {\n if (filterItem) {\n return filterItem(item);\n }\n\n return true;\n });\n }, [filterItem, searchString, searchUserList, selectedUserList]);\n\n useEffect(() => {\n if (_.isEqual(value, prevValue)) {\n return;\n }\n if (Array.isArray(value) && (isUser(value[0]) || isUserGroup(value[0]))) {\n setSelectedUserList(value as TUser);\n } else if (isUser(value) || isUserGroup(value)) {\n setSelectedUserList([value]);\n } else {\n let param: Array<TUserValue | TUserGroupValue> = [];\n if (!value) return;\n if (Array.isArray(value) && value.length === 0) {\n setSelectedUserList([]);\n\n return;\n }\n if (!Array.isArray(value)) {\n param = [value];\n } else {\n param = value;\n }\n queryByIds(param).then((result) => {\n // 如果用户已被删除,则需要展示出id\n const valueNotInResult: TUser = param\n .filter((item) => {\n if (item.type === EUserType.USER) {\n return result\n .filter(isUser)\n .every((user) => user.userId !== item.userId);\n }\n\n return result\n .filter(isUserGroup)\n .every((user) => user.groupId !== item.groupId);\n })\n .map((item) => {\n if (item.type === EUserType.USER) {\n return {\n type: EUserType.USER,\n userId: item.userId,\n } as IUser;\n }\n\n return {\n type: EUserType.USER_GROUP,\n groupId: item.groupId,\n } as IUserGroup;\n });\n\n setSelectedUserList([...result, ...valueNotInResult]);\n });\n }\n }, [value, queryByIds, prevValue]);\n\n const onSelectedChange = useCallback(\n (users: TUser) => {\n if (multiple) {\n setSelectedUserList(users);\n onChange?.(users);\n } else {\n setSelectedUserList(users);\n onChange?.(users[0]);\n }\n setDropdownOpen(false);\n },\n [multiple, onChange, setDropdownOpen],\n );\n\n useEffect(() => {\n if (typeof open === 'boolean') {\n setDropdownOpen(open);\n }\n }, [open]);\n\n useEffect(() => {\n if (!dropdownContentWidth) {\n const resizeEle = triggerRef.current;\n if (!resizeEle) return;\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width } = entry.contentRect;\n setDropdownContentWidth(width);\n }\n });\n resizeObserver.observe(resizeEle);\n\n return () => {\n resizeObserver.unobserve(resizeEle);\n };\n }\n }, [triggerRef, dropdownContentWidth]);\n const onDropdownOpenChange = useCallback(\n (open: boolean) => {\n if (isDisabled) return;\n if (!usesDialog || typeof props.open !== 'boolean') setDropdownOpen(open);\n onOpenChange?.(open);\n },\n [isDisabled, usesDialog, props.open, onOpenChange],\n );\n const mode = useMemo(() => {\n if (multiple === true) {\n if (typeof propsMode === 'object')\n return {\n ...(propsMode || {}),\n type: 'multiple',\n responsive: _.isNil(propsMode.responsive)\n ? true\n : propsMode.responsive,\n } as Mode;\n return {\n type: 'multiple',\n responsive: true,\n } as Mode;\n }\n\n return multiple ? (multiple as Mode) : propsMode;\n }, [multiple, propsMode]);\n\n const selectOpen = typeof open === 'boolean' ? open : dropdownOpen;\n const listboxId = `ald-member-picker-listbox-${React.useId().replace(\n /:/g,\n '',\n )}`;\n const activeMember = totalUserList[0];\n const activeMemberId = activeMember\n ? `ald-member-picker-option-${getId(activeMember)}`\n : undefined;\n\n useEffect(() => {\n const owner = selectOwnerRef.current?.querySelector<HTMLElement>(\n '[role=\"combobox\"], input',\n );\n if (!owner) return;\n owner.setAttribute('aria-controls', listboxId);\n if (selectOpen && activeMemberId) {\n owner.setAttribute('aria-activedescendant', activeMemberId);\n } else {\n owner.removeAttribute('aria-activedescendant');\n }\n }, [activeMemberId, listboxId, selectOpen]);\n\n return (\n <div ref={selectOwnerRef} style={{ display: 'contents' }}>\n <Select\n prefix={propPrefix}\n {...restProps}\n data-testid={dataTestId}\n aria-label={ariaLabel}\n showSearch={!usesDialog}\n searchValue={searchString}\n onSearch={onSearchValueChange}\n aria-controls={listboxId}\n aria-activedescendant={selectOpen ? activeMemberId : undefined}\n size={size}\n className={cn('ald-member-picker-select', className)}\n value={selectedUserList.map((item) => {\n if (item.type === EUserType.USER_GROUP) {\n return item.groupId;\n } else {\n return item.userId;\n }\n })}\n onChange={(value) => {\n if (_.isEmpty(value)) {\n setSelectedUserList([]);\n if (multiple) {\n onChange?.([]);\n } else {\n onChange?.();\n }\n } else {\n const valueList = Array.isArray(value) ? value : [value];\n if (multiple) {\n const users = valueList\n .map((item) => {\n const user = totalUserList.find((user) => {\n if (user.type === EUserType.USER) {\n return user.userId === item;\n }\n\n return user.groupId === item;\n });\n\n return user;\n })\n .filter(Boolean) as TUser;\n setSelectedUserList(users);\n onChange?.(users);\n } else {\n const user = totalUserList.find((user) => {\n if (user.type === EUserType.USER) {\n return user.userId === value;\n }\n\n return user.groupId === value;\n });\n if (user) {\n setSelectedUserList([user]);\n onChange?.(user);\n } else {\n setSelectedUserList([]);\n onChange?.();\n }\n }\n }\n }}\n disabled={isDisabled}\n allowClear={allowClear}\n onClear={() => {\n setSelectedUserList([]);\n if (multiple) {\n onChange?.([]);\n } else {\n onChange?.();\n }\n onClear?.();\n }}\n popupClassName={cn(\n 'ald-member-picker-popup',\n allowOverlap && 'ald-member-picker-popup-adaptive',\n props.popupClassName,\n )}\n mode={mode}\n options={selectedUserList.map((item) => {\n if (item.type === EUserType.USER_GROUP)\n return {\n label: item.name || item.groupId,\n value: item.groupId,\n tag: (\n <div className={'ald-member-picker-tag'}>\n <Avatar type=\"team\" size={20}>\n {item.name || item.groupId}\n </Avatar>\n {item.name || item.groupId}\n </div>\n ),\n };\n\n return {\n label: item.nickname || item.name || item.userId,\n value: item.userId,\n tag: (\n <div className={'ald-member-picker-tag'}>\n <Avatar\n size={20}\n src={item.photo}\n type={item.type === EUserType.USER ? 'user' : 'team'}\n >\n {item.nickname || item.name || item.userId}\n </Avatar>\n {item.nickname || item.name || item.userId}\n </div>\n ),\n };\n })}\n optionLabelProp={multiple ? 'label' : 'tag'}\n open={usesDialog ? false : selectOpen}\n onOpenChange={onDropdownOpenChange}\n popupMatchSelectWidth={panelWidth}\n allowOverlap={allowOverlap}\n placeholder={placeholder}\n dropdownRender={() => {\n return (\n <div\n ref={triggerRef}\n id={listboxId}\n role=\"group\"\n aria-label=\"Member selection results\"\n className={cn({\n 'ald-member-picker-popup-body': allowOverlap,\n })}\n >\n <Panel\n active={dropdownOpen}\n searchFailed={failed}\n useProvidedGroupSearch={\n type === EMemberPicker.BOTH && !usesGroupQuery\n }\n searchFeedback={\n !usesGroupQuery && (\n <SearchFeedback\n failed={failed}\n hasMore={searchResult.hasMore}\n pathWarningCode={searchResult.pathWarningCode}\n retry={retry}\n />\n )\n }\n type={type}\n footer={footer}\n value={selectedUserList}\n dataList={totalUserList}\n lockedIds={lockedIds}\n multiple={!!multiple}\n loading={loading}\n dropdownWidth={dropdownContentWidth}\n onSearchValueChange={onSearchValueChange}\n onChange={onSelectedChange}\n onCancel={() => {\n onCancel?.();\n setDropdownOpen(false);\n }}\n renderItem={renderItem}\n renderSelectedTag={renderSelectedTag}\n searchValue={searchString}\n ></Panel>\n </div>\n );\n }}\n ></Select>\n {usesDialog && !isDisabled && (\n <MemberPickerDialog\n open={selectOpen}\n value={value ?? selectedUserList}\n type={type}\n lockedIds={lockedIds}\n filterItem={filterItem}\n renderItem={renderItem}\n renderSelectedTag={renderSelectedTag}\n onChange={(members) => {\n setSelectedUserList(members);\n if (props.multiple) props.onChange?.(members);\n }}\n footer={footer}\n onCancel={onCancel}\n onClose={() => onDropdownOpenChange(false)}\n />\n )}\n </div>\n );\n};\n\nconst MemberSelector = Component as React.FC<IMemberPickerProps> & {\n MemberPanel: typeof MemberPanel;\n Dialog: typeof MemberPickerDialog;\n};\n\nMemberSelector.MemberPanel = MemberPanel;\nMemberSelector.Dialog = MemberPickerDialog;\nexport default MemberSelector;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/MemberPicker/index.tsx"],"sourcesContent":["import MemberPickerDialog from './dialog/MemberPickerDialog';\nimport DisabledContext from '../ConfigProvider/DisabledContext';\nimport useMemberSearch from './useMemberSearch';\nimport SearchFeedback from './components/SearchFeedback';\nimport { usePrevious } from 'ahooks';\nimport { cn } from '../lib/utils';\nimport _ from 'lodash';\nimport React, {\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport Avatar from '../Avatar';\nimport ConfigProvider from '../ConfigProvider';\nimport './member-picker.css';\nimport {\n EUserType,\n IUser,\n IUserGroup,\n TUser,\n} from '../ConfigProvider/getUserList';\nimport { LocaleContext, getTranslator } from '../locale/default';\nimport Panel from './components/Panel';\nimport MemberPanel from './components/PanelWrapper';\nimport {\n EMemberPicker,\n IMemberPickerProps,\n TUserGroupValue,\n TUserValue,\n} from './interface';\nimport { getId } from './utils';\nimport { isUser, isUserGroup } from './utils/getUsersWithUserId';\nimport Select from '../Select';\nimport { Mode } from '../Select/BaseSelect';\n\nconst DEFAULT_MULTIPLE_DROPDOWN_WIDTH = 480;\n\nconst Component: React.FC<IMemberPickerProps> = (props) => {\n const { locale } = useContext(LocaleContext);\n const t = getTranslator(locale);\n\n const {\n value,\n type = EMemberPicker.USER,\n onChange,\n multiple,\n dropdownWidth,\n open,\n onOpenChange,\n placeholder = t.MemberPicker.select,\n className,\n allowClear,\n disabled,\n onClear,\n size,\n prefix: propPrefix,\n lockedIds = [],\n footer,\n onCancel,\n filterItem,\n mode: propsMode,\n canEnterGroup,\n renderItem,\n renderSelectedTag,\n 'data-testid': dataTestId,\n 'aria-label': ariaLabel,\n ...restProps\n } = props;\n const allowOverlap = props.allowOverlap ?? false;\n const {\n getUsersByIds,\n getUsersByKeywords,\n getUsersByKeywordsWithMeta,\n getNextLevelGroup,\n getNextLevelGroupWithMeta,\n } = ConfigProvider.useGetUserList();\n const inheritedDisabled = useContext(DisabledContext);\n const isDisabled = disabled ?? inheritedDisabled;\n // 呈现方式由选择模式唯一决定,多选使用弹窗,单选保持浮窗。\n const usesDialog = !!multiple;\n const usesGroupQuery =\n (type === EMemberPicker.GROUP ||\n (!getUsersByKeywords && !getUsersByKeywordsWithMeta)) &&\n !!(getNextLevelGroup || getNextLevelGroupWithMeta);\n const [selectedUserList, setSelectedUserList] = useState<TUser>([]);\n const prevValue = usePrevious(value);\n const [searchString, setSearchString] = useState<string>('');\n const [dropdownContentWidth, setDropdownContentWidth] =\n useState(dropdownWidth);\n const selectOwnerRef = useRef<HTMLDivElement>(null);\n // 单选默认浮窗跟触发器等宽(true),调用方传 dropdownWidth 时用具体数字;\n // 多选默认 480,单条记录只有 头像+nickname 时触发器太窄装不下选中标签,故固定值。\n const panelWidth: number | boolean =\n dropdownWidth ?? (multiple ? DEFAULT_MULTIPLE_DROPDOWN_WIDTH : true);\n\n const triggerRef = useRef<HTMLDivElement>(null);\n const onSearchValueChange = useCallback((v: string) => {\n setSearchString(v);\n }, []);\n const [dropdownOpen, setDropdownOpen] = useState<boolean>(!!open);\n const queryByKeywords = useMemo(() => {\n if (getUsersByKeywordsWithMeta) return getUsersByKeywordsWithMeta;\n if (getUsersByKeywords) {\n return getUsersByKeywords;\n }\n\n return (_params = {}) =>\n Promise.reject({\n message: t.MemberPicker.pleaseConfigGetUserByKeywordsMethod,\n });\n }, [\n getUsersByKeywords,\n getUsersByKeywordsWithMeta,\n t.MemberPicker.pleaseConfigGetUserByKeywordsMethod,\n ]);\n const queryByIds = useMemo(() => {\n if (getUsersByIds) {\n return getUsersByIds;\n }\n\n return (_params = {}) =>\n Promise.reject({\n message: t.MemberPicker.pleaseConfigGetUserByIdsMethod,\n });\n }, [getUsersByIds, t.MemberPicker.pleaseConfigGetUserByIdsMethod]);\n const {\n result: searchResult,\n loading,\n failed,\n retry,\n } = useMemberSearch<IUser | IUserGroup>(\n queryByKeywords,\n searchString,\n dropdownOpen && !usesGroupQuery && !usesDialog,\n );\n const searchUserList = searchResult.items;\n\n const totalUserList: TUser = useMemo(() => {\n const totalUsers = searchUserList.concat(\n selectedUserList.filter((item) => {\n const name =\n (item.type === EUserType.USER ? item.nickname : item.name) || '';\n\n return name.toUpperCase().includes(searchString.trim().toUpperCase());\n }),\n );\n\n return _.uniqBy(totalUsers, (data: IUser | IUserGroup) => {\n if (data.type === EUserType.USER) return data.userId;\n\n return data.groupId;\n }).filter((item) => {\n if (filterItem) {\n return filterItem(item);\n }\n\n return true;\n });\n }, [filterItem, searchString, searchUserList, selectedUserList]);\n\n useEffect(() => {\n if (_.isEqual(value, prevValue)) {\n return;\n }\n if (Array.isArray(value) && (isUser(value[0]) || isUserGroup(value[0]))) {\n setSelectedUserList(value as TUser);\n } else if (isUser(value) || isUserGroup(value)) {\n setSelectedUserList([value]);\n } else {\n let param: Array<TUserValue | TUserGroupValue> = [];\n if (!value) return;\n if (Array.isArray(value) && value.length === 0) {\n setSelectedUserList([]);\n\n return;\n }\n if (!Array.isArray(value)) {\n param = [value];\n } else {\n param = value;\n }\n queryByIds(param).then((result) => {\n // 如果用户已被删除,则需要展示出id\n const valueNotInResult: TUser = param\n .filter((item) => {\n if (item.type === EUserType.USER) {\n return result\n .filter(isUser)\n .every((user) => user.userId !== item.userId);\n }\n\n return result\n .filter(isUserGroup)\n .every((user) => user.groupId !== item.groupId);\n })\n .map((item) => {\n if (item.type === EUserType.USER) {\n return {\n type: EUserType.USER,\n userId: item.userId,\n } as IUser;\n }\n\n return {\n type: EUserType.USER_GROUP,\n groupId: item.groupId,\n } as IUserGroup;\n });\n\n setSelectedUserList([...result, ...valueNotInResult]);\n });\n }\n }, [value, queryByIds, prevValue]);\n\n const onSelectedChange = useCallback(\n (users: TUser) => {\n if (multiple) {\n setSelectedUserList(users);\n onChange?.(users);\n } else {\n setSelectedUserList(users);\n onChange?.(users[0]);\n }\n setDropdownOpen(false);\n },\n [multiple, onChange, setDropdownOpen],\n );\n\n useEffect(() => {\n if (typeof open === 'boolean') {\n setDropdownOpen(open);\n }\n }, [open]);\n\n useEffect(() => {\n if (!dropdownContentWidth) {\n const resizeEle = triggerRef.current;\n if (!resizeEle) return;\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width } = entry.contentRect;\n setDropdownContentWidth(width);\n }\n });\n resizeObserver.observe(resizeEle);\n\n return () => {\n resizeObserver.unobserve(resizeEle);\n };\n }\n }, [triggerRef, dropdownContentWidth]);\n const onDropdownOpenChange = useCallback(\n (open: boolean) => {\n if (isDisabled) return;\n if (!usesDialog || typeof props.open !== 'boolean') setDropdownOpen(open);\n onOpenChange?.(open);\n },\n [isDisabled, usesDialog, props.open, onOpenChange],\n );\n const mode = useMemo(() => {\n if (multiple === true) {\n if (typeof propsMode === 'object')\n return {\n ...(propsMode || {}),\n type: 'multiple',\n responsive: _.isNil(propsMode.responsive)\n ? true\n : propsMode.responsive,\n } as Mode;\n return {\n type: 'multiple',\n responsive: true,\n } as Mode;\n }\n\n return multiple ? (multiple as Mode) : propsMode;\n }, [multiple, propsMode]);\n\n const selectOpen = typeof open === 'boolean' ? open : dropdownOpen;\n const listboxId = `ald-member-picker-listbox-${React.useId().replace(\n /:/g,\n '',\n )}`;\n const activeMember = totalUserList[0];\n const activeMemberId = activeMember\n ? `ald-member-picker-option-${getId(activeMember)}`\n : undefined;\n\n useEffect(() => {\n const owner = selectOwnerRef.current?.querySelector<HTMLElement>(\n '[role=\"combobox\"], input',\n );\n if (!owner) return;\n owner.setAttribute('aria-controls', listboxId);\n if (selectOpen && activeMemberId) {\n owner.setAttribute('aria-activedescendant', activeMemberId);\n } else {\n owner.removeAttribute('aria-activedescendant');\n }\n }, [activeMemberId, listboxId, selectOpen]);\n\n return (\n <div ref={selectOwnerRef} style={{ display: 'contents' }}>\n <Select\n prefix={propPrefix}\n {...restProps}\n data-testid={dataTestId}\n aria-label={ariaLabel}\n showSearch={!usesDialog}\n searchValue={searchString}\n onSearch={onSearchValueChange}\n aria-controls={listboxId}\n aria-activedescendant={selectOpen ? activeMemberId : undefined}\n size={size}\n className={cn('ald-member-picker-select', className)}\n value={selectedUserList.map((item) => {\n if (item.type === EUserType.USER_GROUP) {\n return item.groupId;\n } else {\n return item.userId;\n }\n })}\n onChange={(value) => {\n if (_.isEmpty(value)) {\n setSelectedUserList([]);\n if (multiple) {\n onChange?.([]);\n } else {\n onChange?.();\n }\n } else {\n const valueList = Array.isArray(value) ? value : [value];\n if (multiple) {\n const users = valueList\n .map((item) => {\n const user = totalUserList.find((user) => {\n if (user.type === EUserType.USER) {\n return user.userId === item;\n }\n\n return user.groupId === item;\n });\n\n return user;\n })\n .filter(Boolean) as TUser;\n setSelectedUserList(users);\n onChange?.(users);\n } else {\n const user = totalUserList.find((user) => {\n if (user.type === EUserType.USER) {\n return user.userId === value;\n }\n\n return user.groupId === value;\n });\n if (user) {\n setSelectedUserList([user]);\n onChange?.(user);\n } else {\n setSelectedUserList([]);\n onChange?.();\n }\n }\n }\n }}\n disabled={isDisabled}\n allowClear={allowClear}\n onClear={() => {\n setSelectedUserList([]);\n if (multiple) {\n onChange?.([]);\n } else {\n onChange?.();\n }\n onClear?.();\n }}\n popupClassName={cn(\n 'ald-member-picker-popup',\n allowOverlap && 'ald-member-picker-popup-adaptive',\n props.popupClassName,\n )}\n mode={mode}\n options={selectedUserList.map((item) => {\n if (item.type === EUserType.USER_GROUP)\n return {\n label: item.name || item.groupId,\n value: item.groupId,\n tag: (\n <div className={'ald-member-picker-tag'}>\n <Avatar type=\"team\" size={20}>\n {item.name || item.groupId}\n </Avatar>\n {item.name || item.groupId}\n </div>\n ),\n };\n\n return {\n label: item.nickname || item.name || item.userId,\n value: item.userId,\n tag: (\n <div className={'ald-member-picker-tag'}>\n <Avatar\n size={20}\n src={item.photo}\n type={item.type === EUserType.USER ? 'user' : 'team'}\n >\n {item.nickname || item.name || item.userId}\n </Avatar>\n {item.nickname || item.name || item.userId}\n </div>\n ),\n };\n })}\n optionLabelProp={multiple ? 'label' : 'tag'}\n open={usesDialog ? false : selectOpen}\n onOpenChange={onDropdownOpenChange}\n popupMatchSelectWidth={panelWidth}\n allowOverlap={allowOverlap}\n placeholder={placeholder}\n dropdownRender={() => {\n return (\n <div\n ref={triggerRef}\n id={listboxId}\n role=\"group\"\n aria-label=\"Member selection results\"\n className={cn({\n 'ald-member-picker-popup-body': allowOverlap,\n })}\n >\n <Panel\n active={dropdownOpen}\n searchFailed={failed}\n useProvidedGroupSearch={\n type === EMemberPicker.BOTH && !usesGroupQuery\n }\n searchFeedback={\n !usesGroupQuery && (\n <SearchFeedback\n failed={failed}\n hasMore={searchResult.hasMore}\n pathWarningCode={searchResult.pathWarningCode}\n retry={retry}\n />\n )\n }\n type={type}\n footer={footer}\n value={selectedUserList}\n dataList={totalUserList}\n lockedIds={lockedIds}\n multiple={!!multiple}\n loading={loading}\n dropdownWidth={dropdownContentWidth}\n onSearchValueChange={onSearchValueChange}\n onChange={onSelectedChange}\n onCancel={() => {\n onCancel?.();\n setDropdownOpen(false);\n }}\n canEnterGroup={canEnterGroup}\n renderItem={renderItem}\n renderSelectedTag={renderSelectedTag}\n searchValue={searchString}\n ></Panel>\n </div>\n );\n }}\n ></Select>\n {usesDialog && !isDisabled && (\n <MemberPickerDialog\n open={selectOpen}\n value={value ?? selectedUserList}\n type={type}\n lockedIds={lockedIds}\n filterItem={filterItem}\n canEnterGroup={canEnterGroup}\n renderItem={renderItem}\n renderSelectedTag={renderSelectedTag}\n onChange={(members) => {\n setSelectedUserList(members);\n if (props.multiple) props.onChange?.(members);\n }}\n footer={footer}\n onCancel={onCancel}\n onClose={() => onDropdownOpenChange(false)}\n />\n )}\n </div>\n );\n};\n\nconst MemberSelector = Component as React.FC<IMemberPickerProps> & {\n MemberPanel: typeof MemberPanel;\n Dialog: typeof MemberPickerDialog;\n};\n\nMemberSelector.MemberPanel = MemberPanel;\nMemberSelector.Dialog = MemberPickerDialog;\nexport default MemberSelector;\n"],"mappings":"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"}
@@ -20,6 +20,12 @@ export interface IBaseMemberSelectorProps extends Omit<ISelectProps, 'type'> {
20
20
  lockedIds?: Array<TUserValue | TUserGroupValue>;
21
21
  footer?: ReactNode;
22
22
  onCancel?: () => void;
23
+ /**
24
+ * 控制用户组是否允许进入下级。返回 false 时隐藏“下级”入口,且不提供
25
+ * renderItem 的 onNextLevel;不影响该组的选择。省略时保留原有层级行为。
26
+ * 平铺列表可传 () => false,也可按组判断;仅限制原有入口,不推断层级数据。
27
+ */
28
+ canEnterGroup?: (group: IUserGroup) => boolean;
23
29
  filterItem?: (item: IUser | IUserGroup) => boolean;
24
30
  renderItem?: (item: IUser | IUserGroup, context: {
25
31
  isSelected: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"interface.js","names":[],"sources":["../../src/MemberPicker/interface.ts"],"sourcesContent":["import { ReactNode } from 'react';\nimport { IUser, IUserGroup, TUser } from '../ConfigProvider/getUserList';\nimport { ISelectProps } from '../Select/interface';\nimport { RequiredProperties } from '../_utils/type';\nimport { Mode } from '../Select/BaseSelect';\n\nexport type TUserValue = RequiredProperties<IUser, 'type' | 'userId'>;\nexport type TUserGroupValue = RequiredProperties<\n IUserGroup,\n 'type' | 'groupId'\n>;\n\nexport type TMemberPickerValue =\n | TUserValue\n | TUserGroupValue\n | Array<TUserValue | TUserGroupValue>;\n\nexport enum EMemberPicker {\n GROUP = 'GROUP',\n USER = 'USER',\n BOTH = 'BOTH',\n}\n\nexport interface IBaseMemberSelectorProps extends Omit<ISelectProps, 'type'> {\n value?: TMemberPickerValue;\n dropdownWidth?: number;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n type?: EMemberPicker;\n lockedIds?: Array<TUserValue | TUserGroupValue>;\n footer?: ReactNode;\n onCancel?: () => void;\n filterItem?: (item: IUser | IUserGroup) => boolean;\n renderItem?: (\n item: IUser | IUserGroup,\n context: {\n isSelected: boolean;\n disabled: boolean;\n onClick: () => void;\n onNextLevel?: () => void;\n originNode: ReactNode;\n },\n ) => ReactNode;\n renderSelectedTag?: (\n user: IUser | IUserGroup,\n originNode: ReactNode,\n ) => ReactNode;\n}\n\nexport interface IMultipleMemberSelectorProps extends IBaseMemberSelectorProps {\n multiple: true | Mode;\n onChange?(v: TUser): void;\n}\n\nexport interface ISingleMemberSelectorProps extends IBaseMemberSelectorProps {\n multiple?: false;\n onChange?(v?: IUser | IUserGroup): void;\n}\n\nexport type IMemberPickerProps =\n | IMultipleMemberSelectorProps\n | ISingleMemberSelectorProps;\n\nexport type { IUser };\n"],"mappings":";AAiBA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,WAAA;AACA,eAAA,UAAA;AACA,eAAA,UAAA;;KACD"}
1
+ {"version":3,"file":"interface.js","names":[],"sources":["../../src/MemberPicker/interface.ts"],"sourcesContent":["import { ReactNode } from 'react';\nimport { IUser, IUserGroup, TUser } from '../ConfigProvider/getUserList';\nimport { ISelectProps } from '../Select/interface';\nimport { RequiredProperties } from '../_utils/type';\nimport { Mode } from '../Select/BaseSelect';\n\nexport type TUserValue = RequiredProperties<IUser, 'type' | 'userId'>;\nexport type TUserGroupValue = RequiredProperties<\n IUserGroup,\n 'type' | 'groupId'\n>;\n\nexport type TMemberPickerValue =\n | TUserValue\n | TUserGroupValue\n | Array<TUserValue | TUserGroupValue>;\n\nexport enum EMemberPicker {\n GROUP = 'GROUP',\n USER = 'USER',\n BOTH = 'BOTH',\n}\n\nexport interface IBaseMemberSelectorProps extends Omit<ISelectProps, 'type'> {\n value?: TMemberPickerValue;\n dropdownWidth?: number;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n type?: EMemberPicker;\n lockedIds?: Array<TUserValue | TUserGroupValue>;\n footer?: ReactNode;\n onCancel?: () => void;\n /**\n * 控制用户组是否允许进入下级。返回 false 时隐藏“下级”入口,且不提供\n * renderItem 的 onNextLevel;不影响该组的选择。省略时保留原有层级行为。\n * 平铺列表可传 () => false,也可按组判断;仅限制原有入口,不推断层级数据。\n */\n canEnterGroup?: (group: IUserGroup) => boolean;\n filterItem?: (item: IUser | IUserGroup) => boolean;\n renderItem?: (\n item: IUser | IUserGroup,\n context: {\n isSelected: boolean;\n disabled: boolean;\n onClick: () => void;\n onNextLevel?: () => void;\n originNode: ReactNode;\n },\n ) => ReactNode;\n renderSelectedTag?: (\n user: IUser | IUserGroup,\n originNode: ReactNode,\n ) => ReactNode;\n}\n\nexport interface IMultipleMemberSelectorProps extends IBaseMemberSelectorProps {\n multiple: true | Mode;\n onChange?(v: TUser): void;\n}\n\nexport interface ISingleMemberSelectorProps extends IBaseMemberSelectorProps {\n multiple?: false;\n onChange?(v?: IUser | IUserGroup): void;\n}\n\nexport type IMemberPickerProps =\n | IMultipleMemberSelectorProps\n | ISingleMemberSelectorProps;\n\nexport type { IUser };\n"],"mappings":";AAiBA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,WAAA;AACA,eAAA,UAAA;AACA,eAAA,UAAA;;KACD"}
@@ -10,6 +10,10 @@ export interface MenuProps {
10
10
  ariaLabel?: string;
11
11
  /** @internal Requested initial keyboard position when the popup opens. */
12
12
  initialFocus?: 'first' | 'last';
13
+ /** @internal Whether the current popup entry originated from keyboard input. */
14
+ keyboardFocus?: boolean;
15
+ /** @internal Whether opening the popup should transfer focus to an item. */
16
+ captureFocus?: boolean;
13
17
  /** @internal Requests that the owning popup close, including on Escape. */
14
18
  onRequestClose?: (reason: 'click' | 'cancel' | 'blur' | 'scroll') => void;
15
19
  rootClosing?: boolean;
@@ -2,19 +2,23 @@ import { cn } from "../lib/utils.js";
2
2
  import Memo from "../Icon/components/CheckLightLine.js";
3
3
  import Memo$1 from "../Icon/components/ChevronRightLine.js";
4
4
  /* empty css */
5
- import { useCallback, useMemo, useRef } from "react";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import _ from "lodash";
8
8
  import { ControlledMenu, MenuDivider, MenuHeader, MenuItem, SubMenu } from "@szhsin/react-menu";
9
9
  //#region src/Menu/index.tsx
10
10
  function Menu(props) {
11
- const { className, items, onClick: onMenuClick, onBeforeLeafItemClick, selectedKeys, menuStyle, ariaLabel, initialFocus = "first", onRequestClose, rootClosing = false, externalOverflow, preserveRootHeight = false } = props;
11
+ const { className, items, onClick: onMenuClick, onBeforeLeafItemClick, selectedKeys, menuStyle, ariaLabel, initialFocus = "first", keyboardFocus = false, captureFocus = true, onRequestClose, rootClosing = false, externalOverflow, preserveRootHeight = false } = props;
12
12
  const selectedKeySet = new Set(selectedKeys || []);
13
13
  const typeaheadRef = useRef({
14
14
  query: "",
15
15
  timestamp: 0
16
16
  });
17
- const menuItemFocus = useMemo(() => ({ position: initialFocus }), [initialFocus]);
17
+ const [isKeyboardFocus, setIsKeyboardFocus] = useState(keyboardFocus);
18
+ useEffect(() => {
19
+ setIsKeyboardFocus(keyboardFocus);
20
+ }, [keyboardFocus]);
21
+ const menuItemFocus = useMemo(() => captureFocus ? { position: initialFocus } : void 0, [captureFocus, initialFocus]);
18
22
  const onMenuKeyDown = useCallback((event) => {
19
23
  if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.key.length !== 1) return;
20
24
  const now = Date.now();
@@ -71,14 +75,17 @@ function Menu(props) {
71
75
  }),
72
76
  disabled: item.disabled,
73
77
  "aria-label": item.ariaLabel || titleStr || void 0,
74
- itemProps: { className: isLeafSubMenu ? "ald-menu-submenu-leaf-trigger" : void 0 },
78
+ itemProps: {
79
+ className: isLeafSubMenu ? "ald-menu-submenu-leaf-trigger" : void 0,
80
+ onPointerMove: () => setIsKeyboardFocus(false)
81
+ },
75
82
  align: "start",
76
83
  direction: "right",
77
84
  gap: -8,
78
85
  position: "anchor",
79
86
  shift: 0,
80
87
  menuStyle: { ...menuStyle },
81
- menuClassName: cn("ald-menu-submenu-popup", isLeafSubMenu && "ald-menu-submenu-popup-leaf", !isLeafSubMenu && menuStyle?.maxHeight !== void 0 && "ald-menu-submenu-popup-scrollable", item.popupClassName, rootClosing && "ald-menu-submenu-popup-root-closing"),
88
+ menuClassName: cn("ald-menu-submenu-popup", isLeafSubMenu && "ald-menu-submenu-popup-leaf", !isLeafSubMenu && menuStyle?.maxHeight !== void 0 && "ald-menu-submenu-popup-scrollable", item.popupClassName, isKeyboardFocus && "ald-menu-focus-visible", rootClosing && "ald-menu-submenu-popup-root-closing"),
82
89
  overflow: subMenuOverflow,
83
90
  children: getMenuItems(item.children, currPath)
84
91
  }, item.key);
@@ -90,6 +97,7 @@ function Menu(props) {
90
97
  }),
91
98
  disabled: item.disabled,
92
99
  "aria-label": item.ariaLabel || titleStr || void 0,
100
+ onPointerMove: () => setIsKeyboardFocus(false),
93
101
  onClick: ({ syntheticEvent }) => {
94
102
  onBeforeLeafItemClick?.();
95
103
  const menuInfo = {
@@ -142,12 +150,15 @@ function Menu(props) {
142
150
  setDownOverflow: preserveRootHeight,
143
151
  menuStyle: { ...menuStyle },
144
152
  anchorRef: ref,
145
- className: cn(className, "ald-menu", isLeafRootMenu && !externalOverflow && "ald-menu-leaf", !isLeafRootMenu && menuStyle?.maxHeight !== void 0 && !externalOverflow && "ald-menu-scrollable"),
153
+ className: cn(className, "ald-menu", isKeyboardFocus && "ald-menu-focus-visible", isLeafRootMenu && !externalOverflow && "ald-menu-leaf", !isLeafRootMenu && menuStyle?.maxHeight !== void 0 && !externalOverflow && "ald-menu-scrollable"),
146
154
  position: externalOverflow ? "initial" : "auto",
147
155
  "aria-label": ariaLabel,
148
- captureFocus: true,
156
+ captureFocus,
149
157
  menuItemFocus,
150
- onKeyDown: onMenuKeyDown,
158
+ onKeyDown: (event) => {
159
+ setIsKeyboardFocus(true);
160
+ onMenuKeyDown(event);
161
+ },
151
162
  onClose: ({ reason }) => onRequestClose?.(reason),
152
163
  viewScroll: externalOverflow ? "initial" : "auto",
153
164
  overflow: externalOverflow ? "visible" : menuOverflow,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/Menu/index.tsx"],"sourcesContent":["import {\n ControlledMenu,\n MenuDivider,\n MenuHeader,\n MenuItem,\n SubMenu,\n} from '@szhsin/react-menu';\nimport { cn } from '../lib/utils';\nimport _ from 'lodash';\nimport React, { useCallback, useMemo, useRef } from 'react';\nimport { CheckLightLine, ChevronRightLine } from '../Icon';\nimport './menu.css';\n\nexport interface MenuProps {\n onClick?: (info: MenuInfo) => void;\n onBeforeLeafItemClick?: () => void;\n className?: string;\n items: MenuItemType[];\n selectedKeys?: string[];\n menuStyle?: React.CSSProperties;\n /** Accessible name for the command menu when its context is not otherwise clear. */\n ariaLabel?: string;\n /** @internal Requested initial keyboard position when the popup opens. */\n initialFocus?: 'first' | 'last';\n /** @internal Requests that the owning popup close, including on Escape. */\n onRequestClose?: (reason: 'click' | 'cancel' | 'blur' | 'scroll') => void;\n rootClosing?: boolean;\n /** @internal 由 Dropdown 注入,禁用内部定位/overflow 计算。不支持含 SubMenu 的菜单 */\n externalOverflow?: boolean;\n /** @internal Dropdown 已按实际裁剪区域计算根菜单高度,保留其限制。 */\n preserveRootHeight?: boolean;\n}\n\nexport default function Menu(props: MenuProps) {\n const {\n className,\n items,\n onClick: onMenuClick,\n onBeforeLeafItemClick,\n selectedKeys,\n menuStyle,\n ariaLabel,\n initialFocus = 'first',\n onRequestClose,\n rootClosing = false,\n externalOverflow,\n preserveRootHeight = false,\n } = props;\n const selectedKeySet = new Set(selectedKeys || []);\n const typeaheadRef = useRef({ query: '', timestamp: 0 });\n const menuItemFocus = useMemo(\n () => ({ position: initialFocus }),\n [initialFocus],\n );\n const onMenuKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLUListElement>) => {\n if (\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.key.length !== 1\n ) {\n return;\n }\n\n const now = Date.now();\n const previous = typeaheadRef.current;\n const query = `${now - previous.timestamp > 500 ? '' : previous.query}${\n event.key\n }`.toLocaleLowerCase();\n typeaheadRef.current = { query, timestamp: now };\n\n const menuItems = Array.from(\n event.currentTarget.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"]:not([aria-hidden=\"true\"]):not([aria-disabled=\"true\"])',\n ),\n );\n const activeIndex = menuItems.indexOf(\n document.activeElement as HTMLElement,\n );\n const orderedItems = [\n ...menuItems.slice(activeIndex + 1),\n ...menuItems.slice(0, activeIndex + 1),\n ];\n const match = orderedItems.find((item) => {\n const accessibleName =\n item.getAttribute('aria-label') || item.textContent || '';\n return accessibleName.trim().toLocaleLowerCase().startsWith(query);\n });\n\n if (match) {\n event.preventDefault();\n match.focus();\n }\n },\n [],\n );\n\n const hasDirectSelectedItem = (items: MenuItemType[]): boolean =>\n items.some((item) => {\n if (isSubMenuItem(item)) {\n return selectedKeySet.has(item.key);\n }\n if (isHeaderMenuItem(item) || isDivideMenuItem(item)) {\n return false;\n }\n return selectedKeySet.has(item.key);\n });\n\n const getMenuItems = (items: MenuItemType[], parentPath: string[] = []) => {\n const isSelectableGroup = hasDirectSelectedItem(items);\n\n return items.map((item, index) => {\n if (isDivideMenuItem(item)) {\n return <MenuDivider key={`divider_${index}`} />;\n }\n if (isHeaderMenuItem(item)) {\n return <MenuHeader key={`header_${index}`}>{item.label}</MenuHeader>;\n }\n const currPath = [...parentPath, item.key];\n const titleStr = _.isString(item.label) ? item.label : '';\n const isSelected = selectedKeys?.includes(item.key);\n\n if (isSubMenuItem(item)) {\n const isLeafSubMenu = isLeafMenu(item.children);\n const subMenuOverflow =\n isLeafSubMenu || menuStyle?.maxHeight !== undefined\n ? 'auto'\n : undefined;\n const subMenuItemLabel = (\n <div className=\"ald-menu-item-label\" title={titleStr}>\n {item.icon ? (\n <div className=\"ald-menu-item-label-icon\">{item.icon}</div>\n ) : null}\n <div className=\"ald-menu-item-label-text tw-text-typography-body-dense\">\n {item.label}\n </div>\n <div className=\"ald-menu-item-right\">\n {isSelectableGroup ? (\n <div className=\"ald-menu-item-selection-slot\" />\n ) : null}\n <ChevronRightLine size={16} />\n </div>\n </div>\n );\n\n return (\n <SubMenu\n key={item.key}\n label={subMenuItemLabel}\n disabled={item.disabled}\n aria-label={item.ariaLabel || titleStr || undefined}\n itemProps={{\n className: isLeafSubMenu\n ? 'ald-menu-submenu-leaf-trigger'\n : undefined,\n }}\n align=\"start\"\n direction=\"right\"\n gap={-8}\n position=\"anchor\"\n shift={0}\n menuStyle={{\n ...menuStyle,\n }}\n menuClassName={cn(\n 'ald-menu-submenu-popup',\n isLeafSubMenu && 'ald-menu-submenu-popup-leaf',\n !isLeafSubMenu &&\n menuStyle?.maxHeight !== undefined &&\n 'ald-menu-submenu-popup-scrollable',\n item.popupClassName,\n rootClosing && 'ald-menu-submenu-popup-root-closing',\n )}\n overflow={subMenuOverflow}\n >\n {getMenuItems(item.children, currPath)}\n </SubMenu>\n );\n }\n\n return (\n <MenuItem\n key={item.key}\n className={cn(item.className, {\n 'ald-menu-item-danger': item.danger,\n 'ald-menu-item-selected': isSelected,\n })}\n disabled={item.disabled}\n aria-label={item.ariaLabel || titleStr || undefined}\n onClick={({ syntheticEvent }) => {\n onBeforeLeafItemClick?.();\n const menuInfo = {\n key: item.key,\n keyPath: currPath,\n domEvent: syntheticEvent,\n };\n if (item.onClick) {\n item.onClick(menuInfo);\n }\n if (onMenuClick) {\n onMenuClick(menuInfo);\n }\n }}\n >\n <div className=\"ald-menu-item-label\" title={titleStr}>\n {item.icon ? (\n <div className=\"ald-menu-item-label-icon\">{item.icon}</div>\n ) : null}\n <div className=\"ald-menu-item-label-text tw-text-typography-body-dense\">\n {item.label}\n </div>\n {isSelectableGroup ? (\n <div className=\"ald-menu-item-right\">\n <div\n className={cn(\n 'ald-menu-item-selection-slot',\n isSelected &&\n 'ald-menu-item-selection-slot-selected ald-menu-item-selected-icon',\n )}\n >\n {isSelected ? <CheckLightLine /> : null}\n </div>\n </div>\n ) : null}\n </div>\n </MenuItem>\n );\n });\n };\n\n const ref = useRef(null);\n\n const isLeafRootMenu = isLeafMenu(items);\n const menuOverflow =\n isLeafRootMenu || menuStyle?.maxHeight !== undefined ? 'auto' : undefined;\n\n return (\n <>\n <div style={{ display: 'none' }} ref={ref} />\n <ControlledMenu\n state=\"open\"\n transition={{ open: true, close: true }}\n transitionTimeout={200}\n submenuOpenDelay={0}\n submenuCloseDelay={0}\n // Root positioning is owned by the surrounding surface (see menu.css).\n // Keep an explicit caller height: React-Menu otherwise replaces it with\n // its viewport limit. Descendant SubMenus still calculate their own overflow.\n setDownOverflow={preserveRootHeight}\n menuStyle={{\n ...menuStyle,\n }}\n anchorRef={ref}\n className={cn(\n className,\n 'ald-menu',\n isLeafRootMenu && !externalOverflow && 'ald-menu-leaf',\n !isLeafRootMenu &&\n menuStyle?.maxHeight !== undefined &&\n !externalOverflow &&\n 'ald-menu-scrollable',\n )}\n position={externalOverflow ? 'initial' : 'auto'}\n aria-label={ariaLabel}\n captureFocus\n menuItemFocus={menuItemFocus}\n onKeyDown={onMenuKeyDown}\n onClose={({ reason }) => onRequestClose?.(reason)}\n viewScroll={externalOverflow ? 'initial' : 'auto'}\n overflow={externalOverflow ? 'visible' : menuOverflow}\n >\n {getMenuItems(items || [])}\n </ControlledMenu>\n </>\n );\n}\n\nexport function isHeaderMenuItem(\n menuItem: MenuItemType,\n): menuItem is MenuHeaderType {\n return 'type' in menuItem && menuItem.type === 'header';\n}\n\nexport function isDivideMenuItem(\n menuItem: MenuItemType,\n): menuItem is MenuDividerType {\n return 'type' in menuItem && menuItem.type === 'divider';\n}\n\nexport function isSubMenuItem(menuItem: MenuItemType): menuItem is SubMenuType {\n return 'children' in menuItem && !!menuItem.children;\n}\n\nexport function isLeafMenu(menuItems: MenuItemType[]) {\n return !_.some(menuItems, (item) => isSubMenuItem(item));\n}\n\nexport type MenuItemType =\n | ItemType\n | MenuDividerType\n | SubMenuType\n | MenuHeaderType;\n\nexport interface ItemType {\n key: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n onClick?: (info: MenuInfo) => void;\n danger?: boolean;\n disabled?: boolean;\n className?: string;\n ariaLabel?: string;\n}\n\nexport interface MenuDividerType {\n key?: string;\n type: 'divider';\n}\n\nexport interface MenuHeaderType {\n key?: string;\n type: 'header';\n label: string;\n}\n\nexport interface SubMenuType {\n children: MenuItemType[];\n key: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n popupClassName?: string;\n disabled?: boolean;\n ariaLabel?: string;\n}\n\nexport interface MenuInfo {\n key: string;\n keyPath: string[];\n domEvent: MouseEvent | KeyboardEvent;\n // 阻止菜单关闭\n keepOpen?: boolean;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/Menu/index.tsx"],"sourcesContent":["import {\n ControlledMenu,\n MenuDivider,\n MenuHeader,\n MenuItem,\n SubMenu,\n} from '@szhsin/react-menu';\nimport { cn } from '../lib/utils';\nimport _ from 'lodash';\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { CheckLightLine, ChevronRightLine } from '../Icon';\nimport './menu.css';\n\nexport interface MenuProps {\n onClick?: (info: MenuInfo) => void;\n onBeforeLeafItemClick?: () => void;\n className?: string;\n items: MenuItemType[];\n selectedKeys?: string[];\n menuStyle?: React.CSSProperties;\n /** Accessible name for the command menu when its context is not otherwise clear. */\n ariaLabel?: string;\n /** @internal Requested initial keyboard position when the popup opens. */\n initialFocus?: 'first' | 'last';\n /** @internal Whether the current popup entry originated from keyboard input. */\n keyboardFocus?: boolean;\n /** @internal Whether opening the popup should transfer focus to an item. */\n captureFocus?: boolean;\n /** @internal Requests that the owning popup close, including on Escape. */\n onRequestClose?: (reason: 'click' | 'cancel' | 'blur' | 'scroll') => void;\n rootClosing?: boolean;\n /** @internal 由 Dropdown 注入,禁用内部定位/overflow 计算。不支持含 SubMenu 的菜单 */\n externalOverflow?: boolean;\n /** @internal Dropdown 已按实际裁剪区域计算根菜单高度,保留其限制。 */\n preserveRootHeight?: boolean;\n}\n\nexport default function Menu(props: MenuProps) {\n const {\n className,\n items,\n onClick: onMenuClick,\n onBeforeLeafItemClick,\n selectedKeys,\n menuStyle,\n ariaLabel,\n initialFocus = 'first',\n keyboardFocus = false,\n captureFocus = true,\n onRequestClose,\n rootClosing = false,\n externalOverflow,\n preserveRootHeight = false,\n } = props;\n const selectedKeySet = new Set(selectedKeys || []);\n const typeaheadRef = useRef({ query: '', timestamp: 0 });\n const [isKeyboardFocus, setIsKeyboardFocus] = useState(keyboardFocus);\n\n useEffect(() => {\n setIsKeyboardFocus(keyboardFocus);\n }, [keyboardFocus]);\n\n const menuItemFocus = useMemo(\n () => (captureFocus ? { position: initialFocus } : undefined),\n [captureFocus, initialFocus],\n );\n const onMenuKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.key.length !== 1\n ) {\n return;\n }\n\n const now = Date.now();\n const previous = typeaheadRef.current;\n const query = `${now - previous.timestamp > 500 ? '' : previous.query}${\n event.key\n }`.toLocaleLowerCase();\n typeaheadRef.current = { query, timestamp: now };\n\n const menuItems = Array.from(\n event.currentTarget.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"]:not([aria-hidden=\"true\"]):not([aria-disabled=\"true\"])',\n ),\n );\n const activeIndex = menuItems.indexOf(\n document.activeElement as HTMLElement,\n );\n const orderedItems = [\n ...menuItems.slice(activeIndex + 1),\n ...menuItems.slice(0, activeIndex + 1),\n ];\n const match = orderedItems.find((item) => {\n const accessibleName =\n item.getAttribute('aria-label') || item.textContent || '';\n return accessibleName.trim().toLocaleLowerCase().startsWith(query);\n });\n\n if (match) {\n event.preventDefault();\n match.focus();\n }\n },\n [],\n );\n\n const hasDirectSelectedItem = (items: MenuItemType[]): boolean =>\n items.some((item) => {\n if (isSubMenuItem(item)) {\n return selectedKeySet.has(item.key);\n }\n if (isHeaderMenuItem(item) || isDivideMenuItem(item)) {\n return false;\n }\n return selectedKeySet.has(item.key);\n });\n\n const getMenuItems = (items: MenuItemType[], parentPath: string[] = []) => {\n const isSelectableGroup = hasDirectSelectedItem(items);\n\n return items.map((item, index) => {\n if (isDivideMenuItem(item)) {\n return <MenuDivider key={`divider_${index}`} />;\n }\n if (isHeaderMenuItem(item)) {\n return <MenuHeader key={`header_${index}`}>{item.label}</MenuHeader>;\n }\n const currPath = [...parentPath, item.key];\n const titleStr = _.isString(item.label) ? item.label : '';\n const isSelected = selectedKeys?.includes(item.key);\n\n if (isSubMenuItem(item)) {\n const isLeafSubMenu = isLeafMenu(item.children);\n const subMenuOverflow =\n isLeafSubMenu || menuStyle?.maxHeight !== undefined\n ? 'auto'\n : undefined;\n const subMenuItemLabel = (\n <div className=\"ald-menu-item-label\" title={titleStr}>\n {item.icon ? (\n <div className=\"ald-menu-item-label-icon\">{item.icon}</div>\n ) : null}\n <div className=\"ald-menu-item-label-text tw-text-typography-body-dense\">\n {item.label}\n </div>\n <div className=\"ald-menu-item-right\">\n {isSelectableGroup ? (\n <div className=\"ald-menu-item-selection-slot\" />\n ) : null}\n <ChevronRightLine size={16} />\n </div>\n </div>\n );\n\n return (\n <SubMenu\n key={item.key}\n label={subMenuItemLabel}\n disabled={item.disabled}\n aria-label={item.ariaLabel || titleStr || undefined}\n itemProps={{\n className: isLeafSubMenu\n ? 'ald-menu-submenu-leaf-trigger'\n : undefined,\n onPointerMove: () => setIsKeyboardFocus(false),\n }}\n align=\"start\"\n direction=\"right\"\n gap={-8}\n position=\"anchor\"\n shift={0}\n menuStyle={{\n ...menuStyle,\n }}\n menuClassName={cn(\n 'ald-menu-submenu-popup',\n isLeafSubMenu && 'ald-menu-submenu-popup-leaf',\n !isLeafSubMenu &&\n menuStyle?.maxHeight !== undefined &&\n 'ald-menu-submenu-popup-scrollable',\n item.popupClassName,\n isKeyboardFocus && 'ald-menu-focus-visible',\n rootClosing && 'ald-menu-submenu-popup-root-closing',\n )}\n overflow={subMenuOverflow}\n >\n {getMenuItems(item.children, currPath)}\n </SubMenu>\n );\n }\n\n return (\n <MenuItem\n key={item.key}\n className={cn(item.className, {\n 'ald-menu-item-danger': item.danger,\n 'ald-menu-item-selected': isSelected,\n })}\n disabled={item.disabled}\n aria-label={item.ariaLabel || titleStr || undefined}\n onPointerMove={() => setIsKeyboardFocus(false)}\n onClick={({ syntheticEvent }) => {\n onBeforeLeafItemClick?.();\n const menuInfo = {\n key: item.key,\n keyPath: currPath,\n domEvent: syntheticEvent,\n };\n if (item.onClick) {\n item.onClick(menuInfo);\n }\n if (onMenuClick) {\n onMenuClick(menuInfo);\n }\n }}\n >\n <div className=\"ald-menu-item-label\" title={titleStr}>\n {item.icon ? (\n <div className=\"ald-menu-item-label-icon\">{item.icon}</div>\n ) : null}\n <div className=\"ald-menu-item-label-text tw-text-typography-body-dense\">\n {item.label}\n </div>\n {isSelectableGroup ? (\n <div className=\"ald-menu-item-right\">\n <div\n className={cn(\n 'ald-menu-item-selection-slot',\n isSelected &&\n 'ald-menu-item-selection-slot-selected ald-menu-item-selected-icon',\n )}\n >\n {isSelected ? <CheckLightLine /> : null}\n </div>\n </div>\n ) : null}\n </div>\n </MenuItem>\n );\n });\n };\n\n const ref = useRef(null);\n\n const isLeafRootMenu = isLeafMenu(items);\n const menuOverflow =\n isLeafRootMenu || menuStyle?.maxHeight !== undefined ? 'auto' : undefined;\n\n return (\n <>\n <div style={{ display: 'none' }} ref={ref} />\n <ControlledMenu\n state=\"open\"\n transition={{ open: true, close: true }}\n transitionTimeout={200}\n submenuOpenDelay={0}\n submenuCloseDelay={0}\n // Root positioning is owned by the surrounding surface (see menu.css).\n // Keep an explicit caller height: React-Menu otherwise replaces it with\n // its viewport limit. Descendant SubMenus still calculate their own overflow.\n setDownOverflow={preserveRootHeight}\n menuStyle={{\n ...menuStyle,\n }}\n anchorRef={ref}\n className={cn(\n className,\n 'ald-menu',\n isKeyboardFocus && 'ald-menu-focus-visible',\n isLeafRootMenu && !externalOverflow && 'ald-menu-leaf',\n !isLeafRootMenu &&\n menuStyle?.maxHeight !== undefined &&\n !externalOverflow &&\n 'ald-menu-scrollable',\n )}\n position={externalOverflow ? 'initial' : 'auto'}\n aria-label={ariaLabel}\n captureFocus={captureFocus}\n menuItemFocus={menuItemFocus}\n onKeyDown={(event) => {\n setIsKeyboardFocus(true);\n onMenuKeyDown(event);\n }}\n onClose={({ reason }) => onRequestClose?.(reason)}\n viewScroll={externalOverflow ? 'initial' : 'auto'}\n overflow={externalOverflow ? 'visible' : menuOverflow}\n >\n {getMenuItems(items || [])}\n </ControlledMenu>\n </>\n );\n}\n\nexport function isHeaderMenuItem(\n menuItem: MenuItemType,\n): menuItem is MenuHeaderType {\n return 'type' in menuItem && menuItem.type === 'header';\n}\n\nexport function isDivideMenuItem(\n menuItem: MenuItemType,\n): menuItem is MenuDividerType {\n return 'type' in menuItem && menuItem.type === 'divider';\n}\n\nexport function isSubMenuItem(menuItem: MenuItemType): menuItem is SubMenuType {\n return 'children' in menuItem && !!menuItem.children;\n}\n\nexport function isLeafMenu(menuItems: MenuItemType[]) {\n return !_.some(menuItems, (item) => isSubMenuItem(item));\n}\n\nexport type MenuItemType =\n | ItemType\n | MenuDividerType\n | SubMenuType\n | MenuHeaderType;\n\nexport interface ItemType {\n key: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n onClick?: (info: MenuInfo) => void;\n danger?: boolean;\n disabled?: boolean;\n className?: string;\n ariaLabel?: string;\n}\n\nexport interface MenuDividerType {\n key?: string;\n type: 'divider';\n}\n\nexport interface MenuHeaderType {\n key?: string;\n type: 'header';\n label: string;\n}\n\nexport interface SubMenuType {\n children: MenuItemType[];\n key: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n popupClassName?: string;\n disabled?: boolean;\n ariaLabel?: string;\n}\n\nexport interface MenuInfo {\n key: string;\n keyPath: string[];\n domEvent: MouseEvent | KeyboardEvent;\n // 阻止菜单关闭\n keepOpen?: boolean;\n}\n"],"mappings":"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"}