@h-ai/ui 0.1.0-alpha.50 → 0.1.0-alpha.53
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.
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
AiTableEditorProps,
|
|
24
24
|
} from './table-types.js'
|
|
25
25
|
import { SvelteSet } from 'svelte/reactivity'
|
|
26
|
+
import { writeTextToClipboard } from '../../../../internal/browser-safety.js'
|
|
26
27
|
import { uiM } from '../../../../messages.js'
|
|
27
28
|
import { cn, getDataAttributes } from '../../../../utils.js'
|
|
28
29
|
import AiDocumentDownloadMenu from '../AiDocumentDownloadMenu.svelte'
|
|
@@ -969,12 +970,9 @@
|
|
|
969
970
|
return
|
|
970
971
|
}
|
|
971
972
|
|
|
972
|
-
if (
|
|
973
|
-
|
|
973
|
+
if (await writeTextToClipboard(text)) {
|
|
974
|
+
triggerCopiedFeedback()
|
|
974
975
|
}
|
|
975
|
-
|
|
976
|
-
await navigator.clipboard.writeText(text)
|
|
977
|
-
triggerCopiedFeedback()
|
|
978
976
|
}
|
|
979
977
|
|
|
980
978
|
async function handleDownloadTable(): Promise<void> {
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
* 统一封装容易在受限 WebView、隐私模式或不安全上下文中失败的浏览器 API,
|
|
5
5
|
* 让组件在能力缺失时优雅降级而不是抛出运行时错误。
|
|
6
6
|
*/
|
|
7
|
+
/** 宿主页面剪贴板写入器(跨域 iframe 场景由嵌入方注入)。 */
|
|
8
|
+
export type ClipboardHostWriter = (text: string) => Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* 注册宿主页面剪贴板写入器。
|
|
11
|
+
*
|
|
12
|
+
* 当 iframe 内 Clipboard API 与 execCommand 都不可用时,把文本交给宿主页面写入。
|
|
13
|
+
* 宿主页面是顶层文档,始终聚焦且持有完整剪贴板权限,是跨域 iframe 场景的最终兜底。
|
|
14
|
+
*
|
|
15
|
+
* @param writer - 宿主写入器;传入 null 注销
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerClipboardHostWriter(writer: ClipboardHostWriter | null): void;
|
|
7
18
|
/**
|
|
8
19
|
* 安全读取 localStorage。
|
|
9
20
|
*
|
|
@@ -21,7 +32,8 @@ export declare function writeStoredValue(key: string, value: string): boolean;
|
|
|
21
32
|
* 安全写入剪贴板。
|
|
22
33
|
*
|
|
23
34
|
* 优先使用现代 Clipboard API;在不安全上下文(HTTP)、旧 WebView
|
|
24
|
-
* 或浏览器策略禁用时回退到 `document.execCommand('copy')
|
|
35
|
+
* 或浏览器策略禁用时回退到 `document.execCommand('copy')`;
|
|
36
|
+
* 跨域 iframe 中上述两种方式都可能被权限/聚焦策略拒绝时,再回退到宿主页面写入器,
|
|
25
37
|
* 确保尽可能多的环境下复制操作可用。
|
|
26
38
|
*/
|
|
27
39
|
export declare function writeTextToClipboard(text: string): Promise<boolean>;
|
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
* 统一封装容易在受限 WebView、隐私模式或不安全上下文中失败的浏览器 API,
|
|
5
5
|
* 让组件在能力缺失时优雅降级而不是抛出运行时错误。
|
|
6
6
|
*/
|
|
7
|
+
let clipboardHostWriter = null;
|
|
8
|
+
/**
|
|
9
|
+
* 注册宿主页面剪贴板写入器。
|
|
10
|
+
*
|
|
11
|
+
* 当 iframe 内 Clipboard API 与 execCommand 都不可用时,把文本交给宿主页面写入。
|
|
12
|
+
* 宿主页面是顶层文档,始终聚焦且持有完整剪贴板权限,是跨域 iframe 场景的最终兜底。
|
|
13
|
+
*
|
|
14
|
+
* @param writer - 宿主写入器;传入 null 注销
|
|
15
|
+
*/
|
|
16
|
+
export function registerClipboardHostWriter(writer) {
|
|
17
|
+
clipboardHostWriter = writer;
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
20
|
* 安全读取 localStorage。
|
|
9
21
|
*
|
|
@@ -42,15 +54,13 @@ export function writeStoredValue(key, value) {
|
|
|
42
54
|
* 安全写入剪贴板。
|
|
43
55
|
*
|
|
44
56
|
* 优先使用现代 Clipboard API;在不安全上下文(HTTP)、旧 WebView
|
|
45
|
-
* 或浏览器策略禁用时回退到 `document.execCommand('copy')
|
|
57
|
+
* 或浏览器策略禁用时回退到 `document.execCommand('copy')`;
|
|
58
|
+
* 跨域 iframe 中上述两种方式都可能被权限/聚焦策略拒绝时,再回退到宿主页面写入器,
|
|
46
59
|
* 确保尽可能多的环境下复制操作可用。
|
|
47
60
|
*/
|
|
48
61
|
export async function writeTextToClipboard(text) {
|
|
49
|
-
if (typeof navigator === 'undefined') {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
62
|
// 现代 Clipboard API(仅安全上下文可用)
|
|
53
|
-
if (navigator.clipboard?.writeText) {
|
|
63
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
54
64
|
try {
|
|
55
65
|
await navigator.clipboard.writeText(text);
|
|
56
66
|
return true;
|
|
@@ -60,20 +70,31 @@ export async function writeTextToClipboard(text) {
|
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
// fallback:textarea + execCommand(兼容 HTTP、旧浏览器)
|
|
63
|
-
if (typeof document
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if (typeof document !== 'undefined') {
|
|
74
|
+
try {
|
|
75
|
+
const textarea = document.createElement('textarea');
|
|
76
|
+
textarea.value = text;
|
|
77
|
+
textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
|
|
78
|
+
document.body.appendChild(textarea);
|
|
79
|
+
textarea.select();
|
|
80
|
+
const ok = document.execCommand('copy');
|
|
81
|
+
document.body.removeChild(textarea);
|
|
82
|
+
if (ok) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// 继续尝试宿主写入器
|
|
88
|
+
}
|
|
75
89
|
}
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
// 最终兜底:跨域 iframe 场景由宿主页面写入
|
|
91
|
+
if (clipboardHostWriter) {
|
|
92
|
+
try {
|
|
93
|
+
return await clipboardHostWriter(text);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
78
98
|
}
|
|
99
|
+
return false;
|
|
79
100
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.53",
|
|
5
5
|
"description": "Hai Framework UI component library for Svelte 5 Runes.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"svelte": "./dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"mermaid": "^11.12.0",
|
|
53
53
|
"shiki": "^4.0.2",
|
|
54
54
|
"tailwind-merge": "^3.5.0",
|
|
55
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
55
|
+
"@h-ai/core": "0.1.0-alpha.53"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@sveltejs/package": "^2.5.7",
|