UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

1 lines 9.85 kB
{"version":3,"file":"EditorSlashMenu.mjs","names":["item"],"sources":["../../src/EditorSlashMenu/EditorSlashMenu.tsx"],"sourcesContent":["'use client';\n\nimport type {\n AutocompleteRootChangeEventDetails,\n AutocompleteRootProps,\n} from '@base-ui/react/autocomplete';\nimport type React from 'react';\nimport { memo, useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { MenuItemRenderer } from './MenuItemRenderer';\nimport {\n EditorSlashMenuEmpty,\n EditorSlashMenuGroup,\n EditorSlashMenuGroupLabel,\n EditorSlashMenuHiddenInput,\n EditorSlashMenuList,\n EditorSlashMenuPopup,\n EditorSlashMenuPortal,\n EditorSlashMenuPositioner,\n EditorSlashMenuRoot,\n} from './atoms';\nimport type { EditorSlashMenuItems, EditorSlashMenuOption as ItemType } from './type';\nimport { useKeyboardNavigation } from './useKeyboardNavigation';\nimport { useNormalizedItems } from './useNormalizedItems';\nimport { isGroup } from './utils';\n\ntype Props = {\n /** Anchor for positioning (caret virtual element, dom element, ref, etc.) */\n anchor?: React.ComponentProps<typeof EditorSlashMenuPositioner>['anchor'];\n defaultOpen?: boolean;\n\n /** Initial query string (uncontrolled) */\n defaultValue?: string;\n /** Optional custom empty state */\n empty?: React.ReactNode;\n hiddenInputProps?: React.ComponentProps<typeof EditorSlashMenuHiddenInput>;\n items: EditorSlashMenuItems;\n\n listProps?: React.ComponentProps<typeof EditorSlashMenuList>;\n onOpenChange?: (open: boolean, details: AutocompleteRootChangeEventDetails) => void;\n onOpenChangeComplete?: (open: boolean) => void;\n /** Called when a command is selected. */\n onSelect?: (item: ItemType, details: AutocompleteRootChangeEventDetails) => void;\n\n /** Called when query changes. By default, changes caused by item selection are ignored. */\n onValueChange?: (value: string, details: AutocompleteRootChangeEventDetails) => void;\n\n open?: boolean;\n popupProps?: React.ComponentProps<typeof EditorSlashMenuPopup>;\n portalProps?: Omit<React.ComponentProps<typeof EditorSlashMenuPortal>, 'container'> & {\n container?: HTMLElement | null;\n };\n positionerProps?: Omit<React.ComponentProps<typeof EditorSlashMenuPositioner>, 'anchor'>;\n\n /** Optional custom group label renderer */\n renderGroupLabel?: (label: string) => React.ReactNode;\n /** Optional custom item renderer */\n renderItem?: (item: ItemType) => React.ReactNode;\n /** Reserve icon space even when icon is missing */\n reserveIconSpace?: boolean;\n /** Pass-through props */\n rootProps?: Omit<\n AutocompleteRootProps<ItemType>,\n | 'items'\n | 'value'\n | 'defaultValue'\n | 'onValueChange'\n | 'open'\n | 'defaultOpen'\n | 'onOpenChange'\n | 'onOpenChangeComplete'\n | 'itemToStringValue'\n >;\n /** Whether selecting an item should propagate its filled value via `onValueChange`. */\n updateValueOnSelect?: boolean;\n\n /** Current query string (controlled) */\n value?: string;\n /**\n * Render a visually-hidden input element for keyboard navigation / screen readers.\n * Default is `false` because slash menus usually live inside an editor input.\n */\n withHiddenInput?: boolean;\n};\n\nconst EditorSlashMenu = memo<Props>(\n ({\n items,\n anchor,\n\n open,\n defaultOpen,\n onOpenChange,\n onOpenChangeComplete,\n\n value,\n defaultValue,\n onValueChange,\n updateValueOnSelect = false,\n\n onSelect,\n\n empty = 'No results',\n renderGroupLabel,\n renderItem,\n reserveIconSpace = true,\n\n rootProps,\n portalProps,\n positionerProps,\n popupProps,\n listProps,\n\n withHiddenInput = false,\n hiddenInputProps,\n }) => {\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen ?? false);\n\n useEffect(() => {\n if (open === undefined) return;\n setUncontrolledOpen(open);\n }, [open]);\n\n const resolvedOpen = open ?? uncontrolledOpen;\n\n const { resolvedItems, hasAnyIcon } = useNormalizedItems(items);\n const { listRef } = useKeyboardNavigation({ isOpen: resolvedOpen });\n\n const itemToStringValue = useCallback((item: ItemType) => {\n const kws = item.keywords?.length ? ` ${item.keywords.join(' ')}` : '';\n return `${item.label}${kws}`;\n }, []);\n\n const handleValueChange = useCallback(\n (nextValue: string, details: AutocompleteRootChangeEventDetails) => {\n if (!updateValueOnSelect && details.reason === 'item-press') return;\n onValueChange?.(nextValue, details);\n },\n [onValueChange, updateValueOnSelect],\n );\n\n const handleSelect = useCallback(\n (item: ItemType, details: AutocompleteRootChangeEventDetails) => {\n onSelect?.(item, details);\n },\n [onSelect],\n );\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean, details: AutocompleteRootChangeEventDetails) => {\n setUncontrolledOpen(nextOpen);\n onOpenChange?.(nextOpen, details);\n },\n [onOpenChange],\n );\n\n // Stable ref callback that doesn't depend on listProps object\n const setListRef = useCallback(\n (node: HTMLDivElement | null) => {\n listRef.current = node;\n const externalRef = (listProps as any)?.ref;\n if (!externalRef) return;\n if (typeof externalRef === 'function') {\n externalRef(node);\n } else {\n (externalRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }\n },\n [listRef, listProps],\n );\n\n // Memoize render props to prevent recreating on every render\n const menuItemProps = useMemo(\n () => ({\n hasAnyIcon,\n onSelect: handleSelect,\n renderItem,\n reserveIconSpace,\n }),\n [hasAnyIcon, handleSelect, renderItem, reserveIconSpace],\n );\n\n return (\n <EditorSlashMenuRoot<ItemType>\n {...(rootProps as any)}\n defaultOpen={defaultOpen}\n defaultValue={defaultValue}\n itemToStringValue={itemToStringValue as any}\n items={resolvedItems as any}\n onOpenChange={handleOpenChange}\n onOpenChangeComplete={onOpenChangeComplete}\n onValueChange={handleValueChange}\n open={open}\n value={value}\n >\n {withHiddenInput ? <EditorSlashMenuHiddenInput {...(hiddenInputProps as any)} /> : null}\n\n <EditorSlashMenuPortal {...(portalProps as any)}>\n <EditorSlashMenuPositioner {...(positionerProps as any)} anchor={anchor}>\n <EditorSlashMenuPopup {...(popupProps as any)}>\n <EditorSlashMenuList {...(listProps as any)} ref={setListRef as any}>\n {(entry: any) => {\n if (isGroup(entry)) {\n return (\n <EditorSlashMenuGroup\n key={entry.label ?? entry.items.map((i: any) => i.value).join('|')}\n >\n {entry.label\n ? (renderGroupLabel?.(entry.label) ?? (\n <EditorSlashMenuGroupLabel>{entry.label}</EditorSlashMenuGroupLabel>\n ))\n : null}\n {entry.items.map((item: ItemType) => (\n <MenuItemRenderer {...menuItemProps} item={item} key={item.value} />\n ))}\n </EditorSlashMenuGroup>\n );\n }\n\n const item = entry as ItemType;\n return <MenuItemRenderer {...menuItemProps} item={item} key={item.value} />;\n }}\n </EditorSlashMenuList>\n\n <EditorSlashMenuEmpty>{empty}</EditorSlashMenuEmpty>\n </EditorSlashMenuPopup>\n </EditorSlashMenuPositioner>\n </EditorSlashMenuPortal>\n </EditorSlashMenuRoot>\n );\n },\n);\n\nEditorSlashMenu.displayName = 'EditorSlashMenu';\n\nexport default EditorSlashMenu;\n"],"mappings":";;;;;;;;;;;AAqFA,MAAM,kBAAkB,MACrB,EACC,OACA,QAEA,MACA,aACA,cACA,sBAEA,OACA,cACA,eACA,sBAAsB,OAEtB,UAEA,QAAQ,cACR,kBACA,YACA,mBAAmB,MAEnB,WACA,aACA,iBACA,YACA,WAEA,kBAAkB,OAClB,uBACI;CACJ,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,eAAe,MAAM;AAE9E,iBAAgB;AACd,MAAI,SAAS,OAAW;AACxB,sBAAoB,KAAK;IACxB,CAAC,KAAK,CAAC;CAEV,MAAM,eAAe,QAAQ;CAE7B,MAAM,EAAE,eAAe,eAAe,mBAAmB,MAAM;CAC/D,MAAM,EAAE,YAAY,sBAAsB,EAAE,QAAQ,cAAc,CAAC;CAEnE,MAAM,oBAAoB,aAAa,SAAmB;EACxD,MAAM,MAAM,KAAK,UAAU,SAAS,IAAI,KAAK,SAAS,KAAK,IAAI,KAAK;AACpE,SAAO,GAAG,KAAK,QAAQ;IACtB,EAAE,CAAC;CAEN,MAAM,oBAAoB,aACvB,WAAmB,YAAgD;AAClE,MAAI,CAAC,uBAAuB,QAAQ,WAAW,aAAc;AAC7D,kBAAgB,WAAW,QAAQ;IAErC,CAAC,eAAe,oBAAoB,CACrC;CAED,MAAM,eAAe,aAClB,MAAgB,YAAgD;AAC/D,aAAW,MAAM,QAAQ;IAE3B,CAAC,SAAS,CACX;CAED,MAAM,mBAAmB,aACtB,UAAmB,YAAgD;AAClE,sBAAoB,SAAS;AAC7B,iBAAe,UAAU,QAAQ;IAEnC,CAAC,aAAa,CACf;CAGD,MAAM,aAAa,aAChB,SAAgC;AAC/B,UAAQ,UAAU;EAClB,MAAM,cAAe,WAAmB;AACxC,MAAI,CAAC,YAAa;AAClB,MAAI,OAAO,gBAAgB,WACzB,aAAY,KAAK;MAEjB,CAAC,YAA8D,UAAU;IAG7E,CAAC,SAAS,UAAU,CACrB;CAGD,MAAM,gBAAgB,eACb;EACL;EACA,UAAU;EACV;EACA;EACD,GACD;EAAC;EAAY;EAAc;EAAY;EAAiB,CACzD;AAED,QACE,qBAAC;EACC,GAAK;EACQ;EACC;EACK;EACnB,OAAO;EACP,cAAc;EACQ;EACtB,eAAe;EACT;EACC;aAEN,kBAAkB,oBAAC,8BAA2B,GAAK,mBAA4B,GAAG,MAEnF,oBAAC;GAAsB,GAAK;aAC1B,oBAAC;IAA0B,GAAK;IAAiC;cAC/D,qBAAC;KAAqB,GAAK;gBACzB,oBAAC;MAAoB,GAAK;MAAmB,KAAK;iBAC9C,UAAe;AACf,WAAI,QAAQ,MAAM,CAChB,QACE,qBAAC,mCAGE,MAAM,QACF,mBAAmB,MAAM,MAAM,IAC9B,oBAAC,uCAA2B,MAAM,QAAkC,GAEtE,MACH,MAAM,MAAM,KAAK,WAChB,8BAAC;QAAiB,GAAI;QAAe,MAAMA;QAAM,KAAKA,OAAK;SAAS,CACpE,KATG,MAAM,SAAS,MAAM,MAAM,KAAK,MAAW,EAAE,MAAM,CAAC,KAAK,IAAI,CAU7C;OAI3B,MAAM,OAAO;AACb,cAAO,8BAAC;QAAiB,GAAI;QAAqB;QAAM,KAAK,KAAK;SAAS;;OAEzD,EAEtB,oBAAC,kCAAsB,QAA6B;MAC/B;KACG;IACN;GACJ;EAG3B;AAED,gBAAgB,cAAc;AAE9B,8BAAe"}