@djvlc/sandbox 1.0.1 → 1.0.2

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/index.cjs CHANGED
@@ -1,917 +1 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- MessageType: () => MessageType,
24
- SandboxClient: () => SandboxClient,
25
- SandboxHost: () => SandboxHost,
26
- createMessage: () => createMessage,
27
- createResponse: () => createResponse,
28
- generateMessageId: () => generateMessageId,
29
- isDjvlcMessage: () => isDjvlcMessage
30
- });
31
- module.exports = __toCommonJS(index_exports);
32
-
33
- // src/protocol.ts
34
- var MessageType = /* @__PURE__ */ ((MessageType2) => {
35
- MessageType2["READY"] = "ready";
36
- MessageType2["INIT"] = "init";
37
- MessageType2["DESTROY"] = "destroy";
38
- MessageType2["LOAD_PAGE"] = "load_page";
39
- MessageType2["UPDATE_SCHEMA"] = "update_schema";
40
- MessageType2["REFRESH"] = "refresh";
41
- MessageType2["SELECT_COMPONENT"] = "select_component";
42
- MessageType2["HOVER_COMPONENT"] = "hover_component";
43
- MessageType2["UPDATE_COMPONENT"] = "update_component";
44
- MessageType2["DELETE_COMPONENT"] = "delete_component";
45
- MessageType2["ADD_COMPONENT"] = "add_component";
46
- MessageType2["MOVE_COMPONENT"] = "move_component";
47
- MessageType2["SYNC_STATE"] = "sync_state";
48
- MessageType2["SYNC_VARIABLES"] = "sync_variables";
49
- MessageType2["COMPONENT_CLICK"] = "component_click";
50
- MessageType2["COMPONENT_HOVER"] = "component_hover";
51
- MessageType2["COMPONENT_CONTEXT_MENU"] = "component_context_menu";
52
- MessageType2["COMPONENT_DRAG_START"] = "component_drag_start";
53
- MessageType2["COMPONENT_DRAG_END"] = "component_drag_end";
54
- MessageType2["COMPONENT_DROP"] = "component_drop";
55
- MessageType2["ERROR"] = "error";
56
- MessageType2["LOG"] = "log";
57
- return MessageType2;
58
- })(MessageType || {});
59
- function generateMessageId() {
60
- return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
61
- }
62
- function createMessage(type, data) {
63
- return {
64
- type,
65
- id: generateMessageId(),
66
- data,
67
- timestamp: Date.now()
68
- };
69
- }
70
- function createResponse(requestId, type, success, data, error) {
71
- return {
72
- type,
73
- id: generateMessageId(),
74
- requestId,
75
- success,
76
- data,
77
- error,
78
- timestamp: Date.now()
79
- };
80
- }
81
- function isDjvlcMessage(data) {
82
- if (!data || typeof data !== "object") return false;
83
- const msg = data;
84
- return typeof msg.type === "string" && typeof msg.id === "string" && typeof msg.timestamp === "number";
85
- }
86
-
87
- // src/sandbox-host.ts
88
- var SandboxHost = class {
89
- constructor(options) {
90
- this.pendingRequests = /* @__PURE__ */ new Map();
91
- this.isReady = false;
92
- // ==================== 私有方法 ====================
93
- this.handleMessage = (event) => {
94
- if (event.source !== this.options.iframe.contentWindow) {
95
- return;
96
- }
97
- if (this.options.targetOrigin !== "*" && event.origin !== this.options.targetOrigin) {
98
- return;
99
- }
100
- const message = event.data;
101
- if (!isDjvlcMessage(message)) {
102
- return;
103
- }
104
- this.log("debug", "Received message:", message.type, message);
105
- if ("requestId" in message) {
106
- const pending = this.pendingRequests.get(message.requestId);
107
- if (pending) {
108
- clearTimeout(pending.timer);
109
- this.pendingRequests.delete(message.requestId);
110
- if (message.success) {
111
- pending.resolve(message.data);
112
- } else {
113
- pending.reject(new Error(message.error || "Unknown error"));
114
- }
115
- return;
116
- }
117
- }
118
- switch (message.type) {
119
- case "ready" /* READY */:
120
- this.isReady = true;
121
- this.readyResolve();
122
- this.options.onReady?.();
123
- break;
124
- case "component_click" /* COMPONENT_CLICK */:
125
- this.options.onComponentClick?.(
126
- message.data.componentId,
127
- message.data
128
- );
129
- break;
130
- case "component_hover" /* COMPONENT_HOVER */:
131
- this.options.onComponentHover?.(
132
- message.data.componentId,
133
- message.data
134
- );
135
- break;
136
- case "component_context_menu" /* COMPONENT_CONTEXT_MENU */:
137
- this.options.onComponentContextMenu?.(
138
- message.data.componentId,
139
- message.data
140
- );
141
- break;
142
- case "sync_state" /* SYNC_STATE */:
143
- this.options.onSyncState?.(message.data);
144
- break;
145
- case "error" /* ERROR */:
146
- this.options.onError?.(message.data);
147
- break;
148
- case "log" /* LOG */: {
149
- const logData = message.data;
150
- this.options.onLog?.(logData.level, logData.message, ...logData.args || []);
151
- break;
152
- }
153
- }
154
- };
155
- this.options = {
156
- timeout: 3e4,
157
- debug: false,
158
- ...options
159
- };
160
- this.readyPromise = new Promise((resolve) => {
161
- this.readyResolve = resolve;
162
- });
163
- }
164
- /**
165
- * 连接到 iframe
166
- */
167
- connect() {
168
- window.addEventListener("message", this.handleMessage);
169
- this.log("debug", "SandboxHost connected");
170
- }
171
- /**
172
- * 断开连接
173
- */
174
- disconnect() {
175
- window.removeEventListener("message", this.handleMessage);
176
- this.pendingRequests.forEach(({ timer }) => clearTimeout(timer));
177
- this.pendingRequests.clear();
178
- this.isReady = false;
179
- this.log("debug", "SandboxHost disconnected");
180
- }
181
- /**
182
- * 等待就绪
183
- */
184
- async waitReady() {
185
- return this.readyPromise;
186
- }
187
- /**
188
- * 检查是否就绪
189
- */
190
- get ready() {
191
- return this.isReady;
192
- }
193
- // ==================== 页面操作 ====================
194
- /**
195
- * 初始化
196
- */
197
- async init(data) {
198
- await this.send("init" /* INIT */, data);
199
- }
200
- /**
201
- * 加载页面
202
- */
203
- async loadPage(data) {
204
- await this.send("load_page" /* LOAD_PAGE */, data);
205
- }
206
- /**
207
- * 更新 Schema
208
- */
209
- async updateSchema(data) {
210
- await this.send("update_schema" /* UPDATE_SCHEMA */, data);
211
- }
212
- /**
213
- * 刷新
214
- */
215
- async refresh() {
216
- await this.send("refresh" /* REFRESH */, {});
217
- }
218
- // ==================== 组件操作 ====================
219
- /**
220
- * 选择组件
221
- */
222
- async selectComponent(componentId, multi = false) {
223
- await this.send("select_component" /* SELECT_COMPONENT */, {
224
- componentId,
225
- multi
226
- });
227
- }
228
- /**
229
- * 悬停组件
230
- */
231
- async hoverComponent(componentId) {
232
- await this.send("hover_component" /* HOVER_COMPONENT */, {
233
- componentId
234
- });
235
- }
236
- /**
237
- * 更新组件
238
- */
239
- async updateComponent(data) {
240
- await this.send("update_component" /* UPDATE_COMPONENT */, data);
241
- }
242
- /**
243
- * 添加组件
244
- */
245
- async addComponent(data) {
246
- await this.send("add_component" /* ADD_COMPONENT */, data);
247
- }
248
- /**
249
- * 移动组件
250
- */
251
- async moveComponent(data) {
252
- await this.send("move_component" /* MOVE_COMPONENT */, data);
253
- }
254
- /**
255
- * 删除组件
256
- */
257
- async deleteComponent(componentId) {
258
- await this.send("delete_component" /* DELETE_COMPONENT */, {
259
- componentId
260
- });
261
- }
262
- // ==================== 状态同步 ====================
263
- /**
264
- * 同步变量
265
- */
266
- async syncVariables(variables) {
267
- await this.send("sync_variables" /* SYNC_VARIABLES */, { variables });
268
- }
269
- send(type, data) {
270
- return new Promise((resolve, reject) => {
271
- const message = createMessage(type, data);
272
- const timer = setTimeout(() => {
273
- this.pendingRequests.delete(message.id);
274
- reject(new Error(`Request timeout: ${type}`));
275
- }, this.options.timeout);
276
- this.pendingRequests.set(message.id, { resolve, reject, timer });
277
- this.log("debug", "Sending message:", type, message);
278
- this.options.iframe.contentWindow?.postMessage(message, this.options.targetOrigin);
279
- });
280
- }
281
- log(level, message, ...args) {
282
- if (this.options.debug || level !== "debug") {
283
- console[level](`[SandboxHost] ${message}`, ...args);
284
- }
285
- }
286
- };
287
-
288
- // src/sandbox-client.ts
289
- var SandboxClient = class {
290
- constructor(options = {}) {
291
- this.runtime = null;
292
- this.selectedComponents = /* @__PURE__ */ new Set();
293
- this.hoveredComponent = null;
294
- this.dragState = {
295
- isDragging: false,
296
- draggedComponentId: null,
297
- draggedElement: null,
298
- dropTarget: null,
299
- dropPosition: null,
300
- dropIndicator: null
301
- };
302
- // ==================== 私有方法 ====================
303
- this.handleMessage = async (event) => {
304
- const message = event.data;
305
- if (!isDjvlcMessage(message)) {
306
- return;
307
- }
308
- this.log("debug", "Received message:", message.type, message);
309
- try {
310
- let responseData;
311
- switch (message.type) {
312
- case "init" /* INIT */:
313
- responseData = await this.handleInit(message.data);
314
- break;
315
- case "load_page" /* LOAD_PAGE */:
316
- responseData = await this.handleLoadPage(message.data);
317
- break;
318
- case "update_schema" /* UPDATE_SCHEMA */:
319
- responseData = await this.handleUpdateSchema(message.data);
320
- break;
321
- case "refresh" /* REFRESH */:
322
- responseData = await this.handleRefresh();
323
- break;
324
- case "select_component" /* SELECT_COMPONENT */:
325
- responseData = this.handleSelectComponent(message.data);
326
- break;
327
- case "hover_component" /* HOVER_COMPONENT */:
328
- responseData = this.handleHoverComponent(message.data);
329
- break;
330
- case "update_component" /* UPDATE_COMPONENT */:
331
- responseData = this.handleUpdateComponent(message.data);
332
- break;
333
- case "add_component" /* ADD_COMPONENT */:
334
- responseData = this.handleAddComponent(message.data);
335
- break;
336
- case "move_component" /* MOVE_COMPONENT */:
337
- responseData = this.handleMoveComponent(message.data);
338
- break;
339
- case "delete_component" /* DELETE_COMPONENT */:
340
- responseData = this.handleDeleteComponent(message.data);
341
- break;
342
- case "sync_variables" /* SYNC_VARIABLES */:
343
- responseData = this.handleSyncVariables(message.data);
344
- break;
345
- default:
346
- this.log("warn", "Unknown message type:", message.type);
347
- return;
348
- }
349
- this.sendResponse(message.id, message.type, true, responseData);
350
- } catch (error) {
351
- this.sendResponse(
352
- message.id,
353
- message.type,
354
- false,
355
- void 0,
356
- error instanceof Error ? error.message : "Unknown error"
357
- );
358
- }
359
- };
360
- this.options = {
361
- targetOrigin: "*",
362
- debug: false,
363
- enableDrag: true,
364
- dragOptions: {
365
- dragOpacity: 0.5,
366
- showDropIndicator: true,
367
- dropIndicatorColor: "#1890ff"
368
- },
369
- ...options
370
- };
371
- }
372
- /**
373
- * 设置运行时实例
374
- */
375
- setRuntime(runtime) {
376
- this.runtime = runtime;
377
- }
378
- /**
379
- * 连接到父窗口
380
- */
381
- connect() {
382
- window.addEventListener("message", this.handleMessage);
383
- this.log("debug", "SandboxClient connected");
384
- this.sendReady();
385
- }
386
- /**
387
- * 断开连接
388
- */
389
- disconnect() {
390
- window.removeEventListener("message", this.handleMessage);
391
- this.log("debug", "SandboxClient disconnected");
392
- }
393
- /**
394
- * 初始化组件事件监听
395
- */
396
- initComponentEventListeners() {
397
- document.addEventListener("click", (e) => {
398
- const target = e.target;
399
- const componentEl = this.findComponentElement(target);
400
- if (componentEl) {
401
- e.preventDefault();
402
- e.stopPropagation();
403
- const componentId = componentEl.getAttribute("data-component-id");
404
- const componentType = componentEl.getAttribute("data-component-type");
405
- if (componentId) {
406
- this.sendComponentEvent("component_click" /* COMPONENT_CLICK */, {
407
- componentId,
408
- componentType: componentType || "unknown",
409
- bounds: componentEl.getBoundingClientRect(),
410
- position: { x: e.clientX, y: e.clientY }
411
- });
412
- }
413
- }
414
- }, true);
415
- document.addEventListener("mouseover", (e) => {
416
- const target = e.target;
417
- const componentEl = this.findComponentElement(target);
418
- if (componentEl) {
419
- const componentId = componentEl.getAttribute("data-component-id");
420
- const componentType = componentEl.getAttribute("data-component-type");
421
- if (componentId && componentId !== this.hoveredComponent) {
422
- this.hoveredComponent = componentId;
423
- this.sendComponentEvent("component_hover" /* COMPONENT_HOVER */, {
424
- componentId,
425
- componentType: componentType || "unknown",
426
- bounds: componentEl.getBoundingClientRect()
427
- });
428
- }
429
- }
430
- });
431
- document.addEventListener("mouseout", (e) => {
432
- const relatedTarget = e.relatedTarget;
433
- if (!relatedTarget || !this.findComponentElement(relatedTarget)) {
434
- if (this.hoveredComponent) {
435
- this.hoveredComponent = null;
436
- this.sendComponentEvent("component_hover" /* COMPONENT_HOVER */, {
437
- componentId: "",
438
- componentType: ""
439
- });
440
- }
441
- }
442
- });
443
- document.addEventListener("contextmenu", (e) => {
444
- const target = e.target;
445
- const componentEl = this.findComponentElement(target);
446
- if (componentEl) {
447
- e.preventDefault();
448
- const componentId = componentEl.getAttribute("data-component-id");
449
- const componentType = componentEl.getAttribute("data-component-type");
450
- if (componentId) {
451
- this.sendComponentEvent("component_context_menu" /* COMPONENT_CONTEXT_MENU */, {
452
- componentId,
453
- componentType: componentType || "unknown",
454
- bounds: componentEl.getBoundingClientRect(),
455
- position: { x: e.clientX, y: e.clientY }
456
- });
457
- }
458
- }
459
- });
460
- }
461
- /**
462
- * 注入编辑器样式
463
- */
464
- injectEditorStyles() {
465
- const indicatorColor = this.options.dragOptions?.dropIndicatorColor || "#1890ff";
466
- const style = document.createElement("style");
467
- style.id = "djvlc-editor-styles";
468
- style.textContent = `
469
- /* \u9009\u4E2D\u72B6\u6001 */
470
- [data-component-id].djvlc-selected {
471
- outline: 2px solid ${indicatorColor} !important;
472
- outline-offset: 2px;
473
- }
474
-
475
- /* \u60AC\u505C\u72B6\u6001 */
476
- [data-component-id].djvlc-hovered {
477
- outline: 1px dashed ${indicatorColor} !important;
478
- outline-offset: 1px;
479
- }
480
-
481
- /* \u7981\u7528\u4EA4\u4E92 */
482
- [data-component-id] * {
483
- pointer-events: none;
484
- }
485
-
486
- [data-component-id] {
487
- cursor: pointer;
488
- pointer-events: auto;
489
- }
490
-
491
- /* \u53EF\u62D6\u62FD\u72B6\u6001 */
492
- [data-component-id][draggable="true"] {
493
- cursor: grab;
494
- }
495
-
496
- [data-component-id][draggable="true"]:active {
497
- cursor: grabbing;
498
- }
499
-
500
- /* \u62D6\u62FD\u4E2D\u72B6\u6001 */
501
- [data-component-id].djvlc-dragging {
502
- opacity: ${this.options.dragOptions?.dragOpacity || 0.5};
503
- }
504
-
505
- /* \u653E\u7F6E\u76EE\u6807\u72B6\u6001 */
506
- [data-component-id].djvlc-drop-target {
507
- outline: 2px dashed ${indicatorColor} !important;
508
- outline-offset: 4px;
509
- }
510
-
511
- /* \u62D6\u62FD\u5360\u4F4D\u7B26 */
512
- .djvlc-drop-indicator {
513
- position: absolute;
514
- background: ${indicatorColor};
515
- opacity: 0.8;
516
- pointer-events: none;
517
- z-index: 10000;
518
- transition: all 0.1s ease;
519
- }
520
-
521
- .djvlc-drop-indicator.horizontal {
522
- height: 4px;
523
- width: 100%;
524
- border-radius: 2px;
525
- }
526
-
527
- .djvlc-drop-indicator.vertical {
528
- width: 4px;
529
- height: 100%;
530
- border-radius: 2px;
531
- }
532
-
533
- /* \u653E\u7F6E\u63D0\u793A\u7BAD\u5934 */
534
- .djvlc-drop-indicator::before {
535
- content: '';
536
- position: absolute;
537
- width: 8px;
538
- height: 8px;
539
- background: ${indicatorColor};
540
- border-radius: 50%;
541
- left: -4px;
542
- top: -2px;
543
- }
544
-
545
- .djvlc-drop-indicator::after {
546
- content: '';
547
- position: absolute;
548
- width: 8px;
549
- height: 8px;
550
- background: ${indicatorColor};
551
- border-radius: 50%;
552
- right: -4px;
553
- top: -2px;
554
- }
555
- `;
556
- document.head.appendChild(style);
557
- }
558
- /**
559
- * 初始化拖拽事件监听
560
- */
561
- initDragEventListeners() {
562
- if (!this.options.enableDrag) return;
563
- this.makeComponentsDraggable();
564
- document.addEventListener("dragstart", (e) => {
565
- const target = e.target;
566
- const componentEl = this.findComponentElement(target);
567
- if (!componentEl) return;
568
- const componentId = componentEl.getAttribute("data-component-id");
569
- if (!componentId) return;
570
- this.dragState.isDragging = true;
571
- this.dragState.draggedComponentId = componentId;
572
- this.dragState.draggedElement = componentEl;
573
- componentEl.classList.add("djvlc-dragging");
574
- e.dataTransfer?.setData("text/plain", componentId);
575
- e.dataTransfer?.setData("application/json", JSON.stringify({
576
- componentId,
577
- componentType: componentEl.getAttribute("data-component-type")
578
- }));
579
- if (e.dataTransfer) {
580
- e.dataTransfer.effectAllowed = "move";
581
- }
582
- this.log("debug", "Drag start:", componentId);
583
- });
584
- document.addEventListener("dragover", (e) => {
585
- if (!this.dragState.isDragging) return;
586
- e.preventDefault();
587
- const target = e.target;
588
- const componentEl = this.findComponentElement(target);
589
- if (!componentEl || componentEl === this.dragState.draggedElement) {
590
- this.hideDropIndicator();
591
- return;
592
- }
593
- const componentId = componentEl.getAttribute("data-component-id");
594
- if (!componentId) return;
595
- const rect = componentEl.getBoundingClientRect();
596
- const y = e.clientY;
597
- const threshold = rect.height / 3;
598
- let position;
599
- if (y < rect.top + threshold) {
600
- position = "before";
601
- } else if (y > rect.bottom - threshold) {
602
- position = "after";
603
- } else {
604
- position = "inside";
605
- }
606
- if (this.dragState.dropTarget !== componentEl || this.dragState.dropPosition !== position) {
607
- this.dragState.dropTarget?.classList.remove("djvlc-drop-target");
608
- this.dragState.dropTarget = componentEl;
609
- this.dragState.dropPosition = position;
610
- if (position === "inside") {
611
- componentEl.classList.add("djvlc-drop-target");
612
- this.hideDropIndicator();
613
- } else {
614
- componentEl.classList.remove("djvlc-drop-target");
615
- this.showDropIndicator(componentEl, position);
616
- }
617
- }
618
- });
619
- document.addEventListener("dragleave", (e) => {
620
- const target = e.target;
621
- const componentEl = this.findComponentElement(target);
622
- if (componentEl && componentEl === this.dragState.dropTarget) {
623
- const relatedTarget = e.relatedTarget;
624
- if (!relatedTarget || !componentEl.contains(relatedTarget)) {
625
- componentEl.classList.remove("djvlc-drop-target");
626
- }
627
- }
628
- });
629
- document.addEventListener("drop", (e) => {
630
- if (!this.dragState.isDragging) return;
631
- e.preventDefault();
632
- const { draggedComponentId, dropTarget, dropPosition } = this.dragState;
633
- if (draggedComponentId && dropTarget && dropPosition) {
634
- const targetComponentId = dropTarget.getAttribute("data-component-id");
635
- if (targetComponentId && targetComponentId !== draggedComponentId) {
636
- this.log("info", "Drop:", draggedComponentId, dropPosition, targetComponentId);
637
- this.send("component_drop" /* COMPONENT_DROP */, {
638
- componentId: draggedComponentId,
639
- targetComponentId,
640
- position: dropPosition
641
- });
642
- }
643
- }
644
- this.endDrag();
645
- });
646
- document.addEventListener("dragend", () => {
647
- this.endDrag();
648
- });
649
- }
650
- /**
651
- * 使组件可拖拽
652
- */
653
- makeComponentsDraggable() {
654
- const components = document.querySelectorAll("[data-component-id]");
655
- components.forEach((el) => {
656
- el.draggable = true;
657
- });
658
- const observer = new MutationObserver((mutations) => {
659
- mutations.forEach((mutation) => {
660
- mutation.addedNodes.forEach((node) => {
661
- if (node instanceof HTMLElement) {
662
- if (node.hasAttribute("data-component-id")) {
663
- node.draggable = true;
664
- }
665
- node.querySelectorAll("[data-component-id]").forEach((el) => {
666
- el.draggable = true;
667
- });
668
- }
669
- });
670
- });
671
- });
672
- observer.observe(document.body, {
673
- childList: true,
674
- subtree: true
675
- });
676
- }
677
- /**
678
- * 显示放置指示器
679
- */
680
- showDropIndicator(target, position) {
681
- if (!this.options.dragOptions?.showDropIndicator) return;
682
- let indicator = this.dragState.dropIndicator;
683
- if (!indicator) {
684
- indicator = document.createElement("div");
685
- indicator.className = "djvlc-drop-indicator horizontal";
686
- document.body.appendChild(indicator);
687
- this.dragState.dropIndicator = indicator;
688
- }
689
- const rect = target.getBoundingClientRect();
690
- const scrollTop = window.scrollY || document.documentElement.scrollTop;
691
- const scrollLeft = window.scrollX || document.documentElement.scrollLeft;
692
- indicator.style.left = `${rect.left + scrollLeft}px`;
693
- indicator.style.width = `${rect.width}px`;
694
- if (position === "before") {
695
- indicator.style.top = `${rect.top + scrollTop - 2}px`;
696
- } else {
697
- indicator.style.top = `${rect.bottom + scrollTop - 2}px`;
698
- }
699
- indicator.style.display = "block";
700
- }
701
- /**
702
- * 隐藏放置指示器
703
- */
704
- hideDropIndicator() {
705
- if (this.dragState.dropIndicator) {
706
- this.dragState.dropIndicator.style.display = "none";
707
- }
708
- }
709
- /**
710
- * 结束拖拽
711
- */
712
- endDrag() {
713
- this.dragState.draggedElement?.classList.remove("djvlc-dragging");
714
- this.dragState.dropTarget?.classList.remove("djvlc-drop-target");
715
- this.hideDropIndicator();
716
- this.dragState = {
717
- isDragging: false,
718
- draggedComponentId: null,
719
- draggedElement: null,
720
- dropTarget: null,
721
- dropPosition: null,
722
- dropIndicator: this.dragState.dropIndicator
723
- // 保留 indicator 元素
724
- };
725
- }
726
- async handleInit(data) {
727
- this.log("info", "Init with config:", data);
728
- void data;
729
- if (this.runtime) {
730
- try {
731
- await this.runtime.init();
732
- } catch (error) {
733
- this.log("error", "Failed to init runtime:", error);
734
- throw error;
735
- }
736
- }
737
- }
738
- async handleLoadPage(data) {
739
- this.log("info", "Load page:", data.pageUid);
740
- if (!this.runtime) {
741
- throw new Error("Runtime not set");
742
- }
743
- try {
744
- await this.runtime.load();
745
- await this.runtime.render();
746
- this.syncState();
747
- } catch (error) {
748
- this.log("error", "Failed to load page:", error);
749
- throw error;
750
- }
751
- }
752
- async handleUpdateSchema(data) {
753
- this.log("info", "Update schema", data.fullUpdate ? "(full)" : "(partial)");
754
- if (!this.runtime) {
755
- throw new Error("Runtime not set");
756
- }
757
- try {
758
- if (data.fullUpdate) {
759
- await this.runtime.load();
760
- await this.runtime.render();
761
- } else {
762
- await this.runtime.load();
763
- await this.runtime.render();
764
- }
765
- this.syncState();
766
- } catch (error) {
767
- this.log("error", "Failed to update schema:", error);
768
- throw error;
769
- }
770
- }
771
- async handleRefresh() {
772
- this.log("info", "Refresh page");
773
- if (this.runtime) {
774
- try {
775
- await this.runtime.load();
776
- await this.runtime.render();
777
- this.syncState();
778
- } catch (error) {
779
- this.log("error", "Failed to refresh:", error);
780
- window.location.reload();
781
- }
782
- } else {
783
- window.location.reload();
784
- }
785
- }
786
- handleSelectComponent(data) {
787
- if (data.componentId) {
788
- if (data.multi) {
789
- if (this.selectedComponents.has(data.componentId)) {
790
- this.selectedComponents.delete(data.componentId);
791
- } else {
792
- this.selectedComponents.add(data.componentId);
793
- }
794
- } else {
795
- this.selectedComponents.clear();
796
- this.selectedComponents.add(data.componentId);
797
- }
798
- } else {
799
- this.selectedComponents.clear();
800
- }
801
- this.updateSelectionStyles();
802
- this.syncState();
803
- }
804
- handleHoverComponent(data) {
805
- document.querySelectorAll(".djvlc-hovered").forEach((el) => {
806
- el.classList.remove("djvlc-hovered");
807
- });
808
- if (data.componentId) {
809
- const el = document.querySelector(`[data-component-id="${data.componentId}"]`);
810
- if (el && !this.selectedComponents.has(data.componentId)) {
811
- el.classList.add("djvlc-hovered");
812
- }
813
- }
814
- }
815
- handleUpdateComponent(data) {
816
- this.log("info", "Update component:", data.componentId);
817
- this.runtime?.updateComponent(data.componentId, data.props || {});
818
- }
819
- handleAddComponent(data) {
820
- this.log("info", "Add component:", data.component.componentType);
821
- this.log("warn", "addComponent not supported, need to reload page");
822
- }
823
- handleMoveComponent(data) {
824
- this.log("info", "Move component:", data.componentId, "to", data.targetIndex);
825
- this.log("warn", "moveComponent not supported, need to reload page");
826
- }
827
- handleDeleteComponent(data) {
828
- this.log("info", "Delete component:", data.componentId);
829
- this.log("warn", "deleteComponent not supported, need to reload page");
830
- this.selectedComponents.delete(data.componentId);
831
- if (this.hoveredComponent === data.componentId) {
832
- this.hoveredComponent = null;
833
- }
834
- }
835
- handleSyncVariables(data) {
836
- Object.entries(data.variables).forEach(([key, value]) => {
837
- this.runtime?.setVariable(key, value);
838
- });
839
- }
840
- updateSelectionStyles() {
841
- document.querySelectorAll(".djvlc-selected").forEach((el) => {
842
- el.classList.remove("djvlc-selected");
843
- });
844
- this.selectedComponents.forEach((id) => {
845
- const el = document.querySelector(`[data-component-id="${id}"]`);
846
- if (el) {
847
- el.classList.add("djvlc-selected");
848
- }
849
- });
850
- }
851
- syncState() {
852
- const runtimeState = this.runtime?.getState();
853
- const state = {
854
- phase: runtimeState?.phase || "idle",
855
- page: runtimeState?.page ? {
856
- pageUid: runtimeState.page.pageUid,
857
- pageVersionId: runtimeState.page.pageVersionId
858
- } : void 0,
859
- selectedComponents: Array.from(this.selectedComponents),
860
- hoveredComponent: this.hoveredComponent
861
- };
862
- this.send("sync_state" /* SYNC_STATE */, state);
863
- }
864
- sendReady() {
865
- const data = {
866
- runtimeVersion: "0.1.0",
867
- capabilities: [
868
- "select",
869
- "hover",
870
- "update",
871
- "add",
872
- "move",
873
- "delete",
874
- "drag"
875
- ]
876
- };
877
- this.send("ready" /* READY */, data);
878
- }
879
- sendComponentEvent(type, data) {
880
- this.send(type, data);
881
- }
882
- send(type, data) {
883
- const message = createMessage(type, data);
884
- this.log("debug", "Sending message:", type, message);
885
- window.parent.postMessage(message, this.options.targetOrigin || "*");
886
- }
887
- sendResponse(requestId, type, success, data, error) {
888
- const response = createResponse(requestId, type, success, data, error);
889
- this.log("debug", "Sending response:", type, response);
890
- window.parent.postMessage(response, this.options.targetOrigin || "*");
891
- }
892
- findComponentElement(target) {
893
- let current = target;
894
- while (current) {
895
- if (current.hasAttribute("data-component-id")) {
896
- return current;
897
- }
898
- current = current.parentElement;
899
- }
900
- return null;
901
- }
902
- log(level, message, ...args) {
903
- if (this.options.debug || level !== "debug") {
904
- console[level](`[SandboxClient] ${message}`, ...args);
905
- }
906
- }
907
- };
908
- // Annotate the CommonJS export names for ESM import in node:
909
- 0 && (module.exports = {
910
- MessageType,
911
- SandboxClient,
912
- SandboxHost,
913
- createMessage,
914
- createResponse,
915
- generateMessageId,
916
- isDjvlcMessage
917
- });
1
+ var t=(t=>(t.READY="ready",t.INIT="init",t.DESTROY="destroy",t.LOAD_PAGE="load_page",t.UPDATE_SCHEMA="update_schema",t.REFRESH="refresh",t.SELECT_COMPONENT="select_component",t.HOVER_COMPONENT="hover_component",t.UPDATE_COMPONENT="update_component",t.DELETE_COMPONENT="delete_component",t.ADD_COMPONENT="add_component",t.MOVE_COMPONENT="move_component",t.SYNC_STATE="sync_state",t.SYNC_VARIABLES="sync_variables",t.COMPONENT_CLICK="component_click",t.COMPONENT_HOVER="component_hover",t.COMPONENT_CONTEXT_MENU="component_context_menu",t.COMPONENT_DRAG_START="component_drag_start",t.COMPONENT_DRAG_END="component_drag_end",t.COMPONENT_DROP="component_drop",t.ERROR="error",t.LOG="log",t))(t||{});function e(){return`msg_${Date.now()}_${Math.random().toString(36).slice(2,9)}`}function n(t,n){return{type:t,id:e(),data:n,timestamp:Date.now()}}function o(t,n,o,i,s){return{type:n,id:e(),requestId:t,success:o,data:i,error:s,timestamp:Date.now()}}function i(t){if(!t||"object"!=typeof t)return!1;const e=t;return"string"==typeof e.type&&"string"==typeof e.id&&"number"==typeof e.timestamp}exports.MessageType=t,exports.SandboxClient=class{constructor(t={}){this.runtime=null,this.selectedComponents=new Set,this.hoveredComponent=null,this.dragState={isDragging:!1,draggedComponentId:null,draggedElement:null,dropTarget:null,dropPosition:null,dropIndicator:null},this.handleMessage=async t=>{const e=t.data;if(i(e)){this.log("debug","Received message:",e.type,e);try{let t;switch(e.type){case"init":t=await this.handleInit(e.data);break;case"load_page":t=await this.handleLoadPage(e.data);break;case"update_schema":t=await this.handleUpdateSchema(e.data);break;case"refresh":t=await this.handleRefresh();break;case"select_component":t=this.handleSelectComponent(e.data);break;case"hover_component":t=this.handleHoverComponent(e.data);break;case"update_component":t=this.handleUpdateComponent(e.data);break;case"add_component":t=this.handleAddComponent(e.data);break;case"move_component":t=this.handleMoveComponent(e.data);break;case"delete_component":t=this.handleDeleteComponent(e.data);break;case"sync_variables":t=this.handleSyncVariables(e.data);break;default:return void this.log("warn","Unknown message type:",e.type)}this.sendResponse(e.id,e.type,!0,t)}catch(t){this.sendResponse(e.id,e.type,!1,void 0,t instanceof Error?t.message:"Unknown error")}}},this.options={targetOrigin:"*",debug:!1,enableDrag:!0,dragOptions:{dragOpacity:.5,showDropIndicator:!0,dropIndicatorColor:"#1890ff"},...t}}setRuntime(t){this.runtime=t}connect(){window.addEventListener("message",this.handleMessage),this.log("debug","SandboxClient connected"),this.sendReady()}disconnect(){window.removeEventListener("message",this.handleMessage),this.log("debug","SandboxClient disconnected")}initComponentEventListeners(){document.addEventListener("click",t=>{const e=t.target,n=this.findComponentElement(e);if(n){t.preventDefault(),t.stopPropagation();const e=n.getAttribute("data-component-id"),o=n.getAttribute("data-component-type");e&&this.sendComponentEvent("component_click",{componentId:e,componentType:o||"unknown",bounds:n.getBoundingClientRect(),position:{x:t.clientX,y:t.clientY}})}},!0),document.addEventListener("mouseover",t=>{const e=t.target,n=this.findComponentElement(e);if(n){const t=n.getAttribute("data-component-id"),e=n.getAttribute("data-component-type");t&&t!==this.hoveredComponent&&(this.hoveredComponent=t,this.sendComponentEvent("component_hover",{componentId:t,componentType:e||"unknown",bounds:n.getBoundingClientRect()}))}}),document.addEventListener("mouseout",t=>{const e=t.relatedTarget;e&&this.findComponentElement(e)||this.hoveredComponent&&(this.hoveredComponent=null,this.sendComponentEvent("component_hover",{componentId:"",componentType:""}))}),document.addEventListener("contextmenu",t=>{const e=t.target,n=this.findComponentElement(e);if(n){t.preventDefault();const e=n.getAttribute("data-component-id"),o=n.getAttribute("data-component-type");e&&this.sendComponentEvent("component_context_menu",{componentId:e,componentType:o||"unknown",bounds:n.getBoundingClientRect(),position:{x:t.clientX,y:t.clientY}})}})}injectEditorStyles(){const t=this.options.dragOptions?.dropIndicatorColor||"#1890ff",e=document.createElement("style");e.id="djvlc-editor-styles",e.textContent=`\n /* 选中状态 */\n [data-component-id].djvlc-selected {\n outline: 2px solid ${t} !important;\n outline-offset: 2px;\n }\n\n /* 悬停状态 */\n [data-component-id].djvlc-hovered {\n outline: 1px dashed ${t} !important;\n outline-offset: 1px;\n }\n\n /* 禁用交互 */\n [data-component-id] * {\n pointer-events: none;\n }\n\n [data-component-id] {\n cursor: pointer;\n pointer-events: auto;\n }\n\n /* 可拖拽状态 */\n [data-component-id][draggable="true"] {\n cursor: grab;\n }\n\n [data-component-id][draggable="true"]:active {\n cursor: grabbing;\n }\n\n /* 拖拽中状态 */\n [data-component-id].djvlc-dragging {\n opacity: ${this.options.dragOptions?.dragOpacity||.5};\n }\n\n /* 放置目标状态 */\n [data-component-id].djvlc-drop-target {\n outline: 2px dashed ${t} !important;\n outline-offset: 4px;\n }\n\n /* 拖拽占位符 */\n .djvlc-drop-indicator {\n position: absolute;\n background: ${t};\n opacity: 0.8;\n pointer-events: none;\n z-index: 10000;\n transition: all 0.1s ease;\n }\n\n .djvlc-drop-indicator.horizontal {\n height: 4px;\n width: 100%;\n border-radius: 2px;\n }\n\n .djvlc-drop-indicator.vertical {\n width: 4px;\n height: 100%;\n border-radius: 2px;\n }\n\n /* 放置提示箭头 */\n .djvlc-drop-indicator::before {\n content: '';\n position: absolute;\n width: 8px;\n height: 8px;\n background: ${t};\n border-radius: 50%;\n left: -4px;\n top: -2px;\n }\n\n .djvlc-drop-indicator::after {\n content: '';\n position: absolute;\n width: 8px;\n height: 8px;\n background: ${t};\n border-radius: 50%;\n right: -4px;\n top: -2px;\n }\n `,document.head.appendChild(e)}initDragEventListeners(){this.options.enableDrag&&(this.makeComponentsDraggable(),document.addEventListener("dragstart",t=>{const e=t.target,n=this.findComponentElement(e);if(!n)return;const o=n.getAttribute("data-component-id");o&&(this.dragState.isDragging=!0,this.dragState.draggedComponentId=o,this.dragState.draggedElement=n,n.classList.add("djvlc-dragging"),t.dataTransfer?.setData("text/plain",o),t.dataTransfer?.setData("application/json",JSON.stringify({componentId:o,componentType:n.getAttribute("data-component-type")})),t.dataTransfer&&(t.dataTransfer.effectAllowed="move"),this.log("debug","Drag start:",o))}),document.addEventListener("dragover",t=>{if(!this.dragState.isDragging)return;t.preventDefault();const e=t.target,n=this.findComponentElement(e);if(!n||n===this.dragState.draggedElement)return void this.hideDropIndicator();if(!n.getAttribute("data-component-id"))return;const o=n.getBoundingClientRect(),i=t.clientY,s=o.height/3;let a;a=i<o.top+s?"before":i>o.bottom-s?"after":"inside",this.dragState.dropTarget===n&&this.dragState.dropPosition===a||(this.dragState.dropTarget?.classList.remove("djvlc-drop-target"),this.dragState.dropTarget=n,this.dragState.dropPosition=a,"inside"===a?(n.classList.add("djvlc-drop-target"),this.hideDropIndicator()):(n.classList.remove("djvlc-drop-target"),this.showDropIndicator(n,a)))}),document.addEventListener("dragleave",t=>{const e=t.target,n=this.findComponentElement(e);if(n&&n===this.dragState.dropTarget){const e=t.relatedTarget;e&&n.contains(e)||n.classList.remove("djvlc-drop-target")}}),document.addEventListener("drop",t=>{if(!this.dragState.isDragging)return;t.preventDefault();const{draggedComponentId:e,dropTarget:n,dropPosition:o}=this.dragState;if(e&&n&&o){const t=n.getAttribute("data-component-id");t&&t!==e&&(this.log("info","Drop:",e,o,t),this.send("component_drop",{componentId:e,targetComponentId:t,position:o}))}this.endDrag()}),document.addEventListener("dragend",()=>{this.endDrag()}))}makeComponentsDraggable(){document.querySelectorAll("[data-component-id]").forEach(t=>{t.draggable=!0}),new MutationObserver(t=>{t.forEach(t=>{t.addedNodes.forEach(t=>{t instanceof HTMLElement&&(t.hasAttribute("data-component-id")&&(t.draggable=!0),t.querySelectorAll("[data-component-id]").forEach(t=>{t.draggable=!0}))})})}).observe(document.body,{childList:!0,subtree:!0})}showDropIndicator(t,e){if(!this.options.dragOptions?.showDropIndicator)return;let n=this.dragState.dropIndicator;n||(n=document.createElement("div"),n.className="djvlc-drop-indicator horizontal",document.body.appendChild(n),this.dragState.dropIndicator=n);const o=t.getBoundingClientRect(),i=window.scrollY||document.documentElement.scrollTop,s=window.scrollX||document.documentElement.scrollLeft;n.style.left=`${o.left+s}px`,n.style.width=`${o.width}px`,n.style.top="before"===e?o.top+i-2+"px":o.bottom+i-2+"px",n.style.display="block"}hideDropIndicator(){this.dragState.dropIndicator&&(this.dragState.dropIndicator.style.display="none")}endDrag(){this.dragState.draggedElement?.classList.remove("djvlc-dragging"),this.dragState.dropTarget?.classList.remove("djvlc-drop-target"),this.hideDropIndicator(),this.dragState={isDragging:!1,draggedComponentId:null,draggedElement:null,dropTarget:null,dropPosition:null,dropIndicator:this.dragState.dropIndicator}}async handleInit(t){if(this.log("info","Init with config:",t),this.runtime)try{await this.runtime.init()}catch(t){throw this.log("error","Failed to init runtime:",t),t}}async handleLoadPage(t){if(this.log("info","Load page:",t.pageUid),!this.runtime)throw new Error("Runtime not set");try{await this.runtime.load(),await this.runtime.render(),this.syncState()}catch(t){throw this.log("error","Failed to load page:",t),t}}async handleUpdateSchema(t){if(this.log("info","Update schema",t.fullUpdate?"(full)":"(partial)"),!this.runtime)throw new Error("Runtime not set");try{t.fullUpdate,await this.runtime.load(),await this.runtime.render(),this.syncState()}catch(t){throw this.log("error","Failed to update schema:",t),t}}async handleRefresh(){if(this.log("info","Refresh page"),this.runtime)try{await this.runtime.load(),await this.runtime.render(),this.syncState()}catch(t){this.log("error","Failed to refresh:",t),window.location.reload()}else window.location.reload()}handleSelectComponent(t){t.componentId?t.multi?this.selectedComponents.has(t.componentId)?this.selectedComponents.delete(t.componentId):this.selectedComponents.add(t.componentId):(this.selectedComponents.clear(),this.selectedComponents.add(t.componentId)):this.selectedComponents.clear(),this.updateSelectionStyles(),this.syncState()}handleHoverComponent(t){if(document.querySelectorAll(".djvlc-hovered").forEach(t=>{t.classList.remove("djvlc-hovered")}),t.componentId){const e=document.querySelector(`[data-component-id="${t.componentId}"]`);e&&!this.selectedComponents.has(t.componentId)&&e.classList.add("djvlc-hovered")}}handleUpdateComponent(t){this.log("info","Update component:",t.componentId),this.runtime?.updateComponent(t.componentId,t.props||{})}handleAddComponent(t){this.log("info","Add component:",t.component.componentType),this.log("warn","addComponent not supported, need to reload page")}handleMoveComponent(t){this.log("info","Move component:",t.componentId,"to",t.targetIndex),this.log("warn","moveComponent not supported, need to reload page")}handleDeleteComponent(t){this.log("info","Delete component:",t.componentId),this.log("warn","deleteComponent not supported, need to reload page"),this.selectedComponents.delete(t.componentId),this.hoveredComponent===t.componentId&&(this.hoveredComponent=null)}handleSyncVariables(t){Object.entries(t.variables).forEach(([t,e])=>{this.runtime?.setVariable(t,e)})}updateSelectionStyles(){document.querySelectorAll(".djvlc-selected").forEach(t=>{t.classList.remove("djvlc-selected")}),this.selectedComponents.forEach(t=>{const e=document.querySelector(`[data-component-id="${t}"]`);e&&e.classList.add("djvlc-selected")})}syncState(){const t=this.runtime?.getState(),e={phase:t?.phase||"idle",page:t?.page?{pageUid:t.page.pageUid,pageVersionId:t.page.pageVersionId}:void 0,selectedComponents:Array.from(this.selectedComponents),hoveredComponent:this.hoveredComponent};this.send("sync_state",e)}sendReady(){this.send("ready",{runtimeVersion:"0.1.0",capabilities:["select","hover","update","add","move","delete","drag"]})}sendComponentEvent(t,e){this.send(t,e)}send(t,e){const o=n(t,e);this.log("debug","Sending message:",t,o),window.parent.postMessage(o,this.options.targetOrigin||"*")}sendResponse(t,e,n,i,s){const a=o(t,e,n,i,s);this.log("debug","Sending response:",e,a),window.parent.postMessage(a,this.options.targetOrigin||"*")}findComponentElement(t){let e=t;for(;e;){if(e.hasAttribute("data-component-id"))return e;e=e.parentElement}return null}log(t,e,...n){this.options.debug}},exports.SandboxHost=class{constructor(t){this.pendingRequests=new Map,this.isReady=!1,this.handleMessage=t=>{if(t.source!==this.options.iframe.contentWindow)return;if("*"!==this.options.targetOrigin&&t.origin!==this.options.targetOrigin)return;const e=t.data;if(i(e)){if(this.log("debug","Received message:",e.type,e),"requestId"in e){const t=this.pendingRequests.get(e.requestId);if(t)return clearTimeout(t.timer),this.pendingRequests.delete(e.requestId),void(e.success?t.resolve(e.data):t.reject(new Error(e.error||"Unknown error")))}switch(e.type){case"ready":this.isReady=!0,this.readyResolve(),this.options.onReady?.();break;case"component_click":this.options.onComponentClick?.(e.data.componentId,e.data);break;case"component_hover":this.options.onComponentHover?.(e.data.componentId,e.data);break;case"component_context_menu":this.options.onComponentContextMenu?.(e.data.componentId,e.data);break;case"sync_state":this.options.onSyncState?.(e.data);break;case"error":this.options.onError?.(e.data);break;case"log":{const t=e.data;this.options.onLog?.(t.level,t.message,...t.args||[]);break}}}},this.options={timeout:3e4,debug:!1,...t},this.readyPromise=new Promise(t=>{this.readyResolve=t})}connect(){window.addEventListener("message",this.handleMessage),this.log("debug","SandboxHost connected")}disconnect(){window.removeEventListener("message",this.handleMessage),this.pendingRequests.forEach(({timer:t})=>clearTimeout(t)),this.pendingRequests.clear(),this.isReady=!1,this.log("debug","SandboxHost disconnected")}async waitReady(){return this.readyPromise}get ready(){return this.isReady}async init(t){await this.send("init",t)}async loadPage(t){await this.send("load_page",t)}async updateSchema(t){await this.send("update_schema",t)}async refresh(){await this.send("refresh",{})}async selectComponent(t,e=!1){await this.send("select_component",{componentId:t,multi:e})}async hoverComponent(t){await this.send("hover_component",{componentId:t})}async updateComponent(t){await this.send("update_component",t)}async addComponent(t){await this.send("add_component",t)}async moveComponent(t){await this.send("move_component",t)}async deleteComponent(t){await this.send("delete_component",{componentId:t})}async syncVariables(t){await this.send("sync_variables",{variables:t})}send(t,e){return new Promise((o,i)=>{const s=n(t,e),a=setTimeout(()=>{this.pendingRequests.delete(s.id),i(new Error(`Request timeout: ${t}`))},this.options.timeout);this.pendingRequests.set(s.id,{resolve:o,reject:i,timer:a}),this.log("debug","Sending message:",t,s),this.options.iframe.contentWindow?.postMessage(s,this.options.targetOrigin)})}log(t,e,...n){this.options.debug}},exports.createMessage=n,exports.createResponse=o,exports.generateMessageId=e,exports.isDjvlcMessage=i;