5454</template >
5555
5656<script setup lang="ts">
57- import { useRafFn } from ' @vueuse/core'
57+ import { debouncedRef , useRafFn } from ' @vueuse/core'
58+ import { useFuse } from ' @vueuse/integrations/useFuse'
5859import Popover from ' primevue/popover'
5960import { computed , onMounted , onUnmounted , ref , watch } from ' vue'
6061import { useI18n } from ' vue-i18n'
@@ -87,6 +88,7 @@ const popover = ref<InstanceType<typeof Popover>>()
8788const targetElement = ref <HTMLElement | null >(null )
8889const searchInput = ref <HTMLInputElement | null >(null )
8990const searchQuery = ref (' ' )
91+ const debouncedSearchQuery = debouncedRef (searchQuery , 300 )
9092const isTriggeredByToolbox = ref <boolean >(true )
9193// Track open state ourselves so we can restore after drag/move
9294const isOpen = ref (false )
@@ -101,18 +103,75 @@ const { menuOptions, menuOptionsWithSubmenu, bump } = useMoreOptionsMenu()
101103const { toggleSubmenu, hideAllSubmenus } = useSubmenuPositioning ()
102104// const canvasInteractions = useCanvasInteractions()
103105
104- // Filter menu options based on search query
106+ // Prepare searchable menu options (exclude dividers and categories)
107+ const searchableMenuOptions = computed (() =>
108+ menuOptions .value .filter (
109+ (option ) => option .type !== ' divider' && option .type !== ' category'
110+ )
111+ )
112+
113+ // Set up fuzzy search with useFuse
114+ const { results } = useFuse (debouncedSearchQuery , searchableMenuOptions , {
115+ fuseOptions: {
116+ keys: [' label' ],
117+ threshold: 0.4
118+ },
119+ matchAllWhenSearchEmpty: true
120+ })
121+
122+ // Debug logging
123+ watch (searchQuery , (newVal ) => {
124+ console .warn (' [NodeOptions] searchQuery changed:' , newVal )
125+ })
126+
127+ watch (debouncedSearchQuery , (newVal ) => {
128+ console .warn (' [NodeOptions] debouncedSearchQuery changed:' , newVal )
129+ })
130+
131+ watch (results , (newVal ) => {
132+ console .warn (' [NodeOptions] useFuse results:' , newVal )
133+ console .warn (' [NodeOptions] results count:' , newVal .length )
134+ if (newVal .length > 0 ) {
135+ console .warn (' [NodeOptions] first result:' , newVal [0 ])
136+ }
137+ })
138+
139+ watch (searchableMenuOptions , (newVal ) => {
140+ console .warn (' [NodeOptions] searchableMenuOptions:' , newVal )
141+ console .warn (' [NodeOptions] searchableMenuOptions count:' , newVal .length )
142+ })
143+
144+ // Filter menu options based on fuzzy search results
105145const filteredMenuOptions = computed (() => {
106- const query = searchQuery .value .toLowerCase ().trim ()
146+ const query = debouncedSearchQuery .value .trim ()
147+ console .warn (' [NodeOptions] filteredMenuOptions computed - query:' , query )
148+ console .warn (
149+ ' [NodeOptions] filteredMenuOptions computed - results.value:' ,
150+ results .value
151+ )
152+
107153 if (! query ) {
154+ console .warn (
155+ ' [NodeOptions] No query, returning all menuOptions:' ,
156+ menuOptions .value .length
157+ )
108158 return menuOptions .value
109159 }
110160
161+ // Extract matched items from Fuse results and create a Set of labels for fast lookup
162+ const matchedItems = results .value .map ((result ) => result .item )
163+ console .warn (' [NodeOptions] matchedItems:' , matchedItems )
164+ console .warn (' [NodeOptions] matchedItems count:' , matchedItems .length )
165+
166+ // Create a Set of matched labels for O(1) lookup
167+ const matchedLabels = new Set (matchedItems .map ((item ) => item .label ))
168+ console .warn (' [NodeOptions] matchedLabels:' , Array .from (matchedLabels ))
169+
111170 const filtered: MenuOption [] = []
112171 let lastWasDivider = false
113172
173+ // Reconstruct with dividers based on original structure
114174 for (const option of menuOptions .value ) {
115- // Skip category labels and dividers during filtering, add them back contextually
116175 if (option .type === ' divider' ) {
117176 lastWasDivider = true
118177 continue
@@ -122,9 +181,8 @@ const filteredMenuOptions = computed(() => {
122181 continue
123182 }
124183
125- // Check if option matches search query
126- const label = option .label ?.toLowerCase () || ' '
127- if (label .includes (query )) {
184+ // Check if this option was matched by fuzzy search (compare by label)
185+ if (option .label && matchedLabels .has (option .label )) {
128186 // Add divider before this item if the last item was separated by a divider
129187 if (lastWasDivider && filtered .length > 0 ) {
130188 const lastItem = filtered [filtered .length - 1 ]
@@ -137,6 +195,8 @@ const filteredMenuOptions = computed(() => {
137195 }
138196 }
139197
198+ console .warn (' [NodeOptions] final filtered results:' , filtered )
199+ console .warn (' [NodeOptions] final filtered count:' , filtered .length )
140200 return filtered
141201})
142202
0 commit comments