@opentiny/next-sdk 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/{SimulatorMask-BHVXyogh-CARX3Rff.js → SimulatorMask-BHVXyogh-CKmEDsYG.js} +1 -1
  2. package/dist/{index-R_HIbfUX.js → index-CHMa2zG4.js} +1065 -1042
  3. package/dist/index.js +1 -1
  4. package/dist/page-tools/a11y/config.d.ts +6 -3
  5. package/dist/page-tools/a11y/constants.d.ts +20 -0
  6. package/dist/page-tools/a11y/utils.d.ts +14 -0
  7. package/dist/page-tools/constants.d.ts +4 -2
  8. package/dist/page-tools/context.d.ts +2 -0
  9. package/dist/page-tools/handlers/hover.d.ts +9 -0
  10. package/dist/page-tools/schema.d.ts +3 -3
  11. package/dist/page-tools/utils/dom.d.ts +8 -2
  12. package/dist/runtime.js +19 -172
  13. package/package.json +6 -1
  14. package/WebMcpClient.ts +0 -505
  15. package/WebMcpServer.ts +0 -447
  16. package/agent/AgentModelProvider.ts +0 -932
  17. package/agent/type.ts +0 -91
  18. package/agent/utils/generateReActPrompt.ts +0 -71
  19. package/agent/utils/getAISDKTools.ts +0 -34
  20. package/agent/utils/getBuiltinMcpTools.ts +0 -86
  21. package/agent/utils/parseReActAction.ts +0 -34
  22. package/core.ts +0 -26
  23. package/index.ts +0 -91
  24. package/page-tools/a11y/build.ts +0 -74
  25. package/page-tools/a11y/config.ts +0 -465
  26. package/page-tools/a11y/constants.ts +0 -131
  27. package/page-tools/a11y/search.ts +0 -127
  28. package/page-tools/a11y/types.ts +0 -105
  29. package/page-tools/a11y/utils.ts +0 -239
  30. package/page-tools/a11y/vnode.ts +0 -439
  31. package/page-tools/a11y-tree.ts +0 -14
  32. package/page-tools/bridge.ts +0 -722
  33. package/page-tools/configs/console-cloud.ts +0 -172
  34. package/page-tools/constants.ts +0 -12
  35. package/page-tools/context.ts +0 -50
  36. package/page-tools/effects.ts +0 -345
  37. package/page-tools/handlers/browserState.ts +0 -12
  38. package/page-tools/handlers/click.ts +0 -30
  39. package/page-tools/handlers/executeJavascript.ts +0 -22
  40. package/page-tools/handlers/fill.ts +0 -65
  41. package/page-tools/handlers/scroll.ts +0 -66
  42. package/page-tools/handlers/searchTree.ts +0 -27
  43. package/page-tools/handlers/select.ts +0 -39
  44. package/page-tools/initialize-builtin-WebMCP.ts +0 -20
  45. package/page-tools/page-agent-highlight/index.ts +0 -245
  46. package/page-tools/page-agent-mask/SimulatorMask.module.css +0 -14
  47. package/page-tools/page-agent-mask/SimulatorMask.ts +0 -299
  48. package/page-tools/page-agent-mask/checkDarkMode.ts +0 -181
  49. package/page-tools/page-agent-mask/cursor-border.svg +0 -3
  50. package/page-tools/page-agent-mask/cursor-fill.svg +0 -5
  51. package/page-tools/page-agent-mask/cursor.module.css +0 -70
  52. package/page-tools/page-agent-mask/hauwei.svg +0 -25
  53. package/page-tools/page-agent-prompt.md +0 -139
  54. package/page-tools/page-agent-tool-event.ts +0 -113
  55. package/page-tools/page-agent-tool.ts +0 -207
  56. package/page-tools/page-state-cache.ts +0 -78
  57. package/page-tools/schema.ts +0 -52
  58. package/page-tools/tool-config.ts +0 -100
  59. package/page-tools/utils/dom.ts +0 -158
  60. package/page-tools/utils/scroll.ts +0 -58
  61. package/remoter/QrCode.ts +0 -54
  62. package/remoter/createRemoter.ts +0 -957
  63. package/remoter/svgs/chat.svg +0 -43
  64. package/remoter/svgs/icon-copy.svg +0 -25
  65. package/remoter/svgs/link.svg +0 -28
  66. package/remoter/svgs/logo.svg +0 -45
  67. package/remoter/svgs/qrcode.svg +0 -35
  68. package/remoter/svgs/scan.svg +0 -21
  69. package/remoter/tooltips.ts +0 -260
  70. package/runtime.html +0 -39
  71. package/runtime.ts +0 -44
  72. package/script/utils.ts +0 -26
  73. package/skills/index.ts +0 -287
  74. package/test/page-tools/a11y/build.test.ts +0 -638
  75. package/test/page-tools/a11y/config.test.ts +0 -370
  76. package/test/page-tools/configs/console-cloud.test.ts +0 -168
  77. package/test/page-tools/page-agent-highlight.test.ts +0 -110
  78. package/test/page-tools/page-agent-tool-dispatch.test.ts +0 -208
  79. package/test/page-tools/page-agent-tool.test.ts +0 -102
  80. package/test/page-tools/tool-config.test.ts +0 -112
  81. package/test/page-tools/utils/dom.test.ts +0 -122
  82. package/tsconfig.json +0 -16
  83. package/utils/builtinProxy.ts +0 -144
  84. package/utils/env.ts +0 -13
  85. package/utils/uuid.ts +0 -10
  86. package/vite.config.runtime.ts +0 -22
  87. package/vite.config.ts +0 -63
  88. package/vitest.config.ts +0 -10
@@ -1,65 +0,0 @@
1
- import { inputTextElement } from '@page-agent/page-controller'
2
- import { dispatchComposedEvents, isInShadowDom, waitForDomSettled } from '../utils/dom'
3
- import type { ActionContext } from '../context'
4
-
5
- export async function handleFill(args: any, ctx: ActionContext) {
6
- const mode = args.responseMode ?? 'diff'
7
- if (args.index === undefined || typeof args.text !== 'string')
8
- return ctx.actionError('填写结果: 缺少元素索引或文本内容')
9
- const el = ctx.getRefMap().get(args.index)
10
- if (!el) {
11
- return ctx.actionError(
12
- `填写失败: ref 索引 ${args.index} 已失效(页面可能已刷新),已自动重新加载页面状态,请使用新的 ref 索引重试。`
13
- )
14
- }
15
-
16
- let targetEl = el
17
- if (!(targetEl instanceof HTMLInputElement) && !(targetEl instanceof HTMLTextAreaElement)) {
18
- // querySelector 不穿透 shadow boundary,对 shadow host 补查其 shadowRoot
19
- const innerInput =
20
- el.querySelector('input, textarea') ?? (el.shadowRoot?.querySelector('input, textarea') as HTMLElement | null)
21
- if (innerInput) {
22
- targetEl = innerInput as HTMLElement
23
- }
24
- }
25
-
26
- if (targetEl instanceof HTMLInputElement && targetEl.readOnly) {
27
- targetEl.value = args.text
28
- // composed:true 确保事件穿透 shadow boundary,事件委托框架可捕获
29
- dispatchComposedEvents(targetEl, 'input', 'change', 'blur')
30
- } else {
31
- // 优先尝试标准方式(支持 contenteditable、普通 input/textarea)
32
- let fillSuccess = false
33
- try {
34
- await inputTextElement(targetEl, args.text)
35
- fillSuccess = true
36
- } catch (fillErr) {
37
- // inputTextElement 失败时(如 Angular/React 组件包装的密码框),
38
- // 降级使用原生 input value 描述符触发框架变更检测
39
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
40
- (targetEl instanceof HTMLTextAreaElement ? HTMLTextAreaElement : HTMLInputElement).prototype,
41
- 'value'
42
- )?.set
43
- if (nativeInputValueSetter && (targetEl instanceof HTMLInputElement || targetEl instanceof HTMLTextAreaElement)) {
44
- nativeInputValueSetter.call(targetEl, args.text)
45
- // 发送完整事件序列,触发 Angular ngModel / React 合成事件的变更检测
46
- targetEl.dispatchEvent(new Event('focus', { bubbles: true, composed: true }))
47
- targetEl.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
48
- targetEl.dispatchEvent(new Event('change', { bubbles: true, composed: true }))
49
- targetEl.dispatchEvent(new Event('blur', { bubbles: true, composed: true }))
50
- fillSuccess = true
51
- }
52
- }
53
- if (!fillSuccess) {
54
- return ctx.actionError(`填写结果: 无法填写元素 ${args.index},元素不是 input、textarea 或 contenteditable`)
55
- }
56
- // shadow DOM 内元素:inputTextElement 派发的合成事件 composed:false,
57
- // 补发 composed:true 事件确保事件委托框架(如 React)能收到
58
- if (isInShadowDom(targetEl)) {
59
- dispatchComposedEvents(targetEl, 'input', 'change')
60
- }
61
- }
62
- // 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
63
- await waitForDomSettled()
64
- return ctx.buildBrowserStateResponse(mode)
65
- }
@@ -1,66 +0,0 @@
1
- import { getScrollInfo, waitForScrollEnd } from '../utils/scroll'
2
- import type { ActionContext } from '../context'
3
-
4
- export async function handleScroll(args: any, ctx: ActionContext) {
5
- const mode = args.responseMode ?? 'diff'
6
- if (args.down === undefined && args.right === undefined) return ctx.errContent('滚动结果: 缺少滚动方向参数')
7
-
8
- // 确定滚动目标(有 index 时滚动该元素容器,否则滚动整个文档)
9
- let scrollTarget: Element | Window = window
10
- if (args.index !== undefined) {
11
- const el = ctx.getRefMap().get(args.index)
12
- if (!el) return ctx.refreshOnStaleRef('滚动', args.index)
13
- scrollTarget = el
14
- }
15
-
16
- // 滚动前快照
17
- const before = getScrollInfo(scrollTarget)
18
-
19
- if (args.right !== undefined) {
20
- const pixels = args.pixels ?? 300
21
- scrollTarget.scrollBy({ left: args.right ? pixels : -pixels, behavior: 'smooth' })
22
- } else {
23
- const pixels = args.pixels ?? Math.round((args.numPages ?? 1) * window.innerHeight)
24
- scrollTarget.scrollBy({ top: args.down ? pixels : -pixels, behavior: 'smooth' })
25
- }
26
-
27
- // 等待滚动动画完成后再采集状态(scrollend + 超时兜底)
28
- await waitForScrollEnd(scrollTarget)
29
-
30
- // 滚动后快照,计算实际位移
31
- const after = getScrollInfo(scrollTarget)
32
- const deltaY = Math.round(after.scrollY - before.scrollY)
33
- const deltaX = Math.round(after.scrollX - before.scrollX)
34
- const isHorizontal = args.right !== undefined
35
-
36
- let scrollMsg: string
37
- if (Math.abs(deltaY) < 1 && Math.abs(deltaX) < 1) {
38
- // 实际未发生位移,说明已到达边界
39
- if (isHorizontal) {
40
- scrollMsg = args.right ? '⚠️ 已到达右边界,无法继续向右滚动' : '⚠️ 已到达左边界,无法继续向左滚动'
41
- } else {
42
- scrollMsg = args.down ? '⚠️ 已到达底部,无法继续向下滚动' : '⚠️ 已到达顶部,无法继续向上滚动'
43
- }
44
- } else {
45
- const axis = isHorizontal ? `水平滚动 ${deltaX}px` : `垂直滚动 ${deltaY}px`
46
- let boundary = ''
47
- if (isHorizontal) {
48
- if (args.right && after.atRight) boundary = ',已到达右边界'
49
- else if (!args.right && after.atLeft) boundary = ',已到达左边界'
50
- } else {
51
- if (args.down && after.atBottom) boundary = ',已到达底部'
52
- else if (!args.down && after.atTop) boundary = ',已到达顶部'
53
- }
54
- scrollMsg = `✅ ${axis}${boundary}`
55
- }
56
-
57
- // 附加位置信息(参考 page-agent getPageInfo 格式)
58
- const posInfo = args.right !== undefined
59
- ? `当前水平滚动位置: scrollX=${Math.round(after.scrollX)}px,左侧 ${after.pagesAbove.toFixed(1)} 屏,右侧 ${after.pagesBelow.toFixed(1)} 屏`
60
- : `当前滚动位置: scrollY=${Math.round(after.scrollY)}px,上方 ${after.pagesAbove.toFixed(1)} 屏,下方 ${after.pagesBelow.toFixed(1)} 屏`
61
- const scrollResult = `[滚动结果] ${scrollMsg}\n${posInfo}`
62
-
63
- const stateResult = await ctx.buildBrowserStateResponse(mode)
64
- stateResult.content[0].text = `${scrollResult}\n\n${stateResult.content[0].text}`
65
- return stateResult
66
- }
@@ -1,27 +0,0 @@
1
- import { searchA11yTree } from '../a11y-tree'
2
- import { detectPageDialog, detectValidationErrors } from '../utils/dom'
3
- import { getPageAgentToolConfig } from '../tool-config'
4
- import type { ActionContext } from '../context'
5
- import type { PageAgentToolInput } from '../schema'
6
-
7
- export async function handleSearchTree(args: PageAgentToolInput, ctx: ActionContext) {
8
- if (!args.query) return ctx.errContent('搜索失败: 缺少 query 参数')
9
- const result = searchA11yTree(args.query, document.body, {
10
- ...getPageAgentToolConfig().a11yConfig,
11
- contextLines: args.contextLines,
12
- maxMatches: args.maxMatches,
13
- })
14
-
15
- ctx.setRefMap(result.refMap)
16
- ctx.stateCache.update(window.location.href, result.yaml)
17
-
18
- await ctx.pageController.hideMask()
19
- // 检测页面弹窗/遮罩层,帮助 AI 发现可能遮挡目标的确认弹窗
20
- const dialogAlert = detectPageDialog()
21
- // 检测表单校验错误,提醒 AI 优先修复
22
- const validationErrors = detectValidationErrors()
23
- const alerts = `${dialogAlert}${validationErrors}`
24
- return {
25
- content: [{ type: 'text' as const, text: alerts ? `${alerts}\n${result.text}` : result.text }]
26
- }
27
- }
@@ -1,39 +0,0 @@
1
- import { selectOptionElement } from '@page-agent/page-controller'
2
- import { dispatchComposedEvents, isInShadowDom, waitForDomSettled } from '../utils/dom'
3
- import type { ActionContext } from '../context'
4
-
5
- export async function handleSelect(args: any, ctx: ActionContext) {
6
- const mode = args.responseMode ?? 'diff'
7
- if (args.index === undefined || typeof args.text !== 'string')
8
- return ctx.actionError('选择结果: 缺少元素索引或文本内容')
9
- let el = ctx.getRefMap().get(args.index) as HTMLSelectElement | HTMLElement | undefined
10
- if (!el) {
11
- return ctx.actionError(
12
- `选择失败: ref 索引 ${args.index} 已失效(页面可能已刷新),已自动重新加载页面状态,请使用新的 ref 索引重试。`
13
- )
14
- }
15
- // 若 ref 指向 shadow host,解析其内部真正的 <select>(与 fill 一致)
16
- let targetSelect: HTMLSelectElement | null = null
17
- if (el instanceof HTMLSelectElement) {
18
- targetSelect = el
19
- } else {
20
- const innerSelect =
21
- el.querySelector('select') ?? (el.shadowRoot?.querySelector('select') as HTMLSelectElement | null)
22
- if (innerSelect) {
23
- targetSelect = innerSelect
24
- }
25
- }
26
-
27
- if (!targetSelect) {
28
- return ctx.actionError(`选择结果: 无法选择元素 ${args.index},未找到对应的 <select> 元素`)
29
- }
30
-
31
- await selectOptionElement(targetSelect, args.text)
32
- // shadow DOM 内元素:补发 composed:true 的 change 事件
33
- if (isInShadowDom(targetSelect)) {
34
- dispatchComposedEvents(targetSelect, 'change')
35
- }
36
- // 等待框架异步插入的校验错误/条件渲染内容稳定后再采集状态
37
- await waitForDomSettled()
38
- return ctx.buildBrowserStateResponse(mode)
39
- }
@@ -1,20 +0,0 @@
1
- import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill'
2
- import { setupModelContextBridge } from './bridge'
3
- import { isBrowser } from '../utils/env'
4
-
5
- let initialized = false
6
-
7
- export const initializeBuiltinWebMCP = () => {
8
- if (isBrowser()) {
9
- try {
10
- if (initialized) return
11
-
12
- initializeWebMCPPolyfill()
13
- setupModelContextBridge()
14
-
15
- initialized = true
16
- } catch (err) {
17
- console.warn('[next-sdk] 自动注入 modelContext polyfill 和桥接同步失败:', err)
18
- }
19
- }
20
- }
@@ -1,245 +0,0 @@
1
- import { type RefMap } from '../a11y-tree'
2
-
3
- export const HIGHLIGHT_CONTAINER_ID = 'webmcpcli-highlight-container'
4
- export const HIGHLIGHT_CONTAINER_STYLE_ID = 'webmcpcli-highlight-container-style'
5
- const colors = [
6
- '#FF0000',
7
- '#00FF00',
8
- '#0000FF',
9
- '#FFA500',
10
- '#800080',
11
- '#008080',
12
- '#FF69B4',
13
- '#4B0082',
14
- '#FF4500',
15
- '#2E8B57',
16
- '#DC143C',
17
- '#4682B4'
18
- ]
19
- // border + label opacity
20
- const opacity = Math.floor(0.3 * 255)
21
- .toString(16)
22
- .padStart(2, '0')
23
-
24
- const injectStyles = `
25
- #${HIGHLIGHT_CONTAINER_ID} {
26
- position: fixed;
27
- pointer-events: none;
28
- top: 0;
29
- left: 0;
30
- width: 100%;
31
- height: 100%;
32
- z-index: 2147483640;
33
- background-color: transparent;
34
- }
35
-
36
- #${HIGHLIGHT_CONTAINER_ID} div {
37
- position: fixed;
38
- pointer-events: none;
39
- background-color: transparent;
40
- text-align: right;
41
- box-sizing: border-box;
42
- border-radius: 4px;
43
- }
44
-
45
- #${HIGHLIGHT_CONTAINER_ID} div span {
46
- display: inline-block;
47
- color: #fff;
48
- padding: 0 2px;
49
- border-radius: 4px;
50
- }
51
- `
52
-
53
- /** 图标热区回退时父节点宽高上限,避免用到整行容器 */
54
- const MAX_ICON_FALLBACK_SIZE = 48
55
-
56
- export type HighlightRect = { top: number; left: number; width: number; height: number }
57
-
58
- /**
59
- * 解析高亮矩形。
60
- * Tiny3 `ti-icon` / `tp-icon` 字形常画在 ::before 上,宿主 getBoundingClientRect 宽或高为 0,
61
- * 旧逻辑直接跳过导致帮助中心关闭/全屏等图标有 ref 却不高亮。
62
- */
63
- export function resolveHighlightRect(el: HTMLElement): HighlightRect | null {
64
- const rect = el.getBoundingClientRect()
65
- if (rect.width > 0 && rect.height > 0) {
66
- return { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
67
- }
68
-
69
- // 优先用紧凑父节点(帮助中心 header 图标外包 28×28 的可点区域)
70
- let parent: HTMLElement | null = el.parentElement
71
- for (let depth = 0; depth < 3 && parent; depth++) {
72
- const pr = parent.getBoundingClientRect()
73
- if (
74
- pr.width > 0 &&
75
- pr.height > 0 &&
76
- pr.width <= MAX_ICON_FALLBACK_SIZE &&
77
- pr.height <= MAX_ICON_FALLBACK_SIZE
78
- ) {
79
- return { top: pr.top, left: pr.left, width: pr.width, height: pr.height }
80
- }
81
- parent = parent.parentElement
82
- }
83
-
84
- // 单边有尺寸时,用 font-size 补齐另一边
85
- if (rect.width > 0 || rect.height > 0) {
86
- try {
87
- const fs = parseFloat(getComputedStyle(el).fontSize) || 16
88
- const width = rect.width > 0 ? rect.width : fs
89
- const height = rect.height > 0 ? rect.height : fs
90
- return {
91
- top: rect.height > 0 ? rect.top : rect.top - (height - rect.height) / 2,
92
- left: rect.width > 0 ? rect.left : rect.left - (width - rect.width) / 2,
93
- width,
94
- height,
95
- }
96
- } catch {
97
- /* ignore */
98
- }
99
- }
100
-
101
- return null
102
- }
103
-
104
- /** 与视口有交集即可(侧栏轻微负 left、贴边控件不应被整框剔除) */
105
- function isRectInViewport(rect: HighlightRect): boolean {
106
- const vh = window.innerHeight || document.documentElement.clientHeight
107
- const vw = window.innerWidth || document.documentElement.clientWidth
108
- return rect.top < vh && rect.top + rect.height > 0 && rect.left < vw && rect.left + rect.width > 0
109
- }
110
-
111
- const doHighlight = (refMap: RefMap, parentIframe: HTMLIFrameElement | null = null) => {
112
- // 1、保证注入脚本
113
- if (!document.head.querySelector(`#${HIGHLIGHT_CONTAINER_STYLE_ID}`)) {
114
- const style = document.head.appendChild(document.createElement('style'))
115
- style.id = HIGHLIGHT_CONTAINER_STYLE_ID
116
- style.textContent = injectStyles
117
- document.head.appendChild(style)
118
- }
119
-
120
- // 2、保证contrainer( container 在 a11y 时已加入黑名单)
121
- let container = document.getElementById(HIGHLIGHT_CONTAINER_ID)
122
- if (!container) {
123
- container = document.createElement('div')
124
- container.id = HIGHLIGHT_CONTAINER_ID
125
-
126
- document.body.appendChild(container)
127
- } else {
128
- container.innerHTML = ''
129
- }
130
-
131
- let iframeOffset = { x: 0, y: 0 }
132
- if (parentIframe) {
133
- const iframeRect = parentIframe.getBoundingClientRect() // Keep getBoundingClientRect for iframe offset
134
- iframeOffset.x = iframeRect.left
135
- iframeOffset.y = iframeRect.top
136
- }
137
- const fragment = document.createDocumentFragment()
138
-
139
- // 3、 遍历 refMap
140
- for (const [index, el] of refMap.entries()) {
141
- const rect = resolveHighlightRect(el)
142
- if (!rect) continue
143
-
144
- const top = rect.top + iframeOffset.y
145
- const left = rect.left + iframeOffset.x
146
- const viewportRect = { top, left, width: rect.width, height: rect.height }
147
- if (!isRectInViewport(viewportRect)) continue
148
-
149
- const color = colors[index % colors.length]
150
- const textColor = color + opacity
151
-
152
- const overlay = document.createElement('div')
153
- overlay.style.border = `2px solid ${textColor}`
154
- overlay.dataset.refIndex = String(index)
155
-
156
- overlay.style.top = `${top}px`
157
- overlay.style.left = `${left}px`
158
- overlay.style.width = `${rect.width}px`
159
- overlay.style.height = `${rect.height}px`
160
-
161
- // 文字
162
- const text = document.createElement('span')
163
- text.style.backgroundColor = textColor
164
- text.style.height = `${Math.max(rect.height - 4, 0)}px`
165
- text.style.lineHeight = `${Math.max(rect.height - 4, 0)}px`
166
- text.textContent = index.toString()
167
- overlay.appendChild(text)
168
-
169
- fragment.appendChild(overlay)
170
- }
171
-
172
- container.appendChild(fragment)
173
- }
174
-
175
- export const highlight = (refMap: RefMap, parentIframe: HTMLIFrameElement | null = null) => {
176
- doHighlight(refMap, parentIframe)
177
-
178
- // 4、监听滚动和resize事件,更新位置
179
- const updatePositions = () => doHighlight(refMap, parentIframe)
180
- const throttleFunction = (func: (...args: any[]) => void, delay: number) => {
181
- let lastCall = 0
182
- return (...args: any[]) => {
183
- const now = performance.now()
184
- if (now - lastCall < delay) return
185
- lastCall = now
186
- return func(...args)
187
- }
188
- }
189
-
190
- const throttledUpdatePositions = throttleFunction(updatePositions, 16)
191
- window.addEventListener('scroll', throttledUpdatePositions, true)
192
- window.addEventListener('resize', throttledUpdatePositions)
193
-
194
- const cleanupFn = () => {
195
- window.removeEventListener('scroll', throttledUpdatePositions, true)
196
- window.removeEventListener('resize', throttledUpdatePositions)
197
- }
198
- const win = window as any
199
- ;(win._highlightCleanupFunctions = win._highlightCleanupFunctions || []).push(cleanupFn)
200
- }
201
-
202
- /** 移除高亮, 可反复调用 */
203
- export const unhighlight = () => {
204
- document.getElementById(HIGHLIGHT_CONTAINER_ID)?.remove()
205
-
206
- const cleanupFunctions = (window as any)._highlightCleanupFunctions || []
207
- for (const cleanup of cleanupFunctions) {
208
- if (typeof cleanup === 'function') {
209
- cleanup()
210
- }
211
- }
212
-
213
- ;(window as any)._highlightCleanupFunctions = []
214
- }
215
-
216
- /** 全局监听变化,随时准备移除高亮, 可反复调用 */
217
- export const globalRemoveListener = () => {
218
- if ((window as any).__registerGlobalRemoveListener) return
219
- ;(window as any).__registerGlobalRemoveListener = true
220
- window.addEventListener('popstate', () => {
221
- unhighlight()
222
- })
223
- window.addEventListener('hashchange', () => {
224
- unhighlight()
225
- })
226
- window.addEventListener('beforeunload', () => {
227
- unhighlight()
228
- })
229
-
230
- const navigation = (window as any).navigation
231
- if (navigation && typeof navigation.addEventListener === 'function') {
232
- navigation.addEventListener('navigate', () => {
233
- unhighlight()
234
- })
235
- } else {
236
- // 定时器
237
- let currentUrl = window.location.href
238
- setInterval(() => {
239
- if (window.location.href !== currentUrl) {
240
- currentUrl = window.location.href
241
- unhighlight()
242
- }
243
- }, 500)
244
- }
245
- }
@@ -1,14 +0,0 @@
1
- .webmcp-page-agent-wrapper {
2
- position: fixed;
3
- inset: 0;
4
- z-index: 2147483641;
5
- /* 确保在所有元素之上,除了 panel */
6
- cursor: wait;
7
- overflow: hidden;
8
-
9
- display: none;
10
- }
11
-
12
- .webmcp-page-agent-wrapper.visible {
13
- display: block;
14
- }