// ==UserScript== // @name ChatGPT 助手 // @author Hmjz100 // @namespace github.com/hmjz100 // @version 1.0.6.1 // @description 《也许同类型中最好用?》系列 - 支持 模型切换器、降智检测、不发送浏览指纹(防降智)以及添加一个可拖动的语音按钮(点击会打开原始 LiveKit Meet)到镜像站。大屏小屏都能用! // @icon data:image/webp;base64,UklGRnICAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSI8AAAABd6A2kg06/T5EcqGIzkREILWfPrI5RTitte1pXprvniGwKHzbgMMgjMAkHAaB9HhUevJ9U+T/6T6i/wzctm0kde/27hVA1LJgHQAoiCX2Hvw3S62UnAc0rkOEtyHir7C+QPMbSg48lz0vKU1fl0ZUNyHRH8Pf4Tt0/Wb4Ow5/53QI3XzKKRVk8v/IxeB/BgBWUDggvAEAABALAJ0BKiAAIAA+aSqQRaQioZv6rABABoS0gAnAHJxnoP3rMm/kBkKFPL/yvok/1/878zXyp/g/cC/jv9A/x35fdjN7Hf7ANdIJ5yn1lcylRWD9msH+g4lCVG3DwlB27AAA/v/b7x/k//Og4rBakFx39Od09+fJ1x4C+DLOrn57IHc1Tex5U/5cB8l2f3fZBiD+5/7/IowL/9h145ee+E8Tn9Mx//ya2jMrsF0MWumdxn7zRJ7f7BEicBZVfKYEwPy/e9nUZPOkR9QyW1vCwpKLT4HeJVe7ayZ4dcQRfzMR0TeGbvw8U5I+jHciOCqPs43iwvLybOhARP2A/54Jx8B2XKJ3NgHNzVi3W2HkTpisnFaSV2NcPAhURRCHCp9wAwR12+PxPlPateeyuKpswYl1DEzNt/SkXzDPkSq/LyuOSsHuqlcmY8ch6wMlzG7JlrkvSIL3kuB6BS6sByZZXCNqJoa/4vqVhG8NdvpsjyX6hpz5DqxYMDrvqnl1//oedvi9KU2f/8I6YzFjiLYx5bH31D5eTMZBT8kqCpoNVnHwfQz5KqJRnU9pk4JVJRwRO0gt0GsehqDyZAAAAA== // @license MIT // @match *://chatgpt.com/* // @match *://chat.openai.com/* // @match *://*.oaifree.com/* // @match *://chat.rawchat.top/* // @match *://chat.rawchat.cc/* // @match *://chat.sharedchat.cn/* // @match *://chat.sharedchat.fun/* // @match *://chat.chatgptplus.cn/* // @match *://free.share-ai.top/* // @match *://go.github.cn.com/* // @match *://gpt.github.cn.com/* // @match *://share.github.cn.com/* // @match *://free.xyhelper.cn/* // @match *://*.xyhelper.com.cn/* // @match *://chat.freegpts.org/* // @match *://go.gptdsb.com/* // @match *://chat.gptdsb.com/* // @match *://www.opkfc.com/* // @match *://chatgpt.dairoot.cn/* // @match *://web.tu-zi.com/* // @match *://share.tu-zi.com/* // @match *://chatgpt.aicnm.cc/* // @match *://ai.ohsus.me/* // @match *://share.aivvm.org/* // @grant GM_setValue // @grant GM_getValue // @grant GM_openInTab // @grant unsafeWindow // @run-at document-start // @require https://unpkg.com/vue@3.4.27/dist/vue.global.prod.js // @require https://unpkg.com/jquery@3.6.3/dist/jquery.min.js // @downloadURL https://update.greasyfork.icu/scripts/514836/ChatGPT%20%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/514836/ChatGPT%20%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== (function () { 'use strict'; let interval = setInterval(() => { if (typeof unsafeWindow.createNoticePopup !== 'function') return; unsafeWindow.createNoticePopup = function () { }; clearInterval(interval); }, 1); let originalRequset = null; const originalFetch = window.fetch; unsafeWindow.fetch = async function (url, options) { url = new URL(url, location.href).href if (url.includes('sentinel/chat-requirements') && options.method === 'POST') { let response = await originalFetch(url, options); let res = await response.clone().text(); try { res = JSON.parse(res); } catch (e) { throw e } console.log('Pow 数据: \n', res); originalRequset = { url, options } const difficulty = res?.proofofwork?.difficulty ? res?.proofofwork?.difficulty : 'N/A'; updatePow(difficulty); } else if (url.includes("/backend-api/models") && options?.method === "GET") { let response = await originalFetch(url, options); let res = await response.clone().text(); try { res = JSON.parse(res); } catch (e) { } console.log('模型数据: \n', res); let final = res.models .map(model => { const extraModel = extraModels.find(extra => extra.slug === model.slug); const extraCategories = res.categories && Array.isArray(res.categories) ? res.categories.find(categorie => categorie.default_model === model.slug) : null; // 如果不匹配,那就加个默认图标和子标题 model.icon = iconModels["connected"] model.subTitle = model.title.replace("GPT-", "") if (extraCategories) { if (extraCategories.human_category_short_name) model.subTitle = extraCategories.human_category_short_name; if (extraCategories.short_explainer) model.description = extraCategories.short_explainer; } if (extraModel) { if (extraModel.title) model.title = extraModel.title; if (extraModel.description) model.description = extraModel.description; if (extraModel.icon) model.icon = extraModel.icon; if (extraModel.subTitle) model.subTitle = extraModel.subTitle; if (extraModel.beta) model.beta = extraModel.beta; if (extraModel.disabled) model.disabled = extraModel.disabled; } if (extraCategories) { if (extraCategories.icon_filled_src) model.icon.icon_filled_src = extraCategories.icon_filled_src; if (extraCategories.icon_outline_src) model.icon.icon_outline_src = extraCategories.icon_outline_src; if (extraCategories.is_beta) model.beta = extraCategories.is_beta; } return model; }) .sort((a, b) => { // 根据 extraModels 的顺序对匹配的项进行排序 const indexA = extraModels.findIndex(extra => extra.slug === a.slug); const indexB = extraModels.findIndex(extra => extra.slug === b.slug); // 如果都匹配 extraModels 中的项,则按 extraModels 顺序排序 if (indexA !== -1 && indexB !== -1) { return indexA - indexB; } // 如果只有一个匹配,已匹配项排在前面,未匹配项排后面 if (indexA !== -1) return -1; // a 在 b 前 if (indexB !== -1) return 1; // b 在 a 前 return 0; // 如果都没有匹配,不改变顺序 }); models.update(final); return response } else if (state.isEnabled && url.endsWith("/backend-api/conversation") && options?.method === "POST") { let res = { body: JSON.parse(options.body) }; console.log('消息数据: \n', res.body); res.body.model = state.selectedModelSlug; options = { ...options, body: JSON.stringify(res.body) }; } else if (url.includes("/backend-api/me") && options?.method === "GET") { const response = await originalFetch(url, options); let res = await response.clone().text(); try { res = JSON.parse(res); } catch (e) { } console.log('用户数据: \n', res); res.name = "语言如歌 - Language is song"; res.picture = "https://cdn.auth0.com/avatars/la.png"; res.email = "LanguageIsSong@100010001.com" res.phone_number = "100010001" return new Response(JSON.stringify(res), { status: response.status, statusText: response.statusText, headers: response.headers }) } else if (url.includes("/backend-api/model_icons") && options?.method === "GET") { const response = await originalFetch(url, options); let res = await response.clone().text(); try { res = JSON.parse(res); } catch (e) { } console.log('图标数据: \n', res); extraModels.forEach((item) => { if (item.iconSlug && item.icon) { if (!res.hasOwnProperty(item.iconSlug)) { res[item.iconSlug] = { ...item.icon }; } } }) return new Response(JSON.stringify(res), { status: response.status, statusText: response.statusText, headers: response.headers }) } else if (url.includes('/ces/v1/') || url.includes('/v1/rgstr') || url.includes('backend-api/lat/r') || url.includes('browser-intake-datadog')) { // 禁止分析用户浏览数据,以达到“无指纹”的效果。 throw new Error('【ChatGPT 助手】unAnalytics\n已屏蔽数据此数据收集器') } let response = await originalFetch(url, options); return response; }; // 隐藏原来的按钮 waitForKeyElements('div:not(#ChatGPTAssistant, #ChatGPTVoice-Button, #ChatGPTPow-Button, #immersive-translate-popup) svg.icon[width="25"][height="25"], div#voiceButton svg, #of-custom-floating-ball svg, div > div#livekit', function (element) { element.parent().hide(); }); waitForKeyElements('div.flex.w-full.gap-2.items-center.justify-center', function (element) { let text = element.text() if (text.trim().includes('实时语音')) { element.parent().hide(); } }) // 定义不同服务器的配置 let servers = { "new.oaifree.com": { apiPath: "/api/voice/link", apiType: "POST", url: "wss://webrtc.oaifree.com", model: new URL(location.href).searchParams.get('model'), mode: [['标准语音', '高级语音'], ['std', 'adv']], getToken: data => new URL(data.url).searchParams.get('token'), getHash: data => new URL(data.url).hash }, "chat.rawchat.top": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.rawchat.cc": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.sharedchat.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.sharedchat.fun": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.chatgptplus.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "free.share-ai.top": { apiPath: "/frontend-api/getVoice", apiType: "GET", url: data => (data.data.voiceServerUrl || data.data.url), getToken: data => data.data.token, getHash: data => data.data.e2ee_key }, "go.github.cn.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "gpt.github.cn.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "share.github.cn.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "free.xyhelper.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "free.xyhelper.com.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.freegpts.org": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "go.gptdsb.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.gptdsb.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "www.opkfc.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chatgpt.dairoot.cn": { apiPath: "/api/livekit", apiType: "GET", url: data => new URL(data.data).searchParams.get('liveKitUrl'), getToken: data => new URL(data.data).searchParams.get('token'), getHash: data => new URL(data.data).hash }, "web.tu-zi.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "share.tu-zi.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, }; let html = $(`
语音
状态
`) let button = html.find('#ChatGPTVoice-Button'); let buttonPow = html.find('#ChatGPTPow-Button'); let isDragging = false; let offsetY = 0; let dragStartTime; button.css('z-index', 114514 + 1) // 从 GM 获取按钮位置 if (GM_getValue('buttonTop')) { button.css('top', GM_getValue('buttonTop') + 'px'); } // 点击事件处理 button.on('click touchend', handleVoiceClick); buttonPow.on('click touchend', getPow); // 鼠标按下事件 button.on('mousedown touchstart', function (e) { e.preventDefault(); dragStartTime = Date.now(); // 记录拖动开始时间 offsetY = e.clientY - button.offset().top; }); // 鼠标移动事件 $(document).on('mousemove touchmove', function (e) { if (offsetY !== undefined) { let newTop = e.clientY - offsetY; const buttonHeight = button.outerHeight(); const windowHeight = $(window).height(); // 限制按钮位置 if (newTop < 0) newTop = 0; if (newTop + buttonHeight > windowHeight) newTop = windowHeight - buttonHeight; // 判断是否拖动 if (isDragging || (Date.now() - dragStartTime > 100)) { // 如果已经拖动或拖动时间超过100ms isDragging = true; button.addClass('is-dragging'); button.css('top', newTop + 'px'); GM_setValue('buttonTop', newTop); } } }); // 鼠标抬起事件 $(document).on('mouseup touchend', function () { if (isDragging) { setTimeout(function () { isDragging = false; button.removeClass('is-dragging'); }, 100) } offsetY = undefined; // 重置 offsetY }); setInterval(function () { if (!$('#ChatGPTAssistant').length) { $('#ChatGPTAssistant-Style, #ChatGPTVoice-Button, #ChatGPTPow-Button').remove() let host = location.hostname; let config = servers[host]; if (!config) button.remove() $('body').append(html); } }, 1000) // 绑定点击事件到新创建的按钮 async function handleVoiceClick(event) { if (!event?.currentTarget || isDragging) return; let element = $(event.currentTarget); if (element.attr('data-clicked') === 'true') return; element.attr('data-clicked', 'true'); // 异步获取语音链接 await goVoice(element).catch(function (error) { alert('获取语音对话(会议)链接错误: \n' + error.message); console.error(error); element.removeAttr('data-clicked'); }); }; async function goVoice(element) { // 获取当前服务器的域名 let host = location.hostname; // 获取服务器配置 let config = servers[host]; let res, data, url, token, hash; if (!config) { throw new Error(`未支持当前站点: ${host}`); } else { let extra = { method: config.apiType, headers: { 'Content-Type': 'application/json' } } if (config.model !== undefined && config.mode !== undefined && config.apiType === 'POST') { let model = config.model; let mode = config.mode; let modeChoice; if (mode && mode.length) { let modeOptions = mode[0] .map((name, index) => `(${index + 1}) ${name}`) .join(" "); let userChoice = prompt(`请选择语音模式: (不输入则使用${mode[0][0]})\n${modeOptions}`); let choiceIndex = parseInt(userChoice) - 1; if (choiceIndex >= 0 && choiceIndex < mode[1].length) { modeChoice = mode[1][choiceIndex]; } else if (userChoice === null) { return element.removeAttr('data-clicked'); } else { modeChoice = mode[1][0]; } } if (!model) { let userInput = prompt("请输入模型名称: (不输入则使用默认模型)"); if (userInput === null) { return element.removeAttr('data-clicked'); } model = userInput; } extra.body = JSON.stringify({ model, mode: modeChoice }); } // 发送请求到语音API res = await fetch(config.apiPath, extra); // 解析返回的JSON数据 data = await res.json(); console.log('服务数据: \n', data); // 检查是否有url或者token,否则抛出错误 function hasUrl(obj) { if (obj && typeof obj === 'object') { if ('url' in obj) return true; // 如果当前对象包含 url 属性,返回 true if ('data' in obj) return true; // 如果当前对象包含 url 属性,返回 true return Object.values(obj).some(hasUrl); // 递归检查嵌套的对象 } return false; } if (data && !hasUrl(data)) { throw new Error(data.detail || '语音服务未返回所需数据'); } // 获取url、token、hash url = typeof config.url === 'function' ? config.url(data) : config.url; token = config.getToken ? config.getToken(data) : null; hash = config.getHash ? config.getHash(data) : null; } // 打印日志方便调试 console.log('会议数据: \n', { token, hash, url }); // 检查是否有url或者token,否则抛出错误 if (!url || !token || !hash) throw new Error(data.detail || '语音服务未返回数据'); // 构建 meetUrl let meetUrl = new URL('https://meet.livekit.io/custom'); if (url) meetUrl.searchParams.set('liveKitUrl', url); if (token) meetUrl.searchParams.set('token', token); if (hash) meetUrl.hash = hash; // 打开新页面 GM_openInTab(meetUrl.href, { active: true, insert: true, setParent: true }) element.removeAttr('data-clicked'); } async function getPow() { if (!originalRequset) return; const { url, options } = originalRequset; try { const response = await fetch(url, options); let res = await response.clone().text(); try { res = JSON.parse(res); } catch (e) { } console.log('Pow 数据: \n', res); const difficulty = res?.proofofwork?.difficulty ? res?.proofofwork?.difficulty : 'N/A'; updatePow(difficulty); } catch (error) { console.error('重新请求失败:', error); } } // 更新难易度指示器 function updatePow(difficulty) { const result = difficulty === 'N/A' ? { color: '#888', secondaryColor: '#666', textColor: '#888', level: '未知', detail: '服务器未提供 PoW 难度值,可能是因为当前站点不是镜像站点\n(即使网站的 UI 界面与官方相似)' } : (() => { const hexValue = difficulty.replace('0x', '').replace(/^0+/, ''); const decimalValue = parseInt(hexValue, 16); const percentage = (100 - Math.min(decimalValue / 0x0FFFFF * 100, 100)).toFixed(2); const levels = [ { hexLength: 2, color: '#F44336', secondaryColor: '#d32f2f', level: '困难', detailSuffix: '会员模型及功能可能无法正常使用' }, { hexLength: 3, color: '#FFC107', secondaryColor: '#ffa000', level: '中等', detailSuffix: '可能影响部分高级功能' }, { hexLength: 4, color: '#8BC34A', secondaryColor: '#689f38', level: '简单', detailSuffix: '可正常使用 ChatGPT' }, { hexLength: Infinity, color: '#4CAF50', secondaryColor: '#388e3c', level: '极易', detailSuffix: '可舒适地与 ChatGPT 聊天' } ]; const { color, secondaryColor, level, detailSuffix } = levels.find(({ hexLength }) => hexValue.length <= hexLength); return { color, secondaryColor, level, detail: `PoW:${difficulty} (${percentage}%)\n${detailSuffix}\n点击刷新状态。` }; })(); // 更新 UI $('#ChatGPTPow-Button').each(function () { const $button = $(this); const $svgStop1 = $button.find('svg defs linearGradient stop[offset="0%"]'); const $svgStop2 = $button.find('svg defs linearGradient stop[offset="100%"]'); $svgStop1.css('stop-color', result.color); $svgStop2.css('stop-color', result.secondaryColor); $button.find('span').text(result.level); $button.attr('title', result.detail); $button[0].style.background = `linear-gradient(140.91deg, ${result.color} 12.61%, ${result.secondaryColor} 76.89%)`; }); } function waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector) { function findInShadowRoots(root, selector) { let elements = $(root).find(selector).toArray(); $(root).find('*').each(function () { let shadowRoot = this.shadowRoot; if (shadowRoot) { elements = elements.concat(findInShadowRoots(shadowRoot, selector)); } }); return elements; } var targetElements; if (iframeSelector) { targetElements = $(iframeSelector).contents(); } else { targetElements = $(document); } let allElements = findInShadowRoots(targetElements, selectorTxt); if (allElements.length > 0) { allElements.forEach(function (element) { var jThis = $(element); var uniqueIdentifier = 'alreadyFound'; var alreadyFound = jThis.data(uniqueIdentifier) || false; if (!alreadyFound) { var cancelFound = actionFunction(jThis); if (cancelFound) { return false; } else { jThis.data(uniqueIdentifier, true); } } }); } var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace(/[^\w]/g, "_"); var timeControl = controlObj[controlKey]; if (allElements.length > 0 && bWaitOnce && timeControl) { clearInterval(timeControl); delete controlObj[controlKey]; } else { if (!timeControl) { timeControl = setInterval(function () { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector); }, 1000); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } // ChatGPT 模型切换器 let iconModels = { "bolt": { "icon_filled_src": "", "icon_outline_src": "", }, "connected": { "icon_outline_src": "", "icon_filled_src": "", }, "star": { "icon_filled_src": "", "icon_outline_src": "", }, "stars": { "icon_filled_src": "", "icon_outline_src": "", }, "reasoning_mini": { "icon_filled_src": "", "icon_outline_src": "", }, "reasoning": { "icon_filled_src": "", "icon_outline_src": "", } } let extraModels = [ { "title": "GPT-4", "subTitle": "4", "description": "传统模型,浏览、高级数据分析和 DALL·E 现已集成至内部。", "slug": "gpt-4", "iconSlug": "gpt_4", "tags": ["unofficial", "gpt4"], "icon": iconModels["star"], }, { "title": "GPT-4o mini", "subTitle": "4o mini", "description": "精简模型,适合处理日常任务。", "slug": "gpt-4o-mini", "iconSlug": "gpt_3.5", "tags": ["unofficial", "gpt4o", "mini"], "icon": iconModels["bolt"], }, { "title": "GPT-4o", "subTitle": "4o", "description": "最新、最高级的模型,适用于大多数任务。", "slug": "gpt-4o", "iconSlug": "AG8PqS2q", "tags": ["unofficial", "gpt4o"], "icon": iconModels["stars"], }, { "beta": true, "title": "GPT-4o 与 Canvas", "subTitle": "4o canvas", "description": "专为协助创作和编码。", "slug": "gpt-4o-canmore", "iconSlug": "9fdGgEgJ", "tags": ["unofficial", "gpt4o", "canvas"], "icon": iconModels["stars"], }, { "title": "o1-mini", "subTitle": "o1 mini", "description": "优化后的高级模型,推理速度更快。", "slug": "o1-mini", "iconSlug": "o1_mini", "tags": ["unofficial", "o1", "mini"], "icon": iconModels["reasoning"], }, { "title": "o1", "subTitle": "o1", "description": "我们最强大的模型,非常适用于需要创造力和高级推理能力的任务。", "slug": "o1", "iconSlug": "o1", "tags": ["unofficial", "o1"], "icon": iconModels["reasoning"], }, { "title": "o1 pro mode", "subTitle": "o1 pro", "description": "更擅长推理的模型,在逻辑与复杂问题上能够更进一步。", "slug": "o1-pro", "iconSlug": "o1_pro", "tags": ["unofficial", "o1", "pro"], "icon": iconModels["reasoning"], }, { "title": "自动", "subTitle": "自动", "description": "自动选择合适的模型来满足我的请求。", "slug": "auto", "iconSlug": "auto", "tags": ["unofficial", "auto", "gpt4o", "o1", "mini", "canvas", "pro"], "icon": iconModels["connected"], }, { "disabled": true, "title": "GPT-3.5", "subTitle": "3.5", "description": "我们最快的模型,非常适合多数日常任务。(已弃用)", "slug": "text-davinci-002-render-sha", "iconSlug": "gpt_3.5", "tags": ["unofficial", "gpt3.5"], "icon": iconModels["bolt"], }, ]; const models = Vue.reactive({ // 保底 all: [...extraModels], update(newModels) { this.all = [ ...newModels, ...extraModels ] } }); function useControllable(controlledValue, onChange, defaultValue) { let internalValue = Vue.ref(defaultValue == null ? void 0 : defaultValue.value); let isControlled = Vue.computed(() => controlledValue.value !== void 0); return [ Vue.computed(() => isControlled.value ? controlledValue.value : internalValue.value), function (value) { if (isControlled.value) { return onChange == null ? void 0 : onChange(value); } else { internalValue.value = value; return onChange == null ? void 0 : onChange(value); } } ]; } let id = 0; function generateId() { return ++id; } function useId() { return generateId(); } function dom(ref2) { var _a; if (ref2 == null) return null; if (ref2.value == null) return null; let el = (_a = ref2.value.$el) != null ? _a : ref2.value; if (el instanceof Node) { return el; } return null; } function match(value, lookup, ...args) { if (value in lookup) { let returnValue = lookup[value]; return typeof returnValue === "function" ? returnValue(...args) : returnValue; } let error = new Error( `Tried to handle "${value}" but there is no handler defined. Only defined handlers are: ${Object.keys( lookup ).map((key) => `"${key}"`).join(", ")}.` ); if (Error.captureStackTrace) Error.captureStackTrace(error, match); throw error; } var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class Env { constructor() { __publicField(this, "current", this.detect()); __publicField(this, "currentId", 0); } set(env2) { if (this.current === env2) return; this.currentId = 0; this.current = env2; } reset() { this.set(this.detect()); } nextId() { return ++this.currentId; } get isServer() { return this.current === "server"; } get isClient() { return this.current === "client"; } detect() { if (typeof window === "undefined" || typeof document === "undefined") { return "server"; } return "client"; } } let env = new Env(); function getOwnerDocument(element) { if (env.isServer) return null; if (element instanceof Node) return element.ownerDocument; if (element == null ? void 0 : element.hasOwnProperty("value")) { let domElement = dom(element); if (domElement) return domElement.ownerDocument; } return document; } let focusableSelector = [ "[contentEditable=true]", "[tabindex]", "a[href]", "area[href]", "button:not([disabled])", "iframe", "input:not([disabled])", "select:not([disabled])", "textarea:not([disabled])" ].map( (selector) => `${selector}:not([tabindex='-1'])` ).join(","); var Focus = ((Focus2) => { Focus2[Focus2["First"] = 1] = "First"; Focus2[Focus2["Previous"] = 2] = "Previous"; Focus2[Focus2["Next"] = 4] = "Next"; Focus2[Focus2["Last"] = 8] = "Last"; Focus2[Focus2["WrapAround"] = 16] = "WrapAround"; Focus2[Focus2["NoScroll"] = 32] = "NoScroll"; return Focus2; })(Focus || {}); var FocusResult = ((FocusResult2) => { FocusResult2[FocusResult2["Error"] = 0] = "Error"; FocusResult2[FocusResult2["Overflow"] = 1] = "Overflow"; FocusResult2[FocusResult2["Success"] = 2] = "Success"; FocusResult2[FocusResult2["Underflow"] = 3] = "Underflow"; return FocusResult2; })(FocusResult || {}); function getFocusableElements(container = document.body) { if (container == null) return []; return Array.from(container.querySelectorAll(focusableSelector)).sort( // We want to move `:tabindex="0"` to the end of the list, this is what the browser does as well. (a, z) => Math.sign((a.tabIndex || Number.MAX_SAFE_INTEGER) - (z.tabIndex || Number.MAX_SAFE_INTEGER)) ); } var FocusableMode = ((FocusableMode2) => { FocusableMode2[FocusableMode2["Strict"] = 0] = "Strict"; FocusableMode2[FocusableMode2["Loose"] = 1] = "Loose"; return FocusableMode2; })(FocusableMode || {}); function isFocusableElement(element, mode = 0) { var _a; if (element === ((_a = getOwnerDocument(element)) == null ? void 0 : _a.body)) return false; return match(mode, { [ 0 /* Strict */ ]() { return element.matches(focusableSelector); }, [ 1 /* Loose */ ]() { let next = element; while (next !== null) { if (next.matches(focusableSelector)) return true; next = next.parentElement; } return false; } }); } if (typeof window !== "undefined" && typeof document !== "undefined") { document.addEventListener( "keydown", (event) => { if (event.metaKey || event.altKey || event.ctrlKey) { return; } document.documentElement.dataset.headlessuiFocusVisible = ""; }, true ); document.addEventListener( "click", (event) => { if (event.detail === 1) { delete document.documentElement.dataset.headlessuiFocusVisible; } else if (event.detail === 0) { document.documentElement.dataset.headlessuiFocusVisible = ""; } }, true ); } let selectableSelector = ["textarea", "input"].join(","); function isSelectableElement(element) { var _a, _b; return (_b = (_a = element == null ? void 0 : element.matches) == null ? void 0 : _a.call(element, selectableSelector)) != null ? _b : false; } function sortByDomNode(nodes, resolveKey = (i) => i) { return nodes.slice().sort((aItem, zItem) => { let a = resolveKey(aItem); let z = resolveKey(zItem); if (a === null || z === null) return 0; let position = a.compareDocumentPosition(z); if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1; if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1; return 0; }); } function focusIn(container, focus, { sorted = true, relativeTo = null, skipElements = [] } = {}) { var _a; let ownerDocument = (_a = Array.isArray(container) ? container.length > 0 ? container[0].ownerDocument : document : container == null ? void 0 : container.ownerDocument) != null ? _a : document; let elements = Array.isArray(container) ? sorted ? sortByDomNode(container) : container : getFocusableElements(container); if (skipElements.length > 0 && elements.length > 1) { elements = elements.filter((x) => !skipElements.includes(x)); } relativeTo = relativeTo != null ? relativeTo : ownerDocument.activeElement; let direction = (() => { if (focus & (1 | 4)) return 1; if (focus & (2 | 8)) return -1; throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last"); })(); let startIndex = (() => { if (focus & 1) return 0; if (focus & 2) return Math.max(0, elements.indexOf(relativeTo)) - 1; if (focus & 4) return Math.max(0, elements.indexOf(relativeTo)) + 1; if (focus & 8) return elements.length - 1; throw new Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last"); })(); let focusOptions = focus & 32 ? { preventScroll: true } : {}; let offset = 0; let total = elements.length; let next = void 0; do { if (offset >= total || offset + total <= 0) return 0; let nextIdx = startIndex + offset; if (focus & 16) { nextIdx = (nextIdx + total) % total; } else { if (nextIdx < 0) return 3; if (nextIdx >= total) return 1; } next = elements[nextIdx]; next == null ? void 0 : next.focus(focusOptions); offset += direction; } while (next !== ownerDocument.activeElement); if (focus & (4 | 2) && isSelectableElement(next)) { next.select(); } return 2; } function isIOS() { return ( // Check if it is an iPhone /iPhone/gi.test(window.navigator.platform) || // Check if it is an iPad. iPad reports itself as "MacIntel", but we can check if it is a touch // screen. Let's hope that Apple doesn't release a touch screen Mac (or maybe this would then // work as expected 🤔). /Mac/gi.test(window.navigator.platform) && window.navigator.maxTouchPoints > 0 ); } function useDocumentEvent(type, listener, options) { if (env.isServer) return; Vue.watchEffect((onInvalidate) => { document.addEventListener(type, listener, options); onInvalidate(() => document.removeEventListener(type, listener, options)); }); } function useWindowEvent(type, listener, options) { if (env.isServer) return; Vue.watchEffect((onInvalidate) => { window.addEventListener(type, listener, options); onInvalidate(() => window.removeEventListener(type, listener, options)); }); } function useOutsideClick(containers, cb, enabled = Vue.computed(() => true)) { function handleOutsideClick(event, resolveTarget) { if (!enabled.value) return; if (event.defaultPrevented) return; let target = resolveTarget(event); if (target === null) { return; } if (!target.getRootNode().contains(target)) return; let _containers = function resolve(containers2) { if (typeof containers2 === "function") { return resolve(containers2()); } if (Array.isArray(containers2)) { return containers2; } if (containers2 instanceof Set) { return containers2; } return [containers2]; }(containers); for (let container of _containers) { if (container === null) continue; let domNode = container instanceof HTMLElement ? container : dom(container); if (domNode == null ? void 0 : domNode.contains(target)) { return; } if (event.composed && event.composedPath().includes(domNode)) { return; } } if ( !isFocusableElement(target, FocusableMode.Loose) && target.tabIndex !== -1 ) { event.preventDefault(); } return cb(event, target); } let initialClickTarget = Vue.ref(null); useDocumentEvent( "pointerdown", (event) => { var _a, _b; if (enabled.value) { initialClickTarget.value = ((_b = (_a = event.composedPath) == null ? void 0 : _a.call(event)) == null ? void 0 : _b[0]) || event.target; } }, true ); useDocumentEvent( "mousedown", (event) => { var _a, _b; if (enabled.value) { initialClickTarget.value = ((_b = (_a = event.composedPath) == null ? void 0 : _a.call(event)) == null ? void 0 : _b[0]) || event.target; } }, true ); useDocumentEvent( "click", (event) => { if (!initialClickTarget.value) { return; } handleOutsideClick(event, () => { return initialClickTarget.value; }); initialClickTarget.value = null; }, true ); useDocumentEvent( "touchend", (event) => { return handleOutsideClick(event, () => { if (event.target instanceof HTMLElement) { return event.target; } return null; }); }, true ); useWindowEvent( "blur", (event) => { return handleOutsideClick(event, () => { return window.document.activeElement instanceof HTMLIFrameElement ? window.document.activeElement : null; }); }, true ); } function resolveType(type, as) { if (type) return type; let tag = as != null ? as : "button"; if (typeof tag === "string" && tag.toLowerCase() === "button") return "button"; return void 0; } function useResolveButtonType(data, refElement) { let type = Vue.ref(resolveType(data.value.type, data.value.as)); Vue.onMounted(() => { type.value = resolveType(data.value.type, data.value.as); }); Vue.watchEffect(() => { var _a; if (type.value) return; if (!dom(refElement)) return; if (dom(refElement) instanceof HTMLButtonElement && !((_a = dom(refElement)) == null ? void 0 : _a.hasAttribute("type"))) { type.value = "button"; } }); return type; } var Features$1 = ((Features2) => { Features2[Features2["None"] = 0] = "None"; Features2[Features2["RenderStrategy"] = 1] = "RenderStrategy"; Features2[Features2["Static"] = 2] = "Static"; return Features2; })(Features$1 || {}); function render({ visible = true, features = 0, ourProps, theirProps, ...main }) { var _a; let props = mergeProps(theirProps, ourProps); let mainWithProps = Object.assign(main, { props }); if (visible) return _render(mainWithProps); if (features & 2) { if (props.static) return _render(mainWithProps); } if (features & 1) { let strategy = ((_a = props.unmount) != null ? _a : true) ? 0 : 1; return match(strategy, { // 未挂载 [0]() { return null; }, // 隐藏 [1]() { return _render({ ...main, props: { ...props, hidden: true, style: { display: "none" } } }); } }); } return _render(mainWithProps); } function _render({ props, attrs, slots, slot, name }) { var _a, _b; let { as, ...incomingProps } = omit(props, ["unmount", "static"]); let children = (_a = slots.default) == null ? void 0 : _a.call(slots, slot); let dataAttributes = {}; if (slot) { let exposeState = false; let states = []; for (let [k, v] of Object.entries(slot)) { if (typeof v === "boolean") { exposeState = true; } if (v === true) { states.push(k); } } if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(" "); } if (as === "template") { children = flattenFragments(children != null ? children : []); if (Object.keys(incomingProps).length > 0 || Object.keys(attrs).length > 0) { let [firstChild, ...other] = children != null ? children : []; if (!isValidElement(firstChild) || other.length > 0) { throw new Error( [ 'Passing props on "template"!', "", `The current component <${name} /> is rendering a "template".`, `However we need to passthrough the following props:`, Object.keys(incomingProps).concat(Object.keys(attrs)).map((name2) => name2.trim()).filter((current, idx, all) => all.indexOf(current) === idx).sort((a, z) => a.localeCompare(z)).map((line) => ` - ${line}`).join("\n"), "", "You can apply a few solutions:", [ 'Add an `as="..."` prop, to ensure that we render an actual element instead of a "template".', "Render a single element as the child so that we can forward the props onto that element." ].map((line) => ` - ${line}`).join("\n") ].join("\n") ); } let mergedProps = mergeProps((_b = firstChild.props) != null ? _b : {}, incomingProps, dataAttributes); let cloned = Vue.cloneVNode(firstChild, mergedProps, true); for (let prop in mergedProps) { if (prop.startsWith("on")) { cloned.props || (cloned.props = {}); cloned.props[prop] = mergedProps[prop]; } } return cloned; } if (Array.isArray(children) && children.length === 1) { return children[0]; } return children; } return Vue.h(as, Object.assign({}, incomingProps, dataAttributes), { default: () => children }); } function flattenFragments(children) { return children.flatMap((child) => { if (child.type === Vue.Fragment) { return flattenFragments(child.children); } return [child]; }); } function mergeProps(...listOfProps) { var _a; if (listOfProps.length === 0) return {}; if (listOfProps.length === 1) return listOfProps[0]; let target = {}; let eventHandlers = {}; for (let props of listOfProps) { for (let prop in props) { if (prop.startsWith("on") && typeof props[prop] === "function") { (_a = eventHandlers[prop]) != null ? _a : eventHandlers[prop] = []; eventHandlers[prop].push(props[prop]); } else { target[prop] = props[prop]; } } } if (target.disabled || target["aria-disabled"]) { return Object.assign( target, Object.fromEntries(Object.keys(eventHandlers).map((eventName) => [eventName, void 0])) ); } for (let eventName in eventHandlers) { Object.assign(target, { [eventName](event, ...args) { let handlers = eventHandlers[eventName]; for (let handler of handlers) { if (event instanceof Event && event.defaultPrevented) { return; } handler(event, ...args); } } }); } return target; } function compact(object) { let clone = Object.assign({}, object); for (let key in clone) { if (clone[key] === void 0) delete clone[key]; } return clone; } function omit(object, keysToOmit = []) { let clone = Object.assign({}, object); for (let key of keysToOmit) { if (key in clone) delete clone[key]; } return clone; } function isValidElement(input) { if (input == null) return false; if (typeof input.type === "string") return true; if (typeof input.type === "object") return true; if (typeof input.type === "function") return true; return false; } var Features = ((Features2) => { Features2[Features2["None"] = 1] = "None"; Features2[Features2["Focusable"] = 2] = "Focusable"; Features2[Features2["Hidden"] = 4] = "Hidden"; return Features2; })(Features || {}); let Hidden = Vue.defineComponent({ name: "Hidden", props: { as: { type: [Object, String], default: "div" }, features: { type: Number, default: 1 } }, setup(props, { slots, attrs }) { return () => { var _a; let { features, ...theirProps } = props; let ourProps = { "aria-hidden": (features & 2) === 2 ? true : ( // @ts-ignore (_a = theirProps["aria-hidden"]) != null ? _a : void 0 ), hidden: (features & 4) === 4 ? true : void 0, style: { position: "fixed", top: 1, left: 1, width: 1, height: 0, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", borderWidth: "0", ...(features & 4) === 4 && !((features & 2) === 2) && { display: "none" } } }; return render({ ourProps, theirProps, slot: {}, attrs, slots, name: "Hidden" }); }; } }); let Context = Symbol("Context"); var State = ((State2) => { State2[State2["Open"] = 1] = "Open"; State2[State2["Closed"] = 2] = "Closed"; State2[State2["Closing"] = 4] = "Closing"; State2[State2["Opening"] = 8] = "Opening"; return State2; })(State || {}); function useOpenClosed() { return Vue.inject(Context, null); } function useOpenClosedProvider(value) { Vue.provide(Context, value); } var Keys = ((Keys2) => { Keys2["Space"] = " "; Keys2["Enter"] = "Enter"; Keys2["Escape"] = "Escape"; Keys2["Backspace"] = "Backspace"; Keys2["Delete"] = "Delete"; Keys2["ArrowLeft"] = "ArrowLeft"; Keys2["ArrowUp"] = "ArrowUp"; Keys2["ArrowRight"] = "ArrowRight"; Keys2["ArrowDown"] = "ArrowDown"; Keys2["Home"] = "Home"; Keys2["End"] = "End"; Keys2["PageUp"] = "PageUp"; Keys2["PageDown"] = "PageDown"; Keys2["Tab"] = "Tab"; return Keys2; })(Keys || {}); function attemptSubmit(elementInForm) { var _a, _b; let form = (_a = elementInForm == null ? void 0 : elementInForm.form) != null ? _a : elementInForm.closest("form"); if (!form) return; for (let element of form.elements) { if (element === elementInForm) continue; if (element.tagName === "INPUT" && element.type === "submit" || element.tagName === "BUTTON" && element.type === "submit" || element.nodeName === "INPUT" && element.type === "image") { element.click(); return; } } (_b = form.requestSubmit) == null ? void 0 : _b.call(form); } function useEventListener(element, type, listener, options) { if (env.isServer) return; Vue.watchEffect((onInvalidate) => { element = element != null ? element : window; element.addEventListener(type, listener, options); onInvalidate(() => element.removeEventListener(type, listener, options)); }); } var Direction = ((Direction2) => { Direction2[Direction2["Forwards"] = 0] = "Forwards"; Direction2[Direction2["Backwards"] = 1] = "Backwards"; return Direction2; })(Direction || {}); function useTabDirection() { let direction = Vue.ref(0); useWindowEvent("keydown", (event) => { if (event.key === "Tab") { direction.value = event.shiftKey ? 1 : 0; } }); return direction; } function useRootContainers({ defaultContainers = [], portals, mainTreeNodeRef: _mainTreeNodeRef } = {}) { let mainTreeNodeRef = Vue.ref(null); let ownerDocument = getOwnerDocument(mainTreeNodeRef); function resolveContainers() { var _a, _b, _c; let containers = []; for (let container of defaultContainers) { if (container === null) continue; if (container instanceof HTMLElement) { containers.push(container); } else if ("value" in container && container.value instanceof HTMLElement) { containers.push(container.value); } } if (portals == null ? void 0 : portals.value) { for (let portal of portals.value) { containers.push(portal); } } for (let container of (_a = ownerDocument == null ? void 0 : ownerDocument.querySelectorAll("html > *, body > *")) != null ? _a : []) { if (container === document.body) continue; if (container === document.head) continue; if (!(container instanceof HTMLElement)) continue; if (container.id === "headlessui-portal-root") continue; if (container.contains(dom(mainTreeNodeRef))) continue; if (container.contains((_c = (_b = dom(mainTreeNodeRef)) == null ? void 0 : _b.getRootNode()) == null ? void 0 : _c.host)) continue; if (containers.some((defaultContainer) => container.contains(defaultContainer))) continue; containers.push(container); } return containers; } return { resolveContainers, contains(element) { return resolveContainers().some((container) => container.contains(element)); }, mainTreeNodeRef, MainTreeNode() { if (_mainTreeNodeRef != null) return null; return Vue.h(Hidden, { features: Features.Hidden, ref: mainTreeNodeRef }); } }; } function useMainTreeNode() { let mainTreeNodeRef = Vue.ref(null); return { mainTreeNodeRef, MainTreeNode() { return Vue.h(Hidden, { features: Features.Hidden, ref: mainTreeNodeRef }); } }; } let ForcePortalRootContext = Symbol("ForcePortalRootContext"); function usePortalRoot() { return Vue.inject(ForcePortalRootContext, false); } Vue.defineComponent({ name: "ForcePortalRoot", props: { as: { type: [Object, String], default: "template" }, force: { type: Boolean, default: false } }, setup(props, { slots, attrs }) { Vue.provide(ForcePortalRootContext, props.force); return () => { let { force, ...theirProps } = props; return render({ theirProps, ourProps: {}, slot: {}, slots, attrs, name: "ForcePortalRoot" }); }; } }); let DescriptionContext = Symbol("DescriptionContext"); function useDescriptionContext() { let context = Vue.inject(DescriptionContext, null); if (context === null) { throw new Error("Missing parent"); } return context; } function useDescriptions({ slot = Vue.ref({}), name = "Description", props = {} } = {}) { let descriptionIds = Vue.ref([]); function register(value) { descriptionIds.value.push(value); return () => { let idx = descriptionIds.value.indexOf(value); if (idx === -1) return; descriptionIds.value.splice(idx, 1); }; } Vue.provide(DescriptionContext, { register, slot, name, props }); return Vue.computed( () => descriptionIds.value.length > 0 ? descriptionIds.value.join(" ") : void 0 ); } Vue.defineComponent({ name: "Description", props: { as: { type: [Object, String], default: "p" }, id: { type: String, default: () => `headlessui-description-${useId()}` } }, setup(myProps, { attrs, slots }) { let context = useDescriptionContext(); Vue.onMounted(() => Vue.onUnmounted(context.register(myProps.id))); return () => { let { name = "Description", slot = Vue.ref({}), props = {} } = context; let { id: id2, ...theirProps } = myProps; let ourProps = { ...Object.entries(props).reduce( (acc, [key, value]) => Object.assign(acc, { [key]: Vue.unref(value) }), {} ), id: id2 }; return render({ ourProps, theirProps, slot: slot.value, attrs, slots, name }); }; } }); function getPortalRoot(contextElement) { let ownerDocument = getOwnerDocument(contextElement); if (!ownerDocument) { if (contextElement === null) { return null; } throw new Error( `[Headless UI]: Cannot find ownerDocument for contextElement: ${contextElement}` ); } let existingRoot = ownerDocument.getElementById("headlessui-portal-root"); if (existingRoot) return existingRoot; let root = ownerDocument.createElement("div"); root.setAttribute("id", "headlessui-portal-root"); return ownerDocument.body.appendChild(root); } Vue.defineComponent({ name: "Portal", props: { as: { type: [Object, String], default: "div" } }, setup(props, { slots, attrs }) { let element = Vue.ref(null); let ownerDocument = Vue.computed(() => getOwnerDocument(element)); let forcePortalRoot = usePortalRoot(); let groupContext = Vue.inject(PortalGroupContext, null); let myTarget = Vue.ref( forcePortalRoot === true ? getPortalRoot(element.value) : groupContext == null ? getPortalRoot(element.value) : groupContext.resolveTarget() ); let ready = Vue.ref(false); Vue.onMounted(() => { ready.value = true; }); Vue.watchEffect(() => { if (forcePortalRoot) return; if (groupContext == null) return; myTarget.value = groupContext.resolveTarget(); }); let parent = Vue.inject(PortalParentContext, null); let didRegister = false; let instance = Vue.getCurrentInstance(); Vue.watch(element, () => { if (didRegister) return; if (!parent) return; let domElement = dom(element); if (!domElement) return; Vue.onUnmounted(parent.register(domElement), instance); didRegister = true; }); Vue.onUnmounted(() => { var _a, _b; let root = (_a = ownerDocument.value) == null ? void 0 : _a.getElementById("headlessui-portal-root"); if (!root) return; if (myTarget.value !== root) return; if (myTarget.value.children.length <= 0) { (_b = myTarget.value.parentElement) == null ? void 0 : _b.removeChild(myTarget.value); } }); return () => { if (!ready.value) return null; if (myTarget.value === null) return null; let ourProps = { ref: element, "data-headlessui-portal": "" }; return Vue.h( // @ts-expect-error Children can be an object, but TypeScript is not happy // with it. Once this is fixed upstream we can remove this assertion. Vue.Teleport, { to: myTarget.value }, render({ ourProps, theirProps: props, slot: {}, attrs, slots, name: "Portal" }) ); }; } }); let PortalParentContext = Symbol("PortalParentContext"); function useNestedPortals() { let parent = Vue.inject(PortalParentContext, null); let portals = Vue.ref([]); function register(portal) { portals.value.push(portal); if (parent) parent.register(portal); return () => unregister(portal); } function unregister(portal) { let idx = portals.value.indexOf(portal); if (idx !== -1) portals.value.splice(idx, 1); if (parent) parent.unregister(portal); } let api = { register, unregister, portals }; return [ portals, Vue.defineComponent({ name: "PortalWrapper", setup(_, { slots }) { Vue.provide(PortalParentContext, api); return () => { var _a; return (_a = slots.default) == null ? void 0 : _a.call(slots); }; } }) ]; } let PortalGroupContext = Symbol("PortalGroupContext"); Vue.defineComponent({ name: "PortalGroup", props: { as: { type: [Object, String], default: "template" }, target: { type: Object, default: null } }, setup(props, { attrs, slots }) { let api = Vue.reactive({ resolveTarget() { return props.target; } }); Vue.provide(PortalGroupContext, api); return () => { let { target: _, ...theirProps } = props; return render({ theirProps, ourProps: {}, slot: {}, attrs, slots, name: "PortalGroup" }); }; } }); let PopoverContext = Symbol("PopoverContext"); function usePopoverContext(component) { let context = Vue.inject(PopoverContext, null); if (context === null) { let err = new Error(`<${component} /> is missing a parent <${Popover.name} /> component.`); if (Error.captureStackTrace) Error.captureStackTrace(err, usePopoverContext); throw err; } return context; } let PopoverGroupContext = Symbol("PopoverGroupContext"); function usePopoverGroupContext() { return Vue.inject(PopoverGroupContext, null); } let PopoverPanelContext = Symbol("PopoverPanelContext"); function usePopoverPanelContext() { return Vue.inject(PopoverPanelContext, null); } let Popover = Vue.defineComponent({ name: "Popover", inheritAttrs: false, props: { as: { type: [Object, String], default: "div" } }, setup(props, { slots, attrs, expose }) { var _a; let internalPopoverRef = Vue.ref(null); expose({ el: internalPopoverRef, $el: internalPopoverRef }); let popoverState = Vue.ref( 1 /* Closed */ ); let button = Vue.ref(null); let beforePanelSentinel = Vue.ref(null); let afterPanelSentinel = Vue.ref(null); let panel = Vue.ref(null); let ownerDocument = Vue.computed(() => getOwnerDocument(internalPopoverRef)); let isPortalled = Vue.computed(() => { var _a2, _b; if (!dom(button)) return false; if (!dom(panel)) return false; for (let root2 of document.querySelectorAll("body > *")) { if (Number(root2 == null ? void 0 : root2.contains(dom(button))) ^ Number(root2 == null ? void 0 : root2.contains(dom(panel)))) { return true; } } let elements = getFocusableElements(); let buttonIdx = elements.indexOf(dom(button)); let beforeIdx = (buttonIdx + elements.length - 1) % elements.length; let afterIdx = (buttonIdx + 1) % elements.length; let beforeElement = elements[beforeIdx]; let afterElement = elements[afterIdx]; if (!((_a2 = dom(panel)) == null ? void 0 : _a2.contains(beforeElement)) && !((_b = dom(panel)) == null ? void 0 : _b.contains(afterElement))) { return true; } return false; }); let api = { popoverState, buttonId: Vue.ref(null), panelId: Vue.ref(null), panel, button, isPortalled, beforePanelSentinel, afterPanelSentinel, togglePopover() { popoverState.value = match(popoverState.value, { [ 0 /* Open */ ]: 1, [ 1 /* Closed */ ]: 0 /* Open */ }); }, closePopover() { if (popoverState.value === 1) return; popoverState.value = 1; }, close(focusableElement) { api.closePopover(); let restoreElement = (() => { if (!focusableElement) return dom(api.button); if (focusableElement instanceof HTMLElement) return focusableElement; if (focusableElement.value instanceof HTMLElement) return dom(focusableElement); return dom(api.button); })(); restoreElement == null ? void 0 : restoreElement.focus(); } }; Vue.provide(PopoverContext, api); useOpenClosedProvider( Vue.computed( () => match(popoverState.value, { [ 0 /* Open */ ]: State.Open, [ 1 /* Closed */ ]: State.Closed }) ) ); let registerBag = { buttonId: api.buttonId, panelId: api.panelId, close() { api.closePopover(); } }; let groupContext = usePopoverGroupContext(); let registerPopover = groupContext == null ? void 0 : groupContext.registerPopover; let [portals, PortalWrapper] = useNestedPortals(); let root = useRootContainers({ mainTreeNodeRef: groupContext == null ? void 0 : groupContext.mainTreeNodeRef, portals, defaultContainers: [button, panel] }); function isFocusWithinPopoverGroup() { var _a2, _b, _c, _d; return (_d = groupContext == null ? void 0 : groupContext.isFocusWithinPopoverGroup()) != null ? _d : ((_a2 = ownerDocument.value) == null ? void 0 : _a2.activeElement) && (((_b = dom(button)) == null ? void 0 : _b.contains(ownerDocument.value.activeElement)) || ((_c = dom(panel)) == null ? void 0 : _c.contains(ownerDocument.value.activeElement))); } Vue.watchEffect(() => registerPopover == null ? void 0 : registerPopover(registerBag)); useEventListener( (_a = ownerDocument.value) == null ? void 0 : _a.defaultView, "focus", (event) => { var _a2, _b; if (event.target === window) return; if (!(event.target instanceof HTMLElement)) return; if (popoverState.value !== 0) return; if (isFocusWithinPopoverGroup()) return; if (!button) return; if (!panel) return; if (root.contains(event.target)) return; if ((_a2 = dom(api.beforePanelSentinel)) == null ? void 0 : _a2.contains(event.target)) return; if ((_b = dom(api.afterPanelSentinel)) == null ? void 0 : _b.contains(event.target)) return; api.closePopover(); }, true ); useOutsideClick( root.resolveContainers, (event, target) => { var _a2; api.closePopover(); if (!isFocusableElement(target, FocusableMode.Loose)) { event.preventDefault(); (_a2 = dom(button)) == null ? void 0 : _a2.focus(); } }, Vue.computed( () => popoverState.value === 0 /* Open */ ) ); return () => { let slot = { open: popoverState.value === 0, close: api.close }; return Vue.h(Vue.Fragment, [ Vue.h( PortalWrapper, {}, () => render({ theirProps: { ...props, ...attrs }, ourProps: { ref: internalPopoverRef }, slot, slots, attrs, name: "Popover" }) ), Vue.h(root.MainTreeNode) ]); }; } }); let PopoverButton = Vue.defineComponent({ name: "PopoverButton", props: { as: { type: [Object, String], default: "button" }, disabled: { type: [Boolean], default: false }, id: { type: String, default: () => `headlessui-popover-button-${useId()}` } }, inheritAttrs: false, setup(props, { attrs, slots, expose }) { let api = usePopoverContext("PopoverButton"); let ownerDocument = Vue.computed(() => getOwnerDocument(api.button)); expose({ el: api.button, $el: api.button }); Vue.onMounted(() => { api.buttonId.value = props.id; }); Vue.onUnmounted(() => { api.buttonId.value = null; }); let groupContext = usePopoverGroupContext(); let closeOthers = groupContext == null ? void 0 : groupContext.closeOthers; let panelContext = usePopoverPanelContext(); let isWithinPanel = Vue.computed( () => panelContext === null ? false : panelContext.value === api.panelId.value ); let elementRef = Vue.ref(null); let sentinelId = `headlessui-focus-sentinel-${useId()}`; if (!isWithinPanel.value) { Vue.watchEffect(() => { api.button.value = dom(elementRef); }); } let type = useResolveButtonType( Vue.computed(() => ({ as: props.as, type: attrs.type })), elementRef ); function handleKeyDown(event) { var _a, _b, _c, _d, _e; if (isWithinPanel.value) { if (api.popoverState.value === 1) return; switch (event.key) { case Keys.Space: case Keys.Enter: event.preventDefault(); (_b = (_a = event.target).click) == null ? void 0 : _b.call(_a); api.closePopover(); (_c = dom(api.button)) == null ? void 0 : _c.focus(); break; } } else { switch (event.key) { case Keys.Space: case Keys.Enter: event.preventDefault(); event.stopPropagation(); if (api.popoverState.value === 1) closeOthers == null ? void 0 : closeOthers(api.buttonId.value); api.togglePopover(); break; case Keys.Escape: if (api.popoverState.value !== 0) return closeOthers == null ? void 0 : closeOthers(api.buttonId.value); if (!dom(api.button)) return; if (((_d = ownerDocument.value) == null ? void 0 : _d.activeElement) && !((_e = dom(api.button)) == null ? void 0 : _e.contains(ownerDocument.value.activeElement))) return; event.preventDefault(); event.stopPropagation(); api.closePopover(); break; } } } function handleKeyUp(event) { if (isWithinPanel.value) return; if (event.key === Keys.Space) { event.preventDefault(); } } function handleClick(event) { var _a, _b; if (props.disabled) return; if (isWithinPanel.value) { api.closePopover(); (_a = dom(api.button)) == null ? void 0 : _a.focus(); } else { event.preventDefault(); event.stopPropagation(); if (api.popoverState.value === 1) closeOthers == null ? void 0 : closeOthers(api.buttonId.value); api.togglePopover(); (_b = dom(api.button)) == null ? void 0 : _b.focus(); } } function handleMouseDown(event) { event.preventDefault(); event.stopPropagation(); } let direction = useTabDirection(); function handleFocus() { let el = dom(api.panel); if (!el) return; function run() { let result = match(direction.value, { [Direction.Forwards]: () => focusIn(el, Focus.First), [Direction.Backwards]: () => focusIn(el, Focus.Last) }); if (result === FocusResult.Error) { focusIn( getFocusableElements().filter((el2) => el2.dataset.headlessuiFocusGuard !== "true"), match(direction.value, { [Direction.Forwards]: Focus.Next, [Direction.Backwards]: Focus.Previous }), { relativeTo: dom(api.button) } ); } } { run(); } } return () => { let visible = api.popoverState.value === 0; let slot = { open: visible }; let { id: id2, ...theirProps } = props; let ourProps = isWithinPanel.value ? { ref: elementRef, type: type.value, onKeydown: handleKeyDown, onClick: handleClick } : { ref: elementRef, id: id2, type: type.value, "aria-expanded": api.popoverState.value === 0, "aria-controls": dom(api.panel) ? api.panelId.value : void 0, disabled: props.disabled ? true : void 0, onKeydown: handleKeyDown, onKeyup: handleKeyUp, onClick: handleClick, onMousedown: handleMouseDown }; return Vue.h(Vue.Fragment, [ render({ ourProps, theirProps: { ...attrs, ...theirProps }, slot, attrs, slots, name: "PopoverButton" }), visible && !isWithinPanel.value && api.isPortalled.value && Vue.h(Hidden, { id: sentinelId, features: Features.Focusable, "data-headlessui-focus-guard": true, as: "button", type: "button", onFocus: handleFocus }) ]); }; } }); Vue.defineComponent({ name: "PopoverOverlay", props: { as: { type: [Object, String], default: "div" }, static: { type: Boolean, default: false }, unmount: { type: Boolean, default: true } }, setup(props, { attrs, slots }) { let api = usePopoverContext("PopoverOverlay"); let id2 = `headlessui-popover-overlay-${useId()}`; let usesOpenClosedState = useOpenClosed(); let visible = Vue.computed(() => { if (usesOpenClosedState !== null) { return (usesOpenClosedState.value & State.Open) === State.Open; } return api.popoverState.value === 0; }); function handleClick() { api.closePopover(); } return () => { let slot = { open: api.popoverState.value === 0 /* Open */ }; let ourProps = { id: id2, "aria-hidden": true, onClick: handleClick }; return render({ ourProps, theirProps: props, slot, attrs, slots, features: Features$1.RenderStrategy | Features$1.Static, visible: visible.value, name: "PopoverOverlay" }); }; } }); const PopoverPanel = Vue.defineComponent({ name: "PopoverPanel", props: { as: { type: [Object, String], default: "div" }, static: { type: Boolean, default: false }, unmount: { type: Boolean, default: true }, focus: { type: Boolean, default: false }, id: { type: String, default: () => `headlessui-popover-panel-${useId()}` } }, inheritAttrs: false, setup(props, { attrs, slots, expose }) { let { focus } = props; let api = usePopoverContext("PopoverPanel"); let ownerDocument = Vue.computed(() => getOwnerDocument(api.panel)); let beforePanelSentinelId = `headlessui-focus-sentinel-before-${useId()}`; let afterPanelSentinelId = `headlessui-focus-sentinel-after-${useId()}`; expose({ el: api.panel, $el: api.panel }); Vue.onMounted(() => { api.panelId.value = props.id; }); Vue.onUnmounted(() => { api.panelId.value = null; }); Vue.provide(PopoverPanelContext, api.panelId); Vue.watchEffect(() => { var _a, _b; if (!focus) return; if (api.popoverState.value !== 0) return; if (!api.panel) return; let activeElement = (_a = ownerDocument.value) == null ? void 0 : _a.activeElement; if ((_b = dom(api.panel)) == null ? void 0 : _b.contains(activeElement)) return; focusIn(dom(api.panel), Focus.First); }); let usesOpenClosedState = useOpenClosed(); let visible = Vue.computed(() => { if (usesOpenClosedState !== null) { return (usesOpenClosedState.value & State.Open) === State.Open; } return api.popoverState.value === 0; }); function handleKeyDown(event) { var _a, _b; switch (event.key) { case Keys.Escape: if (api.popoverState.value !== 0) return; if (!dom(api.panel)) return; if (ownerDocument.value && !((_a = dom(api.panel)) == null ? void 0 : _a.contains(ownerDocument.value.activeElement))) { return; } event.preventDefault(); event.stopPropagation(); api.closePopover(); (_b = dom(api.button)) == null ? void 0 : _b.focus(); break; } } function handleBlur(event) { var _a, _b, _c, _d, _e; let el = event.relatedTarget; if (!el) return; if (!dom(api.panel)) return; if ((_a = dom(api.panel)) == null ? void 0 : _a.contains(el)) return; api.closePopover(); if (((_c = (_b = dom(api.beforePanelSentinel)) == null ? void 0 : _b.contains) == null ? void 0 : _c.call(_b, el)) || ((_e = (_d = dom(api.afterPanelSentinel)) == null ? void 0 : _d.contains) == null ? void 0 : _e.call(_d, el))) { el.focus({ preventScroll: true }); } } let direction = useTabDirection(); function handleBeforeFocus() { let el = dom(api.panel); if (!el) return; function run() { match(direction.value, { [Direction.Forwards]: () => { var _a; let result = focusIn(el, Focus.First); if (result === FocusResult.Error) { (_a = dom(api.afterPanelSentinel)) == null ? void 0 : _a.focus(); } }, [Direction.Backwards]: () => { var _a; (_a = dom(api.button)) == null ? void 0 : _a.focus({ preventScroll: true }); } }); } { run(); } } function handleAfterFocus() { let el = dom(api.panel); if (!el) return; function run() { match(direction.value, { [Direction.Forwards]: () => { let button = dom(api.button); let panel = dom(api.panel); if (!button) return; let elements = getFocusableElements(); let idx = elements.indexOf(button); let before = elements.slice(0, idx + 1); let after = elements.slice(idx + 1); let combined = [...after, ...before]; for (let element of combined.slice()) { if (element.dataset.headlessuiFocusGuard === "true" || (panel == null ? void 0 : panel.contains(element))) { let idx2 = combined.indexOf(element); if (idx2 !== -1) combined.splice(idx2, 1); } } focusIn(combined, Focus.First, { sorted: false }); }, [Direction.Backwards]: () => { var _a; let result = focusIn(el, Focus.Previous); if (result === FocusResult.Error) { (_a = dom(api.button)) == null ? void 0 : _a.focus(); } } }); } { run(); } } return () => { let slot = { open: api.popoverState.value === 0, close: api.close }; let { id: id2, focus: _focus, ...theirProps } = props; let ourProps = { ref: api.panel, id: id2, onKeydown: handleKeyDown, onFocusout: focus && api.popoverState.value === 0 ? handleBlur : void 0, tabIndex: -1 }; return render({ ourProps, theirProps: { ...attrs, ...theirProps }, attrs, slot, slots: { ...slots, default: (...args) => { var _a; return [ Vue.h(Vue.Fragment, [ visible.value && api.isPortalled.value && Vue.h(Hidden, { id: beforePanelSentinelId, ref: api.beforePanelSentinel, features: Features.Focusable, "data-headlessui-focus-guard": true, as: "button", type: "button", onFocus: handleBeforeFocus }), (_a = slots.default) == null ? void 0 : _a.call(slots, ...args), visible.value && api.isPortalled.value && Vue.h(Hidden, { id: afterPanelSentinelId, ref: api.afterPanelSentinel, features: Features.Focusable, "data-headlessui-focus-guard": true, as: "button", type: "button", onFocus: handleAfterFocus }) ]) ]; } }, features: Features$1.RenderStrategy | Features$1.Static, visible: visible.value, name: "PopoverPanel" }); }; } }); Vue.defineComponent({ name: "PopoverGroup", inheritAttrs: false, props: { as: { type: [Object, String], default: "div" } }, setup(props, { attrs, slots, expose }) { let groupRef = Vue.ref(null); let popovers = Vue.shallowRef([]); let ownerDocument = Vue.computed(() => getOwnerDocument(groupRef)); let root = useMainTreeNode(); expose({ el: groupRef, $el: groupRef }); function unregisterPopover(registerBag) { let idx = popovers.value.indexOf(registerBag); if (idx !== -1) popovers.value.splice(idx, 1); } function registerPopover(registerBag) { popovers.value.push(registerBag); return () => { unregisterPopover(registerBag); }; } function isFocusWithinPopoverGroup() { var _a; let owner = ownerDocument.value; if (!owner) return false; let element = owner.activeElement; if ((_a = dom(groupRef)) == null ? void 0 : _a.contains(element)) return true; return popovers.value.some((bag) => { var _a2, _b; return ((_a2 = owner.getElementById(bag.buttonId.value)) == null ? void 0 : _a2.contains(element)) || ((_b = owner.getElementById(bag.panelId.value)) == null ? void 0 : _b.contains(element)); }); } function closeOthers(buttonId) { for (let popover of popovers.value) { if (popover.buttonId.value !== buttonId) popover.close(); } } Vue.provide(PopoverGroupContext, { registerPopover, unregisterPopover, isFocusWithinPopoverGroup, closeOthers, mainTreeNodeRef: root.mainTreeNodeRef }); return () => { let ourProps = { ref: groupRef }; return Vue.h(Vue.Fragment, [ render({ ourProps, theirProps: { ...props, ...attrs }, slot: {}, attrs, slots, name: "PopoverGroup" }), Vue.h(root.MainTreeNode) ]); }; } }); let LabelContext = Symbol("LabelContext"); function useLabelContext() { let context = Vue.inject(LabelContext, null); if (context === null) { let err = new Error("You used a