@cfp-studio/iframe 18.0.1 → 18.3.0
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/README.md +37 -0
- package/dist/index.d.mts +168 -149
- package/dist/index.mjs +1 -1
- package/package.json +21 -9
- package/preview/__previewjs_internal__/index.ts +2 -3
- package/preview/__previewjs_internal__/links.ts +0 -1
- package/preview/__previewjs_internal__/logs.ts +0 -4
- package/preview/__previewjs_internal__/overlay.ts +187 -0
- package/preview/__previewjs_internal__/run-renderer.ts +4 -3
- package/preview/__previewjs_internal__/runtime-bridge.ts +559 -0
- package/preview/__previewjs_internal__/vscode-control.ts +75 -50
- package/preview/index.html +17 -5
- package/dist/index.d.ts +0 -268
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page-internal overlay channel.
|
|
3
|
+
*
|
|
4
|
+
* Overlays live inside the preview page so they remain aligned with the
|
|
5
|
+
* rendered pixels in external browser mode. A Shadow DOM host isolates styles
|
|
6
|
+
* from the inspected application. Each feature owns a named layer so clearing
|
|
7
|
+
* one overlay cannot clear another feature's overlay.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface PageOverlayItem {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
label?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
borderWidth?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PageOverlayPayload {
|
|
21
|
+
visible: boolean;
|
|
22
|
+
items: PageOverlayItem[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let host: HTMLDivElement | undefined;
|
|
26
|
+
let shadowRoot: ShadowRoot | undefined;
|
|
27
|
+
const layers = new Map<string, HTMLDivElement>();
|
|
28
|
+
|
|
29
|
+
export function setPageOverlay(layerId: string, payload: PageOverlayPayload | undefined): void {
|
|
30
|
+
if (!payload?.visible || payload.items.length === 0) {
|
|
31
|
+
clearPageOverlay(layerId);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
ensureHost();
|
|
35
|
+
const layer = ensureLayer(layerId);
|
|
36
|
+
layer.replaceChildren();
|
|
37
|
+
for (const item of payload.items) {
|
|
38
|
+
const element = document.createElement("div");
|
|
39
|
+
element.className = "cfp-overlay-item";
|
|
40
|
+
element.style.left = `${item.x}px`;
|
|
41
|
+
element.style.top = `${item.y}px`;
|
|
42
|
+
element.style.width = `${item.width}px`;
|
|
43
|
+
element.style.height = `${item.height}px`;
|
|
44
|
+
element.style.borderColor = item.color ?? "#3794ff";
|
|
45
|
+
element.style.borderWidth = `${item.borderWidth ?? 1}px`;
|
|
46
|
+
if (item.label) {
|
|
47
|
+
const label = document.createElement("span");
|
|
48
|
+
label.className = "cfp-overlay-label";
|
|
49
|
+
label.textContent = item.label;
|
|
50
|
+
element.appendChild(label);
|
|
51
|
+
}
|
|
52
|
+
layer.appendChild(element);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function clearPageOverlay(layerId?: string): void {
|
|
57
|
+
if (!layerId) {
|
|
58
|
+
host?.remove();
|
|
59
|
+
host = undefined;
|
|
60
|
+
shadowRoot = undefined;
|
|
61
|
+
layers.clear();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
layers.get(layerId)?.remove();
|
|
65
|
+
layers.delete(layerId);
|
|
66
|
+
if (layers.size === 0) {
|
|
67
|
+
host?.remove();
|
|
68
|
+
host = undefined;
|
|
69
|
+
shadowRoot = undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let shieldHost: HTMLDivElement | undefined;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* inspect 期间拦掉页面自身的指针交互:用户只能和覆盖层打交道。
|
|
77
|
+
*
|
|
78
|
+
* 用一个真实元素接管命中测试,而不是靠 `pointer-events: none` + preventDefault:
|
|
79
|
+
* 这样 CSS `:hover`、光标、文本选择、拖拽、滚动、表单聚焦都不会落到应用上。
|
|
80
|
+
* 代价是 `elementFromPoint` 会返回盾牌本身,所以命中测试要换成
|
|
81
|
+
* `elementsFromPoint` 并跳过覆盖层元素(见 `isOverlayChrome`)。
|
|
82
|
+
*/
|
|
83
|
+
export function setPageInteractionShield(enabled: boolean): void {
|
|
84
|
+
if (typeof document === "undefined") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!enabled) {
|
|
88
|
+
shieldHost?.remove();
|
|
89
|
+
shieldHost = undefined;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (shieldHost?.isConnected) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const element = document.createElement("div");
|
|
96
|
+
element.dataset.cfpShield = "true";
|
|
97
|
+
element.setAttribute("aria-hidden", "true");
|
|
98
|
+
// 与应用样式隔离:`* { cursor: ... }` / `touch-action` 之类的规则不应影响盾牌。
|
|
99
|
+
const root = element.attachShadow({ mode: "closed" });
|
|
100
|
+
const style = document.createElement("style");
|
|
101
|
+
style.textContent = `
|
|
102
|
+
:host {
|
|
103
|
+
all: initial;
|
|
104
|
+
position: fixed;
|
|
105
|
+
inset: 0;
|
|
106
|
+
z-index: 2147483646;
|
|
107
|
+
cursor: crosshair;
|
|
108
|
+
touch-action: none;
|
|
109
|
+
overscroll-behavior: contain;
|
|
110
|
+
}
|
|
111
|
+
div {
|
|
112
|
+
position: fixed;
|
|
113
|
+
inset: 0;
|
|
114
|
+
background: transparent;
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
root.append(style, document.createElement("div"));
|
|
118
|
+
document.documentElement.appendChild(element);
|
|
119
|
+
shieldHost = element;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 覆盖层自身的 DOM(overlay host / 盾牌);命中测试必须跳过它们。 */
|
|
123
|
+
export function isOverlayChrome(element: Element): boolean {
|
|
124
|
+
return Boolean(element.closest("[data-cfp-overlay], [data-cfp-shield]"));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ensureLayer(layerId: string): HTMLDivElement {
|
|
128
|
+
const existing = layers.get(layerId);
|
|
129
|
+
if (existing?.isConnected) {
|
|
130
|
+
return existing;
|
|
131
|
+
}
|
|
132
|
+
if (!shadowRoot) {
|
|
133
|
+
throw new Error("Overlay shadow root is not available");
|
|
134
|
+
}
|
|
135
|
+
const layer = document.createElement("div");
|
|
136
|
+
layer.className = "cfp-overlay-layer";
|
|
137
|
+
layer.dataset.cfpOverlayLayer = layerId;
|
|
138
|
+
shadowRoot.appendChild(layer);
|
|
139
|
+
layers.set(layerId, layer);
|
|
140
|
+
return layer;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ensureHost(): void {
|
|
144
|
+
if (host?.isConnected && shadowRoot) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
host = document.createElement("div");
|
|
148
|
+
host.dataset.cfpOverlay = "true";
|
|
149
|
+
host.setAttribute("aria-hidden", "true");
|
|
150
|
+
shadowRoot = host.attachShadow({ mode: "closed" });
|
|
151
|
+
const style = document.createElement("style");
|
|
152
|
+
style.textContent = `
|
|
153
|
+
:host {
|
|
154
|
+
all: initial;
|
|
155
|
+
position: fixed;
|
|
156
|
+
inset: 0;
|
|
157
|
+
z-index: 2147483647;
|
|
158
|
+
pointer-events: none;
|
|
159
|
+
contain: layout style;
|
|
160
|
+
}
|
|
161
|
+
.cfp-overlay-layer {
|
|
162
|
+
position: fixed;
|
|
163
|
+
inset: 0;
|
|
164
|
+
pointer-events: none;
|
|
165
|
+
}
|
|
166
|
+
.cfp-overlay-item {
|
|
167
|
+
position: fixed;
|
|
168
|
+
box-sizing: border-box;
|
|
169
|
+
pointer-events: none;
|
|
170
|
+
border-style: solid;
|
|
171
|
+
background: rgba(55, 148, 255, 0.12);
|
|
172
|
+
}
|
|
173
|
+
.cfp-overlay-label {
|
|
174
|
+
position: absolute;
|
|
175
|
+
left: 0;
|
|
176
|
+
top: -20px;
|
|
177
|
+
padding: 2px 5px;
|
|
178
|
+
border-radius: 3px;
|
|
179
|
+
background: #3794ff;
|
|
180
|
+
color: white;
|
|
181
|
+
font: 11px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
182
|
+
white-space: nowrap;
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
shadowRoot.appendChild(style);
|
|
186
|
+
document.documentElement.appendChild(host);
|
|
187
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RendererLoader } from "../../src";
|
|
2
|
+
import { publishRuntimeSnapshot } from "./runtime-bridge";
|
|
2
3
|
import { getState } from "./state";
|
|
3
4
|
|
|
4
5
|
export async function runRenderer({
|
|
@@ -10,10 +11,8 @@ export async function runRenderer({
|
|
|
10
11
|
shouldAbortRender,
|
|
11
12
|
loadRenderer,
|
|
12
13
|
}: {
|
|
13
|
-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
14
14
|
wrapperModule: any;
|
|
15
15
|
wrapperName: string;
|
|
16
|
-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
17
16
|
previewableModule: any;
|
|
18
17
|
previewableName: string;
|
|
19
18
|
renderId: number;
|
|
@@ -65,6 +64,9 @@ export async function runRenderer({
|
|
|
65
64
|
if (shouldAbortRender()) {
|
|
66
65
|
return;
|
|
67
66
|
}
|
|
67
|
+
// Publish before reportEvent: custom preview webviews may not install a
|
|
68
|
+
// __PREVIEWJS_CONTROLLER__, while the runtime bridge is independent.
|
|
69
|
+
setTimeout(publishRuntimeSnapshot, 0);
|
|
68
70
|
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
69
71
|
kind: "rendered",
|
|
70
72
|
});
|
|
@@ -75,7 +77,6 @@ export async function runRenderer({
|
|
|
75
77
|
* and shown to the user.
|
|
76
78
|
*/
|
|
77
79
|
|
|
78
|
-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
79
80
|
function transformFunctions(value: any, path: string[]): any {
|
|
80
81
|
if (value && typeof value === "object") {
|
|
81
82
|
if (Array.isArray(value)) {
|