applyFilter(name, items, search, debounce) {
        let count = 0
        let overlay = Tooltip.active[name.replace(/[\s\.#]/g, '_')]
        let options = overlay.options
        let resolve, reject
        let prom = new Promise((res, rej) => {
            resolve = res
            reject = rej
        })
        if (search == null) {
            if (['INPUT', 'TEXTAREA'].includes(overlay.anchor.tagName)) {
                search = overlay.anchor.value
            } else {
                search = ''
            }
        }
        let selectedIds = []
        if (options.selected) {
            if (Array.isArray(options.selected)) {
                selectedIds = options.selected.map(item => {
                    return item?.id ?? item
                })
            } else if (options.selected?.id) {
                selectedIds = [options.selected.id]
            }
        }
        overlay.tmp.activeChain = null
        // if url is defined, get items from it
        let remote = overlay.tmp.remote ?? { hasMore: true, emtpySet: false, search: null, total: -1 }
        if (items == null && options.url && remote.hasMore && remote.search !== search) {
            let proceed = true
            // only when items == null because it is case of nested items
            let msg = w2utils.lang('Loading...')
            if (search.length < options.minLength && remote.emptySet !== true) {
                msg = w2utils.lang('${count} letters or more...', { count: options.minLength })
                proceed = false
                if (search === '') {
                    msg = w2utils.lang(options.msgSearch)
                }
            }
            query(overlay.box).find('.w2ui-no-items').html(msg)
            remote.search = search
            options.items = []
            overlay.tmp.remote = remote
            if (proceed) {
                this.request(overlay, search, debounce)
                    .then(remoteItems => {
                        this.update(name, remoteItems)
                        this.applyFilter(name, null, search).then(data => {
                            resolve(data)
                        })
                    })
                    .catch(error => {
                        console.log('Server Request error', error)
                    })
            }
            return prom
        }
        let edata
        // only trigger search event when data is present and for the top level
        if (items == null) {
            edata = this.trigger('search', { search, overlay, prom, resolve, reject })
            if (edata.isCancelled === true) {
                return prom
            }
        }
        if (items == null) {
            items = overlay.options.items
        }
        if (options.filter === false) {
            resolve({ count: -1, search })
            return prom
        }
        items.forEach(item => {
            let prefix = ''
            let suffix = ''
            if (['is', 'begins', 'begins with'].indexOf(options.match) !== -1) prefix = '^'
            if (['is', 'ends', 'ends with'].indexOf(options.match) !== -1) suffix = '$'
            try {
                let re = new RegExp(prefix + search + suffix, 'i')
                if (re.test(item.text) || item.text === '...') {
                    item.hidden = false
                } else {
                    item.hidden = true
                }
            } catch (e) {}
            // do not show selected items
            if (options.hideSelected && selectedIds.includes(item.id)) {
                item.hidden = true
            }
            // search nested items
            if (Array.isArray(item.items) && item.items.length > 0) {
                delete item._noSearchInside
                this.applyFilter(name, item.items, search).then(data => {
                    let subCount = data.count
                    if (subCount > 0) {
                        count += subCount
                        if (item.hidden) item._noSearchInside = true
                        // only expand items if search is not empty
                        if (search) item.expanded = true
                        item.hidden = false
                    }
                    })
            }
            if (item.hidden !== true) count++
        })
        resolve({ count, search })
        edata?.finish()
        return prom
    }
Loops through the items and markes item.hidden = true for those that need to be hidden, and item.hidden = false
for those that are visible. Return a promise (since items can be on the server) with the number of visible items.