@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.
- package/dist/{SimulatorMask-BHVXyogh-CARX3Rff.js → SimulatorMask-BHVXyogh-CKmEDsYG.js} +1 -1
- package/dist/{index-R_HIbfUX.js → index-CHMa2zG4.js} +1065 -1042
- package/dist/index.js +1 -1
- package/dist/page-tools/a11y/config.d.ts +6 -3
- package/dist/page-tools/a11y/constants.d.ts +20 -0
- package/dist/page-tools/a11y/utils.d.ts +14 -0
- package/dist/page-tools/constants.d.ts +4 -2
- package/dist/page-tools/context.d.ts +2 -0
- package/dist/page-tools/handlers/hover.d.ts +9 -0
- package/dist/page-tools/schema.d.ts +3 -3
- package/dist/page-tools/utils/dom.d.ts +8 -2
- package/dist/runtime.js +19 -172
- package/package.json +6 -1
- package/WebMcpClient.ts +0 -505
- package/WebMcpServer.ts +0 -447
- package/agent/AgentModelProvider.ts +0 -932
- package/agent/type.ts +0 -91
- package/agent/utils/generateReActPrompt.ts +0 -71
- package/agent/utils/getAISDKTools.ts +0 -34
- package/agent/utils/getBuiltinMcpTools.ts +0 -86
- package/agent/utils/parseReActAction.ts +0 -34
- package/core.ts +0 -26
- package/index.ts +0 -91
- package/page-tools/a11y/build.ts +0 -74
- package/page-tools/a11y/config.ts +0 -465
- package/page-tools/a11y/constants.ts +0 -131
- package/page-tools/a11y/search.ts +0 -127
- package/page-tools/a11y/types.ts +0 -105
- package/page-tools/a11y/utils.ts +0 -239
- package/page-tools/a11y/vnode.ts +0 -439
- package/page-tools/a11y-tree.ts +0 -14
- package/page-tools/bridge.ts +0 -722
- package/page-tools/configs/console-cloud.ts +0 -172
- package/page-tools/constants.ts +0 -12
- package/page-tools/context.ts +0 -50
- package/page-tools/effects.ts +0 -345
- package/page-tools/handlers/browserState.ts +0 -12
- package/page-tools/handlers/click.ts +0 -30
- package/page-tools/handlers/executeJavascript.ts +0 -22
- package/page-tools/handlers/fill.ts +0 -65
- package/page-tools/handlers/scroll.ts +0 -66
- package/page-tools/handlers/searchTree.ts +0 -27
- package/page-tools/handlers/select.ts +0 -39
- package/page-tools/initialize-builtin-WebMCP.ts +0 -20
- package/page-tools/page-agent-highlight/index.ts +0 -245
- package/page-tools/page-agent-mask/SimulatorMask.module.css +0 -14
- package/page-tools/page-agent-mask/SimulatorMask.ts +0 -299
- package/page-tools/page-agent-mask/checkDarkMode.ts +0 -181
- package/page-tools/page-agent-mask/cursor-border.svg +0 -3
- package/page-tools/page-agent-mask/cursor-fill.svg +0 -5
- package/page-tools/page-agent-mask/cursor.module.css +0 -70
- package/page-tools/page-agent-mask/hauwei.svg +0 -25
- package/page-tools/page-agent-prompt.md +0 -139
- package/page-tools/page-agent-tool-event.ts +0 -113
- package/page-tools/page-agent-tool.ts +0 -207
- package/page-tools/page-state-cache.ts +0 -78
- package/page-tools/schema.ts +0 -52
- package/page-tools/tool-config.ts +0 -100
- package/page-tools/utils/dom.ts +0 -158
- package/page-tools/utils/scroll.ts +0 -58
- package/remoter/QrCode.ts +0 -54
- package/remoter/createRemoter.ts +0 -957
- package/remoter/svgs/chat.svg +0 -43
- package/remoter/svgs/icon-copy.svg +0 -25
- package/remoter/svgs/link.svg +0 -28
- package/remoter/svgs/logo.svg +0 -45
- package/remoter/svgs/qrcode.svg +0 -35
- package/remoter/svgs/scan.svg +0 -21
- package/remoter/tooltips.ts +0 -260
- package/runtime.html +0 -39
- package/runtime.ts +0 -44
- package/script/utils.ts +0 -26
- package/skills/index.ts +0 -287
- package/test/page-tools/a11y/build.test.ts +0 -638
- package/test/page-tools/a11y/config.test.ts +0 -370
- package/test/page-tools/configs/console-cloud.test.ts +0 -168
- package/test/page-tools/page-agent-highlight.test.ts +0 -110
- package/test/page-tools/page-agent-tool-dispatch.test.ts +0 -208
- package/test/page-tools/page-agent-tool.test.ts +0 -102
- package/test/page-tools/tool-config.test.ts +0 -112
- package/test/page-tools/utils/dom.test.ts +0 -122
- package/tsconfig.json +0 -16
- package/utils/builtinProxy.ts +0 -144
- package/utils/env.ts +0 -13
- package/utils/uuid.ts +0 -10
- package/vite.config.runtime.ts +0 -22
- package/vite.config.ts +0 -63
- package/vitest.config.ts +0 -10
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tool-config.ts
|
|
3
|
-
*
|
|
4
|
-
* registerPageAgentTool 的完整运行期配置:顶层选项(enableHighlight 等)+ 统一无障碍配置(a11yConfig)。
|
|
5
|
-
* 两者合并到同一个配置对象里,提供唯一一套运行期读写 API(getPageAgentToolConfig/setPageAgentToolConfig):
|
|
6
|
-
* 注册时初始化一次,之后可随时读取/修改,无需分别记住两套 get/set 函数。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { A11yConfig, ResolvedA11yConfig } from './a11y/config'
|
|
10
|
-
import { DEFAULT_A11Y_CONFIG, mergeA11yConfig, mergeA11yConfigs } from './a11y/config'
|
|
11
|
-
|
|
12
|
-
export interface PageAgentToolConfig {
|
|
13
|
-
/** 是否启用元素高亮 */
|
|
14
|
-
enableHighlight: boolean
|
|
15
|
-
/** 是否在工具调用后移除遮罩, 默认值为 true 移除 */
|
|
16
|
-
removeMaskAfterToolCall?: boolean
|
|
17
|
-
/**
|
|
18
|
-
* 统一无障碍配置:按角色(roles)、状态(states:selected/disabled/error/warning 等)自定义规则,
|
|
19
|
-
* 以及白名单/黑名单/自定义暴露属性/弹窗选择器。已与默认配置合并(数组类字段是拼接结果)。
|
|
20
|
-
*/
|
|
21
|
-
a11yConfig: ResolvedA11yConfig
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 用于初始化(registerPageAgentTool 的入参)或运行期 setPageAgentToolConfig 的补丁对象:
|
|
26
|
-
* enableHighlight 为覆盖式更新;a11yConfig 为拼接式合并(数组类字段追加,不丢已有规则,语义见 a11y/config.ts)。
|
|
27
|
-
*/
|
|
28
|
-
export interface PageAgentToolConfigPatch {
|
|
29
|
-
/** 是否启用元素高亮 */
|
|
30
|
-
enableHighlight?: boolean
|
|
31
|
-
/** 是否在工具调用后移除遮罩, 默认值为 true 移除 */
|
|
32
|
-
removeMaskAfterToolCall?: boolean
|
|
33
|
-
/** 是否在工具调用后移除遮罩 */
|
|
34
|
-
/** 统一无障碍配置,会与当前生效的 a11yConfig 按数组拼接合并 */
|
|
35
|
-
a11yConfig?: A11yConfig
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** registerPageAgentTool(options) 的入参类型,与运行期补丁 {@link PageAgentToolConfigPatch} 完全一致 */
|
|
39
|
-
export type PageAgentToolOptions = PageAgentToolConfigPatch
|
|
40
|
-
|
|
41
|
-
/** 默认生效的完整工具配置 */
|
|
42
|
-
export const DEFAULT_PAGE_AGENT_TOOL_CONFIG: PageAgentToolConfig = {
|
|
43
|
-
enableHighlight: false,
|
|
44
|
-
removeMaskAfterToolCall: true,
|
|
45
|
-
a11yConfig: DEFAULT_A11Y_CONFIG
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
declare global {
|
|
49
|
-
interface Window {
|
|
50
|
-
/** 运行期唯一生效的 page-agent-tool 完整配置(已与默认值合并),可通过 getPageAgentToolConfig/setPageAgentToolConfig 读写 */
|
|
51
|
-
__webmcpcli_toolConfig?: PageAgentToolConfig
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** 读取当前生效的完整工具配置(已与默认值合并)。未初始化时返回默认配置 */
|
|
56
|
-
export function getPageAgentToolConfig(): PageAgentToolConfig {
|
|
57
|
-
if (typeof window !== 'undefined' && window.__webmcpcli_toolConfig) {
|
|
58
|
-
return window.__webmcpcli_toolConfig
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
enableHighlight: DEFAULT_PAGE_AGENT_TOOL_CONFIG.enableHighlight,
|
|
62
|
-
removeMaskAfterToolCall: DEFAULT_PAGE_AGENT_TOOL_CONFIG.removeMaskAfterToolCall,
|
|
63
|
-
a11yConfig: mergeA11yConfig()
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function resolvePatch(patch: PageAgentToolConfigPatch, base: PageAgentToolConfig): PageAgentToolConfig {
|
|
68
|
-
return {
|
|
69
|
-
enableHighlight: patch.enableHighlight ?? base.enableHighlight,
|
|
70
|
-
removeMaskAfterToolCall: patch.removeMaskAfterToolCall ?? base.removeMaskAfterToolCall,
|
|
71
|
-
a11yConfig: mergeA11yConfigs(base.a11yConfig, patch.a11yConfig ?? {})
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 更新当前生效配置。
|
|
77
|
-
* - patch 为对象时:enableHighlight 覆盖式更新;a11yConfig 与当前配置按数组拼接合并(不丢已有规则)
|
|
78
|
-
* - patch 为函数时:入参为当前生效配置,返回值直接与默认配置合并(而不是再与 current 相加)。
|
|
79
|
-
* 这样函数体内可以对 current.a11yConfig 做任意过滤/裁剪(如按条件"移除"某条旧规则),
|
|
80
|
-
* 返回值即为最终生效的配置,不会因为再与 current 相加而让被过滤掉的旧规则"复活"
|
|
81
|
-
* - options.mode = 'replace' 时(仅影响对象类型的 patch):不与"当前生效配置"合并,而是与默认配置重新合并
|
|
82
|
-
* 返回合并后的最新完整配置,并写回运行期存储供 buildBrowserStateResponse/buildA11yTree 等读取。
|
|
83
|
-
*/
|
|
84
|
-
export function setPageAgentToolConfig(
|
|
85
|
-
patch: PageAgentToolConfigPatch | ((current: PageAgentToolConfig) => PageAgentToolConfigPatch),
|
|
86
|
-
options?: { mode?: 'merge' | 'replace' }
|
|
87
|
-
): PageAgentToolConfig {
|
|
88
|
-
const mode = options?.mode ?? 'merge'
|
|
89
|
-
const current = getPageAgentToolConfig()
|
|
90
|
-
|
|
91
|
-
const next =
|
|
92
|
-
typeof patch === 'function'
|
|
93
|
-
? resolvePatch(patch(current), DEFAULT_PAGE_AGENT_TOOL_CONFIG)
|
|
94
|
-
: resolvePatch(patch, mode === 'replace' ? DEFAULT_PAGE_AGENT_TOOL_CONFIG : current)
|
|
95
|
-
|
|
96
|
-
if (typeof window !== 'undefined') {
|
|
97
|
-
window.__webmcpcli_toolConfig = next
|
|
98
|
-
}
|
|
99
|
-
return next
|
|
100
|
-
}
|
package/page-tools/utils/dom.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { extractSelectors } from '../a11y/config'
|
|
2
|
-
import { getPageAgentToolConfig } from '../tool-config'
|
|
3
|
-
|
|
4
|
-
// ─── 辅助:Shadow DOM 事件穿透 ───────────────────────────────────────────
|
|
5
|
-
// 外部包 inputTextElement/selectOptionElement 派发的合成事件 composed:false,
|
|
6
|
-
// 无法穿透 shadow boundary,导致事件委托框架(如 React)收不到 shadow 内元素的
|
|
7
|
-
// input/change 事件。这里对 shadow DOM 内元素补发 composed:true 事件。
|
|
8
|
-
export function isInShadowDom(el: Element): boolean {
|
|
9
|
-
return el.getRootNode() instanceof ShadowRoot
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function dispatchComposedEvents(el: Element, ...types: string[]) {
|
|
13
|
-
for (const type of types) {
|
|
14
|
-
el.dispatchEvent(new Event(type, { bubbles: true, composed: true }))
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// ─── 辅助:穿透 Shadow DOM 的查询 ─────────────────────────────────────────
|
|
19
|
-
export function deepQuerySelectorAll(selector: string, root: Document | Element | ShadowRoot = document): Element[] {
|
|
20
|
-
const elements = Array.from(root.querySelectorAll(selector))
|
|
21
|
-
|
|
22
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null)
|
|
23
|
-
let currentNode: Node | null = walker.currentNode
|
|
24
|
-
while (currentNode) {
|
|
25
|
-
if ((currentNode as Element).shadowRoot) {
|
|
26
|
-
elements.push(...deepQuerySelectorAll(selector, (currentNode as Element).shadowRoot!))
|
|
27
|
-
}
|
|
28
|
-
currentNode = walker.nextNode()
|
|
29
|
-
}
|
|
30
|
-
return elements
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ─── 辅助:等待 DOM 变更稳定 ────────────────────────────────────────────
|
|
34
|
-
// click/fill/select 操作后,框架(Angular/React/Vue)可能异步插入校验错误、
|
|
35
|
-
// 条件渲染选项等动态内容。用 MutationObserver 监听 DOM 变更,等待变更平息后
|
|
36
|
-
// 再构建 A11y 树,确保动态插入的内容(如 ng-star-inserted 校验提示)被捕获。
|
|
37
|
-
export function waitForDomSettled(timeout = 600, settleTime = 150): Promise<void> {
|
|
38
|
-
return new Promise<void>((resolve) => {
|
|
39
|
-
let settled = false
|
|
40
|
-
let settleTimer: ReturnType<typeof setTimeout> | undefined
|
|
41
|
-
const finish = () => {
|
|
42
|
-
if (settled) return
|
|
43
|
-
settled = true
|
|
44
|
-
observer.disconnect()
|
|
45
|
-
if (settleTimer) clearTimeout(settleTimer)
|
|
46
|
-
resolve()
|
|
47
|
-
}
|
|
48
|
-
// 每次有 DOM 变更时重置 settle 计时器;变更平息 settleTime 后视为稳定
|
|
49
|
-
const observer = new MutationObserver(() => {
|
|
50
|
-
if (settleTimer) clearTimeout(settleTimer)
|
|
51
|
-
settleTimer = setTimeout(finish, settleTime)
|
|
52
|
-
})
|
|
53
|
-
// 若无任何变更,settleTime 后直接结束
|
|
54
|
-
settleTimer = setTimeout(finish, settleTime)
|
|
55
|
-
// 超时保护:timeout 后强制结束,避免无限等待
|
|
56
|
-
setTimeout(finish, timeout)
|
|
57
|
-
observer.observe(document.body, {
|
|
58
|
-
childList: true,
|
|
59
|
-
subtree: true,
|
|
60
|
-
attributes: true,
|
|
61
|
-
attributeFilter: ['class', 'style', 'hidden'],
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ─── 辅助:检测页面级模态弹窗/遮罩层 ────────────────────────────────────
|
|
67
|
-
// 仅检测真正阻塞交互的模态弹窗(确认框、提交对话框等),
|
|
68
|
-
// 不检测顶部通知横幅、站内消息等非模态提示,避免误报导致 AI 循环
|
|
69
|
-
export function detectPageDialog(): string {
|
|
70
|
-
const seen = new Set<Element>()
|
|
71
|
-
const dialogs: string[] = []
|
|
72
|
-
// 仅匹配明确的模态弹窗选择器,避免宽泛子串匹配误报
|
|
73
|
-
// 统一取 getPageAgentToolConfig().a11yConfig.dialogSelectors(已含默认值 + 用户配置)
|
|
74
|
-
const selectors = getPageAgentToolConfig().a11yConfig.dialogSelectors
|
|
75
|
-
|
|
76
|
-
for (const selector of selectors) {
|
|
77
|
-
try {
|
|
78
|
-
for (const el of deepQuerySelectorAll(selector)) {
|
|
79
|
-
if (seen.has(el)) continue
|
|
80
|
-
// 跳过 SDK 自身遮罩
|
|
81
|
-
if (el.id?.includes('page-agent-runtime')) continue
|
|
82
|
-
if (el.closest('#page-agent-runtime_simulator-mask')) continue
|
|
83
|
-
|
|
84
|
-
const rect = el.getBoundingClientRect()
|
|
85
|
-
if (rect.width < 50 || rect.height < 50) continue
|
|
86
|
-
const style = window.getComputedStyle(el as HTMLElement)
|
|
87
|
-
if (style.display === 'none' || style.visibility === 'hidden') continue
|
|
88
|
-
|
|
89
|
-
// 模态性验证:必须是 fixed/absolute 且 z-index 较高
|
|
90
|
-
const isFixed = style.position === 'fixed' || style.position === 'absolute'
|
|
91
|
-
const zIndex = parseInt(style.zIndex || '0', 10)
|
|
92
|
-
if (!isFixed || zIndex < 100) continue
|
|
93
|
-
|
|
94
|
-
// 必须覆盖视口中心区域(真正的模态弹窗会遮挡页面中心)
|
|
95
|
-
const vw = window.innerWidth
|
|
96
|
-
const vh = window.innerHeight
|
|
97
|
-
const coversCenter = rect.left < vw * 0.6 && rect.right > vw * 0.4 &&
|
|
98
|
-
rect.top < vh * 0.6 && rect.bottom > vh * 0.4
|
|
99
|
-
if (!coversCenter) continue
|
|
100
|
-
|
|
101
|
-
seen.add(el)
|
|
102
|
-
const text = (el.textContent || '').trim().replace(/\s+/g, ' ')
|
|
103
|
-
if (text.length > 5) {
|
|
104
|
-
// 提取弹窗内的可交互按钮,帮助 AI 快速决策
|
|
105
|
-
const btns = el.querySelectorAll('button, [role="button"], a')
|
|
106
|
-
const btnTexts = Array.from(btns)
|
|
107
|
-
.map(b => (b.textContent || '').trim())
|
|
108
|
-
.filter(t => t.length > 0 && t.length < 20)
|
|
109
|
-
.slice(0, 5)
|
|
110
|
-
const btnInfo = btnTexts.length ? ` [可操作按钮: ${btnTexts.join(' / ')}]` : ''
|
|
111
|
-
dialogs.push(`${text.substring(0, 300)}${btnInfo}`)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch {
|
|
115
|
-
// 忽略选择器异常
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (dialogs.length === 0) return ''
|
|
120
|
-
return `\n[页面弹窗检测] 检测到 ${dialogs.length} 个模态弹窗,请优先处理:\n${dialogs.map((d, i) => `${i + 1}. ${d}`).join('\n')}\n`
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ─── 辅助:检测页面可见的表单校验错误 ──────────────────────────────────
|
|
124
|
-
// 操作后自动扫描页面中的校验错误提示,提取文本并提醒 AI,
|
|
125
|
-
// 避免 AI 卡住时不知道是因为有校验错误未处理
|
|
126
|
-
export function detectValidationErrors(): string {
|
|
127
|
-
const seen = new Set<Element>()
|
|
128
|
-
const errors: string[] = []
|
|
129
|
-
// 校验错误选择器:ARIA 标准 + 主流 UI 框架(可配置)
|
|
130
|
-
// 统一取 getPageAgentToolConfig().a11yConfig.states.error(已含默认值 + 用户配置)
|
|
131
|
-
const selectors = extractSelectors(getPageAgentToolConfig().a11yConfig.states.error)
|
|
132
|
-
|
|
133
|
-
for (const selector of selectors) {
|
|
134
|
-
try {
|
|
135
|
-
for (const el of deepQuerySelectorAll(selector)) {
|
|
136
|
-
if (seen.has(el)) continue
|
|
137
|
-
// 跳过嵌套在已收集的错误元素内的子元素,避免重复
|
|
138
|
-
if (Array.from(seen).some(s => s.contains(el))) continue
|
|
139
|
-
|
|
140
|
-
const rect = el.getBoundingClientRect()
|
|
141
|
-
if (rect.width < 1 || rect.height < 1) continue
|
|
142
|
-
const style = window.getComputedStyle(el as HTMLElement)
|
|
143
|
-
if (style.display === 'none' || style.visibility === 'hidden') continue
|
|
144
|
-
|
|
145
|
-
const text = (el.textContent || '').trim().replace(/\s+/g, ' ')
|
|
146
|
-
if (text.length > 2 && text.length < 200) {
|
|
147
|
-
seen.add(el)
|
|
148
|
-
errors.push(text)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
} catch {
|
|
152
|
-
// 忽略选择器异常
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (errors.length === 0) return ''
|
|
157
|
-
return `\n[校验提示] 检测到 ${errors.length} 个表单校验错误,请先修复后再继续:\n${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}\n`
|
|
158
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// ─── 辅助:获取滚动目标的当前位置信息(仿 page-agent getPageInfo)────────
|
|
2
|
-
// 同时支持 window(文档滚动)和任意 Element(容器滚动)
|
|
3
|
-
export function getScrollInfo(target: Window | Element = window) {
|
|
4
|
-
if (target === window) {
|
|
5
|
-
const pageHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight)
|
|
6
|
-
const pageWidth = Math.max(document.documentElement.scrollWidth, document.body.scrollWidth)
|
|
7
|
-
const scrollY = window.scrollY
|
|
8
|
-
const scrollX = window.scrollX
|
|
9
|
-
const pixelsBelow = Math.max(0, pageHeight - (window.innerHeight + scrollY))
|
|
10
|
-
const pixelsRight = Math.max(0, pageWidth - (window.innerWidth + scrollX))
|
|
11
|
-
return {
|
|
12
|
-
scrollY, scrollX,
|
|
13
|
-
pixelsAbove: scrollY, pixelsBelow,
|
|
14
|
-
pixelsLeft: scrollX, pixelsRight,
|
|
15
|
-
pagesAbove: window.innerHeight > 0 ? scrollY / window.innerHeight : 0,
|
|
16
|
-
pagesBelow: window.innerHeight > 0 ? pixelsBelow / window.innerHeight : 0,
|
|
17
|
-
atTop: scrollY <= 1,
|
|
18
|
-
atBottom: pixelsBelow <= 1,
|
|
19
|
-
atLeft: scrollX <= 1,
|
|
20
|
-
atRight: pixelsRight <= 1,
|
|
21
|
-
}
|
|
22
|
-
} else {
|
|
23
|
-
const el = target as Element
|
|
24
|
-
const scrollTop = el.scrollTop
|
|
25
|
-
const scrollLeft = el.scrollLeft
|
|
26
|
-
const pixelsBelow = el.scrollHeight - el.clientHeight - scrollTop
|
|
27
|
-
const pixelsRight = el.scrollWidth - el.clientWidth - scrollLeft
|
|
28
|
-
return {
|
|
29
|
-
scrollY: scrollTop, scrollX: scrollLeft,
|
|
30
|
-
pixelsAbove: scrollTop, pixelsBelow: Math.max(0, pixelsBelow),
|
|
31
|
-
pixelsLeft: scrollLeft, pixelsRight: Math.max(0, pixelsRight),
|
|
32
|
-
pagesAbove: el.clientHeight > 0 ? scrollTop / el.clientHeight : 0,
|
|
33
|
-
pagesBelow: el.clientHeight > 0 ? Math.max(0, pixelsBelow) / el.clientHeight : 0,
|
|
34
|
-
atTop: scrollTop <= 1,
|
|
35
|
-
atBottom: pixelsBelow <= 1,
|
|
36
|
-
atLeft: scrollLeft <= 1,
|
|
37
|
-
atRight: pixelsRight <= 1,
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 等待 smooth scroll 结束:优先监听 scrollend,超时兜底(边界无位移时 scrollend 可能不触发)
|
|
43
|
-
export function waitForScrollEnd(target: Window | Element, timeout = 2000): Promise<void> {
|
|
44
|
-
return new Promise((resolve) => {
|
|
45
|
-
let settled = false
|
|
46
|
-
const el = target === window ? document : (target as Element)
|
|
47
|
-
const finish = () => {
|
|
48
|
-
if (settled) return
|
|
49
|
-
settled = true
|
|
50
|
-
el.removeEventListener('scrollend', onScrollEnd)
|
|
51
|
-
clearTimeout(timer)
|
|
52
|
-
resolve()
|
|
53
|
-
}
|
|
54
|
-
const onScrollEnd = () => finish()
|
|
55
|
-
const timer = setTimeout(finish, timeout)
|
|
56
|
-
el.addEventListener('scrollend', onScrollEnd, { once: true })
|
|
57
|
-
})
|
|
58
|
-
}
|
package/remoter/QrCode.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import QRCodePck from 'qrcode'
|
|
2
|
-
|
|
3
|
-
/** QrCode 类的配置信息 */
|
|
4
|
-
export type QrCodeOption = ConstructorParameters<typeof QrCode>[1]
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 二维码工具类,根据传入的value,生成相应的二维码,并输出到 <canvas> 或 <img>上。
|
|
8
|
-
* @example
|
|
9
|
-
* const qr= new QrCode('https://www.baidu.com', { size: 100 })
|
|
10
|
-
*
|
|
11
|
-
* qr.toCanvas(canvasDom)
|
|
12
|
-
* qr.toImage(imgDom)
|
|
13
|
-
*/
|
|
14
|
-
export class QrCode {
|
|
15
|
-
private value: string
|
|
16
|
-
private size: number
|
|
17
|
-
private margin: number
|
|
18
|
-
private color: string
|
|
19
|
-
private bgColor: string
|
|
20
|
-
|
|
21
|
-
constructor(value: string, { size = 200, margin = 4, color = '#000', bgColor = '#fff' }) {
|
|
22
|
-
this.value = value
|
|
23
|
-
this.size = size
|
|
24
|
-
this.margin = margin
|
|
25
|
-
this.color = color
|
|
26
|
-
this.bgColor = bgColor
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
get qrCodeOption() {
|
|
30
|
-
return {
|
|
31
|
-
width: this.size,
|
|
32
|
-
margin: this.margin,
|
|
33
|
-
color: {
|
|
34
|
-
dark: this.color, // 前景色
|
|
35
|
-
light: this.bgColor // 背景色
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 生成二维码的 Data URL(base64 图片) */
|
|
41
|
-
async toDataURL(): Promise<string> {
|
|
42
|
-
return QRCodePck.toDataURL(this.value, this.qrCodeOption)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** 渲染二维码到指定的 canvas 元素 */
|
|
46
|
-
async toCanvas(canvas: HTMLCanvasElement): Promise<void> {
|
|
47
|
-
return QRCodePck.toCanvas(canvas, this.value, this.qrCodeOption)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 渲染二维码到指定的 img 元素 */
|
|
51
|
-
async toImage(img: HTMLImageElement): Promise<void> {
|
|
52
|
-
img.src = await this.toDataURL()
|
|
53
|
-
}
|
|
54
|
-
}
|