)]}'
{"version": 3, "sources": ["/web/static/lib/hoot-dom/helpers/dom.js", "/web/static/lib/hoot-dom/helpers/events.js", "/web/static/lib/hoot-dom/helpers/time.js", "/web/static/lib/hoot-dom/hoot-dom.js", "/web/static/lib/hoot-dom/hoot_dom_utils.js", "/web_tour/static/src/js/tour_step.js", "/web_tour/static/src/js/tour_interactive/tour_interactive.js"], "mappings": "AAAA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpoEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACj4FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/eA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5aA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "sourcesContent": ["/** @odoo-module */\n\nimport { getTag, isFirefox, isInstanceOf, isIterable, parseRegExp } from \"../hoot_dom_utils\";\nimport { waitUntil } from \"./time\";\n\n/**\n * @typedef {number | [number, number] | {\n *  w?: number;\n *  h?: number;\n *  width?: number;\n *  height?: number;\n * }} Dimensions\n *\n * @typedef {{\n *  root?: Target;\n *  tabbable?: boolean;\n * }} FocusableOptions\n *\n * @typedef {{\n *  keepInlineTextNodes?: boolean;\n *  tabSize?: number;\n *  type?: \"html\" | \"xml\";\n * }} FormatXmlOptions\n *\n * @typedef {{\n *  inline: boolean;\n *  level: number;\n *  value: MarkupLayerValue;\n * }} MarkupLayer\n *\n * @typedef {{\n *  close?: string;\n *  open?: string;\n *  textContent?: string;\n * }} MarkupLayerValue\n *\n * @typedef {(node: Node, index: number, nodes: Node[]) => boolean | Node} NodeFilter\n *\n * @typedef {(node: Node, selector: string) => Node[]} NodeGetter\n *\n * @typedef {string | string[] | number | boolean | File[]} NodeValue\n *\n * @typedef {number | [number, number] | {\n *  x?: number;\n *  y?: number;\n *  left?: number;\n *  top?: number,\n *  clientX?: number;\n *  clientY?: number;\n *  pageX?: number;\n *  pageY?: number;\n *  screenX?: number;\n *  screenY?: number;\n * }} Position\n *\n * @typedef {(content: string) => QueryFilter} PseudoClassPredicateBuilder\n *\n * @typedef {string | number | NodeFilter} QueryFilter\n *\n * @typedef {{\n *  contains?: string;\n *  count?: number;\n *  displayed?: boolean;\n *  empty?: boolean;\n *  eq?: number;\n *  first?: boolean;\n *  focusable?: boolean;\n *  has?: boolean;\n *  hidden?: boolean;\n *  iframe?: boolean;\n *  interactive?: boolean;\n *  last?: boolean;\n *  not?: boolean;\n *  only?: boolean;\n *  root?: HTMLElement;\n *  scrollable?: ScrollAxis;\n *  selected?: boolean;\n *  shadow?: boolean;\n *  value?: boolean;\n *  viewPort?: boolean;\n *  visible?: boolean;\n * }} QueryOptions\n *\n * @typedef {{\n *  trimPadding?: boolean;\n * }} QueryRectOptions\n *\n * @typedef {{\n *  inline?: boolean;\n *  raw?: boolean;\n * }} QueryTextOptions\n *\n * @typedef {\"both\" | \"x\" | \"y\"} ScrollAxis\n *\n * @typedef {import(\"./time\").WaitOptions} WaitOptions\n */\n\n/**\n * @template T\n * @typedef {T | Iterable<T>} MaybeIterable\n */\n\n/**\n * @template [T=Node]\n * @typedef {MaybeIterable<T> | string | null | undefined | false} Target\n */\n\n//-----------------------------------------------------------------------------\n// Global\n//-----------------------------------------------------------------------------\n\nconst {\n    document,\n    DOMParser,\n    Error,\n    innerWidth,\n    innerHeight,\n    Map,\n    MutationObserver,\n    Number: { isInteger: $isInteger, isNaN: $isNaN, parseInt: $parseInt, parseFloat: $parseFloat },\n    Object: { entries: $entries, keys: $keys, values: $values },\n    RegExp,\n    Set,\n    String: { raw: $raw },\n    window,\n} = globalThis;\n\n//-----------------------------------------------------------------------------\n// Internal\n//-----------------------------------------------------------------------------\n\n/**\n * @param {Iterable<QueryFilter>} filters\n * @param {Node[]} nodes\n */\nfunction applyFilters(filters, nodes) {\n    for (const filter of filters) {\n        const filteredGroupNodes = [];\n        for (let i = 0; i < nodes.length; i++) {\n            const result = matchFilter(filter, nodes, i);\n            if (result === true) {\n                filteredGroupNodes.push(nodes[i]);\n            } else if (result) {\n                filteredGroupNodes.push(result);\n            }\n        }\n        nodes = filteredGroupNodes;\n        if (globalFilterDescriptors.has(filter)) {\n            globalFilterDescriptors.get(filter).push(nodes.length);\n        } else if (selectorFilterDescriptors.has(filter)) {\n            selectorFilterDescriptors.get(filter).push(nodes.length);\n        }\n    }\n    return nodes;\n}\n\nfunction compilePseudoClassRegex() {\n    const customKeys = [...customPseudoClasses.keys()].filter((k) => k !== \"has\" && k !== \"not\");\n    return new RegExp(`:(${customKeys.join(\"|\")})`);\n}\n\n/**\n * @param {Element[]} elements\n * @param {string} selector\n */\nfunction elementsMatch(elements, selector) {\n    if (!elements.length) {\n        return false;\n    }\n    return parseSelector(selector).some((selectorParts) => {\n        const [baseSelector, ...filters] = selectorParts.at(-1);\n        for (let i = 0; i < elements.length; i++) {\n            if (baseSelector && !elements[i].matches(baseSelector)) {\n                return false;\n            }\n            if (!filters.every((filter) => matchFilter(filter, elements, i))) {\n                return false;\n            }\n        }\n        return true;\n    });\n}\n\n/**\n * @param {QueryOptions} options\n */\nfunction ensureCount(options) {\n    options = { ...options };\n    if (!(\"eq\" in options || \"first\" in options || \"last\" in options)) {\n        options.first = true;\n    }\n    return options;\n}\n\n/**\n * @param {Node} node\n * @returns {Element | null}\n */\nfunction ensureElement(node) {\n    if (node) {\n        if (isDocument(node)) {\n            return node.documentElement;\n        }\n        if (isWindow(node)) {\n            return node.document.documentElement;\n        }\n        if (isElement(node)) {\n            return node;\n        }\n    }\n    return null;\n}\n\n/**\n * @param {Iterable<Node>} nodes\n * @param {number} level\n * @param {boolean} [keepInlineTextNodes]\n */\nfunction extractLayers(nodes, level, keepInlineTextNodes) {\n    /** @type {MarkupLayer[]} */\n    const layers = [];\n    for (const node of nodes) {\n        if (node.nodeType === Node.COMMENT_NODE) {\n            continue;\n        }\n        if (node.nodeType === Node.TEXT_NODE) {\n            const textContent = node.nodeValue.replaceAll(/\\n/g, \"\");\n            const trimmedTextContent = textContent.trim();\n            if (trimmedTextContent) {\n                const inline = textContent === trimmedTextContent;\n                layers.push({ inline, level, value: { textContent: trimmedTextContent } });\n            }\n            continue;\n        }\n        const [open, close] = node.outerHTML.replace(`>${node.innerHTML}<`, \">\\n<\").split(\"\\n\");\n        const layer = { inline: false, level, value: { open, close } };\n        layers.push(layer);\n        const childLayers = extractLayers(node.childNodes, level + 1, false);\n        if (keepInlineTextNodes && childLayers.length === 1 && childLayers[0].inline) {\n            layer.value.textContent = childLayers[0].value.textContent;\n        } else {\n            layers.push(...childLayers);\n        }\n    }\n    return layers;\n}\n\n/**\n * @param {Iterable<Node>} nodesToFilter\n */\nfunction filterUniqueNodes(nodesToFilter) {\n    /** @type {Node[]} */\n    const nodes = [];\n    for (const node of nodesToFilter) {\n        if (isQueryableNode(node) && !nodes.includes(node)) {\n            nodes.push(node);\n        }\n    }\n    return nodes;\n}\n\n/**\n * @param {MarkupLayer[]} layers\n * @param {number} tabSize\n */\nfunction generateStringFromLayers(layers, tabSize) {\n    const result = [];\n    let layerIndex = 0;\n    while (layers.length > 0) {\n        const layer = layers[layerIndex];\n        const { level, value } = layer;\n        const pad = \" \".repeat(tabSize * level);\n        let nextLayerIndex = layerIndex + 1;\n        if (value.open) {\n            if (value.textContent) {\n                // node with inline textContent (no wrapping white-spaces)\n                result.push(`${pad}${value.open}${value.textContent}${value.close}`);\n                layers.splice(layerIndex, 1);\n                nextLayerIndex--;\n            } else {\n                result.push(`${pad}${value.open}`);\n                delete value.open;\n            }\n        } else {\n            if (value.close) {\n                result.push(`${pad}${value.close}`);\n            } else if (value.textContent) {\n                result.push(`${pad}${value.textContent}`);\n            }\n            layers.splice(layerIndex, 1);\n            nextLayerIndex--;\n        }\n        if (nextLayerIndex >= layers.length) {\n            layerIndex = nextLayerIndex - 1;\n            continue;\n        }\n        const nextLayer = layers[nextLayerIndex];\n        if (nextLayerIndex === 0 || nextLayer.level > layers[nextLayerIndex - 1].level) {\n            layerIndex = nextLayerIndex;\n        } else {\n            layerIndex = nextLayerIndex - 1;\n        }\n    }\n    return result.join(\"\\n\");\n}\n\n/**\n * @param {[string, string, number][]} modifierInfo\n */\nfunction getFiltersDescription(modifierInfo) {\n    const description = [];\n    for (const [modifier, content, count = 0] of modifierInfo) {\n        const makeLabel = MODIFIER_SUFFIX_LABELS[modifier];\n        const elements = plural(\"element\", count);\n        if (typeof makeLabel === \"function\") {\n            description.push(`${count} ${elements} ${makeLabel(content)}`);\n        } else {\n            description.push(`${count} ${modifier} ${elements}`);\n        }\n        if (!count) {\n            // Stop at first null count to avoid situations like:\n            // \"found 0 elements, including 0 visible elements, including 0 ...\"\n            break;\n        }\n    }\n    return description;\n}\n\n/**\n * @param {Node} node\n */\nfunction getInlineNodeText(node) {\n    return getNodeText(node, { inline: true });\n}\n\n/**\n * @param {Node} node\n * @returns {NodeValue}\n */\nfunction getNodeContent(node) {\n    switch (getTag(node)) {\n        case \"input\":\n        case \"option\":\n        case \"textarea\":\n            return getNodeValue(node);\n        case \"select\":\n            return [...node.selectedOptions].map(getNodeValue).join(\",\");\n    }\n    return getNodeText(node);\n}\n\n/** @type {NodeFilter} */\nfunction getNodeIframe(node) {\n    // Note: should only apply on `iframe` elements\n    /** @see parseSelector */\n    const doc = node.contentDocument;\n    return doc && doc.readyState !== \"loading\" ? doc : false;\n}\n\n/** @type {NodeFilter} */\nfunction getNodeShadowRoot(node) {\n    return node.shadowRoot;\n}\n\n/**\n * @param {string} pseudoClass\n */\nfunction getQueryFilter(pseudoClass, content) {\n    const makeQueryFilter = customPseudoClasses.get(pseudoClass);\n    try {\n        return makeQueryFilter(content);\n    } catch (err) {\n        let message = `error while parsing pseudo-class ':${pseudoClass}'`;\n        const cause = String(err?.message || err);\n        if (cause) {\n            message += `: ${cause}`;\n        }\n        throw new HootDomError(message);\n    }\n}\n\n/**\n * @param {string} string\n */\nfunction getStringContent(string) {\n    return string.match(R_QUOTE_CONTENT)?.[2] || string;\n}\n\nfunction getWaitForMessage() {\n    const message = `expected at least 1 element after %timeout%ms and ${lastQueryMessage}`;\n    lastQueryMessage = \"\";\n    return message;\n}\n\nfunction getWaitForNoneMessage() {\n    const message = `expected 0 elements after %timeout%ms and ${lastQueryMessage}`;\n    lastQueryMessage = \"\";\n    return message;\n}\n\n/**\n *\n * @param {number} count\n * @param {Parameters<NodeFilter>[0]} _node\n * @param {Parameters<NodeFilter>[1]} _i\n * @param {Parameters<NodeFilter>[2]} nodes\n */\nfunction hasNodeCount(count, _node, _i, nodes) {\n    return count === nodes.length;\n}\n\n/**\n * @param {string} [char]\n */\nfunction isChar(char) {\n    return !!char && R_CHAR.test(char);\n}\n\n/**\n * @template T\n * @param {T} object\n * @returns {T extends Document ? true : false}\n */\nfunction isDocument(object) {\n    return object?.nodeType === Node.DOCUMENT_NODE;\n}\n\n/**\n * @template T\n * @param {T} object\n * @returns {T extends Element ? true: false}\n */\nfunction isElement(object) {\n    return object?.nodeType === Node.ELEMENT_NODE;\n}\n\n/**\n * @param {string} selector\n * @param {Node} node\n */\nfunction isNodeHaving(selector, node) {\n    return !!_queryAll(selector, { root: node }).length;\n}\n\n/** @type {NodeFilter} */\nfunction isNodeHidden(node) {\n    return !isNodeVisible(node);\n}\n\n/** @type {NodeFilter} */\nfunction isNodeInteractive(node) {\n    return (\n        getStyle(node).pointerEvents !== \"none\" &&\n        !node.closest?.(\"[inert]\") &&\n        !getParentFrame(node)?.inert\n    );\n}\n\n/**\n * @param {string} selector\n * @param {Node} node\n */\nfunction isNodeNotMatching(selector, node) {\n    return !matches(node, selector);\n}\n\n/** @type {NodeFilter} */\nfunction isNodeSelected(node) {\n    return !!node.selected;\n}\n\n/** @type {NodeFilter} */\nfunction isOnlyNode(_node, _i, nodes) {\n    return nodes.length === 1;\n}\n\n/**\n * @param {Node} node\n */\nfunction isQueryableNode(node) {\n    return QUERYABLE_NODE_TYPES.includes(node.nodeType);\n}\n\n/**\n * @param {Element} [el]\n */\nfunction isRootElement(el) {\n    return el && R_ROOT_ELEMENT.test(el.nodeName || \"\");\n}\n\n/**\n * @param {Element} el\n */\nfunction isShadowRoot(el) {\n    return el.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !!el.host;\n}\n\n/**\n * @template T\n * @param {T} object\n * @returns {T extends Window ? true : false}\n */\nfunction isWindow(object) {\n    return object?.window === object && object.constructor.name === \"Window\";\n}\n\n/**\n * @param {string} [char]\n */\nfunction isWhiteSpace(char) {\n    return !!char && R_HORIZONTAL_WHITESPACE.test(char);\n}\n\n/**\n * @param {(node: Node) => NodeValue} getContent\n * @param {boolean} exact\n */\nfunction makePseudoClassMatcher(getContent, exact) {\n    return function makePartialMatcher(content) {\n        const regex = parseRegExp(content);\n        if (isInstanceOf(regex, RegExp)) {\n            return function stringMatches(node) {\n                return regex.test(String(getContent(node)));\n            };\n        } else {\n            const lowerContent = content.toLowerCase();\n            if (exact) {\n                return function stringEquals(node) {\n                    return String(getContent(node)).toLowerCase() === lowerContent;\n                };\n            } else {\n                return function stringContains(node) {\n                    return String(getContent(node)).toLowerCase().includes(lowerContent);\n                };\n            }\n        }\n    };\n}\n\n/**\n *\n * @param {QueryFilter} filter\n * @param {Node[]} nodes\n * @param {number} index\n */\nfunction matchFilter(filter, nodes, index) {\n    if (typeof filter === \"number\") {\n        if (filter < 0) {\n            return filter + nodes.length === index;\n        } else {\n            return filter === index;\n        }\n    }\n    const node = nodes[index];\n    if (typeof filter === \"function\") {\n        return filter(node, index, nodes);\n    } else {\n        return !!node.matches?.(String(filter));\n    }\n}\n\n/**\n * flatMap implementation supporting NodeList iterables.\n *\n * @param {Iterable<Node>} nodes\n * @param {(node: Node) => Node | Iterable<Node> | null | undefined} flatMapFn\n */\nfunction nodeFlatMap(nodes, flatMapFn) {\n    /** @type {Node[]} */\n    const result = [];\n    for (const node of nodes) {\n        const nodeList = flatMapFn(node);\n        if (isNode(nodeList)) {\n            result.push(nodeList);\n        } else if (isIterable(nodeList)) {\n            result.push(...nodeList);\n        }\n    }\n    return result;\n}\n\n/**\n * @template T\n * @param {T} value\n * @param {(keyof T)[]} propsA\n * @param {(keyof T)[]} propsB\n * @returns {[number, number]}\n */\nfunction parseNumberTuple(value, propsA, propsB) {\n    let result = [];\n    if (value && typeof value === \"object\") {\n        if (isIterable(value)) {\n            [result[0], result[1]] = [...value];\n        } else {\n            for (const prop of propsA) {\n                result[0] ??= value[prop];\n            }\n            for (const prop of propsB) {\n                result[1] ??= value[prop];\n            }\n        }\n    } else {\n        result = [value, value];\n    }\n    return result.map($parseFloat);\n}\n\n/**\n * @template {any[]} T\n * @param {T} args\n * @returns {string | T}\n */\nfunction parseRawArgs(args) {\n    return args[0]?.raw ? [$raw(...args)] : args;\n}\n\n/**\n * Parses a given selector string into a list of selector groups.\n *\n * - the return value is a list of selector `group` objects (representing comma-separated\n *  selectors);\n * - a `group` is composed of one or more `part` objects (representing space-separated\n *  selector parts inside of a group);\n * - a `part` is composed of a base selector (string) and zero or more 'filters' (predicates).\n *\n * @param {string} selector\n */\nfunction parseSelector(selector) {\n    /**\n     * @param {string} selector\n     */\n    function addToSelector(selector) {\n        registerChar = false;\n        const index = currentPart.length - 1;\n        if (typeof currentPart[index] === \"string\") {\n            currentPart[index] += selector;\n        } else {\n            currentPart.push(selector);\n        }\n    }\n\n    /** @type {(string | ReturnType<PseudoClassPredicateBuilder>)[]} */\n    const firstPart = [\"\"];\n    const firstGroup = [firstPart];\n    const groups = [firstGroup];\n    const parens = [0, 0];\n\n    let currentGroup = groups.at(-1);\n    let currentPart = currentGroup.at(-1);\n    let currentPseudo = null;\n    let currentQuote = null;\n    let registerChar = true;\n\n    for (let i = 0; i < selector.length; i++) {\n        const char = selector[i];\n        registerChar = true;\n        switch (char) {\n            // Group separator (comma)\n            case \",\": {\n                if (!currentQuote && !currentPseudo) {\n                    groups.push([[\"\"]]);\n                    currentGroup = groups.at(-1);\n                    currentPart = currentGroup.at(-1);\n                    registerChar = false;\n                }\n                break;\n            }\n            // Part separator (white space)\n            case \" \":\n            case \"\\t\":\n            case \"\\n\":\n            case \"\\r\":\n            case \"\\f\":\n            case \"\\v\": {\n                if (!currentQuote && !currentPseudo) {\n                    if (currentPart[0] || currentPart.length > 1) {\n                        // Only push new part if the current one is not empty\n                        // (has at least 1 character OR 1 pseudo-class filter)\n                        currentGroup.push([\"\"]);\n                        currentPart = currentGroup.at(-1);\n                    }\n                    registerChar = false;\n                }\n                break;\n            }\n            // Quote delimiters\n            case `'`:\n            case `\"`: {\n                if (char === currentQuote) {\n                    currentQuote = null;\n                } else if (!currentQuote) {\n                    currentQuote = char;\n                }\n                break;\n            }\n            // Combinators\n            case \">\":\n            case \"+\":\n            case \"~\": {\n                if (!currentQuote && !currentPseudo) {\n                    while (isWhiteSpace(selector[i + 1])) {\n                        i++;\n                    }\n                    addToSelector(char);\n                }\n                break;\n            }\n            // Pseudo classes\n            case \":\": {\n                if (!currentQuote && !currentPseudo) {\n                    let pseudo = \"\";\n                    while (isChar(selector[i + 1])) {\n                        pseudo += selector[++i];\n                    }\n                    if (customPseudoClasses.has(pseudo)) {\n                        if (selector[i + 1] === \"(\") {\n                            parens[0]++;\n                            i++;\n                            registerChar = false;\n                        }\n                        currentPseudo = [pseudo, \"\"];\n                    } else {\n                        addToSelector(char + pseudo);\n                    }\n                }\n                break;\n            }\n            // Parentheses\n            case \"(\": {\n                if (!currentQuote) {\n                    parens[0]++;\n                }\n                break;\n            }\n            case \")\": {\n                if (!currentQuote) {\n                    parens[1]++;\n                }\n                break;\n            }\n        }\n\n        if (currentPseudo) {\n            if (parens[0] === parens[1]) {\n                const [pseudo, content] = currentPseudo;\n                if (pseudo === \"iframe\" && !currentPart[0].startsWith(\"iframe\")) {\n                    // Special case: to optimise the \":iframe\" pseudo class, we\n                    // always select actual `iframe` elements.\n                    // Note that this may create \"impossible\" tag names (like \"iframediv\")\n                    // but this pseudo won't work on non-iframe elements anyway.\n                    currentPart[0] = `iframe${currentPart[0]}`;\n                }\n                const filter = getQueryFilter(pseudo, getStringContent(content));\n                selectorFilterDescriptors.set(filter, [pseudo, content]);\n                currentPart.push(filter);\n                currentPseudo = null;\n            } else if (registerChar) {\n                currentPseudo[1] += selector[i];\n            }\n        } else if (registerChar) {\n            addToSelector(selector[i]);\n        }\n    }\n\n    return groups;\n}\n\n/**\n * @param {string} xmlString\n * @param {\"html\" | \"xml\"} type\n */\nfunction parseXml(xmlString, type) {\n    const wrapperTag = type === \"html\" ? \"body\" : \"templates\";\n    const doc = parser.parseFromString(\n        `<${wrapperTag}>${xmlString}</${wrapperTag}>`,\n        `text/${type}`\n    );\n    if (doc.getElementsByTagName(\"parsererror\").length) {\n        const trimmed = xmlString.length > 80 ? xmlString.slice(0, 80) + \"\u2026\" : xmlString;\n        throw new HootDomError(\n            `error while parsing ${trimmed}: ${getNodeText(\n                doc.getElementsByTagName(\"parsererror\")[0]\n            )}`\n        );\n    }\n    return doc.getElementsByTagName(wrapperTag)[0].childNodes;\n}\n\n/**\n * Converts a CSS pixel value to a number, removing the 'px' part.\n *\n * @param {string} val\n */\nfunction pixelValueToNumber(val) {\n    return $parseFloat(val.endsWith(\"px\") ? val.slice(0, -2) : val);\n}\n\n/**\n * @param {string} word\n * @param {number} count\n */\nfunction plural(word, count) {\n    return count === 1 ? word : `${word}s`;\n}\n\n/**\n * @param {Node[]} nodes (assumed not empty)\n * @param {string} selector\n */\nfunction queryWithCustomSelector(nodes, selector) {\n    const selectorGroups = parseSelector(selector);\n    const foundNodes = [];\n    for (const selectorParts of selectorGroups) {\n        let groupNodes = nodes;\n        for (const selectorPart of selectorParts) {\n            let baseSelector = selectorPart[0];\n            let nodeGetter;\n            switch (baseSelector[0]) {\n                case \"+\": {\n                    nodeGetter = NEXT_SIBLING;\n                    break;\n                }\n                case \">\": {\n                    nodeGetter = DIRECT_CHILDREN;\n                    break;\n                }\n                case \"~\": {\n                    nodeGetter = NEXT_SIBLINGS;\n                    break;\n                }\n            }\n\n            // Slices modifier (if any)\n            if (nodeGetter) {\n                baseSelector = baseSelector.slice(1);\n            }\n            nodeGetter ||= DESCENDANTS;\n\n            // Retrieve nodes from current group nodes\n            const currentGroupNodes = nodeFlatMap(groupNodes, (node) =>\n                nodeGetter(node, baseSelector)\n            );\n\n            // Filter/replace nodes based on custom pseudo-classes\n            groupNodes = applyFilters(selectorPart.slice(1), currentGroupNodes);\n        }\n\n        foundNodes.push(...groupNodes);\n    }\n\n    return filterUniqueNodes(foundNodes);\n}\n\n/**\n * Creates a query message if needed, with all the information available used to\n * gather the given nodes (base selector and count of nodes matching it, then each\n * modifier applied as a filter with each associated count).\n *\n * Returns the resulting message only if the final count of nodes doesn't match\n * the given expected count.\n *\n * @param {Node[]} filteredNodes\n * @param {number} [expectedCount]\n */\nfunction registerQueryMessage(filteredNodes, expectedCount) {\n    lastQueryMessage = \"\";\n    const filteredCount = filteredNodes.length;\n    const invalidCount = $isInteger(expectedCount) && filteredCount !== expectedCount;\n    if (shouldRegisterQueryMessage || invalidCount) {\n        const globalModifierInfo = [...globalFilterDescriptors.values()];\n\n        // First message part: final count\n        lastQueryMessage += `found ${filteredCount} ${plural(\"element\", filteredCount)}`;\n        if (invalidCount) {\n            lastQueryMessage += ` instead of ${expectedCount}`;\n        }\n\n        // Next message part: initial element count (with selector if string)\n        const rootModifierInfo = globalModifierInfo.shift();\n        const [, rootContent, initialCount = 0] = rootModifierInfo;\n        if (typeof rootContent === \"string\") {\n            lastQueryMessage += `: ${initialCount} matching ${JSON.stringify(rootContent)}`;\n            if (selectorFilterDescriptors.size) {\n                // Selector filters will only be available with a custom selector\n                const selectorModifierInfo = [...selectorFilterDescriptors.values()];\n                lastQueryMessage += ` (${getFiltersDescription(selectorModifierInfo).join(\" > \")})`;\n            }\n        } else if (filteredCount !== initialCount) {\n            // Do not report count if same as announced initially\n            lastQueryMessage += `: ${initialCount} ${plural(\"element\", initialCount)}`;\n        }\n        if (initialCount) {\n            // Next message parts: each count associated with each modifier\n            lastQueryMessage += getFiltersDescription(globalModifierInfo)\n                .map((part) => `, including ${part}`)\n                .join(\"\");\n        }\n    } else {\n        lastQueryMessage = \"\";\n    }\n    if (queryAllLevel <= 1) {\n        globalFilterDescriptors.clear();\n        selectorFilterDescriptors.clear();\n    }\n    return invalidCount ? lastQueryMessage : \"\";\n}\n\n/**\n * Wrapper around '_queryAll' calls to ensure global variables are properly cleaned\n * up on any thrown error.\n *\n * @param {Target} target\n * @param {QueryOptions} options\n */\nfunction _guardedQueryAll(target, options) {\n    try {\n        return _queryAll(target, options);\n    } catch (error) {\n        queryAllLevel = 0;\n        shouldRegisterQueryMessage = false;\n        globalFilterDescriptors.clear();\n        selectorFilterDescriptors.clear();\n        throw error;\n    }\n}\n\n/**\n * @param {Target} target\n * @param {QueryOptions} options\n */\nfunction _queryAll(target, options) {\n    queryAllLevel++;\n\n    const { count, root, ...modifiers } = options || {};\n    if (count !== null && count !== undefined && (!$isInteger(count) || count <= 0)) {\n        throw new HootDomError(`invalid 'count' option: should be a positive integer`);\n    }\n\n    /** @type {Node[]} */\n    let nodes = [];\n    let selector;\n\n    if (typeof target === \"string\") {\n        if (target) {\n            nodes = root ? _queryAll(root) : [getDefaultRoot()];\n        }\n        selector = target.trim();\n        // HTMLSelectElement is iterable \u00af\\_(\u30c4)_/\u00af\n    } else if (isIterable(target) && !isNode(target)) {\n        nodes = filterUniqueNodes(target);\n    } else if (target) {\n        nodes = filterUniqueNodes([target]);\n    }\n\n    globalFilterDescriptors.set(\"root\", [\"\", target]);\n    if (selector && nodes.length) {\n        if (rCustomPseudoClass.test(selector)) {\n            nodes = queryWithCustomSelector(nodes, selector);\n        } else {\n            nodes = filterUniqueNodes(nodeFlatMap(nodes, (node) => DESCENDANTS(node, selector)));\n        }\n    }\n    globalFilterDescriptors.get(\"root\").push(nodes.length);\n\n    if (modifiers.visible && modifiers.displayed) {\n        throw new HootDomError(\n            `cannot use more than one visibility modifier ('visible' implies 'displayed')`\n        );\n    }\n\n    // Apply option modifiers on matching nodes\n    const modifierFilters = [];\n    for (const [modifier, content] of $entries(modifiers)) {\n        if (content === false || !customPseudoClasses.has(modifier)) {\n            continue;\n        }\n        const filter = getQueryFilter(modifier, content);\n        modifierFilters.push(filter);\n        globalFilterDescriptors.set(filter, [modifier, content]);\n    }\n    const filteredNodes = applyFilters(modifierFilters, nodes);\n\n    // Register query message (if needed), and/or throw an error accordingly\n    const message = registerQueryMessage(filteredNodes, count);\n    if (message) {\n        throw new HootDomError(message);\n    }\n\n    queryAllLevel--;\n\n    return filteredNodes;\n}\n\n/**\n * @param {Target} target\n * @param {QueryOptions} options\n */\nfunction _queryOne(target, options) {\n    return _guardedQueryAll(target, { ...options, count: 1 })[0];\n}\n\n/**\n * @param {Target} target\n * @param {QueryOptions} options\n * @param {boolean} isLast\n */\nfunction _waitForFirst(target, options, isLast) {\n    shouldRegisterQueryMessage = isLast;\n    const result = _guardedQueryAll(target, options)[0];\n    shouldRegisterQueryMessage = false;\n    return result;\n}\n\n/**\n * @param {Target} target\n * @param {QueryOptions} options\n * @param {boolean} isLast\n */\nfunction _waitForNone(target, options, isLast) {\n    shouldRegisterQueryMessage = isLast;\n    const result = _guardedQueryAll(target, options).length === 0;\n    shouldRegisterQueryMessage = false;\n    return result;\n}\n\nclass HootDomError extends Error {\n    name = \"HootDomError\";\n}\n\n// Regexes\nconst R_CHAR = /[\\w-]/;\n/** \\s without \\n and \\v */\nconst R_HORIZONTAL_WHITESPACE =\n    /[\\r\\t\\f \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]+/g;\nconst R_LINEBREAK = /\\s*\\n+\\s*/g;\nconst R_QUOTE_CONTENT = /^\\s*(['\"])?([^]*?)\\1\\s*$/;\nconst R_ROOT_ELEMENT = /^(HTML|HEAD|BODY)$/;\nconst R_SCROLLABLE_OVERFLOW = /\\bauto\\b|\\bscroll\\b/;\n\nconst MODIFIER_SUFFIX_LABELS = {\n    contains: (content) => `with text \"${content}\"`,\n    eq: (content) => `at index ${content}`,\n    has: (content) => `containing selector \"${content}\"`,\n    not: (content) => `not matching \"${content}\"`,\n    value: (content) => `with value \"${content}\"`,\n    viewPort: () => \"in viewport\",\n};\n\nconst QUERYABLE_NODE_TYPES = [Node.ELEMENT_NODE, Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE];\n\nconst parser = new DOMParser();\n\n// Node getters\n\n/** @type {NodeGetter} */\nfunction DIRECT_CHILDREN(node, selector) {\n    const children = [];\n    for (const childNode of node.childNodes) {\n        if (childNode.matches?.(selector)) {\n            children.push(childNode);\n        }\n    }\n    return children;\n}\n\n/** @type {NodeGetter} */\nfunction DESCENDANTS(node, selector) {\n    return node.querySelectorAll?.(selector || \"*\");\n}\n\n/** @type {NodeGetter} */\nfunction NEXT_SIBLING(node, selector) {\n    const sibling = node.nextElementSibling;\n    return sibling?.matches?.(selector) && sibling;\n}\n\n/** @type {NodeGetter} */\nfunction NEXT_SIBLINGS(node, selector) {\n    const siblings = [];\n    while ((node = node.nextElementSibling)) {\n        if (node.matches?.(selector)) {\n            siblings.push(node);\n        }\n    }\n    return siblings;\n}\n\n/** @type {Map<QueryFilter, [string, string | null, number]>} */\nconst globalFilterDescriptors = new Map();\n/** @type {Map<QueryFilter, [string, string | null, number]>} */\nconst selectorFilterDescriptors = new Map();\n/** @type {Map<HTMLElement, { callbacks: Set<MutationCallback>, observer: MutationObserver }>} */\nconst observers = new Map();\nconst currentDimensions = {\n    width: innerWidth,\n    height: innerHeight,\n};\nlet getDefaultRoot = () => document;\nlet lastQueryMessage = \"\";\nlet shouldRegisterQueryMessage = false;\nlet queryAllLevel = 0;\n\n//-----------------------------------------------------------------------------\n// Pseudo classes\n//-----------------------------------------------------------------------------\n\n/** @type {Map<string, PseudoClassPredicateBuilder>} */\nconst customPseudoClasses = new Map();\n\ncustomPseudoClasses\n    .set(\"contains\", makePseudoClassMatcher(getInlineNodeText, false))\n    .set(\"count\", (strCount) => {\n        const count = $parseInt(strCount);\n        if (!$isInteger(count) || count <= 0) {\n            throw new HootDomError(`expected count to be a positive integer (got \"${strCount}\")`);\n        }\n        return hasNodeCount.bind(null, count);\n    })\n    .set(\"displayed\", () => isNodeDisplayed)\n    .set(\"empty\", () => isEmpty)\n    .set(\"eq\", (strIndex) => {\n        const index = $parseInt(strIndex);\n        if (!$isInteger(index)) {\n            throw new HootDomError(`expected index to be an integer (got \"${strIndex}\")`);\n        }\n        return index;\n    })\n    .set(\"first\", () => 0)\n    .set(\"focusable\", () => isNodeFocusable)\n    .set(\"has\", (selector) => isNodeHaving.bind(null, selector))\n    .set(\"hidden\", () => isNodeHidden)\n    .set(\"iframe\", () => getNodeIframe)\n    .set(\"interactive\", () => isNodeInteractive)\n    .set(\"last\", () => -1)\n    .set(\"not\", (selector) => isNodeNotMatching.bind(null, selector))\n    .set(\"only\", () => isOnlyNode)\n    .set(\"scrollable\", (axis) => isNodeScrollable.bind(null, axis))\n    .set(\"selected\", () => isNodeSelected)\n    .set(\"shadow\", () => getNodeShadowRoot)\n    .set(\"text\", makePseudoClassMatcher(getInlineNodeText, true))\n    .set(\"value\", makePseudoClassMatcher(getNodeValue, false))\n    .set(\"viewPort\", () => isNodeInViewPort)\n    .set(\"visible\", () => isNodeVisible);\n\nconst rCustomPseudoClass = compilePseudoClassRegex();\n\n//-----------------------------------------------------------------------------\n// Internal exports (inside Hoot/Hoot-DOM)\n//-----------------------------------------------------------------------------\n\nexport function cleanupDOM() {\n    // Dimensions\n    currentDimensions.width = innerWidth;\n    currentDimensions.height = innerHeight;\n\n    // Observers\n    const remainingObservers = observers.size;\n    if (remainingObservers) {\n        for (const { observer } of observers.values()) {\n            observer.disconnect();\n        }\n        observers.clear();\n    }\n}\n\n/**\n * @param {Node | () => Node} node\n */\nexport function defineRootNode(node) {\n    if (typeof node === \"function\") {\n        getDefaultRoot = node;\n    } else if (node) {\n        getDefaultRoot = () => node;\n    } else {\n        getDefaultRoot = () => document;\n    }\n}\n\nexport function getCurrentDimensions() {\n    return currentDimensions;\n}\n\n/**\n * @param {Node} [node]\n * @returns {Document}\n */\nexport function getDocument(node) {\n    if (!node) {\n        return document;\n    }\n    return isDocument(node) ? node : node.ownerDocument || document;\n}\n\n/**\n * @param {Node} node\n * @param {string} attribute\n * @returns {string | null}\n */\nexport function getNodeAttribute(node, attribute) {\n    return node.getAttribute?.(attribute) ?? null;\n}\n\n/**\n * @param {Node} node\n * @returns {NodeValue}\n */\nexport function getNodeValue(node) {\n    switch (node.type) {\n        case \"checkbox\":\n        case \"radio\":\n            return node.checked;\n        case \"file\":\n            return [...node.files];\n        case \"number\":\n        case \"range\":\n            return node.valueAsNumber;\n        case \"date\":\n        case \"datetime-local\":\n        case \"month\":\n        case \"time\":\n        case \"week\":\n            return node.valueAsDate.toISOString();\n    }\n    return node.value;\n}\n\n/**\n * @param {Node} node\n * @param {QueryRectOptions} [options]\n */\nexport function getNodeRect(node, options) {\n    if (!isElement(node)) {\n        return new DOMRect();\n    }\n\n    /** @type {DOMRect} */\n    const rect = node.getBoundingClientRect();\n    const parentFrame = getParentFrame(node);\n    if (parentFrame) {\n        const parentRect = getNodeRect(parentFrame);\n        rect.x -= parentRect.x;\n        rect.y -= parentRect.y;\n    }\n\n    if (!options?.trimPadding) {\n        return rect;\n    }\n\n    const style = getStyle(node);\n    const { x, y, width, height } = rect;\n    const [pl, pr, pt, pb] = [\"left\", \"right\", \"top\", \"bottom\"].map((side) =>\n        pixelValueToNumber(style.getPropertyValue(`padding-${side}`))\n    );\n\n    return new DOMRect(x + pl, y + pt, width - (pl + pr), height - (pt + pb));\n}\n\n/**\n * @param {Node} node\n * @param {QueryTextOptions} [options]\n * @returns {string}\n */\nexport function getNodeText(node, options) {\n    let content;\n    if (typeof node.innerText === \"string\") {\n        content = node.innerText;\n    } else {\n        content = node.textContent;\n    }\n    if (!options?.raw) {\n        content = content.replace(R_HORIZONTAL_WHITESPACE, \" \").trim();\n    }\n    if (options?.inline) {\n        content = content.replace(R_LINEBREAK, \" \");\n    }\n    return content;\n}\n\n/**\n * @param {Node} node\n * @returns {Node | null}\n */\nexport function getInteractiveNode(node) {\n    let currentEl = ensureElement(node);\n    if (!currentEl) {\n        return null;\n    }\n    while (currentEl && !isNodeInteractive(currentEl)) {\n        currentEl = currentEl.parentElement;\n    }\n    return currentEl;\n}\n\n/**\n * @template {Node} T\n * @param {T} node\n * @returns {T extends Element ? CSSStyleDeclaration : null}\n */\nexport function getStyle(node) {\n    return isElement(node) ? getComputedStyle(node) : null;\n}\n\n/**\n * @param {Node} [node]\n * @returns {Window}\n */\nexport function getWindow(node) {\n    if (!node) {\n        return window;\n    }\n    return isWindow(node) ? node : getDocument(node).defaultView;\n}\n\n/**\n * @param {Node} node\n * @returns {boolean}\n */\nexport function isCheckable(node) {\n    switch (getTag(node)) {\n        case \"input\":\n            return node.type === \"checkbox\" || node.type === \"radio\";\n        case \"label\":\n            return isCheckable(node.control);\n        default:\n            return false;\n    }\n}\n\n/**\n * @param {unknown} value\n * @returns {boolean}\n */\nexport function isEmpty(value) {\n    if (!value) {\n        return true;\n    }\n    if (typeof value === \"object\") {\n        if (isNode(value)) {\n            return isEmpty(getNodeContent(value));\n        }\n        if (!isIterable(value)) {\n            value = $keys(value);\n        }\n        return [...value].length === 0;\n    }\n    return false;\n}\n\n/**\n * Returns whether the given object is an {@link EventTarget}.\n *\n * @template T\n * @param {T} object\n * @returns {T extends EventTarget ? true : false}\n * @example\n *  isEventTarget(window); // true\n * @example\n *  isEventTarget(new App()); // false\n */\nexport function isEventTarget(object) {\n    return object && typeof object.addEventListener === \"function\";\n}\n\n/**\n * Returns whether the given object is a {@link Node} object.\n * Note that it is independant from the {@link Node} class itself to support\n * cross-window checks.\n *\n * @template T\n * @param {T} object\n * @returns {T extends Node ? true : false}\n */\nexport function isNode(object) {\n    return object && typeof object.nodeType === \"number\" && typeof object.nodeName === \"string\";\n}\n\n/**\n * @param {Node} node\n */\nexport function isNodeCssVisible(node) {\n    const element = ensureElement(node);\n    if (element === getDefaultRoot() || isRootElement(element)) {\n        return true;\n    }\n    const style = getStyle(element);\n    if (style?.visibility === \"hidden\" || style?.opacity === \"0\") {\n        return false;\n    }\n    const parent = element.parentNode;\n    return !parent || isNodeCssVisible(isShadowRoot(parent) ? parent.host : parent);\n}\n\n/**\n * @param {Window | Node} node\n */\nexport function isNodeDisplayed(node) {\n    const element = ensureElement(node);\n    if (!isInDOM(element)) {\n        return false;\n    }\n    if (isRootElement(element) || element.offsetParent || element.closest(\"svg\")) {\n        return true;\n    }\n    // `position=fixed` elements in Chrome do not have an `offsetParent`\n    return !isFirefox() && getStyle(element)?.position === \"fixed\";\n}\n\n/**\n * @param {Node} node\n * @param {FocusableOptions} [options]\n */\nexport function isNodeFocusable(node, options) {\n    return (\n        isNodeDisplayed(node) &&\n        node.matches?.(FOCUSABLE_SELECTOR) &&\n        (!options?.tabbable || node.tabIndex >= 0)\n    );\n}\n\n/**\n * @param {Window | Node} node\n */\nexport function isNodeInViewPort(node) {\n    const element = ensureElement(node);\n    const { x, y } = getNodeRect(element);\n\n    return y > 0 && y < currentDimensions.height && x > 0 && x < currentDimensions.width;\n}\n\n/**\n * @param {ScrollAxis} axis\n * @param {Window | Node} node\n */\nexport function isNodeScrollable(axis, node) {\n    if (!isElement(node)) {\n        return false;\n    }\n    const isScrollableX = node.clientWidth < node.scrollWidth;\n    const isScrollableY = node.clientHeight < node.scrollHeight;\n    switch (axis) {\n        case \"both\": {\n            if (!isScrollableX || !isScrollableY) {\n                return false;\n            }\n            break;\n        }\n        case \"x\": {\n            if (!isScrollableX) {\n                return false;\n            }\n            break;\n        }\n        case \"y\": {\n            if (!isScrollableY) {\n                return false;\n            }\n            break;\n        }\n        default: {\n            // Check for any scrollable axis\n            if (!isScrollableX && !isScrollableY) {\n                return false;\n            }\n        }\n    }\n    const overflow = getStyle(node).getPropertyValue(\"overflow\");\n    if (R_SCROLLABLE_OVERFLOW.test(overflow)) {\n        return true;\n    }\n    return false;\n}\n\n/**\n * @param {Window | Node} node\n */\nexport function isNodeVisible(node) {\n    const element = ensureElement(node);\n\n    // Must be displayed and not hidden by CSS\n    if (!isNodeDisplayed(element) || !isNodeCssVisible(element)) {\n        return false;\n    }\n\n    let visible = false;\n\n    // Check size (width & height)\n    const { width, height } = getNodeRect(element);\n    visible = width > 0 && height > 0;\n\n    // Check content (if display=contents)\n    if (!visible && getStyle(element)?.display === \"contents\") {\n        for (const child of element.childNodes) {\n            if (isNodeVisible(child)) {\n                return true;\n            }\n        }\n    }\n\n    return visible;\n}\n\n/**\n * @param {Dimensions} dimensions\n * @returns {[number, number]}\n */\nexport function parseDimensions(dimensions) {\n    return parseNumberTuple(dimensions, [\"width\", \"w\"], [\"height\", \"h\"]);\n}\n\n/**\n * @param {Position} position\n * @returns {[number, number]}\n */\nexport function parsePosition(position) {\n    return parseNumberTuple(\n        position,\n        [\"x\", \"left\", \"clientX\", \"pageX\", \"screenX\"],\n        [\"y\", \"top\", \"clientY\", \"pageY\", \"screenY\"]\n    );\n}\n\n/**\n * @param {number} width\n * @param {number} height\n */\nexport function setDimensions(width, height) {\n    const defaultRoot = getDefaultRoot();\n    if (!$isNaN(width)) {\n        currentDimensions.width = width;\n        defaultRoot.style?.setProperty(\"width\", `${width}px`, \"important\");\n    }\n    if (!$isNaN(height)) {\n        currentDimensions.height = height;\n        defaultRoot.style?.setProperty(\"height\", `${height}px`, \"important\");\n    }\n}\n\n/**\n * @param {Node} node\n * @param {{ object?: boolean }} [options]\n * @returns {string | string[]}\n */\nexport function toSelector(node, options) {\n    const parts = {\n        tag: node.nodeName.toLowerCase(),\n    };\n    if (node.id) {\n        parts.id = `#${node.id}`;\n    }\n    if (node.classList?.length) {\n        parts.class = `.${[...node.classList].join(\".\")}`;\n    }\n    return options?.object ? parts : $values(parts).join(\"\");\n}\n\n// Following selector is based on this spec:\n// https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex\nexport const FOCUSABLE_SELECTOR = [\n    \"a[href]\",\n    \"area[href]\",\n    \"button:enabled\",\n    \"details > summary:first-of-type\",\n    \"iframe\",\n    \"input:enabled\",\n    \"select:enabled\",\n    \"textarea:enabled\",\n    \"[tabindex]\",\n    \"[contenteditable=true]\",\n].join(\",\");\n\n//-----------------------------------------------------------------------------\n// Exports\n//-----------------------------------------------------------------------------\n\n/**\n * Returns a standardized representation of the given `string` value as a human-readable\n * XML string template (or HTML if the `type` option is `\"html\"`).\n *\n * @param {string} value\n * @param {FormatXmlOptions} [options]\n * @returns {string}\n */\nexport function formatXml(value, options) {\n    const nodes = parseXml(value, options?.type || \"xml\");\n    const layers = extractLayers(nodes, 0, options?.keepInlineTextNodes ?? false);\n    return generateStringFromLayers(layers, options?.tabSize ?? 4);\n}\n\n/**\n * Returns the active element in the given document. Further checks are performed\n * in the following cases:\n * - the given node is an iframe (checks in its content document);\n * - the given node has a shadow root (checks in that shadow root document);\n * - the given node is the body of an iframe (checks in the parent document).\n *\n * @param {Node} [node]\n */\nexport function getActiveElement(node) {\n    const doc = getDocument(node);\n    const view = doc.defaultView;\n    const { activeElement } = doc;\n    const { contentDocument, shadowRoot } = activeElement;\n\n    if (contentDocument && contentDocument.activeElement !== contentDocument.body) {\n        // Active element is an \"iframe\" element (with an active element other than its own body):\n        if (contentDocument.activeElement === contentDocument.body) {\n            // Active element is the body of the iframe:\n            // -> returns that element\n            return contentDocument.activeElement;\n        } else {\n            // Active element is something else than the body:\n            // -> get the active element inside the iframe document\n            return getActiveElement(contentDocument);\n        }\n    }\n\n    if (shadowRoot) {\n        // Active element has a shadow root:\n        // -> get the active element inside its root\n        return shadowRoot.activeElement;\n    }\n\n    if (activeElement === doc.body && view !== view.parent) {\n        // Active element is the body of an iframe:\n        // -> get the active element of its parent frame (recursively)\n        return getActiveElement(view.parent.document);\n    }\n\n    return activeElement;\n}\n\n/**\n * Returns the list of focusable elements in the given parent, sorted by their `tabIndex`\n * property.\n *\n * @see {@link isFocusable} for more information\n * @param {FocusableOptions} [options]\n * @returns {Element[]}\n * @example\n *  getFocusableElements();\n */\nexport function getFocusableElements(options) {\n    const parent = _queryOne(options?.root || getDefaultRoot());\n    if (typeof parent.querySelectorAll !== \"function\") {\n        return [];\n    }\n    const byTabIndex = {};\n    for (const element of parent.querySelectorAll(FOCUSABLE_SELECTOR)) {\n        const { tabIndex } = element;\n        if ((options?.tabbable && tabIndex < 0) || !isNodeDisplayed(element)) {\n            continue;\n        }\n        if (!byTabIndex[tabIndex]) {\n            byTabIndex[tabIndex] = [];\n        }\n        byTabIndex[tabIndex].push(element);\n    }\n    const withTabIndexZero = byTabIndex[0] || [];\n    delete byTabIndex[0];\n    return [...$values(byTabIndex).flat(), ...withTabIndexZero];\n}\n\n/**\n * Returns the next focusable element after the current active element if it is\n * contained in the given parent.\n *\n * @see {@link getFocusableElements}\n * @param {FocusableOptions} [options]\n * @returns {Element | null}\n * @example\n *  getPreviousFocusableElement();\n */\nexport function getNextFocusableElement(options) {\n    const parent = _queryOne(options?.root || getDefaultRoot());\n    const focusableEls = getFocusableElements({ ...options, parent });\n    const index = focusableEls.indexOf(getActiveElement(parent));\n    return focusableEls[index + 1] || null;\n}\n\n/**\n * Returns the parent `<iframe>` of a given node (if any).\n *\n * @param {Node} node\n * @returns {HTMLIFrameElement | null}\n */\nexport function getParentFrame(node) {\n    const doc = getDocument(node);\n    if (!doc) {\n        return null;\n    }\n    const view = doc.defaultView;\n    if (view !== view.parent) {\n        for (const iframe of view.parent.document.getElementsByTagName(\"iframe\")) {\n            if (iframe.contentWindow === view) {\n                return iframe;\n            }\n        }\n    }\n    return null;\n}\n\n/**\n * Returns the previous focusable element before the current active element if it is\n * contained in the given parent.\n *\n * @see {@link getFocusableElements}\n * @param {FocusableOptions} [options]\n * @returns {Element | null}\n * @example\n *  getPreviousFocusableElement();\n */\nexport function getPreviousFocusableElement(options) {\n    const parent = _queryOne(options?.root || getDefaultRoot());\n    const focusableEls = getFocusableElements({ ...options, parent });\n    const index = focusableEls.indexOf(getActiveElement(parent));\n    return index < 0 ? focusableEls.at(-1) : focusableEls[index - 1] || null;\n}\n\n/**\n * Checks whether a target is displayed, meaning that it has an offset parent and\n * is contained in the current document.\n *\n * Note that it does not mean that the target is \"visible\" (it can still be hidden\n * by CSS properties such as `width`, `opacity`, `visiblity`, etc.).\n *\n * @param {Target} target\n * @returns {boolean}\n */\nexport function isDisplayed(target) {\n    return _guardedQueryAll(target, { displayed: true }).length > 0;\n}\n\n/**\n * Returns whether the given node is editable, meaning that it is an `\":enabled\"`\n * `<input>` or `<textarea>` {@link Element};\n *\n * Note: this does **NOT** support elements with `contenteditable=\"true\"`.\n *\n * @param {Node} node\n * @returns {boolean}\n * @example\n *  isEditable(document.querySelector(\"input\")); // true\n * @example\n *  isEditable(document.body); // false\n */\nexport function isEditable(node) {\n    return (\n        isElement(node) &&\n        !node.matches?.(\":disabled\") &&\n        [\"input\", \"textarea\"].includes(getTag(node))\n    );\n}\n\n/**\n * Returns whether an element is focusable. Focusable elements are either:\n * - `<a>` or `<area>` elements with an `href` attribute;\n * - *enabled* `<button>`, `<input>`, `<select>` and `<textarea>` elements;\n * - `<iframe>` elements;\n * - any element with its `contenteditable` attribute set to `\"true\"`.\n *\n * A focusable element must also not have a `tabIndex` property set to less than 0.\n *\n * @see {@link FOCUSABLE_SELECTOR}\n * @param {Target} target\n * @returns {boolean}\n */\nexport function isFocusable(target) {\n    return _guardedQueryAll(target, { focusable: true }).length > 0;\n}\n\n/**\n * Returns whether the given target is contained in the current root document.\n *\n * @param {Window | Node} target\n * @returns {boolean}\n * @example\n *  isInDOM(queryFirst(\"div\")); // true\n * @example\n *  isInDOM(document.createElement(\"div\")); // Not attached -> false\n */\nexport function isInDOM(target) {\n    return ensureElement(target)?.isConnected;\n}\n\n/**\n * Checks whether a target is *at least partially* visible in the current viewport.\n *\n * @param {Target} target\n * @returns {boolean}\n */\nexport function isInViewPort(target) {\n    return _guardedQueryAll(target, { viewPort: true }).length > 0;\n}\n\n/**\n * Returns whether an element is scrollable.\n *\n * @param {Target} target\n * @param {ScrollAxis} [axis]\n * @returns {boolean}\n */\nexport function isScrollable(target, axis) {\n    return _guardedQueryAll(target, { scrollable: axis }).length > 0;\n}\n\n/**\n * Checks whether a target is visible, meaning that it is \"displayed\" (see {@link isDisplayed}),\n * has a non-zero width and height, and is not hidden by \"opacity\" or \"visibility\"\n * CSS properties.\n *\n * Note that it does not account for:\n *  - the position of the target in the viewport (e.g. negative x/y coordinates)\n *  - the color of the target (e.g. transparent text with no background).\n *\n * @param {Target} target\n * @returns {boolean}\n */\nexport function isVisible(target) {\n    return _guardedQueryAll(target, { visible: true }).length > 0;\n}\n\n/**\n * Equivalent to the native `node.matches(selector)`, with a few differences:\n * - it can take any {@link Target} (strings, nodes and iterable of nodes);\n * - it supports custom pseudo-classes, such as \":contains\" or \":visible\".\n *\n * @param {Target} target\n * @param {string} selector\n * @returns {boolean}\n * @example\n *  matches(\"input[name=surname]\", \":value(John)\");\n * @example\n *  matches(buttonEl, \":contains(Submit)\");\n */\nexport function matches(target, selector) {\n    return elementsMatch(_guardedQueryAll(target), selector);\n}\n\n/**\n * Listens for DOM mutations on a given target.\n *\n * This helper has 2 main advantages over directly calling the native MutationObserver:\n * - it ensures a single observer is created for a given target, even if multiple\n *  callbacks are registered;\n * - it keeps track of these observers, which allows to check whether an observer\n *  is still running while it should not, and to disconnect all running observers\n *  at once.\n *\n * @param {HTMLElement} target\n * @param {MutationCallback} callback\n */\nexport function observe(target, callback) {\n    if (observers.has(target)) {\n        observers.get(target).callbacks.add(callback);\n    } else {\n        const callbacks = new Set([callback]);\n        const observer = new MutationObserver((mutations, observer) => {\n            for (const callback of callbacks) {\n                callback(mutations, observer);\n            }\n        });\n        observer.observe(target, {\n            attributes: true,\n            characterData: true,\n            childList: true,\n            subtree: true,\n        });\n        observers.set(target, { callbacks, observer });\n    }\n\n    return function disconnect() {\n        if (!observers.has(target)) {\n            return;\n        }\n        const { callbacks, observer } = observers.get(target);\n        callbacks.delete(callback);\n        if (!callbacks.size) {\n            observer.disconnect();\n            observers.delete(target);\n        }\n    };\n}\n\n/**\n * Returns a list of nodes matching the given {@link Target}.\n * This function can either be used as a **template literal tag** (only supports\n * string selector without options) or invoked the usual way.\n *\n * The target can be:\n * - a {@link Node} (or an iterable of nodes), or {@link Window} object;\n * - a {@link Document} object (which will be converted to its body);\n * - a string representing a *custom selector* (which will be queried in the `root` option);\n *\n * This function allows all string selectors supported by the native {@link Element.querySelector}\n * along with some additional custom pseudo-classes:\n *\n * - `:contains(text)`: matches nodes whose *text content* includes the given *text*.\n *      * The match is **partial** and **case-insensitive**;\n *      * Given *text* also supports regular expressions (e.g. `:contains(/^foo.+/)`).\n * - `:count`: return nodes if their count match the given *count*.\n *      If not matching, an error is thrown;\n * - `:displayed`: matches nodes that are \"displayed\" (see {@link isDisplayed});\n * - `:empty`: matches nodes that have an empty *content* (**value** or **inner text**);\n * - `:eq(n)`: matches the *nth* node (0-based index);\n * - `:first`: matches the first node matching the selector (regardless of its actual\n *  DOM siblings);\n * - `:focusable`: matches nodes that can be focused (see {@link isFocusable});\n * - `:hidden`: matches nodes that are **not** \"visible\" (see {@link isVisible});\n * - `:interactive`: matches nodes that are not affected by 'pointer-events: none'\n * - `:iframe`: matches nodes that are `<iframe>` elements, and returns their `body`\n *  if it is ready;\n * - `:last`: matches the last node matching the selector (regardless of its actual\n *  DOM siblings);\n * - `:selected`: matches nodes that are selected (e.g. `<option>` elements);\n * - `:shadow`: matches nodes that have shadow roots, and returns their shadow root;\n * - `:scrollable(axis)`: matches nodes that are scrollable (see {@link isScrollable});\n * - `:text(text)`: matches nodes whose *content* is strictly equal to the given *text*;\n *      * The match is **exact**, and **case-insensitive**;\n *      * Given *text* also supports regular expressions (e.g. `:text(/^foo.+/)`).\n * - `:value(value)`: matches nodes whose *value* is strictly equal to the given *value*;\n *      * The match is **partial**, and **case-insensitive**;\n *      * Given *value* also supports regular expressions (e.g. `:value(/^foo.+/)`).\n * - `:viewPort`: matches nodes that are contained in the current view port (see\n *  {@link isInViewPort});\n * - `:visible`: matches nodes that are \"visible\" (see {@link isVisible});\n *\n * An `options` object can be specified to filter[1] the results:\n * - `root`: the root node to query the selector in (defaults to the current fixture);\n * - any of the *custom pseudo-classes* can be given as an option, with the value\n *  being a boolean for standalone pseudo-classes (e.g. `{ empty: true }`), or a\n *  string for the others (e.g. `{ contains: \"text\" }`).\n *\n * [1] these filters (except for `count` and `root`) achieve the same result as\n *  using their homonym pseudo-classes on the final group of the given selector\n *  string (e.g. ```queryAll`ul > li:visible`;``` = ```queryAll(\"ul > li\", { visible: true })```).\n *\n * @param {Target} target\n * @param {QueryOptions} [options]\n * @returns {Element[]}\n * @example\n *  // regular selectors\n *  queryAll`window`; // -> []\n *  queryAll`input#name`; // -> [input]\n *  queryAll`div`; // -> [div, div, ...]\n *  queryAll`ul > li`; // -> [li, li, ...]\n * @example\n *  // custom selectors\n *  queryAll`div:visible:contains(Lorem ipsum)`; // -> [div, div, ...]\n *  queryAll`div:visible:contains(${/^L\\w+\\si.*m$/})`; // -> [div, div, ...]\n *  queryAll`:focusable`; // -> [a, button, input, ...]\n *  queryAll`.o_iframe:iframe p`; // -> [p, p, ...] (inside iframe)\n *  queryAll`#editor:shadow div`; // -> [div, div, ...] (inside shadow DOM)\n * @example\n *  // with options\n *  queryAll(`div:first`, { count: 1 }); // -> [div]\n *  queryAll(`div`, { root: queryOne`iframe` }); // -> [div, div, ...]\n *  // the next 2 queries will return the same results\n *  queryAll(`button:visible`); // -> [button, button, ...]\n *  queryAll(`button`, { visible: true }); // -> [button, button, ...]\n */\nexport function queryAll(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return _guardedQueryAll(target, options);\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments and returns a list of the\n * *attribute values* of the matching nodes.\n *\n * @param {Target} target\n * @param {string} attribute\n * @param {QueryOptions} [options]\n * @returns {string[]}\n */\nexport function queryAllAttributes(target, attribute, options) {\n    return _guardedQueryAll(target, options).map((node) => getNodeAttribute(node, attribute));\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments and returns a list of the\n * *properties* of the matching nodes.\n *\n * @param {Target} target\n * @param {string} property\n * @param {QueryOptions} [options]\n * @returns {any[]}\n */\nexport function queryAllProperties(target, property, options) {\n    return _guardedQueryAll(target, options).map((node) => node[property]);\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments and returns a list of the\n * {@link DOMRect} of the matching nodes.\n *\n * There are a few differences with the native {@link Element.getBoundingClientRect}:\n * - rects take their positions relative to the top window element (instead of their\n *  parent `<iframe>` if any);\n * - they can be trimmed to remove padding with the `trimPadding` option.\n *\n * @param {Target} target\n * @param {QueryOptions & QueryRectOptions} [options]\n * @returns {DOMRect[]}\n */\nexport function queryAllRects(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return _guardedQueryAll(target, options).map(getNodeRect);\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments and returns a list of the\n * *texts* of the matching nodes.\n *\n * @param {Target} target\n * @param {QueryOptions & QueryTextOptions} [options]\n * @returns {string[]}\n */\nexport function queryAllTexts(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return _guardedQueryAll(target, options).map((node) => getNodeText(node, options));\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments and returns a list of the\n * *values* of the matching nodes.\n *\n * @param {Target} target\n * @param {QueryOptions} [options]\n * @returns {NodeValue[]}\n */\nexport function queryAllValues(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return _guardedQueryAll(target, options).map(getNodeValue);\n}\n\n/**\n * Performs a {@link queryOne} with the given arguments, with a default 'first'\n * option, to ensure that *at least* one element is returned.\n *\n * 'first' can be overridden by 'last' or 'eq' if needed.\n *\n * @param {Target} target\n * @param {QueryOptions} [options]\n * @returns {Node}\n */\nexport function queryAny(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return _queryOne(target, ensureCount(options));\n}\n\n/**\n * Performs a {@link queryOne} with the given arguments and returns the value of\n * the given *attribute* of the matching node.\n *\n * @param {Target} target\n * @param {string} attribute\n * @param {QueryOptions} [options]\n * @returns {string | null}\n */\nexport function queryAttribute(target, attribute, options) {\n    return getNodeAttribute(_queryOne(target, options), attribute);\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments and returns the first result\n * or `null`.\n *\n * @param {Target} target\n * @param {QueryOptions} options\n * @returns {Element | null}\n */\nexport function queryFirst(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return _guardedQueryAll(target, options)[0] || null;\n}\n\n/**\n * Performs a {@link queryAll} with the given arguments, along with a forced `count: 1`\n * option to ensure only one node matches the given {@link Target}.\n *\n * The returned value is a single node instead of a list of nodes.\n *\n * @param {Target} target\n * @param {Omit<QueryOptions, \"count\">} [options]\n * @returns {Element}\n */\nexport function queryOne(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    if ($isInteger(options?.count)) {\n        throw new HootDomError(\n            `cannot call \\`queryOne\\` with 'count'=${options.count}: did you mean to use \\`queryAll\\`?`\n        );\n    }\n    return _queryOne(target, options);\n}\n\n/**\n * Performs a {@link queryOne} with the given arguments and returns the {@link DOMRect}\n * of the matching node.\n *\n * There are a few differences with the native {@link Element.getBoundingClientRect}:\n * - rects take their positions relative to the top window element (instead of their\n *  parent `<iframe>` if any);\n * - they can be trimmed to remove padding with the `trimPadding` option.\n *\n * @param {Target} target\n * @param {QueryOptions & QueryRectOptions} [options]\n * @returns {DOMRect}\n */\nexport function queryRect(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return getNodeRect(_queryOne(target, options), options);\n}\n\n/**\n * Performs a {@link queryOne} with the given arguments and returns the *text* of\n * the matching node.\n *\n * @param {Target} target\n * @param {QueryOptions & QueryTextOptions} [options]\n * @returns {string}\n */\nexport function queryText(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return getNodeText(_queryOne(target, options), options);\n}\n\n/**\n * Performs a {@link queryOne} with the given arguments and returns the *value* of\n * the matching node.\n *\n * @param {Target} target\n * @param {QueryOptions} [options]\n * @returns {NodeValue}\n */\nexport function queryValue(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return getNodeValue(_queryOne(target, options));\n}\n\n/**\n * Combination of {@link queryAll} and {@link waitUntil}: waits for a given target\n * to match elements in the DOM and returns the first matching node when it appears\n * (or immediately if it is already present).\n *\n * @see {@link queryAll}\n * @see {@link waitUntil}\n * @param {Target} target\n * @param {QueryOptions & WaitOptions} [options]\n * @returns {Promise<Element>}\n * @example\n *  const button = await waitFor(`button`);\n *  button.click();\n */\nexport function waitFor(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return waitUntil(_waitForFirst.bind(null, target, options), {\n        message: getWaitForMessage,\n        ...options,\n    });\n}\n\n/**\n * Opposite of {@link waitFor}: waits for a given target to disappear from the DOM\n * (resolves instantly if the selector is already missing).\n *\n * @see {@link waitFor}\n * @param {Target} target\n * @param {QueryOptions & WaitOptions} [options]\n * @returns {Promise<number>}\n * @example\n *  await waitForNone(`button`);\n */\nexport function waitForNone(target, options) {\n    [target, options] = parseRawArgs(arguments);\n    return waitUntil(_waitForNone.bind(null, target, options), {\n        message: getWaitForNoneMessage,\n        ...options,\n    });\n}\n", "/** @odoo-module */\n\nimport { getColorHex, getTag, isFirefox, isInstanceOf, isIterable } from \"../hoot_dom_utils\";\nimport {\n    getActiveElement,\n    getDocument,\n    getInteractiveNode,\n    getNextFocusableElement,\n    getNodeRect,\n    getNodeValue,\n    getParentFrame,\n    getPreviousFocusableElement,\n    getStyle,\n    getWindow,\n    isCheckable,\n    isEditable,\n    isEventTarget,\n    isNode,\n    isNodeFocusable,\n    parseDimensions,\n    parsePosition,\n    queryAll,\n    queryAny,\n    setDimensions,\n    toSelector,\n} from \"./dom\";\nimport { microTick } from \"./time\";\n\n/**\n * @typedef {Target | Promise<Target>} AsyncTarget\n *\n * @typedef {\"auto\" | \"blur\" | \"enter\" | \"tab\" | false} ConfirmAction\n *\n * @typedef {{\n *  dataTransfer?: DataTransfer;\n *  dropEffect: \"none\" | \"copy\" | \"link\" | \"move\";\n *  effectAllowed?: \"none\" | \"copy\" | \"copyLink\" | \"copyMove\" | \"link\" | \"linkMove\" | \"move\" | \"all\" | \"uninitialized\";\n *  files?: File[];\n *  items?: [string, string][];\n * }} DataTransferOptions\n *\n * @typedef {{\n *  cancel: (options?: EventOptions) => Promise<EventList>;\n *  drop: (to?: AsyncTarget | DragOptions, options?: DragOptions) => Promise<EventList>;\n *  moveTo: (to?: AsyncTarget | DragOptions, options?: DragOptions) => Promise<DragHelpers>;\n * }} DragHelpers\n *\n * @typedef {PointerOptions & DataTransferOptions} DragOptions\n *\n * @typedef {import(\"./dom\").Position} Position\n *\n * @typedef {import(\"./dom\").Dimensions} Dimensions\n *\n * @typedef {((ev: Event) => boolean) | EventType} EventListPredicate\n *\n * @typedef {{\n *  eventInit?: EventInit;\n * }} EventOptions generic event options\n *\n * @typedef {{\n *  clientX: number;\n *  clientY: number;\n *  pageX: number;\n *  pageY: number;\n *  screenX: number;\n *  screenY: number;\n * }} EventPosition\n *\n * @typedef {keyof HTMLElementEventMap | keyof WindowEventMap} EventType\n *\n * @typedef {EventOptions & {\n *  confirm?: ConfirmAction;\n *  composition?: boolean;\n *  instantly?: boolean;\n * }} FillOptions\n *\n * @typedef {string | number | MaybeIterable<File>} InputValue\n *\n * @typedef {EventOptions & KeyboardEventInit} KeyboardOptions\n *\n * @typedef {string | string[]} KeyStrokes\n *\n * @typedef {EventOptions & QueryOptions & {\n *  button?: number,\n *  position?: Side | `${Side}-${Side}` | Position;\n *  relative?: boolean;\n * }} PointerOptions\n *\n * @typedef {import(\"./dom\").QueryOptions} QueryOptions\n *\n * @typedef {EventOptions & QueryOptions & {\n *  force?: boolean;\n *  initiator?: \"keyboard\" | \"scrollbar\" | \"wheel\" | null;\n *  relative?: boolean;\n * }} ScrollOptions\n *\n * @typedef {EventOptions & {\n *  target: AsyncTarget;\n * }} SelectOptions\n *\n * @typedef {\"bottom\" | \"left\" | \"right\" | \"top\"} Side\n */\n\n/**\n * @template [T=EventInit]\n * @typedef {T & {\n *  target: EventTarget;\n *  type: EventType;\n * }} FullEventInit\n */\n\n/**\n * @template T\n * @typedef {T | Iterable<T>} MaybeIterable\n */\n\n/**\n * @template [T=Node]\n * @typedef {import(\"./dom\").Target<T>} Target\n */\n\n//-----------------------------------------------------------------------------\n// Global\n//-----------------------------------------------------------------------------\n\nconst {\n    AnimationEvent,\n    ClipboardEvent,\n    CompositionEvent,\n    console: { dir: $dir, groupCollapsed: $groupCollapsed, groupEnd: $groupEnd, log: $log },\n    DataTransfer,\n    document,\n    DragEvent,\n    Error,\n    ErrorEvent,\n    Event,\n    File,\n    FocusEvent,\n    HashChangeEvent,\n    KeyboardEvent,\n    Math: { ceil: $ceil, max: $max, min: $min },\n    MouseEvent,\n    Number: { isInteger: $isInteger, isNaN: $isNaN, parseFloat: $parseFloat },\n    Object: {\n        assign: $assign,\n        create: $create,\n        defineProperties: $defineProperties,\n        values: $values,\n    },\n    PointerEvent,\n    PromiseRejectionEvent,\n    String,\n    SubmitEvent,\n    Touch,\n    TouchEvent,\n    TypeError,\n    WheelEvent,\n} = globalThis;\n/** @type {Document[\"createRange\"]} */\nconst $createRange = document.createRange.bind(document);\nconst $toString = Object.prototype.toString;\n\n//-----------------------------------------------------------------------------\n// Internal\n//-----------------------------------------------------------------------------\n\n/**\n * @param {Event} ev\n */\nfunction cancelTrustedEvent(ev) {\n    if (ev.isTrusted && runTime.eventsToIgnore.includes(ev.type)) {\n        runTime.eventsToIgnore.splice(runTime.eventsToIgnore.indexOf(ev.type), 1);\n        ev.stopPropagation();\n        ev.stopImmediatePropagation();\n        ev.preventDefault();\n    }\n}\n\n/**\n * @param {HTMLElement} target\n * @param {number} start\n * @param {number} end\n */\nasync function changeSelection(target, start, end) {\n    if (!isNil(start) && !isNil(target.selectionStart)) {\n        target.selectionStart = start;\n    }\n    if (!isNil(end) && !isNil(target.selectionEnd)) {\n        target.selectionEnd = end;\n    }\n}\n\n/**\n * @param {HTMLElement} target\n * @param {number} x\n */\nfunction constrainScrollX(target, x) {\n    let { offsetWidth, scrollWidth } = target;\n    const document = getDocument(target);\n    if (target === document || target === document.documentElement) {\n        // <html> elements in iframes consider the width of the <iframe> element\n        const iframe = getParentFrame(target);\n        if (iframe) {\n            ({ offsetWidth } = iframe);\n        }\n    }\n    const maxScrollLeft = scrollWidth - offsetWidth;\n    const { direction } = getStyle(target);\n    const [min, max] = direction === \"rtl\" ? [-maxScrollLeft, 0] : [0, maxScrollLeft];\n    return $min($max(x, min), max);\n}\n\n/**\n * @param {HTMLElement} target\n * @param {number} y\n */\nfunction constrainScrollY(target, y) {\n    let { offsetHeight, scrollHeight } = target;\n    const document = getDocument(target);\n    if (target === document || target === document.documentElement) {\n        // <html> elements in iframes consider the height of the <iframe> element\n        const iframe = getParentFrame(target);\n        if (iframe) {\n            ({ offsetHeight } = iframe);\n        }\n    }\n    return $min($max(y, 0), scrollHeight - offsetHeight);\n}\n\n/**\n * @param {DataTransferOptions} options\n */\nfunction createDataTransfer(options) {\n    const dataTransfer = isInstanceOf(options?.dataTransfer, DataTransfer)\n        ? options.dataTransfer\n        : new DataTransfer();\n    for (const file of options?.files || []) {\n        if (!isInstanceOf(file, File)) {\n            throw new TypeError(`'DataTransfer.files' list only accepts 'File' objects`);\n        }\n        dataTransfer.items.add(file);\n    }\n    for (const [data, type] of options?.items || []) {\n        dataTransfer.items.add(data, type);\n    }\n\n    $defineProperties(dataTransfer, {\n        dropEffect: { value: options?.dropEffect || \"none\", writable: true },\n        effectAllowed: { value: options?.effectAllowed || \"all\", writable: true },\n    });\n\n    return dataTransfer;\n}\n\n/**\n * @param {HTMLInputElement | HTMLTextAreaElement} target\n */\nfunction deleteSelection(target) {\n    const { selectionStart, selectionEnd, value } = target;\n    return value.slice(0, selectionStart) + value.slice(selectionEnd);\n}\n\n/**\n * @template {EventTarget} T\n * @param {{\n *  target: T;\n *  events: EventType[];\n *  additionalEvents?: EventType[];\n *  callback?: (target: T) => any;\n *  options?: EventInit;\n * }} params\n */\nasync function dispatchAndIgnore({ target, events, additionalEvents = [], callback, options }) {\n    for (const eventType of [...events, ...additionalEvents]) {\n        runTime.eventsToIgnore.push(eventType);\n    }\n    if (callback) {\n        callback(target);\n    }\n    for (const eventType of events) {\n        await _dispatch(target, eventType, options);\n    }\n}\n\n/**\n *\n * @param {EventTarget} target\n * @param {EventType} eventType\n * @param {PointerEventInit} eventInit\n * @param {{\n *  mouse?: [EventType, MouseEventInit];\n *  touch?: [EventType, TouchEventInit];\n * }} additionalEvents\n */\nasync function dispatchPointerEvent(target, eventType, eventInit, { mouse, touch }) {\n    const pointerEvent = await _dispatch(target, eventType, eventInit);\n    let prevented = isPrevented(pointerEvent);\n    if (hasTouch()) {\n        if (touch && runTime.pointerDownTarget) {\n            const [touchEventType, touchEventInit] = touch;\n            await _dispatch(runTime.pointerDownTarget, touchEventType, touchEventInit || eventInit);\n        }\n    } else {\n        if (mouse && !prevented) {\n            const [mouseEventType, mouseEventInit] = mouse;\n            const mouseEvent = await _dispatch(target, mouseEventType, mouseEventInit || eventInit);\n            prevented = isPrevented(mouseEvent);\n        }\n    }\n    return prevented;\n}\n\n/**\n * @param {Iterable<Event>} events\n * @param {EventType} eventType\n * @param {EventInit} eventInit\n */\nasync function dispatchRelatedEvents(events, eventType, eventInit) {\n    for (const event of events) {\n        if (!event.target || isPrevented(event)) {\n            break;\n        }\n        await _dispatch(event.target, eventType, eventInit);\n    }\n}\n\n/**\n * @template T\n * @param {MaybeIterable<T>} value\n * @returns {T[]}\n */\nfunction ensureArray(value) {\n    return isIterable(value) ? [...value] : [value];\n}\n\nfunction getCurrentEvents() {\n    const eventType = currentEventTypes.at(-1);\n    if (!eventType) {\n        return [];\n    }\n    currentEvents[eventType] ||= [];\n    return currentEvents[eventType];\n}\n\nfunction getDefaultRunTimeValue() {\n    return {\n        isComposing: false,\n\n        // Data transfers\n        /** @type {DataTransfer | null} */\n        clipboardData: null,\n        /** @type {DataTransfer | null} */\n        dataTransfer: null,\n\n        // Drag & drop\n        canStartDrag: false,\n        isDragging: false,\n        lastDragOverCancelled: false,\n\n        // Pointer\n        clickCount: 0,\n        key: null,\n        /** @type {HTMLElement | null} */\n        pointerDownTarget: null,\n        pointerDownTimeout: 0,\n        /** @type {HTMLElement | null} */\n        pointerTarget: null,\n        /** @type {EventPosition | {}} */\n        position: {},\n        /** @type {HTMLElement | null} */\n        previousPointerDownTarget: null,\n        /** @type {EventPosition | {}} */\n        touchStartPosition: {},\n\n        // File\n        fileInput: null,\n\n        // Buttons\n        buttons: 0,\n\n        // Modifier keys\n        modifierKeys: {},\n\n        /**\n         * Ignored events (\"select\" by default since it is sometimes dispatched by\n         * focusing an input).\n         * @type {EventType[]}\n         */\n        eventsToIgnore: [],\n    };\n}\n\n/**\n * Returns the list of nodes containing n2 (included) that do not contain n1.\n *\n * @param {Element} [el1]\n * @param {Element} [el2]\n */\nfunction getDifferentParents(el1, el2) {\n    if (!el1 && !el2) {\n        // No given elements => no parents\n        return [];\n    } else if (!el1 && el2) {\n        // No first element => only parents of second element\n        [el1, el2] = [el2, el1];\n    }\n    const parents = [el2 || el1];\n    while (parents[0].parentElement) {\n        const parent = parents[0].parentElement;\n        if (el2 && parent.contains(el1)) {\n            break;\n        }\n        parents.unshift(parent);\n    }\n    return parents;\n}\n\n/**\n * @template {typeof Event} T\n * @param {EventType} eventType\n * @returns {[T, ((attrs: FullEventInit) => EventInit), number]}\n */\nfunction getEventConstructor(eventType) {\n    switch (eventType) {\n        // Mouse events\n        case \"dblclick\":\n        case \"mousedown\":\n        case \"mouseup\":\n        case \"mousemove\":\n        case \"mouseover\":\n        case \"mouseout\":\n            return [MouseEvent, mapMouseEvent, BUBBLES | CANCELABLE | VIEW];\n        case \"mouseenter\":\n        case \"mouseleave\":\n            return [MouseEvent, mapMouseEvent, VIEW];\n\n        // Pointer events\n        case \"auxclick\":\n        case \"click\":\n        case \"contextmenu\":\n        case \"pointerdown\":\n        case \"pointerup\":\n        case \"pointermove\":\n        case \"pointerover\":\n        case \"pointerout\":\n            return [PointerEvent, mapPointerEvent, BUBBLES | CANCELABLE | VIEW];\n        case \"pointerenter\":\n        case \"pointerleave\":\n        case \"pointercancel\":\n            return [PointerEvent, mapPointerEvent, VIEW];\n\n        // Focus events\n        case \"blur\":\n        case \"focus\":\n            return [FocusEvent, mapEvent];\n        case \"focusin\":\n        case \"focusout\":\n            return [FocusEvent, mapEvent, BUBBLES];\n\n        // Clipboard events\n        case \"cut\":\n        case \"copy\":\n        case \"paste\":\n            return [ClipboardEvent, mapEvent, BUBBLES];\n\n        // Keyboard events\n        case \"keydown\":\n        case \"keyup\":\n            return [KeyboardEvent, mapKeyboardEvent, BUBBLES | CANCELABLE | VIEW];\n\n        // Drag events\n        case \"drag\":\n        case \"dragend\":\n        case \"dragenter\":\n        case \"dragstart\":\n        case \"dragleave\":\n        case \"dragover\":\n        case \"drop\":\n            return [DragEvent, mapEvent, BUBBLES | CANCELABLE];\n\n        // Input events\n        case \"beforeinput\":\n            return [InputEvent, mapInputEvent, BUBBLES | CANCELABLE | VIEW];\n        case \"input\":\n            return [InputEvent, mapInputEvent, BUBBLES | VIEW];\n\n        // Composition events\n        case \"compositionstart\":\n        case \"compositionend\":\n            return [CompositionEvent, mapEvent, BUBBLES];\n\n        // Selection events\n        case \"select\":\n        case \"selectionchange\":\n            return [Event, mapEvent, BUBBLES];\n\n        // Touch events\n        case \"touchstart\":\n        case \"touchend\":\n        case \"touchmove\":\n            return [TouchEvent, mapTouchEvent, BUBBLES | CANCELABLE | VIEW];\n        case \"touchcancel\":\n            return [TouchEvent, mapTouchEvent, BUBBLES | VIEW];\n\n        // Resize events\n        case \"resize\":\n            return [Event, mapEvent];\n\n        // Submit events\n        case \"submit\":\n            return [SubmitEvent, mapEvent, BUBBLES | CANCELABLE];\n\n        // Wheel events\n        case \"wheel\":\n            return [WheelEvent, mapWheelEvent, BUBBLES | VIEW];\n\n        // Animation events\n        case \"animationcancel\":\n        case \"animationend\":\n        case \"animationiteration\":\n        case \"animationstart\": {\n            return [AnimationEvent, mapEvent, BUBBLES | CANCELABLE];\n        }\n\n        // Error events\n        case \"error\":\n            return [ErrorEvent, mapEvent];\n        case \"unhandledrejection\":\n            return [PromiseRejectionEvent, mapEvent, CANCELABLE];\n\n        // Unload events (BeforeUnloadEvent cannot be constructed)\n        case \"beforeunload\":\n            return [Event, mapEvent, CANCELABLE];\n        case \"unload\":\n            return [Event, mapEvent];\n\n        // URL events\n        case \"hashchange\":\n            return [HashChangeEvent, mapEvent];\n\n        // Default: base Event constructor\n        default:\n            return [Event, mapEvent, BUBBLES];\n    }\n}\n\n/**\n * @param {Node} [a]\n * @param {Node} [b]\n */\nfunction getFirstCommonParent(a, b) {\n    if (!a || !b || a.ownerDocument !== b.ownerDocument) {\n        return null;\n    }\n\n    const range = document.createRange();\n    range.setStart(a, 0);\n    range.setEnd(b, 0);\n\n    if (range.collapsed) {\n        // Re-arranges range if the first node comes after the second\n        range.setStart(b, 0);\n        range.setEnd(a, 0);\n    }\n\n    return range.commonAncestorContainer;\n}\n\n/**\n * Returns the interactive pointer target from a given element, unless the element\n * is falsy, or the 'interactive' option is set to `false`.\n *\n * If an 'originalTarget' is given, the helper will deliberately throw an error if\n * no interactive elements are found.\n *\n * @param {HTMLElement} element\n * @param {QueryOptions} options\n * @param {AsyncTarget} [originalTarget]\n */\nfunction getPointerTarget(element, options, originalTarget) {\n    if (!element || options?.interactive === false) {\n        return element;\n    }\n    const interactiveElement = getInteractiveNode(element);\n    if (!interactiveElement && originalTarget) {\n        queryAny(originalTarget, { ...options, interactive: true }); // Will throw if no elements are found\n    }\n    return interactiveElement;\n}\n\n/**\n * @param {HTMLElement} element\n * @param {PointerOptions} [options]\n */\nfunction getPosition(element, options) {\n    const { position, relative } = options || {};\n    const isString = typeof position === \"string\";\n    const [posX, posY] = parsePosition(position);\n\n    if (!isString && !relative && !$isNaN(posX) && !$isNaN(posY)) {\n        // Absolute position\n        return toEventPosition(posX, posY, position);\n    }\n\n    const { x, y, width, height } = getNodeRect(element);\n    let clientX = x;\n    let clientY = y;\n\n    if (isString) {\n        const positions = position.split(\"-\");\n\n        // X position\n        if (positions.includes(\"left\")) {\n            clientX -= 1;\n        } else if (positions.includes(\"right\")) {\n            clientX += $ceil(width) + 1;\n        } else {\n            clientX += width / 2;\n        }\n\n        // Y position\n        if (positions.includes(\"top\")) {\n            clientY -= 1;\n        } else if (positions.includes(\"bottom\")) {\n            clientY += $ceil(height) + 1;\n        } else {\n            clientY += height / 2;\n        }\n    } else {\n        // X position\n        if ($isNaN(posX)) {\n            clientX += width / 2;\n        } else {\n            if (relative) {\n                clientX += posX || 0;\n            } else {\n                clientX = posX || 0;\n            }\n        }\n\n        // Y position\n        if ($isNaN(posY)) {\n            clientY += height / 2;\n        } else {\n            if (relative) {\n                clientY += posY || 0;\n            } else {\n                clientY = posY || 0;\n            }\n        }\n    }\n\n    return toEventPosition(clientX, clientY, position);\n}\n\n/**\n * @param {HTMLInputElement | HTMLTextAreaElement} target\n */\nfunction getStringSelection(target) {\n    return (\n        $isInteger(target.selectionStart) &&\n        $isInteger(target.selectionEnd) &&\n        [target.selectionStart, target.selectionEnd].join(\",\")\n    );\n}\n\n/**\n * @param {Node} node\n * @param {...string} tagNames\n */\nfunction hasTagName(node, ...tagNames) {\n    return tagNames.includes(getTag(node));\n}\n\nfunction hasTouch() {\n    return (\n        globalThis.ontouchstart !== undefined || globalThis.matchMedia(\"(pointer:coarse)\").matches\n    );\n}\n\n/**\n * @param {Position | null} position\n */\nfunction isDifferentPosition(position) {\n    if (!runTime.position || !position) {\n        return runTime.position !== position;\n    }\n    for (const key in position) {\n        if (runTime.position[key] !== position[key]) {\n            return true;\n        }\n    }\n    return false;\n}\n\n/**\n * @param {unknown} object\n */\nfunction isDictionary(object) {\n    return $toString.call(object) === \"[object Object]\";\n}\n\n/**\n * @param {unknown} value\n */\nfunction isNil(value) {\n    return value === null || value === undefined;\n}\n\n/**\n * @param {Event} event\n */\nfunction isPrevented(event) {\n    return event && event.defaultPrevented;\n}\n\n/**\n * @param {KeyStrokes} keyStrokes\n * @param {KeyboardEventInit} [options]\n * @returns {KeyboardEventInit}\n */\nfunction parseKeyStrokes(keyStrokes, options) {\n    return (isIterable(keyStrokes) ? [...keyStrokes] : [keyStrokes]).map((key) => {\n        const lower = key.toLowerCase();\n        return {\n            ...options,\n            key: lower.length === 1 ? key : KEY_ALIASES[lower] || key,\n        };\n    });\n}\n\n/**\n * Redirects all 'submit' events to explicit network requests.\n *\n * This allows the `mockFetch` helper to take control over submit requests.\n *\n * @param {SubmitEvent} ev\n */\nfunction redirectSubmit(ev) {\n    if (isPrevented(ev)) {\n        return;\n    }\n\n    ev.preventDefault();\n\n    /** @type {HTMLFormElement} */\n    const form = ev.target;\n\n    globalThis.fetch(form.action, {\n        method: form.method,\n        body: new FormData(form, ev.submitter),\n    });\n}\n\n/**\n * @param {PointerEventInit} eventInit\n * @param {boolean} toggle\n */\nfunction registerButton(eventInit, toggle) {\n    let value = 0;\n    switch (eventInit.button) {\n        case btn.LEFT: {\n            // Main button (left button)\n            value = 1;\n            break;\n        }\n        case btn.MIDDLE: {\n            // Auxiliary button (middle button)\n            value = 4;\n            break;\n        }\n        case btn.RIGHT: {\n            // Secondary button (right button)\n            value = 2;\n            break;\n        }\n        case btn.BACK: {\n            // Fourth button (Browser Back)\n            value = 8;\n            break;\n        }\n        case btn.FORWARD: {\n            // Fifth button (Browser Forward)\n            value = 16;\n            break;\n        }\n    }\n\n    runTime.buttons = $max(runTime.buttons + (toggle ? value : -value), 0);\n}\n\n/**\n * @param {Event} ev\n */\nfunction registerFileInput({ target }) {\n    const actualTarget = target.shadowRoot ? target.shadowRoot.activeElement : target;\n    if (getTag(actualTarget) === \"input\" && actualTarget.type === \"file\") {\n        runTime.fileInput = actualTarget;\n    } else {\n        runTime.fileInput = null;\n    }\n}\n\n/**\n * @param {EventTarget} target\n * @param {string} initialValue\n * @param {ConfirmAction} confirmAction\n */\nasync function registerForChange(target, initialValue, confirmAction) {\n    function dispatchChange() {\n        return target.value !== initialValue && _dispatch(target, \"change\");\n    }\n\n    confirmAction &&= confirmAction.toLowerCase();\n    if (confirmAction === \"auto\") {\n        confirmAction = getTag(target) === \"input\" ? \"enter\" : \"blur\";\n    }\n    if (getTag(target) === \"input\") {\n        changeTargetListeners.push(\n            on(target, \"keydown\", (ev) => {\n                if (isPrevented(ev) || ev.key !== \"Enter\") {\n                    return;\n                }\n                removeChangeTargetListeners();\n                afterNextDispatch = dispatchChange;\n            })\n        );\n    } else if (confirmAction === \"enter\") {\n        throw new HootInteractionError(\n            `\"enter\" confirm action is only supported on <input/> elements`\n        );\n    }\n\n    changeTargetListeners.push(\n        on(target, \"blur\", () => {\n            removeChangeTargetListeners();\n            dispatchChange();\n        }),\n        on(target, \"change\", removeChangeTargetListeners)\n    );\n\n    switch (confirmAction) {\n        case \"blur\": {\n            await _hover(\n                getDocument(target).body,\n                { position: { x: 0, y: 0 } },\n                { originalTarget: target }\n            );\n            await _click();\n            break;\n        }\n        case \"enter\": {\n            await _press(target, { key: \"Enter\" });\n            break;\n        }\n        case \"tab\": {\n            await _press(target, { key: \"Tab\" });\n            break;\n        }\n    }\n}\n\n/**\n * @param {KeyboardEventInit} eventInit\n * @param {boolean} toggle\n */\nfunction registerSpecialKey(eventInit, toggle) {\n    switch (eventInit.key) {\n        case \"Alt\": {\n            runTime.modifierKeys.altKey = toggle;\n            break;\n        }\n        case \"Control\": {\n            runTime.modifierKeys.ctrlKey = toggle;\n            break;\n        }\n        case \"Meta\": {\n            runTime.modifierKeys.metaKey = toggle;\n            break;\n        }\n        case \"Shift\": {\n            runTime.modifierKeys.shiftKey = toggle;\n            break;\n        }\n    }\n}\n\nfunction removeChangeTargetListeners() {\n    while (changeTargetListeners.length) {\n        changeTargetListeners.pop()();\n    }\n}\n\n/**\n * @param {HTMLElement | null} target\n * @param {QueryOptions} [options]\n */\nfunction setPointerDownTarget(target, options) {\n    if (runTime.pointerDownTarget) {\n        runTime.previousPointerDownTarget = runTime.pointerDownTarget;\n    }\n    runTime.pointerDownTarget = getPointerTarget(target, options);\n    runTime.canStartDrag = false;\n}\n\n/**\n * @param {string} type\n * @param {EventOptions} type\n */\nfunction setupEvents(type, options) {\n    currentEventTypes.push(type);\n    $assign(currentEventInit, options?.eventInit);\n\n    return function finalizeEvents() {\n        for (const eventType in currentEventInit) {\n            delete currentEventInit[eventType];\n        }\n        const events = new EventList(getCurrentEvents());\n        const currentType = currentEventTypes.pop();\n        delete currentEvents[currentType];\n        if (!allowLogs) {\n            return events;\n        }\n        const groupName = [\n            `%c[${type}]%c dispatched`,\n            `color: ${getColorHex(\"purple\")}`,\n            \"\",\n            events.length,\n            `events`,\n        ];\n        $groupCollapsed(...groupName);\n        for (const event of events) {\n            /** @type {string[]} */\n            const colors = [getColorHex(\"text-report-html-tag\")];\n\n            const typeList = [event.type];\n            if (event.key) {\n                typeList.push(event.key);\n            } else if (event.button) {\n                typeList.push(event.button);\n            }\n            [...Array(typeList.length)].forEach(() =>\n                colors.push(getColorHex(\"text-report-string\"))\n            );\n\n            const typeString = typeList.map((t) => `%c\"${t}\"%c`).join(\", \");\n            let message = `%c${event.constructor.name}%c<${typeString}>`;\n            if (event.__bubbleCount) {\n                message += ` (${event.__bubbleCount})`;\n            }\n            const target = event.__originalTarget || event.target;\n            if (isNode(target)) {\n                const targetParts = toSelector(target, { object: true });\n                colors.push(getColorHex(\"text-report-html-tag\"));\n                if (targetParts.id) {\n                    colors.push(getColorHex(\"text-report-html-id\"));\n                }\n                if (targetParts.class) {\n                    colors.push(getColorHex(\"text-report-html-class\"));\n                }\n                const targetString = $values(targetParts)\n                    .map((part) => `%c${part}%c`)\n                    .join(\"\");\n                message += ` @${targetString}`;\n            }\n            const messageColors = colors.flatMap((color) => [\n                `color: ${color}; font-weight: normal`,\n                \"\",\n            ]);\n\n            $groupCollapsed(message, ...messageColors);\n            $dir(event);\n            $log(target);\n            $groupEnd();\n        }\n        $groupEnd();\n\n        return events;\n    };\n}\n\n/**\n * @param {number} clientX\n * @param {number} clientY\n * @param {Partial<EventPosition>} [position]\n */\nfunction toEventPosition(clientX, clientY, position) {\n    clientX ||= 0;\n    clientY ||= 0;\n    return {\n        clientX,\n        clientY,\n        pageX: position?.pageX ?? clientX,\n        pageY: position?.pageY ?? clientY,\n        screenX: position?.screenX ?? clientX,\n        screenY: position?.screenY ?? clientY,\n    };\n}\n\n/**\n * @param {EventTarget} target\n * @param {PointerEventInit} pointerInit\n */\nasync function triggerClick(target, pointerInit) {\n    if (target.disabled) {\n        return;\n    }\n    const eventType = (pointerInit.button ?? 0) === btn.LEFT ? \"click\" : \"auxclick\";\n    const clickEvent = await _dispatch(target, eventType, pointerInit);\n    if (isPrevented(clickEvent)) {\n        return;\n    }\n    if (isFirefox()) {\n        // Thanks Firefox\n        switch (getTag(target)) {\n            case \"label\": {\n                /**\n                 * @firefox\n                 * Special action: label 'Click'\n                 *  On: unprevented 'click' on a <label/>\n                 *  Do: triggers a 'click' event on the first <input/> descendant\n                 */\n                target = target.control;\n                if (target) {\n                    await triggerClick(target, pointerInit);\n                }\n                break;\n            }\n            case \"option\": {\n                /**\n                 * @firefox\n                 * Special action: option 'Click'\n                 *  On: unprevented 'click' on an <option/>\n                 *  Do: triggers a 'change' event on the parent <select/>\n                 */\n                const parent = target.parentElement;\n                if (parent && getTag(parent) === \"select\") {\n                    await _dispatch(parent, \"change\");\n                }\n                break;\n            }\n        }\n    }\n}\n\n/**\n * @param {EventTarget} target\n * @param {DragEventInit} eventInit\n */\nasync function triggerDrag(target, eventInit) {\n    await _dispatch(target, \"drag\", eventInit);\n    // Only \"dragover\" being prevented is taken into account for \"drop\" events\n    const dragOverEvent = await _dispatch(target, \"dragover\", eventInit);\n    return isPrevented(dragOverEvent);\n}\n\n/**\n * @param {EventTarget} target\n */\nasync function triggerFocus(target) {\n    const previous = getActiveElement(target);\n    if (previous === target) {\n        return;\n    }\n    if (previous !== target.ownerDocument.body) {\n        await dispatchAndIgnore({\n            target: previous,\n            events: [\"blur\", \"focusout\"],\n            callback: (el) => el.blur(),\n            options: { relatedTarget: target },\n        });\n    }\n    if (isNodeFocusable(target)) {\n        const previousSelection = getStringSelection(target);\n        await dispatchAndIgnore({\n            target,\n            events: [\"focus\", \"focusin\"],\n            additionalEvents: [\"select\"],\n            callback: (el) => el.focus(),\n            options: { relatedTarget: previous },\n        });\n        if (previousSelection && previousSelection === getStringSelection(target)) {\n            changeSelection(target, target.value.length, target.value.length);\n        }\n    }\n}\n\n/**\n * @param {EventTarget} target\n * @param {FillOptions} [options]\n */\nasync function _clear(target, options) {\n    // Inputs and text areas\n    const initialValue = target.value;\n\n    // Simulates 2 key presses:\n    // - Control + A: selects all the text\n    // - Backspace: deletes the text\n    fullClear = true;\n    await _press(target, { ctrlKey: true, key: \"a\" });\n    await _press(target, { key: \"Backspace\" });\n    fullClear = false;\n\n    await registerForChange(target, initialValue, options?.confirm);\n}\n\n/**\n * @param {PointerOptions} [options]\n */\nasync function _click(options) {\n    await _pointerDown(options);\n    await _pointerUp(options);\n}\n\n/**\n * @template {EventType} T\n * @template {HTMLBodyElementEventMap[T]} I\n * @param {EventTarget} target\n * @param {T} type\n * @param {Partial<I> | { eventInit: Record<T, Partial<I>> }} [eventInit]\n * @returns {Promise<I>}\n */\nasync function _dispatch(target, type, eventInit) {\n    eventInit = { ...eventInit, ...currentEventInit[type] };\n    for (const key in eventInit) {\n        if (key in DEPRECATED_EVENT_PROPERTIES) {\n            throw new HootInteractionError(\n                `cannot dispatch \"${type}\" event: property \"${key}\" is deprecated, use \"${DEPRECATED_EVENT_PROPERTIES[key]}\" instead`\n            );\n        }\n    }\n\n    const [Constructor, processParams, flags] = getEventConstructor(type);\n    const params = processParams({\n        composed: true,\n        ...eventInit,\n        target,\n        type,\n    });\n    if (flags & BUBBLES) {\n        params.bubbles = true;\n    }\n    if (flags & CANCELABLE) {\n        params.cancelable = true;\n    }\n    if (flags & VIEW) {\n        params.view ||= getWindow(target);\n    }\n    const event = new Constructor(type, params);\n\n    target.dispatchEvent(event);\n    await Promise.resolve();\n\n    getCurrentEvents().push(event);\n\n    if (afterNextDispatch) {\n        const callback = afterNextDispatch;\n        afterNextDispatch = null;\n        await microTick().then(callback);\n    }\n\n    return event;\n}\n\n/**\n * @param {EventTarget} target\n * @param {InputValue} value\n * @param {FillOptions} [options]\n */\nasync function _fill(target, value, options) {\n    const initialValue = target.value;\n\n    if (getTag(target) === \"input\") {\n        switch (target.type) {\n            case \"color\":\n            case \"time\": {\n                target.value = String(value);\n                await _dispatch(target, \"input\");\n                await _dispatch(target, \"change\");\n                return;\n            }\n            case \"file\": {\n                const files = ensureArray(value);\n                if (files.length > 1 && !target.multiple) {\n                    throw new HootInteractionError(\n                        `input[type=\"file\"] does not support multiple files`\n                    );\n                }\n                target.files = createDataTransfer({ files }).files;\n\n                await _dispatch(target, \"change\");\n                return;\n            }\n            case \"range\": {\n                const numberValue = $parseFloat(value);\n                if ($isNaN(numberValue)) {\n                    throw new TypeError(`input[type=\"range\"] only accept 'number' values`);\n                }\n\n                target.value = String(numberValue);\n                await _dispatch(target, \"input\");\n                await _dispatch(target, \"change\");\n                return;\n            }\n        }\n    }\n\n    if (options?.instantly) {\n        // Simulates filling the clipboard with the value (can be from external source)\n        globalThis.navigator.clipboard.writeText(value).catch();\n        await _press(target, { ctrlKey: true, key: \"v\" });\n    } else {\n        if (options?.composition) {\n            runTime.isComposing = true;\n            // Simulates the start of a composition\n            await _dispatch(target, \"compositionstart\");\n        }\n        for (const char of String(value)) {\n            const key = char.toLowerCase();\n            await _press(target, { key, shiftKey: key !== char });\n        }\n        if (options?.composition) {\n            runTime.isComposing = false;\n            // Simulates the end of a composition\n            await _dispatch(target, \"compositionend\");\n        }\n    }\n\n    await registerForChange(target, initialValue, options?.confirm);\n}\n\n/**\n * @param {EventTarget | null} target\n * @param {PointerOptions | null} options\n * @param {{ implicit?: boolean, originalTarget: AsyncTarget }} hoverOptions\n */\nasync function _hover(target, options, hoverOptions) {\n    const pointerTarget = getPointerTarget(target, options, hoverOptions.originalTarget);\n    const position = target && getPosition(target, options);\n\n    const previousPT = runTime.pointerTarget;\n    const previousPosition = runTime.position;\n\n    const isDifferentTarget = previousPT !== pointerTarget;\n\n    if (hoverOptions?.implicit && !isDifferentTarget && !isDifferentPosition(position)) {\n        // Implicit hover: do not perform hover if the pointer target is the same\n        // and the position didn't change.\n        return;\n    }\n    if (runTime.canStartDrag) {\n        /**\n         * Special action: drag start\n         *  On: unprevented 'pointerdown' on a draggable element (DESKTOP ONLY)\n         *  Do: triggers a 'dragstart' event\n         */\n        const dragStartEvent = await _dispatch(previousPT, \"dragstart\", {\n            dataTransfer: runTime.dataTransfer,\n        });\n\n        runTime.isDragging = !isPrevented(dragStartEvent);\n        runTime.canStartDrag = false;\n    }\n\n    runTime.pointerTarget = pointerTarget;\n    runTime.position = position;\n\n    if (\n        isDifferentTarget &&\n        previousPT &&\n        (!pointerTarget || !previousPT.contains(pointerTarget))\n    ) {\n        // Leaves previous target\n        const leaveEventInit = {\n            ...previousPosition,\n            relatedTarget: pointerTarget,\n            button: options?.button || 0,\n        };\n\n        if (runTime.isDragging) {\n            // If dragging, only drag events are triggered\n            const leaveEventInitWithDT = { ...leaveEventInit, dataTransfer: runTime.dataTransfer };\n            runTime.lastDragOverCancelled = await triggerDrag(previousPT, leaveEventInitWithDT);\n            await _dispatch(previousPT, \"dragleave\", leaveEventInitWithDT);\n        } else {\n            // Regular case: pointer events are triggered\n            await dispatchPointerEvent(previousPT, \"pointermove\", leaveEventInit, {\n                mouse: [\"mousemove\"],\n                touch: [\"touchmove\"],\n            });\n            await dispatchPointerEvent(previousPT, \"pointerout\", leaveEventInit, {\n                mouse: [\"mouseout\"],\n            });\n            const leaveEvents = await Promise.all(\n                getDifferentParents(pointerTarget, previousPT).map((element) =>\n                    _dispatch(element, \"pointerleave\", leaveEventInit)\n                )\n            );\n            if (!hasTouch()) {\n                await dispatchRelatedEvents(leaveEvents, \"mouseleave\", leaveEventInit);\n            }\n        }\n    }\n\n    if (!pointerTarget) {\n        return;\n    }\n\n    const enterEventInit = {\n        ...runTime.position,\n        relatedTarget: previousPT,\n        button: options?.button || 0,\n    };\n    if (runTime.isDragging) {\n        // If dragging, only drag events are triggered\n        const enterEventInitWithDT = { ...enterEventInit, dataTransfer: runTime.dataTransfer };\n        runTime.lastDragOverCancelled = false;\n        if (isDifferentTarget) {\n            const dragEnterEvent = await _dispatch(\n                pointerTarget,\n                \"dragenter\",\n                enterEventInitWithDT\n            );\n            runTime.lastDragOverCancelled = isPrevented(dragEnterEvent);\n        }\n        runTime.lastDragOverCancelled ||= await triggerDrag(pointerTarget, enterEventInitWithDT);\n    } else {\n        // Regular case: pointer events are triggered\n        if (isDifferentTarget) {\n            await dispatchPointerEvent(pointerTarget, \"pointerover\", enterEventInit, {\n                mouse: [\"mouseover\"],\n            });\n            const enterEvents = await Promise.all(\n                getDifferentParents(previousPT, pointerTarget).map((element) =>\n                    _dispatch(element, \"pointerenter\", enterEventInit)\n                )\n            );\n            if (!hasTouch()) {\n                await dispatchRelatedEvents(enterEvents, \"mouseenter\", enterEventInit);\n            }\n        }\n        await dispatchPointerEvent(pointerTarget, \"pointermove\", enterEventInit, {\n            mouse: [\"mousemove\"],\n            touch: [\"touchmove\"],\n        });\n    }\n}\n\n/**\n * @param {EventTarget} target\n * @param {KeyboardEventInit} eventInit\n */\nasync function _keyDown(target, eventInit) {\n    eventInit = { ...eventInit, ...currentEventInit.keydown };\n    registerSpecialKey(eventInit, true);\n\n    const repeat =\n        typeof eventInit.repeat === \"boolean\" ? eventInit.repeat : runTime.key === eventInit.key;\n    runTime.key = eventInit.key;\n    const keyDownEvent = await _dispatch(target, \"keydown\", { ...eventInit, repeat });\n\n    if (isPrevented(keyDownEvent)) {\n        return;\n    }\n\n    /**\n     * @param {string} toInsert\n     * @param {string} type\n     */\n    function insertValue(toInsert, type) {\n        const { selectionStart, selectionEnd, value } = target;\n        inputData = toInsert;\n        inputType = type;\n        if (isNil(selectionStart) && isNil(selectionEnd)) {\n            nextValue += toInsert;\n        } else {\n            nextValue = value.slice(0, selectionStart) + toInsert + value.slice(selectionEnd);\n            if (selectionStart === selectionEnd) {\n                nextSelectionStart = nextSelectionEnd = selectionStart + 1;\n            }\n        }\n    }\n\n    const { ctrlKey, key, shiftKey } = keyDownEvent;\n    const initialValue = target.value;\n    let inputData = null;\n    let inputType = null;\n    let nextSelectionEnd = null;\n    let nextSelectionStart = null;\n    let nextValue = initialValue;\n    let triggerSelect = false;\n\n    if (isEditable(target)) {\n        switch (key) {\n            case \"ArrowDown\":\n            case \"ArrowLeft\":\n            case \"ArrowUp\":\n            case \"ArrowRight\": {\n                const { selectionStart, selectionEnd, value } = target;\n                if (isNil(selectionStart) || isNil(selectionEnd)) {\n                    break;\n                }\n                const start = key === \"ArrowLeft\" || key === \"ArrowUp\";\n                let selectionTarget;\n                if (ctrlKey) {\n                    // Move to the start/end of the line\n                    selectionTarget = start ? 0 : value.length;\n                } else {\n                    // Move the cursor left or right\n                    selectionTarget = start ? selectionStart - 1 : selectionEnd + 1;\n                }\n                nextSelectionStart = nextSelectionEnd = $max(\n                    $min(selectionTarget, value.length),\n                    0\n                );\n                triggerSelect = shiftKey;\n                break;\n            }\n            case \"Backspace\": {\n                const { selectionStart, selectionEnd, value } = target;\n                if (fullClear) {\n                    // Remove all characters\n                    nextValue = \"\";\n                } else if (isNil(selectionStart) || isNil(selectionEnd)) {\n                    // Remove last character\n                    nextValue = value.slice(0, -1);\n                } else if (selectionStart === selectionEnd) {\n                    // Remove previous character from target value\n                    nextValue = value.slice(0, selectionStart - 1) + value.slice(selectionEnd);\n                } else {\n                    // Remove current selection from target value\n                    nextValue = deleteSelection(target);\n                }\n                inputType = \"deleteContentBackward\";\n                break;\n            }\n            case \"Delete\": {\n                const { selectionStart, selectionEnd, value } = target;\n                if (fullClear) {\n                    // Remove all characters\n                    nextValue = \"\";\n                } else if (isNil(selectionStart) || isNil(selectionEnd)) {\n                    // Remove first character\n                    nextValue = value.slice(1);\n                } else if (selectionStart === selectionEnd) {\n                    // Remove next character from target value\n                    nextValue = value.slice(0, selectionStart) + value.slice(selectionEnd + 1);\n                } else {\n                    // Remove current selection from target value\n                    nextValue = deleteSelection(target);\n                }\n                inputType = \"deleteContentForward\";\n                break;\n            }\n            case \"Enter\": {\n                if (target.tagName === \"TEXTAREA\") {\n                    // Insert new line\n                    insertValue(\"\\n\", \"insertLineBreak\");\n                }\n                break;\n            }\n            default: {\n                if (key.length === 1 && !ctrlKey) {\n                    // Character coming from the keystroke\n                    // ! TODO: Doesn't work with non-roman locales\n                    insertValue(\n                        shiftKey ? key.toUpperCase() : key.toLowerCase(),\n                        runTime.isComposing ? \"insertCompositionText\" : \"insertText\"\n                    );\n                }\n            }\n        }\n    }\n\n    switch (key) {\n        case \"a\": {\n            if (ctrlKey) {\n                // Select all\n                if (isEditable(target)) {\n                    nextSelectionStart = 0;\n                    nextSelectionEnd = target.value.length;\n                    triggerSelect = true;\n                } else {\n                    const selection = globalThis.getSelection();\n                    const range = $createRange();\n                    range.selectNodeContents(target);\n                    selection.removeAllRanges();\n                    selection.addRange(range);\n                }\n            }\n            break;\n        }\n        /**\n         * Special action: copy\n         *  On: unprevented 'Control + c' keydown\n         *  Do: copy current selection to clipboard\n         */\n        case \"c\": {\n            if (ctrlKey) {\n                // Get selection from window\n                const text = globalThis.getSelection().toString();\n                globalThis.navigator.clipboard.writeText(text).catch();\n\n                runTime.clipboardData = createDataTransfer(eventInit);\n                await _dispatch(target, \"copy\", { clipboardData: runTime.clipboardData });\n            }\n            break;\n        }\n        case \"Enter\": {\n            const tag = getTag(target);\n            const parentForm = target.closest(\"form\");\n            if (parentForm && target.type !== \"button\") {\n                /**\n                 * Special action: <form> 'Enter'\n                 *  On: unprevented 'Enter' keydown on any element that\n                 *      is not a <button type=\"button\"/> in a form element\n                 *  Do: triggers a 'submit' event on the form\n                 */\n                await _dispatch(parentForm, \"submit\");\n            } else if (\n                !keyDownEvent.repeat &&\n                (tag === \"a\" || tag === \"button\" || (tag === \"input\" && target.type === \"button\"))\n            ) {\n                /**\n                 * Special action: <a>, <button> or <input type=\"button\"> 'Enter'\n                 *  On: unprevented and unrepeated 'Enter' keydown on mentioned elements\n                 *  Do: triggers a 'click' event on the element\n                 */\n                await _dispatch(target, \"click\", { button: btn.LEFT });\n            }\n            break;\n        }\n        case \"Escape\": {\n            runTime.dataTransfer = null;\n            runTime.isDragging = false;\n            break;\n        }\n        /**\n         * Special action: shift focus\n         *  On: unprevented 'Tab' keydown\n         *  Do: focus next (or previous with 'Shift') focusable element\n         */\n        case \"Tab\": {\n            const next = shiftKey\n                ? getPreviousFocusableElement({ tabbable: true })\n                : getNextFocusableElement({ tabbable: true });\n            if (next) {\n                await triggerFocus(next);\n            }\n            break;\n        }\n        /**\n         * Special action: paste\n         *  On: unprevented 'Control + v' keydown on editable element\n         *  Do: paste current clipboard content to current element\n         */\n        case \"v\": {\n            if (ctrlKey && isEditable(target)) {\n                // Set target value (if possible)\n                try {\n                    nextValue = await globalThis.navigator.clipboard.readText();\n                } catch {\n                    // Ignore clipboard errors\n                }\n                inputType = \"insertFromPaste\";\n\n                await _dispatch(target, \"paste\", {\n                    clipboardData: runTime.clipboardData || createDataTransfer(eventInit),\n                });\n                runTime.clipboardData = null;\n            }\n            break;\n        }\n        /**\n         * Special action: cut\n         *  On: unprevented 'Control + x' keydown on editable element\n         *  Do: cut current selection to clipboard and remove selection\n         */\n        case \"x\": {\n            if (ctrlKey && isEditable(target)) {\n                // Get selection from window\n                const text = globalThis.getSelection().toString();\n                globalThis.navigator.clipboard.writeText(text).catch();\n\n                nextValue = deleteSelection(target);\n                inputType = \"deleteByCut\";\n\n                runTime.clipboardData = createDataTransfer(eventInit);\n                await _dispatch(target, \"cut\", { clipboardData: runTime.clipboardData });\n            }\n            break;\n        }\n    }\n\n    if (initialValue !== nextValue) {\n        target.value = nextValue;\n        const inputEventInit = {\n            data: inputData,\n            inputType,\n        };\n        const beforeInputEvent = await _dispatch(target, \"beforeinput\", inputEventInit);\n        if (!isPrevented(beforeInputEvent)) {\n            await _dispatch(target, \"input\", inputEventInit);\n        }\n    }\n    changeSelection(target, nextSelectionStart, nextSelectionEnd);\n    if (triggerSelect) {\n        await dispatchAndIgnore({\n            target,\n            events: [\"select\"],\n        });\n    }\n}\n\n/**\n * @param {EventTarget} target\n * @param {KeyboardEventInit} eventInit\n */\nasync function _keyUp(target, eventInit) {\n    eventInit = { ...eventInit, ...currentEventInit.keyup };\n    await _dispatch(target, \"keyup\", eventInit);\n\n    runTime.key = null;\n    registerSpecialKey(eventInit, false);\n\n    if (eventInit.key === \" \" && getTag(target) === \"input\" && target.type === \"checkbox\") {\n        /**\n         * Special action: input[type=checkbox] 'Space'\n         *  On: unprevented ' ' keydown on an <input type=\"checkbox\"/>\n         *  Do: triggers a 'click' event on the input\n         */\n        await triggerClick(target, { button: btn.LEFT });\n    }\n}\n\n/**\n * @param {DragOptions} [options]\n */\nasync function _pointerDown(options) {\n    setPointerDownTarget(runTime.pointerTarget, options);\n\n    if (options?.dataTransfer || options?.files || options?.items) {\n        runTime.dataTransfer = createDataTransfer(options);\n    }\n\n    const pointerDownTarget = runTime.pointerDownTarget;\n    const eventInit = {\n        ...runTime.position,\n        ...currentEventInit.pointerdown,\n        button: options?.button || btn.LEFT,\n    };\n\n    registerButton(eventInit, true);\n\n    if (pointerDownTarget !== runTime.previousPointerDownTarget) {\n        runTime.clickCount = 0;\n    }\n\n    runTime.touchStartPosition = { ...runTime.position };\n    runTime.touchStartTimeOffset = globalThis.Date.now();\n    const prevented = await dispatchPointerEvent(pointerDownTarget, \"pointerdown\", eventInit, {\n        mouse: !pointerDownTarget.disabled && [\n            \"mousedown\",\n            { ...eventInit, detail: runTime.clickCount + 1 },\n        ],\n        touch: [\"touchstart\"],\n    });\n\n    if (prevented) {\n        return;\n    }\n\n    // Focus the element (if focusable)\n    await triggerFocus(pointerDownTarget);\n\n    if (\n        eventInit.button === btn.LEFT &&\n        !hasTouch() &&\n        (pointerDownTarget.draggable || runTime.dataTransfer)\n    ) {\n        runTime.canStartDrag = true;\n    } else if (eventInit.button === btn.RIGHT) {\n        /**\n         * Special action: context menu\n         *  On: unprevented 'pointerdown' with right click and its related\n         *      event on an element\n         *  Do: triggers a 'contextmenu' event\n         */\n        await _dispatch(pointerDownTarget, \"contextmenu\", eventInit);\n    }\n}\n\n/**\n * @param {PointerOptions} [options]\n */\nasync function _pointerUp(options) {\n    const target = runTime.pointerTarget;\n    const isLongTap = globalThis.Date.now() - runTime.touchStartTimeOffset > LONG_TAP_DELAY;\n    const pointerDownTarget = runTime.pointerDownTarget;\n    const pointerUpTarget = getPointerTarget(target, options);\n    const eventInit = {\n        ...runTime.position,\n        ...currentEventInit.pointerup,\n        button: options?.button || btn.LEFT,\n    };\n\n    registerButton(eventInit, false);\n\n    if (runTime.isDragging) {\n        // If dragging, only drag events are triggered\n        const eventInitWithDT = { ...eventInit, dataTransfer: runTime.dataTransfer };\n        runTime.dataTransfer = null;\n        runTime.isDragging = false;\n        if (runTime.lastDragOverCancelled) {\n            /**\n             * Special action: drop\n             * - On: pointer up after a prevented 'dragover' or 'dragenter'\n             * - Do: triggers a 'drop' event on the target\n             */\n            await _dispatch(pointerUpTarget, \"drop\", eventInitWithDT);\n        }\n\n        await _dispatch(pointerUpTarget, \"dragend\", eventInitWithDT);\n        return;\n    }\n\n    const mouseEventInit = {\n        ...eventInit,\n        detail: runTime.clickCount + 1,\n    };\n    await dispatchPointerEvent(pointerUpTarget, \"pointerup\", eventInit, {\n        mouse: !pointerUpTarget.disabled && [\"mouseup\", mouseEventInit],\n        touch: [\"touchend\"],\n    });\n\n    const touchStartPosition = runTime.touchStartPosition;\n    runTime.touchStartPosition = {};\n\n    if (hasTouch() && (isDifferentPosition(touchStartPosition) || isLongTap)) {\n        // No further event is triggered:\n        // there was a swiping motion since the \"touchstart\" event\n        // or a long press was detected.\n        return;\n    }\n\n    let clickTarget;\n    if (hasTouch()) {\n        clickTarget = pointerDownTarget === pointerUpTarget && pointerUpTarget;\n    } else {\n        clickTarget = getFirstCommonParent(pointerUpTarget, pointerDownTarget);\n    }\n    if (clickTarget) {\n        await triggerClick(clickTarget, mouseEventInit);\n        if (mouseEventInit.button === btn.LEFT) {\n            runTime.clickCount++;\n            if (!hasTouch() && runTime.clickCount % 2 === 0) {\n                await _dispatch(clickTarget, \"dblclick\", mouseEventInit);\n            }\n        }\n    }\n\n    setPointerDownTarget(null, options);\n    if (runTime.pointerDownTimeout) {\n        globalThis.clearTimeout(runTime.pointerDownTimeout);\n    }\n    runTime.pointerDownTimeout = globalThis.setTimeout(() => {\n        // Use `globalThis.setTimeout` to potentially make use of the mock timeouts\n        // since the events run in the same temporal context as the tests\n        runTime.clickCount = 0;\n        runTime.pointerDownTimeout = 0;\n    }, DOUBLE_CLICK_DELAY);\n}\n\n/**\n * @param {EventTarget} target\n * @param {KeyboardEventInit} eventInit\n */\nasync function _press(target, eventInit) {\n    await _keyDown(target, eventInit);\n    await _keyUp(target, eventInit);\n}\n\n/**\n * @param {EventTarget} target\n * @param {string | number | (string | number)[]} value\n */\nasync function _select(target, value) {\n    const values = ensureArray(value).map(String);\n    let found = false;\n    for (const option of target.options) {\n        option.selected = values.includes(option.value);\n        found ||= option.selected;\n    }\n    if (!value) {\n        target.selectedIndex = -1;\n    } else if (!found) {\n        throw new HootInteractionError(\n            `error when calling \\`select()\\`: no option found with value \"${values.join(\", \")}\"`\n        );\n    }\n    await _dispatch(target, \"change\");\n}\n\nclass HootInteractionError extends Error {\n    name = \"HootInteractionError\";\n}\n\nconst btn = {\n    LEFT: 0,\n    MIDDLE: 1,\n    RIGHT: 2,\n    BACK: 3,\n    FORWARD: 4,\n};\nconst CAPTURE = { capture: true };\nconst DEPRECATED_EVENT_PROPERTIES = {\n    keyCode: \"key\",\n    which: \"key\",\n};\nconst DEPRECATED_EVENTS = {\n    keypress: \"keydown\",\n    mousewheel: \"wheel\",\n};\nconst DOUBLE_CLICK_DELAY = 500;\n\n/**\n * Ignore certain trusted events (dispatched by `focus()`, `scroll()`, etc.)\n * @type {[EventType, (event: Event) => any, AddEventListenerOptions][]}\n */\nconst GLOBAL_TRUSTED_EVENTS_CANCELERS = [\n    [\"blur\", cancelTrustedEvent, CAPTURE],\n    [\"focus\", cancelTrustedEvent, CAPTURE],\n    [\"focusin\", cancelTrustedEvent, CAPTURE],\n    [\"focusout\", cancelTrustedEvent, CAPTURE],\n    [\"scroll\", cancelTrustedEvent, CAPTURE],\n    [\"scrollend\", cancelTrustedEvent, CAPTURE],\n    [\"select\", cancelTrustedEvent, CAPTURE],\n];\n/**\n * Register file input on click & focus events\n * @type {[EventType, (event: Event) => any, AddEventListenerOptions][]}\n */\nconst GLOBAL_FILE_INPUT_REGISTERERS = [\n    [\"click\", registerFileInput, CAPTURE],\n    [\"focus\", registerFileInput, CAPTURE],\n];\n/**\n * Redirect events to other features\n * @type {[EventType, (event: Event) => any, AddEventListenerOptions][]}\n */\nconst GLOBAL_SUBMIT_FORWARDERS = [[\"submit\", redirectSubmit]];\n\nconst KEY_ALIASES = {\n    // case insensitive aliases\n    alt: \"Alt\",\n    arrowdown: \"ArrowDown\",\n    arrowleft: \"ArrowLeft\",\n    arrowright: \"ArrowRight\",\n    arrowup: \"ArrowUp\",\n    backspace: \"Backspace\",\n    control: \"Control\",\n    delete: \"Delete\",\n    enter: \"Enter\",\n    escape: \"Escape\",\n    meta: \"Meta\",\n    shift: \"Shift\",\n    tab: \"Tab\",\n\n    // Other aliases\n    caps: \"Shift\",\n    cmd: \"Meta\",\n    command: \"Meta\",\n    ctrl: \"Control\",\n    del: \"Delete\",\n    down: \"ArrowDown\",\n    esc: \"Escape\",\n    left: \"ArrowLeft\",\n    right: \"ArrowRight\",\n    space: \" \",\n    up: \"ArrowUp\",\n    win: \"Meta\",\n};\nconst LONG_TAP_DELAY = 500;\n\n/** @type {Record<string, Event[]>} */\nconst currentEvents = $create(null);\n/** @type {Record<EventType, EventInit>} */\nconst currentEventInit = $create(null);\n/** @type {string[]} */\nconst currentEventTypes = [];\n/** @type {(() => Promise<void>) | null} */\nlet afterNextDispatch = null;\nlet allowLogs = false;\nlet fullClear = false;\n\n// Keyboard global variables\nconst changeTargetListeners = [];\n\n// Other global variables\nconst runTime = getDefaultRunTimeValue();\n\n//-----------------------------------------------------------------------------\n// Event init attributes mappers\n//-----------------------------------------------------------------------------\n\nconst BUBBLES = 0b1;\nconst CANCELABLE = 0b10;\nconst VIEW = 0b100;\n\n// Generic mappers\n// ---------------\n\n/**\n * - does not bubble\n * - cannot be canceled\n * @param {FullEventInit} eventInit\n */\nfunction mapEvent(eventInit) {\n    return eventInit;\n}\n\n// Pointer, mouse & wheel event mappers\n// ------------------------------------\n\n/**\n * @param {FullEventInit<MouseEventInit>} eventInit\n */\nfunction mapMouseEvent(eventInit) {\n    return {\n        button: -1,\n        buttons: runTime.buttons,\n        clientX: eventInit.clientX ?? eventInit.pageX ?? eventInit.screenX ?? 0,\n        clientY: eventInit.clientY ?? eventInit.pageY ?? eventInit.screenY ?? 0,\n        ...runTime.modifierKeys,\n        ...eventInit,\n    };\n}\n\n/**\n * @param {FullEventInit<PointerEventInit>} eventInit\n */\nfunction mapPointerEvent(eventInit) {\n    return {\n        ...mapMouseEvent(eventInit),\n        button: btn.LEFT,\n        isPrimary: eventInit?.btn ? eventInit.btn === btn.LEFT : true,\n        pointerId: 1,\n        pointerType: hasTouch() ? \"touch\" : \"mouse\",\n        ...eventInit,\n    };\n}\n\n/**\n * @param {FullEventInit<WheelEventInit>} eventInit\n */\nfunction mapWheelEvent(eventInit) {\n    return {\n        ...mapMouseEvent(eventInit),\n        button: btn.LEFT,\n        ...eventInit,\n    };\n}\n\n// Touch event mappers\n// -------------------\n\n/**\n * @param {FullEventInit<TouchEventInit>} eventInit\n */\nfunction mapTouchEvent(eventInit) {\n    const touches = eventInit.targetTouches ||\n        eventInit.touches || [new Touch({ identifier: 0, ...eventInit })];\n    return {\n        ...eventInit,\n        changedTouches: eventInit.changedTouches || touches,\n        target: eventInit.target,\n        targetTouches: eventInit.targetTouches || touches,\n        touches: eventInit.touches || (eventInit.type === \"touchend\" ? [] : touches),\n    };\n}\n\n// Keyboard & input event mappers\n// ------------------------------\n\n/**\n * @param {FullEventInit<InputEventInit>} eventInit\n */\nfunction mapInputEvent(eventInit) {\n    return {\n        data: null,\n        isComposing: !!runTime.isComposing,\n        ...eventInit,\n    };\n}\n\n/**\n * @param {FullEventInit<KeyboardEventInit>} eventInit\n */\nfunction mapKeyboardEvent(eventInit) {\n    return {\n        isComposing: !!runTime.isComposing,\n        ...runTime.modifierKeys,\n        ...eventInit,\n    };\n}\n\n//-----------------------------------------------------------------------------\n// Exports\n//-----------------------------------------------------------------------------\n\n/**\n * Ensures that the given {@link AsyncTarget} is checked.\n *\n * If it is not checked, a click is simulated on the input.\n * If the input is still not checked after the click, an error is thrown.\n *\n * @see {@link click}\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  check(\"input[type=checkbox]\"); // Checks the first <input> checkbox element\n */\nexport async function check(target, options) {\n    const finalizeEvents = setupEvents(\"check\", options);\n    const element = queryAny(await target, options);\n    if (!isCheckable(element)) {\n        throw new HootInteractionError(\n            `cannot call \\`check()\\`: target should be a checkbox or radio input`\n        );\n    }\n\n    const checkTarget = getTag(element) === \"label\" ? element.control : element;\n    if (!checkTarget.checked) {\n        await _hover(element, options, { implicit: true, originalTarget: target });\n        await _click(options);\n\n        if (!checkTarget.checked) {\n            throw new HootInteractionError(\n                `error when calling \\`check()\\`: target is not checked after interaction`\n            );\n        }\n    }\n\n    return finalizeEvents();\n}\n\nexport function cleanupEvents() {\n    if (runTime.pointerDownTimeout) {\n        globalThis.clearTimeout(runTime.pointerDownTimeout);\n    }\n\n    removeChangeTargetListeners();\n\n    // Runtime global variables\n    $assign(runTime, getDefaultRunTimeValue());\n}\n\n/**\n * Clears the **value** of the current **active element**.\n *\n * This is done using the following sequence:\n * - pressing \"Control\" + \"A\" to select the whole value;\n * - pressing \"Backspace\" to delete the value;\n * - (optional) triggering a \"change\" event by pressing \"Enter\".\n *\n * @param {FillOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  clear(); // Clears the value of the current active element\n */\nexport async function clear(options) {\n    const finalizeEvents = setupEvents(\"clear\", options);\n    const element = getActiveElement();\n\n    if (!hasTagName(element, \"select\") && !isEditable(element)) {\n        throw new HootInteractionError(\n            `cannot call \\`clear()\\`: target should be editable or a <select> element`\n        );\n    }\n\n    if (isEditable(element)) {\n        await _clear(element, options);\n    } else {\n        // Selects\n        await _select(element, \"\");\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a click sequence on the given {@link AsyncTarget}.\n *\n * The event sequence is as follows:\n *  - `pointerdown`\n *  - [desktop] `mousedown`\n *  - [touch] `touchstart`\n *  - [target is not active element] `blur`\n *  - [target is focusable] `focus`\n *  - `pointerup`\n *  - [desktop] `mouseup`\n *  - [touch] `touchend`\n *  - `click`\n *  - `dblclick` if click is not prevented & current click count is even\n *\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  click(\"button\"); // Clicks on the first <button> element\n */\nexport async function click(target, options) {\n    const finalizeEvents = setupEvents(\"click\", options);\n    const element = queryAny(await target, options);\n\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _click(options);\n\n    return finalizeEvents();\n}\n\n/**\n * Performs two click sequences on the given {@link AsyncTarget}.\n *\n * @see {@link click}\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  dblclick(\"button\"); // Double-clicks on the first <button> element\n */\nexport async function dblclick(target, options) {\n    const finalizeEvents = setupEvents(\"dblclick\", options);\n    const element = queryAny(await target, options);\n\n    options = { ...options, button: btn.LEFT };\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _click(options);\n    await _click(options);\n\n    return finalizeEvents();\n}\n\n/**\n * Disclaimer: avoid using this method; it is meant to support niche event sequences,\n * or as a temporary fix for unsupported use cases. Prefer using other 'hoot-dom'\n * helpers such as {@link click}, {@link press} or {@link edit}.\n *\n * Creates a new DOM {@link Event} of the given type and dispatches it on the given\n * {@link Target}.\n *\n * Note that this function is free of side-effects and does not trigger any other\n * event or special action. It also only supports standard DOM events, and will\n * crash when trying to dispatch a non-standard or deprecated event.\n *\n * @template {EventType} T\n * @template {HTMLBodyElementEventMap[T]} I\n * @param {EventTarget} target\n * @param {T} type\n * @param {Partial<I> | { eventInit: Record<T, Partial<I>> }} [eventInit]\n * @example\n *  await manuallyDispatchProgrammaticEvent(\"input\", \"paste\"); // Dispatches a \"paste\" event on the given <input>\n * @returns {Promise<EventList>}\n */\nexport async function dispatch(target, type, eventInit) {\n    const finalizeEvents = setupEvents(\"dispatch\");\n\n    if (!isEventTarget(target)) {\n        throw new HootInteractionError(\n            `cannot dispatch \"${type}\" event: expected target to be an 'EventTarget', got: ${target}`\n        );\n    }\n    if (type in DEPRECATED_EVENTS) {\n        throw new HootInteractionError(\n            `cannot dispatch \"${type}\" event: this event type is deprecated, use \"${DEPRECATED_EVENTS[type]}\" instead`\n        );\n    }\n    if (type !== type.toLowerCase()) {\n        throw new HootInteractionError(\n            `cannot dispatch \"${type}\" event: this event type is either non-standard or deprecated`\n        );\n    }\n\n    await _dispatch(target, type, eventInit);\n\n    return finalizeEvents();\n}\n\n/**\n * Starts a drag sequence on the given {@link AsyncTarget}.\n *\n * Returns a set of helper functions to direct the sequence:\n * - `moveTo`: moves the pointer to the given target;\n * - `drop`: drops the dragged element on the given target (if any);\n * - `cancel`: cancels the drag sequence.\n *\n * @param {AsyncTarget} target\n * @param {DragOptions} [options]\n * @returns {Promise<DragHelpers>}\n * @example\n *  drag(\".card:first\").drop(\".card:last\"); // Drags the first card onto the last one\n * @example\n *  drag(\".card:first\").moveTo(\".card:last\").drop(); // Same as above\n * @example\n *  const { cancel, moveTo } = await drag(\".card:first\"); // Starts the drag sequence\n *  moveTo(\".card:eq(3)\"); // Moves the dragged card to the 4th card\n *  cancel(); // Cancels the drag sequence\n */\nexport async function drag(target, options) {\n    /**\n     * @template T\n     * @param {T} fn\n     * @param {boolean} endDrag\n     * @returns {T}\n     */\n    function expectIsDragging(fn, endDrag) {\n        return {\n            async [fn.name](...args) {\n                if (dragEndReason) {\n                    throw new HootInteractionError(\n                        `cannot execute drag helper \\`${fn.name}\\`: drag sequence has been ended by \\`${dragEndReason}\\``\n                    );\n                }\n                const result = await fn(...args);\n                if (endDrag) {\n                    dragEndReason = fn.name;\n                }\n                return result;\n            },\n        }[fn.name];\n    }\n\n    const cancel = expectIsDragging(\n        /** @type {DragHelpers[\"cancel\"]} */\n        async function cancel(options) {\n            const finalizeEvents = setupEvents(\"drag & drop: cancel\", options);\n            const bodyElement = getDocument(runTime.pointerTarget).body;\n\n            // Reset buttons\n            runTime.buttons = 0;\n\n            await _press(bodyElement, { key: \"Escape\" });\n\n            dragEvents.push(...finalizeEvents());\n\n            return dragEvents;\n        },\n        true\n    );\n\n    const drop = expectIsDragging(\n        /** @type {DragHelpers[\"drop\"]} */\n        async function drop(to, options) {\n            if (to) {\n                await moveTo(to, options);\n\n                if (isDictionary(to)) {\n                    options = to;\n                }\n            }\n\n            const finalizeEvents = setupEvents(\"drag & drop: drop\", options);\n\n            await _pointerUp(options);\n\n            dragEvents.push(...finalizeEvents());\n\n            return dragEvents;\n        },\n        true\n    );\n\n    const moveTo = expectIsDragging(\n        /** @type {DragHelpers[\"moveTo\"]} */\n        async function moveTo(to, options) {\n            if (isDictionary(to)) {\n                [to, options] = [null, to];\n            }\n            const finalizeEvents = setupEvents(\"drag & drop: move\", options);\n\n            const nextElement = to ? queryAny(await to, options) : runTime.pointerTarget;\n            await _hover(nextElement, options, { originalTarget: to });\n\n            dragEvents.push(...finalizeEvents());\n\n            return dragHelpers;\n        },\n        false\n    );\n\n    const finalizeEvents = setupEvents(\"drag & drop: start\", options);\n    const dragHelpers = { cancel, drop, moveTo };\n    const dragStartTarget = queryAny(await target, options);\n    let dragEndReason = null;\n\n    // Pointer down on main target\n    await _hover(dragStartTarget, options, { implicit: true, originalTarget: target });\n    await _pointerDown(options);\n\n    const dragEvents = finalizeEvents();\n\n    return dragHelpers;\n}\n\n/**\n * Combination of {@link clear} and {@link fill}:\n * - first, clears the input value (if any)\n * - then fills the input with the given value\n *\n * @see {@link clear}\n * @see {@link fill}\n * @param {InputValue} value\n * @param {FillOptions} options\n * @returns {Promise<EventList>}\n * @example\n *  fill(\"foo\"); // Types \"foo\" in the active element\n *  edit(\"Hello World\"); // Replaces \"foo\" by \"Hello World\"\n */\nexport async function edit(value, options) {\n    const finalizeEvents = setupEvents(\"edit\", options);\n    const element = getActiveElement();\n    if (!isEditable(element)) {\n        throw new HootInteractionError(`cannot call \\`edit()\\`: target should be editable`);\n    }\n\n    if (getNodeValue(element)) {\n        await _clear(element);\n    }\n    await _fill(element, value, options);\n\n    return finalizeEvents();\n}\n\n/**\n * @param {boolean} toggle\n */\nexport function enableEventLogs(toggle) {\n    allowLogs = toggle ?? true;\n}\n\n/**\n * Fills the current **active element** with the given `value`. This helper is intended\n * for `<input>` and `<textarea>` elements, with the exception of `\"checkbox\"` and\n * `\"radio\"` types, which should be selected using the {@link check} helper.\n *\n * If the target is an editable input, its string `value` will be input one character\n * at a time, each generating its corresponding keyboard event sequence. This behavior\n * can be overriden by passing the `instantly` option, which will instead simulate\n * a `control` + `v` keyboard sequence, resulting in the whole text being pasted.\n *\n * Note that the given value is **appended** to the current value of the element.\n *\n * If the active element is a `<input type=\"file\"/>`, the `value` should be a\n * `File`/list of `File` object(s).\n *\n * @param {InputValue} value\n * @param {FillOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  fill(\"Hello World\"); // Types \"Hello World\" in the active element\n * @example\n *  fill(\"Hello World\", { instantly: true }); // Pastes \"Hello World\" in the active element\n * @example\n *  fill(new File([\"Hello World\"], \"hello.txt\")); // Uploads a file named \"hello.txt\" with \"Hello World\" as content\n */\nexport async function fill(value, options) {\n    const finalizeEvents = setupEvents(\"fill\", options);\n    const element = getActiveElement();\n\n    if (!isEditable(element)) {\n        throw new HootInteractionError(`cannot call \\`fill()\\`: target should be editable`);\n    }\n\n    await _fill(element, value, options);\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a hover sequence on the given {@link AsyncTarget}.\n *\n * The event sequence is as follows:\n *  - `pointerover`\n *  - [desktop] `mouseover`\n *  - `pointerenter`\n *  - [desktop] `mouseenter`\n *  - `pointermove`\n *  - [desktop] `mousemove`\n *  - [touch] `touchmove`\n *\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  hover(\"button\"); // Hovers the first <button> element\n */\nexport async function hover(target, options) {\n    const finalizeEvents = setupEvents(\"hover\", options);\n    const element = queryAny(await target, options);\n\n    await _hover(element, options, { originalTarget: target });\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a key down sequence on the current **active element**.\n *\n * The event sequence is as follows:\n *  - `keydown`\n *\n * Additional actions will be performed depending on the key pressed:\n * - `Tab`: focus next (or previous with `shift`) focusable element;\n * - `c`: copy current selection to clipboard;\n * - `v`: paste current clipboard content to current element;\n * - `Enter`: submit the form if the target is a `<button type=\"button\">` or\n *  a `<form>` element, or trigger a `change` event on the target if it is\n *  an `<input>` element;\n * - `Space`: trigger a `click` event on the target if it is an `<input type=\"checkbox\">`\n *  element.\n *\n * @param {KeyStrokes} keyStrokes\n * @param {KeyboardOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  keyDown(\" \"); // Space key\n */\nexport async function keyDown(keyStrokes, options) {\n    const finalizeEvents = setupEvents(\"keyDown\", options);\n    const eventInits = parseKeyStrokes(keyStrokes, options);\n    for (const eventInit of eventInits) {\n        await _keyDown(getActiveElement(), eventInit);\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a key up sequence on the current **active element**.\n *\n * The event sequence is as follows:\n *  - `keyup`\n *\n * @param {KeyStrokes} keyStrokes\n * @param {KeyboardOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  keyUp(\"Enter\");\n */\nexport async function keyUp(keyStrokes, options) {\n    const finalizeEvents = setupEvents(\"keyUp\", options);\n    const eventInits = parseKeyStrokes(keyStrokes, options);\n    for (const eventInit of eventInits) {\n        await _keyUp(getActiveElement(), eventInit);\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a leave sequence on the current **window**.\n *\n * The event sequence is as follows:\n *  - `pointermove`\n *  - [desktop] `mousemove`\n *  - [touch] `touchmove`\n *  - `pointerout`\n *  - [desktop] `mouseout`\n *  - `pointerleave`\n *  - [desktop] `mouseleave`\n *\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  leave(\"button\"); // Moves out of <button>\n */\nexport async function leave(options) {\n    const finalizeEvents = setupEvents(\"leave\", options);\n\n    await _hover(null, options, { originalTarget: window });\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a middle-click sequence on the given {@link AsyncTarget}.\n *\n * @see {@link click}\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  middleClick(\"button\"); // Middle-clicks on the first <button> element\n */\nexport async function middleClick(target, options) {\n    const finalizeEvents = setupEvents(\"middleClick\", options);\n    const element = queryAny(await target, options);\n\n    options = { ...options, button: btn.MIDDLE };\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _click(options);\n\n    return finalizeEvents();\n}\n\n/**\n * Shorthand helper to attach an event listener to the given {@link Target}, and\n * returning a function to remove the listener.\n *\n * @template {EventType} T\n * @param {Target<EventTarget>} target\n * @param {T} type\n * @param {(event: GlobalEventHandlersEventMap[T]) => any} listener\n * @param {boolean | AddEventListenerOptions} [options]\n * @returns {() => void}\n * @example\n *  const off = on(\"button\", \"click\", onClick);\n *  after(off);\n */\nexport function on(target, type, listener, options) {\n    const targets = isEventTarget(target) ? [target] : queryAll(target);\n    if (!targets.length) {\n        throw new HootInteractionError(`expected at least 1 event target, got none`);\n    }\n    for (const eventTarget of targets) {\n        eventTarget.addEventListener(type, listener, options);\n    }\n\n    return function off() {\n        for (const eventTarget of targets) {\n            eventTarget.removeEventListener(type, listener, options);\n        }\n    };\n}\n\n/**\n * Performs a pointer down on the given {@link AsyncTarget}.\n *\n * The event sequence is as follows:\n *  - `pointerdown`\n *  - [desktop] `mousedown`\n *  - [touch] `touchstart`\n *  - [target is not active element] `blur`\n *  - [target is focusable] `focus`\n *\n * @param {AsyncTarget} target\n * @param {PointerOptions | DragOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  pointerDown(\"button\"); // Focuses to the first <button> element\n */\nexport async function pointerDown(target, options) {\n    const finalizeEvents = setupEvents(\"pointerDown\", options);\n    const element = queryAny(await target, options);\n\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _pointerDown(options);\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a pointer up on the given {@link AsyncTarget}.\n *\n * The event sequence is as follows:\n * - `pointerup`\n * - [desktop] `mouseup`\n * - [touch] `touchend`\n *\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  pointerUp(\"body\"); // Triggers a pointer up on the <body> element\n */\nexport async function pointerUp(target, options) {\n    const finalizeEvents = setupEvents(\"pointerUp\", options);\n    const element = queryAny(await target, options);\n\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _pointerUp(options);\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a keyboard event sequence on the current **active element**.\n *\n * The event sequence is as follows:\n *  - `keydown`\n *  - `keyup`\n *\n * @param {KeyStrokes} keyStrokes\n * @param {KeyboardOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  pointerDown(\"button[type=submit]\"); // Moves focus to <button>\n *  keyDown(\"Enter\"); // Submits the form\n * @example\n *  keyDown(\"Shift+Tab\"); // Focuses previous focusable element\n * @example\n *  keyDown([\"ctrl\", \"v\"]); // Pastes current clipboard content\n */\nexport async function press(keyStrokes, options) {\n    const finalizeEvents = setupEvents(\"press\", options);\n    const eventInits = parseKeyStrokes(keyStrokes, options);\n    const activeElement = getActiveElement();\n\n    for (const eventInit of eventInits) {\n        await _keyDown(activeElement, eventInit);\n    }\n    for (const eventInit of eventInits.reverse()) {\n        await _keyUp(activeElement, eventInit);\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a resize event sequence on the current **window**.\n *\n * The event sequence is as follows:\n *  - `resize`\n *\n * The target will be resized to the given dimensions, enforced by `!important` style\n * attributes.\n *\n * @param {Dimensions} dimensions\n * @param {EventOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  resize(\"body\", { width: 1000, height: 500 }); // Resizes <body> to 1000x500\n */\nexport async function resize(dimensions, options) {\n    const finalizeEvents = setupEvents(\"resize\", options);\n    const [width, height] = parseDimensions(dimensions);\n\n    setDimensions(width, height);\n\n    await _dispatch(getWindow(), \"resize\");\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a right-click sequence on the given {@link AsyncTarget}.\n *\n * @see {@link click}\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  rightClick(\"button\"); // Middle-clicks on the first <button> element\n */\nexport async function rightClick(target, options) {\n    const finalizeEvents = setupEvents(\"rightClick\", options);\n    const element = queryAny(await target, options);\n\n    options = { ...options, button: btn.RIGHT };\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _click(options);\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a scroll event sequence on the given {@link AsyncTarget}.\n *\n * The event sequence is as follows:\n *  - [desktop] `wheel`\n *  - `scroll`\n *\n * @param {AsyncTarget} target\n * @param {Position} position\n * @param {ScrollOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  scroll(\"body\", { y: 0 }); // Scrolls to the top of <body>\n */\nexport async function scroll(target, position, options) {\n    const finalizeEvents = setupEvents(\"scroll\", options);\n\n    // Parse position and assign default options\n    let [x, y] = parsePosition(position);\n    options = {\n        initiator: \"wheel\",\n        scrollable: x && y ? \"both\" : x ? \"x\" : y ? \"y\" : true,\n        ...options,\n    };\n\n    const { force, initiator, relative } = options;\n    /** @type {ScrollToOptions} */\n    const scrollTopOptions = {};\n    const element = queryAny(await target, options);\n    if (relative) {\n        x += element.scrollLeft;\n        y += element.scrollTop;\n    }\n    if (!$isNaN(x)) {\n        const targetX = force ? x : constrainScrollX(element, x);\n        if (targetX !== element.scrollLeft) {\n            scrollTopOptions.left = targetX;\n        }\n    }\n    if (!$isNaN(y)) {\n        const targetY = force ? y : constrainScrollY(element, y);\n        if (targetY !== element.scrollTop) {\n            scrollTopOptions.top = targetY;\n        }\n    }\n    const keys = [];\n    if (initiator === \"keyboard\") {\n        if (x < element.scrollLeft) {\n            keys.push(\"ArrowRight\");\n        } else if (x > element.scrollLeft) {\n            keys.push(\"ArrowLeft\");\n        }\n        if (y < element.scrollTop) {\n            keys.push(\"ArrowDown\");\n        } else if (y > element.scrollTop) {\n            keys.push(\"ArrowUp\");\n        }\n        await Promise.all(keys.map((key) => _keyDown(key)));\n    } else if (!hasTouch() && initiator === \"wheel\") {\n        /** @type {WheelEventInit} */\n        const wheelEventInit = {};\n        if (!$isNaN(x)) {\n            wheelEventInit.deltaX = x - element.scrollLeft;\n        }\n        if (!$isNaN(y)) {\n            wheelEventInit.deltaY = y - element.scrollTop;\n        }\n        await _dispatch(element, \"wheel\", wheelEventInit);\n    }\n    if (force || $values(scrollTopOptions).length) {\n        await dispatchAndIgnore({\n            target: element,\n            events: [\"scroll\", \"scrollend\"],\n            callback: (el) => el.scrollTo(scrollTopOptions),\n        });\n    }\n    if (initiator === \"keyboard\") {\n        await Promise.all(keys.map((key) => _keyUp(key)));\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Performs a selection event sequence on the current **active element**. This helper\n * is intended for `<select>` elements only.\n *\n * The event sequence is as follows:\n *  - `change`\n *\n * @param {string | number | (string | number)[]} value\n * @param {SelectOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  click(\"select[name=country]\"); // Focuses <select> element\n *  select(\"belgium\"); // Selects the <option value=\"belgium\"> element\n */\nexport async function select(value, options) {\n    const finalizeEvents = setupEvents(\"select\", options);\n    const target = options?.target || getActiveElement();\n    const element = queryAny(await target);\n\n    if (!hasTagName(element, \"select\")) {\n        throw new HootInteractionError(\n            `cannot call \\`select()\\`: target should be a <select> element`\n        );\n    }\n\n    if (options?.target) {\n        await _hover(element, null, { implicit: true, originalTarget: target });\n        await _pointerDown();\n    }\n    await _select(element, value);\n    if (options?.target) {\n        await _pointerUp();\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Gives the given {@link File} list to the current file input. This helper only\n * works if a file input has been previously interacted with (by clicking on it).\n *\n * @param {MaybeIterable<File>} files\n * @param {EventOptions} [options]\n * @returns {Promise<EventList>}\n */\nexport async function setInputFiles(files, options) {\n    if (!runTime.fileInput) {\n        throw new HootInteractionError(\n            `cannot call \\`setInputFiles()\\`: no file input has been interacted with`\n        );\n    }\n\n    const finalizeEvents = setupEvents(\"setInputFiles\", options);\n\n    await _fill(runTime.fileInput, files, options);\n\n    runTime.fileInput = null;\n\n    return finalizeEvents();\n}\n\n/**\n * Sets the given value to the given \"input[type=range]\" {@link AsyncTarget}.\n *\n * The event sequence is as follows:\n *  - `pointerdown`\n *  - `input`\n *  - `change`\n *  - `pointerup`\n *\n * @param {AsyncTarget} target\n * @param {number} value\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n */\nexport async function setInputRange(target, value, options) {\n    const finalizeEvents = setupEvents(\"setInputRange\", options);\n    const element = queryAny(await target, options);\n\n    await _hover(element, options, { implicit: true, originalTarget: target });\n    await _pointerDown(options);\n    await _fill(element, value, options);\n    await _pointerUp(options);\n\n    return finalizeEvents();\n}\n\n/**\n * @param {HTMLElement} target\n * @param {{\n *  allowSubmit?: boolean;\n *  allowTrustedEvents?: boolean;\n *  noFileInputRegistration?: boolean;\n * }} [options]\n */\nexport function setupEventActions(target, options) {\n    const eventHandlers = [];\n    if (!options?.allowTrustedEvents) {\n        eventHandlers.push(...GLOBAL_TRUSTED_EVENTS_CANCELERS);\n    }\n    if (!options?.noFileInputRegistration) {\n        eventHandlers.push(...GLOBAL_FILE_INPUT_REGISTERERS);\n    }\n    if (!options?.allowSubmit) {\n        eventHandlers.push(...GLOBAL_SUBMIT_FORWARDERS);\n    }\n\n    const view = getWindow(target);\n    for (const [eventType, handler, options] of eventHandlers) {\n        view.addEventListener(eventType, handler, options);\n    }\n\n    return function cleanupEventActions() {\n        for (const [eventType, handler, options] of eventHandlers) {\n            view.removeEventListener(eventType, handler, options);\n        }\n    };\n}\n\n/**\n * Ensures that the given {@link AsyncTarget} is unchecked.\n *\n * If it is checked, a click is triggered on the input.\n * If the input is still checked after the click, an error is thrown.\n *\n * @see {@link click}\n * @param {AsyncTarget} target\n * @param {PointerOptions} [options]\n * @returns {Promise<EventList>}\n * @example\n *  uncheck(\"input[type=checkbox]\"); // Unchecks the first <input> checkbox element\n */\nexport async function uncheck(target, options) {\n    const finalizeEvents = setupEvents(\"uncheck\", options);\n    const element = queryAny(await target, options);\n    if (!isCheckable(element)) {\n        throw new HootInteractionError(\n            `cannot call \\`uncheck()\\`: target should be a checkbox or radio input`\n        );\n    }\n\n    const checkTarget = getTag(element) === \"label\" ? element.control : element;\n    if (checkTarget.checked) {\n        await _hover(element, options, { implicit: true, originalTarget: target });\n        await _click(options);\n\n        if (checkTarget.checked) {\n            throw new HootInteractionError(\n                `error when calling \\`uncheck()\\`: target is still checked after interaction`\n            );\n        }\n    }\n\n    return finalizeEvents();\n}\n\n/**\n * Triggers a \"beforeunload\" event on the current **window**.\n *\n * @param {EventOptions} [options]\n * @returns {Promise<EventList>}\n */\nexport async function unload(options) {\n    const finalizeEvents = setupEvents(\"unload\", options);\n\n    await _dispatch(getWindow(), \"beforeunload\");\n\n    return finalizeEvents();\n}\n\n/** @extends {Array<Event>} */\nexport class EventList extends Array {\n    constructor(...args) {\n        super(...args.flat());\n    }\n\n    /**\n     * @param {EventListPredicate} predicate\n     */\n    get(predicate) {\n        return this.getAll(predicate)[0] || null;\n    }\n\n    /**\n     * @param {EventListPredicate} predicate\n     */\n    getAll(predicate) {\n        if (typeof predicate !== \"function\") {\n            const type = predicate;\n            predicate = function isSameType(ev) {\n                return ev.type === type;\n            };\n        }\n        return this.filter(predicate);\n    }\n}\n", "/** @odoo-module */\n\nimport { isInstanceOf } from \"../hoot_dom_utils\";\n\n/**\n * @typedef {{\n *  animationFrame?: boolean;\n *  blockTimers?: boolean;\n * }} AdvanceTimeOptions\n *\n * @typedef {{\n *  message?: string | () => string;\n *  timeout?: number;\n * }} WaitOptions\n */\n\n//-----------------------------------------------------------------------------\n// Global\n//-----------------------------------------------------------------------------\n\nconst {\n    cancelAnimationFrame,\n    clearInterval,\n    clearTimeout,\n    Error,\n    Math: { ceil: $ceil, floor: $floor, max: $max, min: $min },\n    Number,\n    performance,\n    Promise,\n    requestAnimationFrame,\n    setInterval,\n    setTimeout,\n} = globalThis;\n/** @type {Performance[\"now\"]} */\nconst $performanceNow = performance.now.bind(performance);\n\n//-----------------------------------------------------------------------------\n// Internal\n//-----------------------------------------------------------------------------\n\n/**\n * @param {number} id\n */\nfunction animationToId(id) {\n    return ID_PREFIX.animation + String(id);\n}\n\nfunction getNextTimerValues() {\n    /** @type {[number, () => any, string] | null} */\n    let timerValues = null;\n    for (const [internalId, [callback, init, delay]] of timers.entries()) {\n        const timeout = init + delay;\n        if (!timerValues || timeout < timerValues[0]) {\n            timerValues = [timeout, callback, internalId];\n        }\n    }\n    return timerValues;\n}\n\n/**\n * @param {string} id\n */\nfunction idToAnimation(id) {\n    return Number(id.slice(ID_PREFIX.animation.length));\n}\n\n/**\n * @param {string} id\n */\nfunction idToInterval(id) {\n    return Number(id.slice(ID_PREFIX.interval.length));\n}\n\n/**\n * @param {string} id\n */\nfunction idToTimeout(id) {\n    return Number(id.slice(ID_PREFIX.timeout.length));\n}\n\n/**\n * @param {number} id\n */\nfunction intervalToId(id) {\n    return ID_PREFIX.interval + String(id);\n}\n\n/**\n * Converts a given value to a **natural number** (or 0 if failing to do so).\n *\n * @param {unknown} value\n */\nfunction parseNat(value) {\n    return $max($floor(Number(value)), 0) || 0;\n}\n\nfunction now() {\n    return (frozen ? 0 : $performanceNow()) + timeOffset;\n}\n\n/**\n * @param {number} id\n */\nfunction timeoutToId(id) {\n    return ID_PREFIX.timeout + String(id);\n}\n\nclass HootTimingError extends Error {\n    name = \"HootTimingError\";\n}\n\nconst ID_PREFIX = {\n    animation: \"a_\",\n    interval: \"i_\",\n    timeout: \"t_\",\n};\n\n/** @type {Map<string, [() => any, number, number]>} */\nconst timers = new Map();\n\nlet allowTimers = false;\nlet frozen = false;\nlet frameDelay = 1000 / 60;\nlet nextDummyId = 1;\nlet timeOffset = 0;\n\n//-----------------------------------------------------------------------------\n// Exports\n//-----------------------------------------------------------------------------\n\n/**\n * @param {number} [frameCount]\n * @param {AdvanceTimeOptions} [options]\n */\nexport function advanceFrame(frameCount, options) {\n    return advanceTime(frameDelay * parseNat(frameCount), options);\n}\n\n/**\n * Advances the current time by the given amount of milliseconds. This will\n * affect all timeouts, intervals, animations and date objects.\n *\n * It returns a promise resolved after all related callbacks have been executed.\n *\n * @param {number} ms\n * @param {AdvanceTimeOptions} [options]\n * @returns {Promise<number>} time consumed by timers (in ms).\n */\nexport async function advanceTime(ms, options) {\n    ms = parseNat(ms);\n\n    if (options?.blockTimers) {\n        allowTimers = false;\n    }\n\n    const targetTime = now() + ms;\n    let remaining = ms;\n    /** @type {ReturnType<typeof getNextTimerValues>} */\n    let timerValues;\n    while ((timerValues = getNextTimerValues()) && timerValues[0] <= targetTime) {\n        const [timeout, handler, id] = timerValues;\n        const diff = timeout - now();\n        if (diff > 0) {\n            timeOffset += $min(remaining, diff);\n            remaining = $max(remaining - diff, 0);\n        }\n        if (timers.has(id)) {\n            handler(timeout);\n        }\n    }\n\n    if (remaining > 0) {\n        timeOffset += remaining;\n    }\n\n    if (options?.animationFrame ?? true) {\n        await animationFrame();\n    }\n\n    allowTimers = true;\n\n    return ms;\n}\n\n/**\n * Returns a promise resolved after the next animation frame, typically allowing\n * Owl components to render.\n *\n * @returns {Promise<void>}\n */\nexport function animationFrame() {\n    return new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve)));\n}\n\n/**\n * Cancels all current timeouts, intervals and animations.\n */\nexport function cancelAllTimers() {\n    for (const id of timers.keys()) {\n        if (id.startsWith(ID_PREFIX.animation)) {\n            globalThis.cancelAnimationFrame(idToAnimation(id));\n        } else if (id.startsWith(ID_PREFIX.interval)) {\n            globalThis.clearInterval(idToInterval(id));\n        } else if (id.startsWith(ID_PREFIX.timeout)) {\n            globalThis.clearTimeout(idToTimeout(id));\n        }\n    }\n}\n\nexport function cleanupTime() {\n    allowTimers = false;\n    frozen = false;\n\n    cancelAllTimers();\n\n    // Wait for remaining async code to run\n    return delay();\n}\n\n/**\n * Returns a promise resolved after a given amount of milliseconds (default to 0).\n *\n * @param {number} [duration]\n * @returns {Promise<void>}\n * @example\n *  await delay(1000); // waits for 1 second\n */\nexport function delay(duration) {\n    return new Promise((resolve) => setTimeout(resolve, duration));\n}\n\nexport function freezeTime() {\n    frozen = true;\n}\n\nexport function unfreezeTime() {\n    frozen = false;\n}\n\nexport function getTimeOffset() {\n    return timeOffset;\n}\n\nexport function isTimeFrozen() {\n    return frozen;\n}\n\n/**\n * Returns a promise resolved after the next microtask tick.\n *\n * @returns {Promise<void>}\n */\nexport function microTick() {\n    return new Promise(queueMicrotask);\n}\n\n/** @type {typeof cancelAnimationFrame} */\nexport function mockedCancelAnimationFrame(handle) {\n    if (!frozen) {\n        cancelAnimationFrame(handle);\n    }\n    timers.delete(animationToId(handle));\n}\n\n/** @type {typeof clearInterval} */\nexport function mockedClearInterval(intervalId) {\n    if (!frozen) {\n        clearInterval(intervalId);\n    }\n    timers.delete(intervalToId(intervalId));\n}\n\n/** @type {typeof clearTimeout} */\nexport function mockedClearTimeout(timeoutId) {\n    if (!frozen) {\n        clearTimeout(timeoutId);\n    }\n    timers.delete(timeoutToId(timeoutId));\n}\n\n/** @type {typeof requestAnimationFrame} */\nexport function mockedRequestAnimationFrame(callback) {\n    if (!allowTimers) {\n        return 0;\n    }\n\n    function handler() {\n        mockedCancelAnimationFrame(handle);\n        return callback(now());\n    }\n\n    const animationValues = [handler, now(), frameDelay];\n    const handle = frozen ? nextDummyId++ : requestAnimationFrame(handler);\n    const internalId = animationToId(handle);\n    timers.set(internalId, animationValues);\n\n    return handle;\n}\n\n/** @type {typeof setInterval} */\nexport function mockedSetInterval(callback, ms, ...args) {\n    if (!allowTimers) {\n        return 0;\n    }\n\n    ms = parseNat(ms);\n\n    function handler() {\n        if (allowTimers) {\n            intervalValues[1] = $max(now(), intervalValues[1] + ms);\n        } else {\n            mockedClearInterval(intervalId);\n        }\n        return callback(...args);\n    }\n\n    const intervalValues = [handler, now(), ms];\n    const intervalId = frozen ? nextDummyId++ : setInterval(handler, ms);\n    const internalId = intervalToId(intervalId);\n    timers.set(internalId, intervalValues);\n\n    return intervalId;\n}\n\n/** @type {typeof setTimeout} */\nexport function mockedSetTimeout(callback, ms, ...args) {\n    if (!allowTimers) {\n        return 0;\n    }\n\n    ms = parseNat(ms);\n\n    function handler() {\n        mockedClearTimeout(timeoutId);\n        return callback(...args);\n    }\n\n    const timeoutValues = [handler, now(), ms];\n    const timeoutId = frozen ? nextDummyId++ : setTimeout(handler, ms);\n    const internalId = timeoutToId(timeoutId);\n    timers.set(internalId, timeoutValues);\n\n    return timeoutId;\n}\n\nexport function resetTimeOffset() {\n    timeOffset = 0;\n}\n\n/**\n * Calculates the amount of time needed to run all current timeouts, intervals and\n * animations, and then advances the current time by that amount.\n *\n * @see {@link advanceTime}\n * @param {AdvanceTimeOptions} [options]\n * @returns {Promise<number>} time consumed by timers (in ms).\n */\nexport function runAllTimers(options) {\n    if (!timers.size) {\n        return 0;\n    }\n\n    const endts = $max(...[...timers.values()].map(([, init, delay]) => init + delay));\n    return advanceTime($ceil(endts - now()), options);\n}\n\n/**\n * Sets the current frame rate (in fps) used by animation frames (default to 60fps).\n *\n * @param {number} frameRate\n */\nexport function setFrameRate(frameRate) {\n    frameRate = parseNat(frameRate);\n    if (frameRate < 1 || frameRate > 1000) {\n        throw new HootTimingError(\"frame rate must be an number between 1 and 1000\");\n    }\n    frameDelay = 1000 / frameRate;\n}\n\nexport function setupTime() {\n    allowTimers = true;\n}\n\n/**\n * Returns a promise resolved after the next task tick.\n *\n * @returns {Promise<void>}\n */\nexport function tick() {\n    return delay();\n}\n\n/**\n * Returns a promise fulfilled when the given `predicate` returns a truthy value,\n * with the value of the promise being the return value of the `predicate`.\n *\n * The `predicate` is run once initially, and then on each animation frame until\n * it succeeds or fail.\n *\n * The promise automatically rejects after a given `timeout` (defaults to 5 seconds).\n *\n * @template T\n * @param {(last: boolean) => T} predicate\n * @param {WaitOptions} [options]\n * @returns {Promise<T>}\n * @example\n *  await waitUntil(() => []); // -> []\n * @example\n *  const button = await waitUntil(() => queryOne(\"button:visible\"));\n *  button.click();\n */\nexport async function waitUntil(predicate, options) {\n    await Promise.resolve();\n\n    // Early check before running the loop\n    const result = predicate(false);\n    if (result) {\n        return result;\n    }\n\n    const timeout = $floor(options?.timeout ?? 200);\n    const maxFrameCount = $ceil(timeout / frameDelay);\n    let frameCount = 0;\n    let handle;\n    return new Promise((resolve, reject) => {\n        function runCheck() {\n            const isLast = ++frameCount >= maxFrameCount;\n            const result = predicate(isLast);\n            if (result) {\n                resolve(result);\n            } else if (!isLast) {\n                handle = requestAnimationFrame(runCheck);\n            } else {\n                let message =\n                    options?.message || `'waitUntil' timed out after %timeout% milliseconds`;\n                if (typeof message === \"function\") {\n                    message = message();\n                }\n                if (isInstanceOf(message, Error)) {\n                    reject(message);\n                } else {\n                    reject(new HootTimingError(message.replace(\"%timeout%\", String(timeout))));\n                }\n            }\n        }\n\n        handle = requestAnimationFrame(runCheck);\n    }).finally(() => {\n        cancelAnimationFrame(handle);\n    });\n}\n\n/**\n * Manually resolvable and rejectable promise. It introduces 2 new methods:\n *  - {@link reject} rejects the deferred with the given reason;\n *  - {@link resolve} resolves the deferred with the given value.\n *\n * @template [T=unknown]\n */\nexport class Deferred extends Promise {\n    /** @type {typeof Promise.resolve<T>} */\n    _resolve;\n    /** @type {typeof Promise.reject<T>} */\n    _reject;\n\n    /**\n     * @param {(resolve: (value?: T) => any, reject: (reason?: any) => any) => any} [executor]\n     */\n    constructor(executor) {\n        let _resolve, _reject;\n\n        super(function deferredResolver(resolve, reject) {\n            _resolve = resolve;\n            _reject = reject;\n            executor?.(_resolve, _reject);\n        });\n\n        this._resolve = _resolve;\n        this._reject = _reject;\n    }\n\n    /**\n     * @param {any} [reason]\n     */\n    async reject(reason) {\n        return this._reject(reason);\n    }\n\n    /**\n     * @param {T} [value]\n     */\n    async resolve(value) {\n        return this._resolve(value);\n    }\n}\n", "/** @odoo-module alias=@odoo/hoot-dom default=false */\n\nimport * as dom from \"./helpers/dom\";\nimport * as events from \"./helpers/events\";\nimport * as time from \"./helpers/time\";\nimport { interactor } from \"./hoot_dom_utils\";\n\n/**\n * @typedef {import(\"./helpers/dom\").Dimensions} Dimensions\n * @typedef {import(\"./helpers/dom\").FormatXmlOptions} FormatXmlOptions\n * @typedef {import(\"./helpers/dom\").Position} Position\n * @typedef {import(\"./helpers/dom\").QueryOptions} QueryOptions\n * @typedef {import(\"./helpers/dom\").QueryRectOptions} QueryRectOptions\n * @typedef {import(\"./helpers/dom\").QueryTextOptions} QueryTextOptions\n * @typedef {import(\"./helpers/dom\").Target} Target\n *\n * @typedef {import(\"./helpers/events\").DragHelpers} DragHelpers\n * @typedef {import(\"./helpers/events\").DragOptions} DragOptions\n * @typedef {import(\"./helpers/events\").EventType} EventType\n * @typedef {import(\"./helpers/events\").FillOptions} FillOptions\n * @typedef {import(\"./helpers/events\").InputValue} InputValue\n * @typedef {import(\"./helpers/events\").KeyStrokes} KeyStrokes\n * @typedef {import(\"./helpers/events\").PointerOptions} PointerOptions\n */\n\nexport {\n    formatXml,\n    getActiveElement,\n    getFocusableElements,\n    getNextFocusableElement,\n    getParentFrame,\n    getPreviousFocusableElement,\n    isDisplayed,\n    isEditable,\n    isFocusable,\n    isInDOM,\n    isInViewPort,\n    isScrollable,\n    isVisible,\n    matches,\n    queryAll,\n    queryAllAttributes,\n    queryAllProperties,\n    queryAllRects,\n    queryAllTexts,\n    queryAllValues,\n    queryAny,\n    queryAttribute,\n    queryFirst,\n    queryOne,\n    queryRect,\n    queryText,\n    queryValue,\n} from \"./helpers/dom\";\nexport { on } from \"./helpers/events\";\nexport {\n    animationFrame,\n    cancelAllTimers,\n    Deferred,\n    delay,\n    freezeTime,\n    unfreezeTime,\n    microTick,\n    setFrameRate,\n    tick,\n    waitUntil,\n} from \"./helpers/time\";\n\n//-----------------------------------------------------------------------------\n// Interactors\n//-----------------------------------------------------------------------------\n\n// DOM\nexport const observe = interactor(\"query\", dom.observe);\nexport const waitFor = interactor(\"query\", dom.waitFor);\nexport const waitForNone = interactor(\"query\", dom.waitForNone);\n\n// Events\nexport const check = interactor(\"interaction\", events.check);\nexport const clear = interactor(\"interaction\", events.clear);\nexport const click = interactor(\"interaction\", events.click);\nexport const dblclick = interactor(\"interaction\", events.dblclick);\nexport const drag = interactor(\"interaction\", events.drag);\nexport const edit = interactor(\"interaction\", events.edit);\nexport const fill = interactor(\"interaction\", events.fill);\nexport const hover = interactor(\"interaction\", events.hover);\nexport const keyDown = interactor(\"interaction\", events.keyDown);\nexport const keyUp = interactor(\"interaction\", events.keyUp);\nexport const leave = interactor(\"interaction\", events.leave);\nexport const manuallyDispatchProgrammaticEvent = interactor(\"interaction\", events.dispatch);\nexport const middleClick = interactor(\"interaction\", events.middleClick);\nexport const pointerDown = interactor(\"interaction\", events.pointerDown);\nexport const pointerUp = interactor(\"interaction\", events.pointerUp);\nexport const press = interactor(\"interaction\", events.press);\nexport const resize = interactor(\"interaction\", events.resize);\nexport const rightClick = interactor(\"interaction\", events.rightClick);\nexport const scroll = interactor(\"interaction\", events.scroll);\nexport const select = interactor(\"interaction\", events.select);\nexport const setInputFiles = interactor(\"interaction\", events.setInputFiles);\nexport const setInputRange = interactor(\"interaction\", events.setInputRange);\nexport const uncheck = interactor(\"interaction\", events.uncheck);\nexport const unload = interactor(\"interaction\", events.unload);\n\n// Time\nexport const advanceFrame = interactor(\"time\", time.advanceFrame);\nexport const advanceTime = interactor(\"time\", time.advanceTime);\nexport const runAllTimers = interactor(\"time\", time.runAllTimers);\n\n// Debug\nexport { exposeHelpers } from \"./hoot_dom_utils\";\n", "/** @odoo-module */\n\n/**\n * @typedef {ArgumentPrimitive | `${ArgumentPrimitive}[]` | null} ArgumentType\n *\n * @typedef {\"any\"\n *  | \"bigint\"\n *  | \"boolean\"\n *  | \"error\"\n *  | \"function\"\n *  | \"integer\"\n *  | \"node\"\n *  | \"number\"\n *  | \"object\"\n *  | \"regex\"\n *  | \"string\"\n *  | \"symbol\"\n *  | \"undefined\"} ArgumentPrimitive\n *\n * @typedef {[string, string | undefined, any[], any]} InteractionDetails\n *\n * @typedef {\"interaction\" | \"query\" | \"server\" | \"time\"} InteractionType\n */\n\n/**\n * @template T\n * @typedef {T | Iterable<T>} MaybeIterable\n */\n\n/**\n * @template T\n * @typedef {T | PromiseLike<T>} MaybePromise\n */\n\n//-----------------------------------------------------------------------------\n// Global\n//-----------------------------------------------------------------------------\n\nconst {\n    Array: { isArray: $isArray },\n    matchMedia,\n    navigator: { userAgent: $userAgent },\n    Object: { assign: $assign, getPrototypeOf: $getPrototypeOf },\n    RegExp,\n    SyntaxError,\n} = globalThis;\nconst $toString = Object.prototype.toString;\n\n//-----------------------------------------------------------------------------\n// Internal\n//-----------------------------------------------------------------------------\n\n/**\n * @template {(...args: any[]) => any} T\n * @param {InteractionType} type\n * @param {T} fn\n * @param {string} name\n * @param {string} [alias]\n * @returns {T}\n */\nfunction makeInteractorFn(type, fn, name, alias) {\n    return {\n        [alias || name](...args) {\n            const result = fn(...args);\n            if (isInstanceOf(result, Promise)) {\n                for (let i = 0; i < args.length; i++) {\n                    if (isInstanceOf(args[i], Promise)) {\n                        // Get promise result for async arguments if possible\n                        args[i].then((result) => (args[i] = result));\n                    }\n                }\n                return result.then((promiseResult) =>\n                    dispatchInteraction(type, name, alias, args, promiseResult)\n                );\n            } else {\n                return dispatchInteraction(type, name, alias, args, result);\n            }\n        },\n    }[alias || name];\n}\n\nfunction polyfillIsError(value) {\n    return $toString.call(value) === \"[object Error]\";\n}\n\nconst GRAYS = {\n    100: \"#f1f5f9\",\n    200: \"#e2e8f0\",\n    300: \"#cbd5e1\",\n    400: \"#94a3b8\",\n    500: \"#64748b\",\n    600: \"#475569\",\n    700: \"#334155\",\n    800: \"#1e293b\",\n    900: \"#0f172a\",\n};\n\nconst COLORS = {\n    default: {\n        // Generic colors\n        black: \"#000000\",\n        white: \"#ffffff\",\n\n        // Grays\n        \"gray-100\": GRAYS[100],\n        \"gray-200\": GRAYS[200],\n        \"gray-300\": GRAYS[300],\n        \"gray-400\": GRAYS[400],\n        \"gray-500\": GRAYS[500],\n        \"gray-600\": GRAYS[600],\n        \"gray-700\": GRAYS[700],\n        \"gray-800\": GRAYS[800],\n        \"gray-900\": GRAYS[900],\n    },\n    light: {\n        // Generic colors\n        primary: \"#714b67\",\n        secondary: \"#74b4b9\",\n        amber: \"#f59e0b\",\n        \"amber-900\": \"#fef3c7\",\n        blue: \"#3b82f6\",\n        \"blue-900\": \"#dbeafe\",\n        cyan: \"#0891b2\",\n        \"cyan-900\": \"#e0f2fe\",\n        emerald: \"#047857\",\n        \"emerald-900\": \"#ecfdf5\",\n        gray: GRAYS[400],\n        lime: \"#84cc16\",\n        \"lime-900\": \"#f7fee7\",\n        orange: \"#ea580c\",\n        \"orange-900\": \"#ffedd5\",\n        purple: \"#581c87\",\n        \"purple-900\": \"#f3e8ff\",\n        rose: \"#9f1239\",\n        \"rose-900\": \"#fecdd3\",\n\n        // App colors\n        bg: GRAYS[100],\n        text: GRAYS[900],\n        \"status-bg\": GRAYS[300],\n        \"link-text-hover\": \"var(--primary)\",\n        \"btn-bg\": \"#714b67\",\n        \"btn-bg-hover\": \"#624159\",\n        \"btn-text\": \"#ffffff\",\n        \"bg-result\": \"rgba(255, 255, 255, 0.6)\",\n        \"border-result\": GRAYS[300],\n        \"border-search\": \"#d8dadd\",\n        \"shadow-opacity\": 0.1,\n\n        // HootReporting colors\n        \"bg-report\": \"#ffffff\",\n        \"text-report\": \"#202124\",\n        \"border-report\": \"#f0f0f0\",\n        \"bg-report-error\": \"#fff0f0\",\n        \"text-report-error\": \"#ff0000\",\n        \"border-report-error\": \"#ffd6d6\",\n        \"text-report-number\": \"#1a1aa6\",\n        \"text-report-string\": \"#c80000\",\n        \"text-report-key\": \"#881280\",\n        \"text-report-html-tag\": \"#881280\",\n        \"text-report-html-id\": \"#1a1aa8\",\n        \"text-report-html-class\": \"#994500\",\n    },\n    dark: {\n        // Generic colors\n        primary: \"#14b8a6\",\n        amber: \"#fbbf24\",\n        \"amber-900\": \"#422006\",\n        blue: \"#60a5fa\",\n        \"blue-900\": \"#172554\",\n        cyan: \"#22d3ee\",\n        \"cyan-900\": \"#083344\",\n        emerald: \"#34d399\",\n        \"emerald-900\": \"#064e3b\",\n        gray: GRAYS[500],\n        lime: \"#bef264\",\n        \"lime-900\": \"#365314\",\n        orange: \"#fb923c\",\n        \"orange-900\": \"#431407\",\n        purple: \"#a855f7\",\n        \"purple-900\": \"#3b0764\",\n        rose: \"#fb7185\",\n        \"rose-900\": \"#4c0519\",\n\n        // App colors\n        bg: GRAYS[900],\n        text: GRAYS[100],\n        \"status-bg\": GRAYS[700],\n        \"btn-bg\": \"#00dac5\",\n        \"btn-bg-hover\": \"#00c1ae\",\n        \"btn-text\": \"#000000\",\n        \"bg-result\": \"rgba(0, 0, 0, 0.5)\",\n        \"border-result\": GRAYS[600],\n        \"border-search\": \"#3c3f4c\",\n        \"shadow-opacity\": 0.4,\n\n        // HootReporting colors\n        \"bg-report\": \"#202124\",\n        \"text-report\": \"#e8eaed\",\n        \"border-report\": \"#3a3a3a\",\n        \"bg-report-error\": \"#290000\",\n        \"text-report-error\": \"#ff8080\",\n        \"border-report-error\": \"#5c0000\",\n        \"text-report-number\": \"#9980ff\",\n        \"text-report-string\": \"#f28b54\",\n        \"text-report-key\": \"#5db0d7\",\n        \"text-report-html-tag\": \"#5db0d7\",\n        \"text-report-html-id\": \"#f29364\",\n        \"text-report-html-class\": \"#9bbbdc\",\n    },\n};\nconst DEBUG_NAMESPACE = \"hoot\";\n\nconst isError = typeof Error.isError === \"function\" ? Error.isError : polyfillIsError;\nconst interactionBus = new EventTarget();\nconst preferredColorScheme = matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n\n//-----------------------------------------------------------------------------\n// Exports\n//-----------------------------------------------------------------------------\n\n/**\n * @param {Iterable<InteractionType>} types\n * @param {(event: CustomEvent<InteractionDetails>) => any} callback\n */\nexport function addInteractionListener(types, callback) {\n    for (const type of types) {\n        interactionBus.addEventListener(type, callback);\n    }\n\n    return function removeInteractionListener() {\n        for (const type of types) {\n            interactionBus.removeEventListener(type, callback);\n        }\n    };\n}\n\n/**\n * @param {InteractionType} type\n * @param {string} name\n * @param {string | undefined} alias\n * @param {any[]} args\n * @param {any} returnValue\n */\nexport function dispatchInteraction(type, name, alias, args, returnValue) {\n    interactionBus.dispatchEvent(\n        new CustomEvent(type, {\n            detail: [name, alias, args, returnValue],\n        })\n    );\n    return returnValue;\n}\n\n/**\n * @param {...any} helpers\n */\nexport function exposeHelpers(...helpers) {\n    let nameSpaceIndex = 1;\n    let nameSpace = DEBUG_NAMESPACE;\n    while (nameSpace in globalThis) {\n        nameSpace = `${DEBUG_NAMESPACE}${nameSpaceIndex++}`;\n    }\n    globalThis[nameSpace] = new HootDebugHelpers(...helpers);\n    return nameSpace;\n}\n\n/**\n * @param {keyof typeof COLORS} [scheme]\n */\nexport function getAllColors(scheme) {\n    return scheme ? COLORS[scheme] : COLORS;\n}\n\n/**\n * @param {keyof typeof COLORS[\"light\"]} varName\n */\nexport function getColorHex(varName) {\n    return COLORS[preferredColorScheme][varName];\n}\n\nexport function getPreferredColorScheme() {\n    return preferredColorScheme;\n}\n\n/**\n * @param {Node} node\n */\nexport function getTag(node) {\n    return node?.nodeName?.toLowerCase() || \"\";\n}\n\n/**\n * @template {(...args: any[]) => any} T\n * @param {InteractionType} type\n * @param {T} fn\n * @returns {T & {\n *  as: (name: string) => T;\n *  readonly silent: T;\n * }}\n */\nexport function interactor(type, fn) {\n    return $assign(makeInteractorFn(type, fn, fn.name), {\n        as(alias) {\n            return makeInteractorFn(type, fn, fn.name, alias);\n        },\n        get silent() {\n            return fn;\n        },\n    });\n}\n\n/**\n * @returns {boolean}\n */\nexport function isFirefox() {\n    return /firefox/i.test($userAgent);\n}\n\n/**\n * Cross-realm equivalent to 'instanceof'.\n * Can be called with multiple constructors, and will return true if the given object\n * is an instance of any of them.\n *\n * @param {unknown} instance\n * @param {...{ name: string }} classes\n */\nexport function isInstanceOf(instance, ...classes) {\n    if (!classes.length) {\n        return instance instanceof classes[0];\n    }\n    if (!instance || Object(instance) !== instance) {\n        // Object is falsy or a primitive (null, undefined and primitives cannot be the instance of anything)\n        return false;\n    }\n    for (const cls of classes) {\n        if (instance instanceof cls) {\n            return true;\n        }\n        const targetName = cls.name;\n        if (!targetName) {\n            return false;\n        }\n        if (targetName === \"Array\") {\n            return $isArray(instance);\n        }\n        if (targetName === \"Error\") {\n            return isError(instance);\n        }\n        if ($toString.call(instance) === `[object ${targetName}]`) {\n            return true;\n        }\n        let { constructor } = instance;\n        while (constructor) {\n            if (constructor.name === targetName) {\n                return true;\n            }\n            constructor = $getPrototypeOf(constructor);\n        }\n    }\n    return false;\n}\n\n/**\n * Returns whether the given object is iterable (*excluding strings*).\n *\n * @template T\n * @template {T | Iterable<T>} V\n * @param {V} object\n * @returns {V extends Iterable<T> ? true : false}\n */\nexport function isIterable(object) {\n    return !!(object && typeof object === \"object\" && object[Symbol.iterator]);\n}\n\n/**\n * @param {string} value\n * @param {{ safe?: boolean }} [options]\n * @returns {string | RegExp}\n */\nexport function parseRegExp(value, options) {\n    const regexParams = value.match(R_REGEX);\n    if (regexParams) {\n        const unified = regexParams[1].replace(R_WHITE_SPACE, \"\\\\s+\");\n        const flag = regexParams[2];\n        try {\n            return new RegExp(unified, flag);\n        } catch (error) {\n            if (isInstanceOf(error, SyntaxError) && options?.safe) {\n                return value;\n            } else {\n                throw error;\n            }\n        }\n    }\n    return value;\n}\n\n/**\n * @param {Node} node\n * @param {{ raw?: boolean }} [options]\n */\nexport function toSelector(node, options) {\n    const tagName = getTag(node);\n    const id = node.id ? `#${node.id}` : \"\";\n    const classNames = node.classList\n        ? [...node.classList].map((className) => `.${className}`)\n        : [];\n    if (options?.raw) {\n        return { tagName, id, classNames };\n    } else {\n        return [tagName, id, ...classNames].join(\"\");\n    }\n}\n\nexport class HootDebugHelpers {\n    /**\n     * @param {...any} helpers\n     */\n    constructor(...helpers) {\n        $assign(this, ...helpers);\n    }\n}\n\nexport const REGEX_MARKER = \"/\";\n\n// Common regular expressions\nexport const R_REGEX = new RegExp(`^${REGEX_MARKER}(.*)${REGEX_MARKER}([dgimsuvy]+)?$`);\nexport const R_WHITE_SPACE = /\\s+/g;\n", "import { session } from \"@web/session\";\nimport { utils } from \"@web/core/ui/ui_service\";\nimport * as hoot from \"@odoo/hoot-dom\";\nimport { pick } from \"@web/core/utils/objects\";\n\n/**\n * @typedef TourStep\n * @property {\"enterprise\"|\"community\"|\"mobile\"|\"desktop\"|HootSelector[][]} isActive Active the step following {@link isActiveStep} filter\n * @property {string} [id]\n * @property {HootSelector} trigger The node on which the action will be executed.\n * @property {string} [content] Description of the step.\n * @property {\"top\" | \"bottom\" | \"left\" | \"right\"} [position] The position where the UI helper is shown.\n * @property {RunCommand} [run] The action to perform when trigger conditions are verified.\n * @property {number} [timeout] By default, when the trigger node isn't found after 10000 milliseconds, it throws an error.\n * You can change this value to lengthen or shorten the time before the error occurs [ms].\n */\nexport class TourStep {\n    constructor(data, tour) {\n        Object.assign(this, data);\n        this.tour = tour;\n    }\n\n    /**\n     * Check if a step is active dependant on step.isActive property\n     * Note that when step.isActive is not defined, the step is active by default.\n     * When a step is not active, it's just skipped and the tour continues to the next step.\n     */\n    get active() {\n        this.checkHasTour();\n        const mode = this.tour.mode;\n        const isSmall = utils.isSmall();\n        const standardKeyWords = [\"enterprise\", \"community\", \"mobile\", \"desktop\", \"auto\", \"manual\"];\n        const isActiveArray = Array.isArray(this.isActive) ? this.isActive : [];\n        if (isActiveArray.length === 0) {\n            return true;\n        }\n        const selectors = isActiveArray.filter((key) => !standardKeyWords.includes(key));\n        if (selectors.length) {\n            // if one of selectors is not found, step is skipped\n            for (const selector of selectors) {\n                const el = hoot.queryFirst(selector);\n                if (!el) {\n                    return false;\n                }\n            }\n        }\n        const checkMode =\n            isActiveArray.includes(mode) ||\n            (!isActiveArray.includes(\"manual\") && !isActiveArray.includes(\"auto\"));\n        const edition =\n            (session.server_version_info || \"\").at(-1) === \"e\" ? \"enterprise\" : \"community\";\n        const checkEdition =\n            isActiveArray.includes(edition) ||\n            (!isActiveArray.includes(\"enterprise\") && !isActiveArray.includes(\"community\"));\n        const onlyForMobile = isActiveArray.includes(\"mobile\") && isSmall;\n        const onlyForDesktop = isActiveArray.includes(\"desktop\") && !isSmall;\n        const checkDevice =\n            onlyForMobile ||\n            onlyForDesktop ||\n            (!isActiveArray.includes(\"mobile\") && !isActiveArray.includes(\"desktop\"));\n        return checkEdition && checkDevice && checkMode;\n    }\n\n    checkHasTour() {\n        if (!this.tour) {\n            throw new Error(`TourStep instance must have a tour`);\n        }\n    }\n\n    get describeMe() {\n        this.checkHasTour();\n        return (\n            `[${this.index + 1}/${this.tour.steps.length}] Tour ${this.tour.name} \u2192 Step ` +\n            (this.content ? `${this.content} (trigger: ${this.trigger})` : this.trigger)\n        );\n    }\n\n    get stringify() {\n        return (\n            JSON.stringify(\n                pick(\n                    this,\n                    \"isActive\",\n                    \"content\",\n                    \"trigger\",\n                    \"run\",\n                    \"tooltipPosition\",\n                    \"timeout\",\n                    \"expectUnloadPage\"\n                ),\n                (_key, value) => {\n                    if (typeof value === \"function\") {\n                        return \"[function]\";\n                    } else {\n                        return value;\n                    }\n                },\n                2\n            ) + \",\"\n        );\n    }\n}\n", "import { tourState } from \"@web_tour/js/tour_state\";\nimport { debounce } from \"@web/core/utils/timing\";\nimport * as hoot from \"@odoo/hoot-dom\";\nimport { utils } from \"@web/core/ui/ui_service\";\nimport { TourStep } from \"@web_tour/js/tour_step\";\nimport { MacroMutationObserver } from \"@web/core/macro\";\nimport { getScrollParent } from \"@web_tour/js/utils/tour_utils\";\n\n/**\n * @typedef ConsumeEvent\n * @property {string} name\n * @property {Element} target\n * @property {(ev: Event) => boolean} conditional\n */\n\nexport class TourInteractive {\n    mode = \"manual\";\n    currentAction;\n    currentActionIndex;\n    anchorEls = [];\n    removeListeners = () => {};\n\n    /**\n     * @param {Tour} data\n     */\n    constructor(data) {\n        Object.assign(this, data);\n        this.steps = this.steps.map((step) => new TourStep(step, this));\n        this.actions = this.steps.flatMap((s) => this.getSubActions(s));\n        this.isBusy = false;\n    }\n\n    /**\n     * @param {import(\"@web_tour/js/tour_pointer/tour_pointer\").TourPointer} pointer\n     * @param {Function} onTourEnd\n     */\n    start(env, pointer, onTourEnd) {\n        env.bus.addEventListener(\"ACTION_MANAGER:UPDATE\", () => (this.isBusy = true));\n        env.bus.addEventListener(\"ACTION_MANAGER:UI-UPDATED\", () => (this.isBusy = false));\n\n        this.pointer = pointer;\n        this.debouncedToggleOpen = debounce(this.pointer.showContent, 50, true);\n        this.onTourEnd = onTourEnd;\n        this.observer = new MacroMutationObserver(() => this._onMutation());\n        this.observer.observe(document.body);\n        this.currentActionIndex = tourState.getCurrentIndex();\n        this.play();\n    }\n\n    backward() {\n        let tempIndex = this.currentActionIndex;\n        let tempAction,\n            tempAnchors = [];\n        while (!tempAnchors.length && tempIndex >= 0) {\n            tempIndex--;\n            tempAction = this.actions.at(tempIndex);\n            if (!tempAction.step.active || tempAction.event === \"warn\") {\n                continue;\n            }\n            tempAnchors = tempAction && this.findTriggers(tempAction.anchor);\n        }\n\n        if (tempIndex >= 0) {\n            this.currentActionIndex = tempIndex;\n            this.play();\n        }\n    }\n\n    /**\n     * @returns {HTMLElement[]}\n     */\n    findTriggers(anchor) {\n        if (!anchor) {\n            anchor = this.currentAction.anchor;\n        }\n\n        return anchor\n            .split(/,\\s*(?![^(]*\\))/)\n            .map((part) => hoot.queryFirst(part, { visible: true }))\n            .filter((el) => !!el)\n            .map((el) => this.getAnchorEl(el, this.currentAction.event))\n            .filter((el) => !!el);\n    }\n\n    play() {\n        this.removeListeners();\n        if (this.currentActionIndex === this.actions.length) {\n            this.observer.disconnect();\n            this.onTourEnd();\n            return;\n        }\n\n        this.currentAction = this.actions.at(this.currentActionIndex);\n\n        if (!this.currentAction.step.active || this.currentAction.event === \"warn\") {\n            if (this.currentAction.event === \"warn\") {\n                console.warn(`Step '${this.currentAction.anchor}' ignored.`);\n            }\n            this.currentActionIndex++;\n            this.play();\n            return;\n        }\n\n        console.log(this.currentAction.event, this.currentAction.anchor);\n\n        tourState.setCurrentIndex(this.currentActionIndex);\n        this.anchorEls = this.findTriggers();\n        this.setActionListeners();\n        this.updatePointer();\n    }\n\n    updatePointer() {\n        if (this.anchorEls.length) {\n            this.pointer.pointTo(\n                this.anchorEls[0],\n                this.currentAction.pointerInfo,\n                this.currentAction.event === \"drop\"\n            );\n            this.pointer.setState({\n                onMouseEnter: () => this.debouncedToggleOpen(true),\n                onMouseLeave: () => this.debouncedToggleOpen(false),\n            });\n        }\n    }\n\n    setActionListeners() {\n        const cleanups = this.anchorEls.flatMap((anchorEl, index) => {\n            const toListen = {\n                anchorEl,\n                consumeEvents: this.getConsumeEventType(anchorEl, this.currentAction.event),\n                onConsume: () => {\n                    this.pointer.hide();\n                    this.currentActionIndex++;\n                    this.play();\n                },\n                onError: () => {\n                    if (this.currentAction.event === \"drop\") {\n                        this.pointer.hide();\n                        this.currentActionIndex--;\n                        this.play();\n                    }\n                },\n            };\n            if (index === 0) {\n                return this.setupListeners({\n                    ...toListen,\n                    onMouseEnter: () => this.pointer.showContent(true),\n                    onMouseLeave: () => this.pointer.showContent(false),\n                    onScroll: () => this.updatePointer(),\n                });\n            } else {\n                return this.setupListeners({\n                    ...toListen,\n                    onScroll: () => {},\n                });\n            }\n        });\n        this.removeListeners = () => {\n            this.anchorEls = [];\n            while (cleanups.length) {\n                cleanups.pop()();\n            }\n        };\n    }\n\n    /**\n     * @param {HTMLElement} params.anchorEl\n     * @param {import(\"../../tour_utils\").ConsumeEvent[]} params.consumeEvents\n     * @param {() => void} params.onMouseEnter\n     * @param {() => void} params.onMouseLeave\n     * @param {(ev: Event) => any} params.onScroll\n     * @param {(ev: Event) => any} params.onConsume\n     * @param {() => any} params.onError\n     */\n    setupListeners({\n        anchorEl,\n        consumeEvents,\n        onMouseEnter,\n        onMouseLeave,\n        onScroll,\n        onConsume,\n        onError = () => {},\n    }) {\n        consumeEvents = consumeEvents.map((c) => ({\n            target: c.target,\n            type: c.name,\n            listener: function (ev) {\n                if (!c.conditional || c.conditional(ev)) {\n                    onConsume();\n                } else {\n                    onError();\n                }\n            },\n        }));\n\n        for (const consume of consumeEvents) {\n            consume.target.addEventListener(consume.type, consume.listener, true);\n        }\n        anchorEl.addEventListener(\"mouseenter\", onMouseEnter);\n        anchorEl.addEventListener(\"mouseleave\", onMouseLeave);\n\n        const cleanups = [\n            () => {\n                for (const consume of consumeEvents) {\n                    consume.target.removeEventListener(consume.type, consume.listener, true);\n                }\n                anchorEl.removeEventListener(\"mouseenter\", onMouseEnter);\n                anchorEl.removeEventListener(\"mouseleave\", onMouseLeave);\n            },\n        ];\n\n        const scrollEl = getScrollParent(anchorEl);\n        if (scrollEl) {\n            const debouncedOnScroll = debounce(onScroll, 50);\n            scrollEl.addEventListener(\"scroll\", debouncedOnScroll);\n            cleanups.push(() => scrollEl.removeEventListener(\"scroll\", debouncedOnScroll));\n        }\n\n        return cleanups;\n    }\n\n    /**\n     *\n     * @param {import(\"../tour_service\").TourStep} step\n     * @returns {{\n     *  event: string,\n     *  anchor: string,\n     *  pointerInfo: { tooltipPosition: string?, content: string? },\n     * }[]}\n     */\n    getSubActions(step) {\n        const actions = [];\n        if (!step.run || typeof step.run === \"function\") {\n            actions.push({\n                step,\n                event: \"warn\",\n                anchor: step.trigger,\n            });\n            return actions;\n        }\n\n        for (const todo of step.run.split(\"&&\")) {\n            const m = String(todo)\n                .trim()\n                .match(/^(?<action>\\w*) *\\(? *(?<arguments>.*?)\\)?$/);\n\n            let action = m.groups?.action;\n            const anchor = m.groups?.arguments || step.trigger;\n            const pointerInfo = {\n                content: step.content,\n                tooltipPosition: step.tooltipPosition,\n            };\n\n            if (action === \"drag_and_drop\") {\n                actions.push({\n                    step,\n                    event: \"drag\",\n                    anchor: step.trigger,\n                    pointerInfo,\n                });\n                action = \"drop\";\n            }\n\n            actions.push({\n                step,\n                event: action,\n                anchor: action === \"edit\" ? step.trigger : anchor,\n                pointerInfo,\n            });\n        }\n\n        return actions;\n    }\n\n    /**\n     * @param {HTMLElement} [element]\n     * @param {string} [runCommand]\n     * @returns {ConsumeEvent[]}\n     */\n    getConsumeEventType(element, runCommand) {\n        const consumeEvents = [];\n        if (runCommand === \"click\") {\n            consumeEvents.push({\n                name: \"click\",\n                target: element,\n            });\n\n            // Click on a field widget with an autocomplete should be also completed with a selection though Enter or Tab\n            // This case is for the steps that click on field_widget\n            if (element.querySelector(\".o-autocomplete--input\")) {\n                consumeEvents.push({\n                    name: \"keydown\",\n                    target: element.querySelector(\".o-autocomplete--input\"),\n                    conditional: (ev) =>\n                        [\"Tab\", \"Enter\"].includes(ev.key) &&\n                        ev.target.parentElement.querySelector(\n                            \".o-autocomplete--dropdown-item .ui-state-active\"\n                        ),\n                });\n            }\n\n            // Click on an element of a dropdown should be also completed with a selection though Enter or Tab\n            // This case is for the steps that click on a dropdown-item\n            if (element.closest(\".o-autocomplete--dropdown-menu\")) {\n                consumeEvents.push({\n                    name: \"keydown\",\n                    target: element.closest(\".o-autocomplete\").querySelector(\"input\"),\n                    conditional: (ev) => [\"Tab\", \"Enter\"].includes(ev.key),\n                });\n            }\n\n            // Press enter on a button do the same as a click\n            if (element.tagName === \"BUTTON\") {\n                consumeEvents.push({\n                    name: \"keydown\",\n                    target: element,\n                    conditional: (ev) => ev.key === \"Enter\",\n                });\n\n                // Pressing enter in the input group does the same as clicking on the button\n                if (element.closest(\".input-group\")) {\n                    for (const inputEl of element.parentElement.querySelectorAll(\"input\")) {\n                        consumeEvents.push({\n                            name: \"keydown\",\n                            target: inputEl,\n                            conditional: (ev) => ev.key === \"Enter\",\n                        });\n                    }\n                }\n            }\n        }\n\n        if ([\"fill\", \"edit\"].includes(runCommand)) {\n            if (\n                utils.isSmall() &&\n                element.closest(\".o_field_widget\")?.matches(\".o_field_many2one, .o_field_many2many\")\n            ) {\n                consumeEvents.push({\n                    name: \"click\",\n                    target: element,\n                });\n            } else {\n                consumeEvents.push({\n                    name: \"input\",\n                    target: element,\n                });\n                if (element.classList.contains(\"o-autocomplete--input\")) {\n                    consumeEvents.push({\n                        name: \"keydown\",\n                        target: element,\n                        conditional: (ev) => {\n                            if (\n                                [\"Tab\", \"Enter\"].includes(ev.key) &&\n                                ev.target.parentElement.querySelector(\n                                    \".o-autocomplete--dropdown-item .ui-state-active\"\n                                )\n                            ) {\n                                const nextStep = this.actions.at(this.currentActionIndex + 1);\n                                if (\n                                    this.findTriggers(nextStep.anchor)\n                                        .at(0)\n                                        ?.closest(\".o-autocomplete--dropdown-item\")\n                                ) {\n                                    // Skip the next step if the next one is a click on a dropdown item\n                                    this.currentActionIndex++;\n                                }\n                                return true;\n                            }\n                        },\n                    });\n                    consumeEvents.push({\n                        name: \"click\",\n                        target: element.ownerDocument,\n                        conditional: (ev) => {\n                            if (ev.target.closest(\".o-autocomplete--dropdown-item\")) {\n                                const nextStep = this.actions.at(this.currentActionIndex + 1);\n                                if (\n                                    this.findTriggers(nextStep.anchor)\n                                        .at(0)\n                                        ?.closest(\".o-autocomplete--dropdown-item\")\n                                ) {\n                                    // Skip the next step if the next one is a click on a dropdown item\n                                    this.currentActionIndex++;\n                                }\n                                return true;\n                            }\n                        },\n                    });\n                }\n            }\n        }\n\n        // Drag & drop run command\n        if (runCommand === \"drag\") {\n            consumeEvents.push({\n                name: \"pointerdown\",\n                target: element,\n            });\n        }\n\n        if (runCommand === \"drop\") {\n            consumeEvents.push({\n                name: \"pointerup\",\n                target: element.ownerDocument,\n                conditional: (ev) =>\n                    element.ownerDocument\n                        .elementsFromPoint(ev.clientX, ev.clientY)\n                        .includes(element),\n            });\n            consumeEvents.push({\n                name: \"drop\",\n                target: element.ownerDocument,\n                conditional: (ev) =>\n                    element.ownerDocument\n                        .elementsFromPoint(ev.clientX, ev.clientY)\n                        .includes(element),\n            });\n        }\n\n        return consumeEvents;\n    }\n\n    /**\n     * Returns the element that will be used in listening to the `consumeEvent`.\n     * @param {HTMLElement} el\n     * @param {string} consumeEvent\n     */\n    getAnchorEl(el, consumeEvent) {\n        if (consumeEvent === \"drag\") {\n            // jQuery-ui draggable triggers 'drag' events on the .ui-draggable element,\n            // but the tip is attached to the .ui-draggable-handle element which may\n            // be one of its children (or the element itself\n            return el.closest(\n                \".ui-draggable, .o_draggable, .o_we_draggable, .o-draggable, [draggable='true']\"\n            );\n        }\n\n        if (consumeEvent === \"input\" && ![\"textarea\", \"input\"].includes(el.tagName.toLowerCase())) {\n            return el.closest(\"[contenteditable='true']\");\n        }\n        if (consumeEvent === \"sort\") {\n            // when an element is dragged inside a sortable container (with classname\n            // 'ui-sortable'), jQuery triggers the 'sort' event on the container\n            return el.closest(\".ui-sortable, .o_sortable\");\n        }\n        return el;\n    }\n\n    _onMutation() {\n        if (this.currentAction) {\n            const tempAnchors = this.findTriggers();\n            if (\n                tempAnchors.length &&\n                (tempAnchors.some((a) => !this.anchorEls.includes(a)) ||\n                    this.anchorEls.some((a) => !tempAnchors.includes(a)))\n            ) {\n                this.removeListeners();\n                this.anchorEls = tempAnchors;\n                this.setActionListeners();\n            } else if (!tempAnchors.length && this.anchorEls.length) {\n                this.pointer.hide();\n                if (\n                    !hoot.queryFirst(\".o_home_menu\", { visible: true }) &&\n                    !hoot.queryFirst(\".dropdown-item.o_loading\", { visible: true }) &&\n                    !this.isBusy\n                ) {\n                    this.backward();\n                }\n                return;\n            }\n            this.updatePointer();\n        }\n    }\n}\n"], "file": "/web/assets/1/1e911b3/web_tour.interactive.js", "sourceRoot": "../../../../"}