/* eslint-disable prefer-const */ import * as React from "react"; import { FormElementProps, FormElementRegistration } from "@vertigis/workflow"; import { styled } from '@mui/material/styles'; import List from '@vertigis/web/ui/List'; import ListItemButton from '@vertigis/web/ui/ListItemButton'; import ListItemIcon from '@vertigis/web/ui/ListItemIcon'; import ListItemText from '@vertigis/web/ui/ListItemText'; import Collapse from '@vertigis/web/ui/Collapse'; import FolderCopyIcon from '@mui/icons-material/FolderCopy'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; import SearchIcon from '@vertigis/web/ui/icons/Search'; import InputBase from '@mui/material/InputBase'; import Box from "@vertigis/web/ui/Box" import Typography from '@vertigis/web/ui/Typography'; import { createTheme } from "@mui/material/styles"; import { ThemeProvider } from "@emotion/react"; import Checkbox from '@vertigis/web/ui/Checkbox'; import IconButton from '@vertigis/web/ui/IconButton'; import { Category, Clear as ClearIcon } from '@mui/icons-material'; import Chip from '@mui/material/Chip'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; /** * The generic type argument provided to `FormElementProps` controls the type * of `props.value` and `props.setValue(value)`. If your element doesn't need a * `value`, you can omit the type argument. * * You can also declare additional public properties of your element by adding * properties to this interface. The properties will be managed by the Workflow * runtime, and passed in as `props`. You can update the properties by using * `props.setProperty(key, value)`. */ interface ItemSearcher_V2Props extends FormElementProps { itemData: Category[]; // Structured data passed as prop setting?: settingConfig; request?: { type: string, timestamp: number, data?: any }; openCategories: Set;//Status storage property only searchQuery:string;//Status storage property only selection:{ [key: string]: { [key: string]: Item[] } }//Status storage property only loadDataTime:number;//provide cabability of loading a totally different data } /** * Provide strictly structured item data to display them and search. * @displayName ItemSearcher_V2 * @description Display list of items and allow user to search based on name, description & tag. * @param props collapseIcon?: string; expandIcon?: string;//replacing the drop down icon on right checkbox?: boolean;//If true, make items selectable checkbox rather than click checkboxGroup?:boolean;//If true, make category itself selectable triggerChange?:boolean;//If true, trigger changed event when being checkbox mode simpleSearch?: boolean; // decide two mode in search result: clear Search show items only and rank them by relativity. If false, use filter on original Hierarchy, show items has keyword or group whose name has keyword and all its children. initialCollapse?: boolean // decide whether collapse all group at beginning, default is collapse all */ const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( ))(({ theme }) => ({ [`& .${tooltipClasses.tooltip}`]: { boxShadow: theme.shadows[1], fontSize: 10, }, })); const Search = styled('div')(({ theme }) => ({ position: 'relative', borderRadius: theme.shape.borderRadius, backgroundColor: 'var(--alertGrayForeground)', marginRight: theme.spacing(2), marginLeft: 0, width: '100%', [theme.breakpoints.up('sm')]: { marginLeft: theme.spacing(3), width: 'auto', }, })); const SearchIconWrapper = styled('div')(({ theme }) => ({ padding: theme.spacing(0, 2), height: '100%', position: 'absolute', pointerEvents: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', })); interface Item { name: string; description?: string; tag?: string[]; value?: any; img?: string; score?: number; parentValue?: any; action?: "displayLayer" | "tick"; } interface Category { name: string; items?: Item[]; childCategory?: Category[]; collapsed?: boolean; //true to hide children. It's above setting Config initial collapse. img?: string; checkbox?: boolean; //If true, show checkbox that makes all child items selected/unselected index?: number; //as a child Category, indicate the position of this child category compared to other sibling items/categories parentCate?: string; description?:string; } interface settingConfig { checkbox?: boolean; //If true, show checkbox rather than raise click event collapseIcon?: string;//replace the fold up icon on right expandIcon?: string;//replacing the drop down icon on right triggerChange?: boolean;//If true, trigger changed event when under checkbox mode triggerClick?: boolean;//If true, trigger click event when under checkbox mode simpleSearch?: boolean; // decide two mode in search result: clear Search show items only and rank them by relativity. If false, use filter on original Hierarchy, show items has keyword or group whose name has keyword and all its children. initialCollapse?: boolean;// decide whether collapse all group at beginning, default is collapse all tooltip?: "tag" | "description" | "value" | Array<"tag" | "description" | "value">;//tooltip of item content. Can be any combination of description, value (link etc), tag (property unvisible but used for searching) searchHolder?: string; //Text placeholder used in search bar requestLayer?: boolean;//true if the item is a layer group url and need further process reset?: number; //If loading another totally different content, give timestamp number for reset parameter to reload itemData showCount?: boolean; // display item count after category name flatResut?: boolean;//decide the output to be result/layer or result/group/layer matchAnyKeyword?:boolean;//When search keywords (detected seperation by many spliters), fulfil all keywords or any of keyword. maxSelectableItems?:number;//maximum number of items that can be selected at the same time. If 0, no limit. forceSort?:boolean;//if true, sort the items in the same order as the original data. If false, sort by name. } const StyledInputBase = styled(InputBase)(({ theme }) => ({ color: 'inherit', '& .MuiInputBase-input': { padding: theme.spacing(1, 1, 1, 0), paddingLeft: `calc(1em + ${theme.spacing(4)})`, transition: theme.transitions.create('width'), width: '100%', [theme.breakpoints.up('md')]: { width: '100%', }, }, })); const defaultTheme = createTheme({ components: { MuiListItemText: { styleOverrides: { secondary: { color: 'inherit', }, }, }, }, typography: { fontSize: 18 }, }); // Function to get all items from category and its subcategories function ItemSearcher_V2(props: ItemSearcher_V2Props): React.ReactElement { const { raiseEvent ,openCategories,searchQuery,selection,loadDataTime, setValue, setProperty} = props; if (openCategories ==undefined){setProperty("openCategories" as any,new Set())} if (searchQuery ==undefined){setProperty("searchQuery" as any,'')} if (selection ==undefined){setProperty("selection" as any,{})} if (loadDataTime ==undefined){setProperty("loadDataTime" as any,0)} const [data, setData] = React.useState(props.itemData); const preSearchOpenCategories = React.useRef>(new Set()); const currentOpenCategories = React.useRef >(new Set()); const prevSearchValue = React.useRef('') const [filteredData, setFilteredData] = React.useState([]); // New state for filtered data const requestTime = React.useRef(null); // Handle search input changes const handleSearchChange = (event: React.ChangeEvent) => { setProperty("searchQuery" as any,event.target.value); // Update search query }; function getTotalSelectedItems() { let total = 0; for (let catKey in selection) { for (let subcatKey in selection[catKey]) { total += selection[catKey][subcatKey].length; } } return total; } function expandIcon(collapseIcon: string, expandIcon: string, openCats, catName: string) { openCategories.has(catName) ? : if (Boolean(expandIcon) && openCats.has(catName)) return () else if (Boolean(collapseIcon) && !openCats.has(catName)) return () else if (!expandIcon && openCats.has(catName)) return () else if (!expandIcon && !openCats.has(catName)) return () } function findCategory(item: Item) { return props.itemData.find(category => category.items?.some(existingItem => existingItem.value == item.value) ) || data[0]; } function tickItem(item: Item, category: Category) { let selectCopy = structuredClone(selection) if (isSelected(item, category.name)) { //uncheck item for (let key in selectCopy) { let element = selectCopy[key] // Check if the element is an object containing nested arrays of Items for (let subKey in element) { if (element[subKey]) { let index = element[subKey].findIndex(a => a.name === item.name && (a.value === item.value || a.description === item.description)); if (index !== -1) { element[subKey].splice(index, 1); // If the subcategory is empty after removal, delete the subcategory if (element[subKey].length === 0) { delete element[subKey]; } } } } if (Object.values(selectCopy[key]).length == 0) { delete selectCopy[key]; }// If the category is empty after removal, delete the category } } else { // Check if we've reached maximum selectable items limit const totalSelectedItems = getTotalSelectedItems(); if (props.setting?.maxSelectableItems && totalSelectedItems >= props.setting.maxSelectableItems) { // Prevent adding more items if limit is reached raiseEvent("error" as any, JSON.stringify({ type: "maxItemsReached", message: `You can only select up to ${props.setting.maxSelectableItems} items` })); return; } // Add the item to the specified category if (category) { if (category.parentCate) { if (!selectCopy[category.parentCate]) { selectCopy[category.parentCate] = {}; } if (!selectCopy[category.parentCate][category.name]) { selectCopy[category.parentCate][category.name] = []; } selectCopy[category.parentCate][category.name].push((delete item.score, item)); } else { if (!selectCopy[category.name]) { selectCopy[category.name] = {}; } if (!selectCopy[category.name]["directItem"]) { selectCopy[category.name]["directItem"] = []; } selectCopy[category.name]["directItem"].push((delete item.score, item)); } } } setProperty("selection" as any, selectCopy) } // Handle click on items to raise an event const handleItemClick = (item: Item, category: Category) => { if (props.setting?.checkbox) { if (props.setting?.triggerChange) { raiseEvent("changed", JSON.stringify(item)); // Pass the entire item data structure } if (props.setting?.triggerClick) { raiseEvent("clicked", JSON.stringify(item)); // Pass the entire item data structure } if (props.setting?.requestLayer && item.action == "displayLayer") { raiseEvent("clicked", JSON.stringify({ action: "openLayer", data: item })); // Pass the entire item data structure } if (!props.setting?.requestLayer || !(item.action == "displayLayer")) { tickItem(item, category) } } else { raiseEvent("clicked", JSON.stringify(item)); // Pass the entire item data structure } }; const handleCategoryCheck = (event: React.MouseEvent, category: Category) => { event.stopPropagation(); let selectCopy = structuredClone(selection); // For "allSelected" case, just remove all items (no limit check needed) if (isChildSelected(category) === "allSelected") { if (!category.parentCate) { delete selectCopy[category.name]; } else { delete selectCopy[category.parentCate][category.name]; } } // For adding items, check limit else { const currentTotal = getTotalSelectedItems(); let itemsToAdd = 0; // Calculate how many items would be added if (!category.parentCate) { if (category.items) { for (let item of category.items) { if (!isSelected(item, category.name)) { itemsToAdd++; } } } } else { if (category.items) { for (let item of category.items) { if (!isSelected(item, category.name)) { itemsToAdd++; } } } } // Check if adding these items would exceed the limit if (props.setting?.maxSelectableItems && (currentTotal + itemsToAdd > props.setting.maxSelectableItems)) { raiseEvent("error" as any, JSON.stringify({ type: "maxItemsReached", message: `You can only select up to ${props.setting.maxSelectableItems} items` })); return; } // Proceed with adding items if within limit if (!category.parentCate) { //only one level if (category.items) { for (let item of category.items) { if (!isSelected(item, category.name)) { // If item is not selected, check it if (!selectCopy[category.name]) { selectCopy[category.name] = {} selectCopy[category.name]["directItem"] = []; } selectCopy[category.name]["directItem"].push((delete item.score, item)); } } } } else { //two levels if (category.items) { for (let item of category.items) { if (!isSelected(item, category.name)) { if (!selectCopy[category.parentCate]) { selectCopy[category.parentCate] = {}; } // If item is not selected, check it if (!selectCopy[category.parentCate][category.name]) { selectCopy[category.parentCate][category.name] = []; } selectCopy[category.parentCate][category.name].push((delete item.score, item)); } } } } } // Update selection state setProperty("selection" as any, selectCopy); }; // Handle category toggle open/close manually const handleCategoryToggle = (categoryName: string) => { const updatedOpenCategories = new Set(openCategories); // Clone current state if (updatedOpenCategories.has(categoryName)) { updatedOpenCategories.delete(categoryName); // Close category } else { updatedOpenCategories.add(categoryName); // Open category } setProperty("openCategories" as any, updatedOpenCategories); // Update with new value }; function countAllItems(category: Category): number { let itemCount = 0; // Add items of the current category (if any) if (category.items) { itemCount += category.items.length; } // Recursively count items in child categories (if any) if (category.childCategory) { category.childCategory.forEach(childCategory => { itemCount += countAllItems(childCategory); // Recursive call for each child category }); } return itemCount; } function isSelected(item: Item, cateName?: string) { // Type guard to check if an element is an Item if (!cateName) { for (let key in selection) { for (let subkey in selection[key]) { let index = selection[key][subkey].findIndex(a => a.name === item.name && a.value === item.value); if (index !== -1) { return true; } } } } else { if (selection[cateName]?.["directItem"]) { let index = selection[cateName]["directItem"].findIndex(a => a.name === item.name && a.value === item.value); if (index !== -1) { return true; } } for (let key in selection) { if (selection[key][cateName]) { let index = selection[key][cateName].findIndex(a => a.name === item.name && a.value === item.value); if (index !== -1) { return true; } } } } return false; } const isChildSelected = (category: Category) => { let allSelected = true; let partSelected = false; let noneSelected = true; // Check items of the current category if (category.items) { for (let item of category.items) { const selected = isSelected(item, category.name); if (selected) { partSelected = true; noneSelected = false; } else { allSelected = false; } } } // Recursively check child categories if (category.childCategory) { for (let child of category.childCategory) { const childStatus = isChildSelected(child); if (childStatus === 'notSelected') { noneSelected = true; } else if (childStatus === 'partSelected') { partSelected = true; } else if (childStatus === 'allSelected') { allSelected = true; } } } // Determine the category selection state if (allSelected) return 'allSelected'; if (partSelected) return 'partSelected'; if (noneSelected) return 'notSelected'; return "error" } // Get search rank for an item based on search query const getSearchRank = (item: Item, query: string) => { const keywords = query.split(/[\s,+;]+/).map((word) => word.trim().toLowerCase()).filter(Boolean); let score = 0; keywords.forEach((keyword) => { if (item.name.toLowerCase().includes(keyword)) score += 2.5; else if (item.description && item.description.toLowerCase().includes(keyword)) score += 2; else if (item.tag && item.tag.some((tag) => tag.toLowerCase().includes(keyword))) score += 1.5; }); return score; }; // Recursively get all items from category and its child categories function getAllItemsFromCategory(category: Category): Item[] { let flattenItems: Item[] = []; if (category.items) { flattenItems = category.items.map((item) => ({ ...item, score: getSearchRank(item, searchQuery), })); } if (category.childCategory && Array.isArray(category.childCategory)) { for (const child of category.childCategory) { flattenItems = flattenItems.concat(getAllItemsFromCategory(child)); } } return flattenItems; } const sumSelection = (category: Category): Item[] => { let result: Item[] = [] if (!category.parentCate) { //category is first level, get all direct item and child cate items for (let key in selection[category.name]) { result = result.concat(selection[category.name][key]) } } else { result = selection[category.parentCate]?.[category.name] || [] } return result.length > 0 ? result : []; }; function cleanSelection(selection: { [key1: string]: { [key2: string]: Item[] } }) { for (const key1 in selection) { if (Object.keys(selection[key1]).length>0) { const key2Object = selection[key1]; // Iterate over key2 within key1 for (const key2 in key2Object) { if (key2Object.key2) { // If key2 has an empty array, delete the key2 if (key2Object[key2].length === 0) { delete key2Object[key2]; } } } // If key1 has no more key2s left, delete key1 if (Object.keys(key2Object).length === 0) { delete selection[key1]; } } else{ delete selection[key1] } } return selection; } // Render categories with items and nested child categories const renderCategory = (category: Category, level: number): React.ReactNode => { function truncateString(str) { if (str.length > 10) { return str.substring(0, 8) + '...'; } return str; } const chipLabel = () => { return sumSelection(category)?.length > 0 ? sumSelection(category)?.length > 1 ? sumSelection(category).length + " selected" : truncateString(sumSelection(category)[0].name) : "" } const handleDeleteSelection = (category: Category) => { if (category.parentCate) { const updatedSelection = { ...selection }; updatedSelection[category.parentCate][category.name] = []; setProperty("selection" as any,updatedSelection); } else { const updatedSelection = { ...selection }; updatedSelection[category.name] = {}; setProperty("selection" as any,updatedSelection); } }; const chipElement = ( handleDeleteSelection(category)} /> ); const itemContent = (item: Item, cateName?: string) => (
{item.img && typeof item.img === 'string' && item.img.trim() !== '' && ( )} {props.setting?.checkbox ? : null}
) return ( handleCategoryToggle(category.name)} sx={{ pl: level * 2, py:0 , minHeight:'35px'}}> {props.setting?.checkbox && category.checkbox ? handleCategoryCheck(event, category)} /> : null } {category.img && typeof category.img === 'string' && category.img.trim() !== '' ? ( ) : ( ) } {//selection chip props.setting?.checkbox && sumSelection(category).length > 0 ? a.name).join(',')} arrow> {chipElement} : <> } {expandIcon(props.setting?.collapseIcon ?? '', props.setting?.expandIcon ?? '', openCategories, category.name)} {getMergedChildren(category).map((child, idx) => { if (child.type === 'item') { const item:Item = child.content; return ( handleItemClick(item, category)} > {props.setting?.tooltip ? ( typeof props.setting?.tooltip == "string" ? ( {itemContent(item,category.name)} ) : ( item[key]).join(" ; ")} arrow > {itemContent(item,category.name)} ) ) : ( itemContent(item,category.name) )} ); } else { // Render child category const childCategory = child.content; return renderCategory(childCategory, level + 1); } })} ); }; const getMergedChildren = (category: Category) => { const items = category.items || []; const childCategories = category.childCategory || []; // Convert items to an array with type marker const itemsWithType = items.map(item => ({ type: 'item', content: item })); // Convert child categories to an array with type marker const categoriesWithType = childCategories.map(category => ({ type: 'category', content: category })); // Sort based on forceSort setting if (props.setting?.forceSort !== false) { // Default behavior: sort alphabetically categoriesWithType.sort((a, b) => a.content.name.localeCompare(b.content.name) ); itemsWithType.sort((a, b) => a.content.name.localeCompare(b.content.name) ); } else { // When forceSort is false: respect original order // For child categories, sort by index if available categoriesWithType.sort((a, b) => { if (a.content.index !== undefined && b.content.index !== undefined) { return a.content.index - b.content.index; } return 0; // Keep original order if no index }); // For items, we keep the original order by not sorting } // Return categories first, then items return [...categoriesWithType, ...itemsWithType]; }; // Filter items based on the search query const filteredItems = data .flatMap((category) => getAllItemsFromCategory(category)) .map((item) => ({ ...item, score: getSearchRank(item, searchQuery), })) .filter((item) => item.score > 0) .sort((a, b) => b.score - a.score); // Sort by total score in descending order React.useEffect(() => { function filterData(data: Category[], query: string): Category[] { const keywords = query.split(/[\s,+;]+/).map((word) => word.trim().toLowerCase()).filter(Boolean); function searchCategory(category: Category): Category | null { // Different matching logic based on the matchAnyKeyword setting let categoryMatches: boolean; if (props.setting?.matchAnyKeyword) { // ANY keyword matching (original logic) - using regex const keywordRegex = new RegExp(keywords.join("|"), "i"); categoryMatches = keywordRegex.test(category.name); // Filter items based on name, description, or tags const matchedItems = category.items?.filter(item => keywordRegex.test(item.name) || (item.description && keywordRegex.test(item.description)) || (item.tag && item.tag.some(tag => keywordRegex.test(tag))) ); // Recursively filter child categories const matchedChildCategories = category.childCategory ?.map(childCat => searchCategory(childCat)) .filter((childCat): childCat is Category => childCat !== null); if (categoryMatches) { return { ...category, items: category.items || [], childCategory: category.childCategory || [] }; } // If no match in the category name, check if items or child categories match if ((matchedItems && matchedItems.length > 0) || (matchedChildCategories && matchedChildCategories.length > 0)) { return { ...category, items: matchedItems || [], childCategory: matchedChildCategories || [] }; } } else { // ALL keywords matching logic categoryMatches = keywords.every(keyword => category.name.toLowerCase().includes(keyword) ); // Filter items that match ALL keywords const matchedItems = category.items?.filter(item => keywords.every(keyword => item.name.toLowerCase().includes(keyword) || (item.description && item.description.toLowerCase().includes(keyword)) || (item.tag && item.tag.some(tag => tag.toLowerCase().includes(keyword))) ) ); // Recursively filter child categories const matchedChildCategories = category.childCategory ?.map(childCat => searchCategory(childCat)) .filter((childCat): childCat is Category => childCat !== null); if (categoryMatches) { return { ...category, items: category.items || [], childCategory: category.childCategory || [] }; } // If no match in the category name, check if items or child categories match if ((matchedItems && matchedItems.length > 0) || (matchedChildCategories && matchedChildCategories.length > 0)) { return { ...category, items: matchedItems || [], childCategory: matchedChildCategories || [] }; } } // Exclude this category if no matches found in items or child categories return null; } // Apply the search to each root category and filter out null results const result = data.map(category => searchCategory(category)).filter((cat): cat is Category => cat !== null); return result } if (searchQuery) { // Store the current state when search begins if (searchQuery.length === 1) { preSearchOpenCategories.current = new Set(currentOpenCategories.current); } const searchResults = filterData(data, searchQuery); setFilteredData(searchResults); // Expand categories with search results const searchOpenCategories = new Set(); const addToOpenCategories = (category: Category) => { searchOpenCategories.add(category.name); category.childCategory?.forEach(addToOpenCategories); }; searchResults.forEach(addToOpenCategories); setProperty("openCategories" as any,searchOpenCategories); } if (!searchQuery) { // Restore pre-search state if exists if (prevSearchValue.current==''){ currentOpenCategories.current = openCategories; } else { if (preSearchOpenCategories.current.size > 0) { setProperty("openCategories" as any,preSearchOpenCategories.current); setFilteredData([]); preSearchOpenCategories.current = new Set() } return; } } prevSearchValue.current=searchQuery }, [searchQuery, data, props.setting?.matchAnyKeyword, openCategories, setValue, setProperty]); React.useEffect(() => { function removeDirectItems() { let selectCopy = structuredClone(selection) let result = {} for (let key in selection) { result[key] = selectCopy[key].directItem; } return result; } if (props.setting?.checkbox && props.request && props.request.type == "submit" && (requestTime.current) !== (props.request.timestamp)) { raiseEvent("clicked", JSON.stringify({ action: "submit", selectionResult: props.setting.flatResut ? removeDirectItems() : cleanSelection(selection) })); // Pass the entire item data structure requestTime.current = props.request.timestamp; } if (props.setting?.checkbox && props.request && props.request.type == "clearSelection" && (requestTime.current) !== (props.request.timestamp)) { setProperty("selection" as any,{}) requestTime.current = props.request.timestamp; } if (props.setting?.checkbox){ setValue( JSON.stringify( {selectionResult:props.setting.flatResut ? removeDirectItems() : cleanSelection(selection)})) setProperty("selectionResult" as any,props.setting.flatResut ? removeDirectItems() : cleanSelection(selection)) } }, [props.request, props.setting, raiseEvent, selection, setProperty, setValue]) React.useEffect(() => { if (props.request && props.request.type == "displayLayer" && (requestTime.current) !== (props.request.timestamp)) { let dataCopy = structuredClone(data) dataCopy.forEach((category: Category) => { if (category.items) { const item = category.items.find(item => item.name === props.request?.data.title); const foundindex = category.items.findIndex(item => item.name === props.request?.data.title) - 1; if (item !== undefined) { if (!category.childCategory) { category.childCategory = []; } category.childCategory.push({ name: props.request?.data.title, index: foundindex, checkbox: true, items: props.request?.data.layers.map(sublayer => ({ "name": sublayer.title, "value": sublayer.id, "parentName": item.name, "parentValue": item.value, })) }) category.items = category.items.filter(item => item.name !== props.request?.data.title); const newState = new Set(openCategories); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument newState.add(props.request?.data.title || ''); // Open category if it's closed setProperty("openCategories" as any, newState); } } }) setData(dataCopy) requestTime.current = props.request.timestamp } }, [data, openCategories, props.request, setProperty]) React.useEffect(() => { if (props.setting?.reset && (loadDataTime) !== (props.setting.reset)) { setData(props.itemData) setProperty("selection" as any,{}) setProperty("openCategories" as any,new Set()) setProperty("searchQuery" as any,'') setProperty("loadDataTime" as any,props.setting.reset) } }, [loadDataTime, props.itemData, props.setting?.reset, setProperty]) return ( <> {searchQuery && ( { setProperty("searchQuery" as any,'') }} size="large" sx={{ position: 'absolute', right: '8px', color: 'var(--alertGrayBackground) !important' }} > )} {props.setting?.simpleSearch && searchQuery && filteredItems.length > 0 ? ( filteredItems.map((item, index) => ( {item.img && typeof item.img === 'string' && item.img.trim() !== '' && ( )} {props.setting?.checkbox ? : null} handleItemClick(item, findCategory(item))} primaryTypographyProps={{ style: { fontWeight: '600' } }} /> )) ) : searchQuery && filteredData.length > 0 ? ( filteredData.map((category) => renderCategory(category, 0)) ) : searchQuery && filteredData.length === 0 ? ( No result found ) : ( data.map((category) => renderCategory(category, 0)) )} ); } const ItemSearcher_V2ElementRegistration: FormElementRegistration = { component: ItemSearcher_V2, id: "ItemSearcher_V2", getInitialProperties: () => ({ itemData: [{name:"Cate1",items:[{name:"ItemA"}]},{name:"Cate2",checkbox:true,items:[{name:"ItemB"}]}], setting: {initialCollapse:true,checkbox:true,showCount:true}, }), }; export default ItemSearcher_V2ElementRegistration;