plusui-native-core 0.1.99 → 0.1.101
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/Core/Features/FileDrop/filedrop.ts +20 -0
- package/Core/Features/Window/webview.cpp +214 -117
- package/Core/Features/Window/window.cpp +233 -233
- package/package.json +1 -1
|
@@ -58,11 +58,31 @@ export function createDropZone(name: string, el?: HTMLElement | null): DropZone
|
|
|
58
58
|
|
|
59
59
|
if (element) {
|
|
60
60
|
element.setAttribute('data-dropzone', name);
|
|
61
|
+
|
|
62
|
+
// Prevent browser default on drop (file navigation)
|
|
63
|
+
// The C++ backend handles actual file delivery via WM_DROPFILES
|
|
61
64
|
element.addEventListener('drop', (e: DragEvent) => {
|
|
62
65
|
e.preventDefault();
|
|
63
66
|
e.stopPropagation();
|
|
64
67
|
element.classList.remove('dropzone-active');
|
|
65
68
|
});
|
|
69
|
+
|
|
70
|
+
// Visual feedback: the injected C++ script manages dropzone-active
|
|
71
|
+
// via document-level listeners, but we also handle cleanup here
|
|
72
|
+
element.addEventListener('dragleave', (e: DragEvent) => {
|
|
73
|
+
// Only remove if actually leaving the element (not entering a child)
|
|
74
|
+
const related = e.relatedTarget as Node | null;
|
|
75
|
+
if (!related || !element.contains(related)) {
|
|
76
|
+
element.classList.remove('dropzone-active');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
element.addEventListener('dragover', (e: DragEvent) => {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
if (e.dataTransfer) {
|
|
83
|
+
try { e.dataTransfer.dropEffect = 'copy'; } catch (_) {}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
66
86
|
}
|
|
67
87
|
|
|
68
88
|
return {
|
|
@@ -123,36 +123,75 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
123
123
|
nullptr);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
126
|
+
// Block browser default drag-drop behavior (prevents
|
|
127
|
+
// the browser from navigating to the dropped file)
|
|
128
|
+
// while still showing visual feedback on drop zones.
|
|
129
|
+
// File delivery is handled by WM_DROPFILES in wndProc.
|
|
130
|
+
if (pImpl->config.disableWebviewDragDrop ||
|
|
131
|
+
pImpl->config.enableFileDrop) {
|
|
132
|
+
std::string disableDragDropScript = R"(
|
|
133
|
+
(function() {
|
|
134
|
+
if (window.__plusui_dropzone_init) return;
|
|
135
|
+
window.__plusui_dropzone_init = true;
|
|
136
|
+
|
|
137
|
+
var activeZone = null;
|
|
138
|
+
|
|
139
|
+
var findDropZone = function(e) {
|
|
140
|
+
var target = null;
|
|
141
|
+
if (e && typeof e.clientX === 'number' && document.elementFromPoint) {
|
|
142
|
+
target = document.elementFromPoint(e.clientX, e.clientY);
|
|
143
|
+
}
|
|
144
|
+
if (!target && e && e.target) target = e.target;
|
|
145
|
+
if (!target || !target.closest) return null;
|
|
146
|
+
return target.closest('[data-dropzone]');
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
var updateActiveZone = function(zone) {
|
|
150
|
+
if (activeZone === zone) return;
|
|
151
|
+
if (activeZone) activeZone.classList.remove('dropzone-active');
|
|
152
|
+
activeZone = zone;
|
|
153
|
+
if (activeZone) activeZone.classList.add('dropzone-active');
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
document.addEventListener('dragenter', function(e) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
var zone = findDropZone(e);
|
|
159
|
+
updateActiveZone(zone);
|
|
160
|
+
if (e.dataTransfer) {
|
|
161
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
162
|
+
}
|
|
163
|
+
}, true);
|
|
164
|
+
|
|
165
|
+
document.addEventListener('dragover', function(e) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
var zone = findDropZone(e);
|
|
168
|
+
updateActiveZone(zone);
|
|
169
|
+
if (e.dataTransfer) {
|
|
170
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
171
|
+
}
|
|
172
|
+
}, true);
|
|
173
|
+
|
|
174
|
+
document.addEventListener('dragleave', function(e) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
var zone = findDropZone(e);
|
|
177
|
+
updateActiveZone(zone);
|
|
178
|
+
}, true);
|
|
179
|
+
|
|
180
|
+
document.addEventListener('drop', function(e) {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
updateActiveZone(null);
|
|
183
|
+
}, true);
|
|
184
|
+
|
|
185
|
+
window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);
|
|
186
|
+
window.addEventListener('drop', function(e) { e.preventDefault(); }, true);
|
|
187
|
+
})();
|
|
188
|
+
)";
|
|
189
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
190
|
+
std::wstring(disableDragDropScript.begin(),
|
|
191
|
+
disableDragDropScript.end())
|
|
192
|
+
.c_str(),
|
|
193
|
+
nullptr);
|
|
194
|
+
}
|
|
156
195
|
|
|
157
196
|
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
158
197
|
pImpl->webview->add_WebMessageReceived(
|
|
@@ -519,32 +558,30 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
519
558
|
<< "[PlusUI] Unknown menu method: "
|
|
520
559
|
<< menuMethod << std::endl;
|
|
521
560
|
}
|
|
522
|
-
} else if (method.find("webview.") == 0) {
|
|
523
|
-
// WebView API routing
|
|
524
|
-
std::string webviewMethod = method.substr(8);
|
|
525
|
-
if (webviewMethod == "setWebviewDragDropEnabled") {
|
|
526
|
-
size_t p1 = msg.find("[");
|
|
527
|
-
size_t p2 = msg.find("]");
|
|
528
|
-
if (p1 != std::string::npos &&
|
|
529
|
-
p2 != std::string::npos) {
|
|
530
|
-
std::string params =
|
|
531
|
-
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
532
|
-
bool enabled = params.find("true") != std::string::npos;
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
script = "(function() { if (window.
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
success = true;
|
|
547
|
-
}
|
|
561
|
+
} else if (method.find("webview.") == 0) {
|
|
562
|
+
// WebView API routing
|
|
563
|
+
std::string webviewMethod = method.substr(8);
|
|
564
|
+
if (webviewMethod == "setWebviewDragDropEnabled") {
|
|
565
|
+
size_t p1 = msg.find("[");
|
|
566
|
+
size_t p2 = msg.find("]");
|
|
567
|
+
if (p1 != std::string::npos &&
|
|
568
|
+
p2 != std::string::npos) {
|
|
569
|
+
std::string params =
|
|
570
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
571
|
+
bool enabled = params.find("true") != std::string::npos;
|
|
572
|
+
std::string script;
|
|
573
|
+
if (enabled) {
|
|
574
|
+
script = "(function() { delete window.__plusui_dropzone_init; })();";
|
|
575
|
+
} else {
|
|
576
|
+
// Re-install the dropzone blocker script
|
|
577
|
+
script = "(function() { if (window.__plusui_dropzone_init) return; window.__plusui_dropzone_init = true; var activeZone = null; var findDropZone = function(e) { var target = null; if (e && typeof e.clientX === 'number' && document.elementFromPoint) { target = document.elementFromPoint(e.clientX, e.clientY); } if (!target && e && e.target) target = e.target; if (!target || !target.closest) return null; return target.closest('[data-dropzone]'); }; var updateActiveZone = function(zone) { if (activeZone === zone) return; if (activeZone) activeZone.classList.remove('dropzone-active'); activeZone = zone; if (activeZone) activeZone.classList.add('dropzone-active'); }; document.addEventListener('dragenter', function(e) { e.preventDefault(); var zone = findDropZone(e); updateActiveZone(zone); if (e.dataTransfer) { try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {} } }, true); document.addEventListener('dragover', function(e) { e.preventDefault(); var zone = findDropZone(e); updateActiveZone(zone); if (e.dataTransfer) { try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {} } }, true); document.addEventListener('dragleave', function(e) { e.preventDefault(); var zone = findDropZone(e); updateActiveZone(zone); }, true); document.addEventListener('drop', function(e) { e.preventDefault(); updateActiveZone(null); }, true); window.addEventListener('dragover', function(e) { e.preventDefault(); }, true); window.addEventListener('drop', function(e) { e.preventDefault(); }, true); })();";
|
|
578
|
+
}
|
|
579
|
+
pImpl->webview->ExecuteScript(
|
|
580
|
+
std::wstring(script.begin(), script.end()).c_str(),
|
|
581
|
+
nullptr);
|
|
582
|
+
}
|
|
583
|
+
success = true;
|
|
584
|
+
}
|
|
548
585
|
} else if (method.find("webgpu.") == 0) {
|
|
549
586
|
// WebGPU API routing
|
|
550
587
|
std::string webgpuMethod = method.substr(
|
|
@@ -661,29 +698,60 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
661
698
|
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
662
699
|
}
|
|
663
700
|
|
|
664
|
-
//
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
"
|
|
674
|
-
"
|
|
675
|
-
"e.
|
|
676
|
-
|
|
677
|
-
"
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
701
|
+
// Block browser default drag-drop while allowing drop zone visual feedback.
|
|
702
|
+
// File delivery is handled natively by macOS drag APIs, not browser events.
|
|
703
|
+
if (win.pImpl->config.disableWebviewDragDrop ||
|
|
704
|
+
win.pImpl->config.enableFileDrop) {
|
|
705
|
+
NSString *disableDragDropScript = @"(function() {"
|
|
706
|
+
"if (window.__plusui_dropzone_init) return;"
|
|
707
|
+
"window.__plusui_dropzone_init = true;"
|
|
708
|
+
"var activeZone = null;"
|
|
709
|
+
"var findDropZone = function(e) {"
|
|
710
|
+
"var target = null;"
|
|
711
|
+
"if (e && typeof e.clientX === 'number' && document.elementFromPoint) {"
|
|
712
|
+
"target = document.elementFromPoint(e.clientX, e.clientY);"
|
|
713
|
+
"}"
|
|
714
|
+
"if (!target && e && e.target) target = e.target;"
|
|
715
|
+
"if (!target || !target.closest) return null;"
|
|
716
|
+
"return target.closest('[data-dropzone]');"
|
|
717
|
+
"};"
|
|
718
|
+
"var updateActiveZone = function(zone) {"
|
|
719
|
+
"if (activeZone === zone) return;"
|
|
720
|
+
"if (activeZone) activeZone.classList.remove('dropzone-active');"
|
|
721
|
+
"activeZone = zone;"
|
|
722
|
+
"if (activeZone) activeZone.classList.add('dropzone-active');"
|
|
723
|
+
"};"
|
|
724
|
+
"document.addEventListener('dragenter', function(e) {"
|
|
725
|
+
"e.preventDefault();"
|
|
726
|
+
"var zone = findDropZone(e);"
|
|
727
|
+
"updateActiveZone(zone);"
|
|
728
|
+
"if (e.dataTransfer) { try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {} }"
|
|
729
|
+
"}, true);"
|
|
730
|
+
"document.addEventListener('dragover', function(e) {"
|
|
731
|
+
"e.preventDefault();"
|
|
732
|
+
"var zone = findDropZone(e);"
|
|
733
|
+
"updateActiveZone(zone);"
|
|
734
|
+
"if (e.dataTransfer) { try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {} }"
|
|
735
|
+
"}, true);"
|
|
736
|
+
"document.addEventListener('dragleave', function(e) {"
|
|
737
|
+
"e.preventDefault();"
|
|
738
|
+
"var zone = findDropZone(e);"
|
|
739
|
+
"updateActiveZone(zone);"
|
|
740
|
+
"}, true);"
|
|
741
|
+
"document.addEventListener('drop', function(e) {"
|
|
742
|
+
"e.preventDefault();"
|
|
743
|
+
"updateActiveZone(null);"
|
|
744
|
+
"}, true);"
|
|
745
|
+
"window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);"
|
|
746
|
+
"window.addEventListener('drop', function(e) { e.preventDefault(); }, true);"
|
|
747
|
+
"})();";
|
|
748
|
+
|
|
749
|
+
WKUserScript *userScript = [[WKUserScript alloc]
|
|
750
|
+
initWithSource:disableDragDropScript
|
|
751
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
752
|
+
forMainFrameOnly:NO];
|
|
753
|
+
[config.userContentController addUserScript:userScript];
|
|
754
|
+
}
|
|
687
755
|
|
|
688
756
|
// Hide scrollbars if disabled
|
|
689
757
|
if (!win.pImpl->config.scrollbars) {
|
|
@@ -1104,44 +1172,73 @@ void Window::injectCSS(const std::string &css) {
|
|
|
1104
1172
|
executeScript(script);
|
|
1105
1173
|
}
|
|
1106
1174
|
|
|
1107
|
-
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1175
|
+
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
1176
|
+
if (enabled) {
|
|
1177
|
+
executeScript(R"(
|
|
1178
|
+
(function() {
|
|
1179
|
+
delete window.__plusui_dropzone_init;
|
|
1180
|
+
})();
|
|
1181
|
+
)");
|
|
1182
|
+
} else {
|
|
1183
|
+
executeScript(R"(
|
|
1184
|
+
(function() {
|
|
1185
|
+
if (window.__plusui_dropzone_init) return;
|
|
1186
|
+
window.__plusui_dropzone_init = true;
|
|
1187
|
+
|
|
1188
|
+
var activeZone = null;
|
|
1189
|
+
|
|
1190
|
+
var findDropZone = function(e) {
|
|
1191
|
+
var target = null;
|
|
1192
|
+
if (e && typeof e.clientX === 'number' && document.elementFromPoint) {
|
|
1193
|
+
target = document.elementFromPoint(e.clientX, e.clientY);
|
|
1194
|
+
}
|
|
1195
|
+
if (!target && e && e.target) target = e.target;
|
|
1196
|
+
if (!target || !target.closest) return null;
|
|
1197
|
+
return target.closest('[data-dropzone]');
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
var updateActiveZone = function(zone) {
|
|
1201
|
+
if (activeZone === zone) return;
|
|
1202
|
+
if (activeZone) activeZone.classList.remove('dropzone-active');
|
|
1203
|
+
activeZone = zone;
|
|
1204
|
+
if (activeZone) activeZone.classList.add('dropzone-active');
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
document.addEventListener('dragenter', function(e) {
|
|
1208
|
+
e.preventDefault();
|
|
1209
|
+
var zone = findDropZone(e);
|
|
1210
|
+
updateActiveZone(zone);
|
|
1211
|
+
if (e.dataTransfer) {
|
|
1212
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1213
|
+
}
|
|
1214
|
+
}, true);
|
|
1215
|
+
|
|
1216
|
+
document.addEventListener('dragover', function(e) {
|
|
1217
|
+
e.preventDefault();
|
|
1218
|
+
var zone = findDropZone(e);
|
|
1219
|
+
updateActiveZone(zone);
|
|
1220
|
+
if (e.dataTransfer) {
|
|
1221
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1222
|
+
}
|
|
1223
|
+
}, true);
|
|
1224
|
+
|
|
1225
|
+
document.addEventListener('dragleave', function(e) {
|
|
1226
|
+
e.preventDefault();
|
|
1227
|
+
var zone = findDropZone(e);
|
|
1228
|
+
updateActiveZone(zone);
|
|
1229
|
+
}, true);
|
|
1230
|
+
|
|
1231
|
+
document.addEventListener('drop', function(e) {
|
|
1232
|
+
e.preventDefault();
|
|
1233
|
+
updateActiveZone(null);
|
|
1234
|
+
}, true);
|
|
1235
|
+
|
|
1236
|
+
window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);
|
|
1237
|
+
window.addEventListener('drop', function(e) { e.preventDefault(); }, true);
|
|
1238
|
+
})();
|
|
1239
|
+
)");
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1145
1242
|
|
|
1146
1243
|
void Window::onNavigationStart(NavigationCallback callback) {
|
|
1147
1244
|
pImpl->navigationCallback = callback;
|
|
@@ -244,58 +244,56 @@ struct Window::Impl {
|
|
|
244
244
|
jsonEscape(mimeTypeFromPath(path)) +
|
|
245
245
|
"\",\"size\":" + std::to_string(sizeBytes) + "}";
|
|
246
246
|
}
|
|
247
|
-
filesJson += "]";
|
|
248
|
-
|
|
249
|
-
DragFinish
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
.Get());
|
|
298
|
-
return 0;
|
|
247
|
+
filesJson += "]";
|
|
248
|
+
|
|
249
|
+
// Must query drop point BEFORE DragFinish invalidates hDrop
|
|
250
|
+
POINT dropPoint = {0, 0};
|
|
251
|
+
DragQueryPoint(hDrop, &dropPoint);
|
|
252
|
+
|
|
253
|
+
DragFinish(hDrop);
|
|
254
|
+
|
|
255
|
+
// Always fire the C++ callback regardless of zone
|
|
256
|
+
if (targetImpl->fileDropCallback) {
|
|
257
|
+
targetImpl->fileDropCallback(filesJson);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Build the event script that dispatches to the frontend.
|
|
261
|
+
// We always fire the global CustomEvent so any listener can react.
|
|
262
|
+
// If the drop lands on a [data-dropzone] element we also call the
|
|
263
|
+
// zone-specific callback (__plusui_fileDrop__).
|
|
264
|
+
int dpx = dropPoint.x;
|
|
265
|
+
int dpy = dropPoint.y;
|
|
266
|
+
|
|
267
|
+
std::string eventScript =
|
|
268
|
+
"(function(){"
|
|
269
|
+
"var files=" +
|
|
270
|
+
filesJson +
|
|
271
|
+
";"
|
|
272
|
+
// Global event — always fires
|
|
273
|
+
"window.dispatchEvent(new "
|
|
274
|
+
"CustomEvent('plusui:fileDrop.filesDropped',"
|
|
275
|
+
" { detail: { files: files } }));"
|
|
276
|
+
// Zone-specific delivery
|
|
277
|
+
"var el=document.elementFromPoint(" +
|
|
278
|
+
std::to_string(dpx) + "," + std::to_string(dpy) +
|
|
279
|
+
");"
|
|
280
|
+
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
281
|
+
"var zoneName=zone?zone.getAttribute('data-dropzone'):null;"
|
|
282
|
+
"if(zoneName&&window.__plusui_fileDrop__){"
|
|
283
|
+
"window.__plusui_fileDrop__(zoneName,files);"
|
|
284
|
+
"}"
|
|
285
|
+
// If no zone matched but there is only one registered zone,
|
|
286
|
+
// deliver there anyway so a simple single-zone app always works.
|
|
287
|
+
"if(!zoneName&&window.__plusui_fileDrop__&&"
|
|
288
|
+
"window.__plusui_fileDrop_default__){"
|
|
289
|
+
"window.__plusui_fileDrop_default__(files);"
|
|
290
|
+
"}"
|
|
291
|
+
"})();";
|
|
292
|
+
|
|
293
|
+
targetImpl->webview->ExecuteScript(
|
|
294
|
+
std::wstring(eventScript.begin(), eventScript.end()).c_str(),
|
|
295
|
+
nullptr);
|
|
296
|
+
return 0;
|
|
299
297
|
}
|
|
300
298
|
case WM_SETFOCUS:
|
|
301
299
|
impl->state.isFocused = true;
|
|
@@ -980,17 +978,17 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
980
978
|
Window::Impl::embeddedWebviewByParent[parentHwnd] =
|
|
981
979
|
pImpl.get();
|
|
982
980
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
981
|
+
// Always allow external drops at the WebView2 level
|
|
982
|
+
// so that WM_DROPFILES on the parent HWND fires.
|
|
983
|
+
// Browser-level drop behavior (navigating to the file)
|
|
984
|
+
// is blocked by the injected JS below instead.
|
|
985
|
+
ComPtr<ICoreWebView2Controller4> controller4;
|
|
986
|
+
if (controller &&
|
|
987
|
+
SUCCEEDED(controller->QueryInterface(
|
|
988
|
+
IID_PPV_ARGS(&controller4))) &&
|
|
989
|
+
controller4) {
|
|
990
|
+
controller4->put_AllowExternalDrop(TRUE);
|
|
991
|
+
}
|
|
994
992
|
|
|
995
993
|
pImpl->nativeWebView = pImpl->webview.Get();
|
|
996
994
|
pImpl->ready = true;
|
|
@@ -1020,10 +1018,11 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1020
1018
|
nullptr);
|
|
1021
1019
|
}
|
|
1022
1020
|
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1026
|
-
//
|
|
1021
|
+
// Block browser default drag-drop behavior (prevents
|
|
1022
|
+
// the browser from navigating to the dropped file) while
|
|
1023
|
+
// still allowing visual feedback on drop zones.
|
|
1024
|
+
// The actual file delivery happens via WM_DROPFILES
|
|
1025
|
+
// handled by the C++ WndProc, not by browser events.
|
|
1027
1026
|
std::string dropzoneScript = R"(
|
|
1028
1027
|
(function() {
|
|
1029
1028
|
if (window.__plusui_dropzone_init) return;
|
|
@@ -1048,35 +1047,49 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1048
1047
|
if (activeZone) activeZone.classList.add('dropzone-active');
|
|
1049
1048
|
};
|
|
1050
1049
|
|
|
1051
|
-
|
|
1050
|
+
// Always preventDefault to stop browser from navigating to file,
|
|
1051
|
+
// but show visual feedback when over a drop zone
|
|
1052
|
+
document.addEventListener('dragenter', function(e) {
|
|
1053
|
+
e.preventDefault();
|
|
1052
1054
|
var zone = findDropZone(e);
|
|
1053
1055
|
updateActiveZone(zone);
|
|
1054
1056
|
if (e.dataTransfer) {
|
|
1055
1057
|
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1056
1058
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1059
|
+
}, true);
|
|
1060
|
+
|
|
1061
|
+
document.addEventListener('dragover', function(e) {
|
|
1062
|
+
e.preventDefault();
|
|
1063
|
+
var zone = findDropZone(e);
|
|
1064
|
+
updateActiveZone(zone);
|
|
1065
|
+
if (e.dataTransfer) {
|
|
1066
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1060
1067
|
}
|
|
1061
|
-
};
|
|
1068
|
+
}, true);
|
|
1062
1069
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1070
|
+
document.addEventListener('dragleave', function(e) {
|
|
1071
|
+
e.preventDefault();
|
|
1072
|
+
var zone = findDropZone(e);
|
|
1073
|
+
updateActiveZone(zone);
|
|
1074
|
+
}, true);
|
|
1066
1075
|
|
|
1067
1076
|
document.addEventListener('drop', function(e) {
|
|
1077
|
+
e.preventDefault();
|
|
1068
1078
|
updateActiveZone(null);
|
|
1069
1079
|
}, true);
|
|
1080
|
+
|
|
1081
|
+
// Also block at window level as a safety net
|
|
1082
|
+
window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);
|
|
1083
|
+
window.addEventListener('drop', function(e) { e.preventDefault(); }, true);
|
|
1070
1084
|
})();
|
|
1071
1085
|
)";
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
1086
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1087
|
+
std::wstring(dropzoneScript.begin(),
|
|
1088
|
+
dropzoneScript.end())
|
|
1089
|
+
.c_str(),
|
|
1090
|
+
nullptr);
|
|
1091
|
+
|
|
1092
|
+
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
1080
1093
|
pImpl->webview->add_WebMessageReceived(
|
|
1081
1094
|
Callback<
|
|
1082
1095
|
ICoreWebView2WebMessageReceivedEventHandler>(
|
|
@@ -1495,21 +1508,19 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1495
1508
|
: FALSE);
|
|
1496
1509
|
}
|
|
1497
1510
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
controller4
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1511
|
+
// Always keep external drops allowed
|
|
1512
|
+
// at the WebView2 level so WM_DROPFILES
|
|
1513
|
+
// fires on the parent HWND
|
|
1514
|
+
if (pImpl->controller) {
|
|
1515
|
+
ComPtr<ICoreWebView2Controller4>
|
|
1516
|
+
controller4;
|
|
1517
|
+
if (SUCCEEDED(pImpl->controller.As(
|
|
1518
|
+
&controller4)) &&
|
|
1519
|
+
controller4) {
|
|
1520
|
+
controller4->put_AllowExternalDrop(
|
|
1521
|
+
TRUE);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1513
1524
|
}
|
|
1514
1525
|
success = true;
|
|
1515
1526
|
} else if (fileDropMethod == "isEnabled") {
|
|
@@ -1616,60 +1627,61 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1616
1627
|
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
1617
1628
|
}
|
|
1618
1629
|
|
|
1619
|
-
//
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
"
|
|
1626
|
-
"
|
|
1627
|
-
"var
|
|
1628
|
-
"
|
|
1629
|
-
"
|
|
1630
|
-
"
|
|
1631
|
-
"
|
|
1632
|
-
"
|
|
1633
|
-
"if (!target
|
|
1634
|
-
"
|
|
1635
|
-
".
|
|
1636
|
-
"};"
|
|
1637
|
-
"var
|
|
1638
|
-
"
|
|
1639
|
-
"if (
|
|
1640
|
-
"
|
|
1641
|
-
"
|
|
1642
|
-
"
|
|
1643
|
-
"
|
|
1644
|
-
"
|
|
1645
|
-
"var
|
|
1646
|
-
"
|
|
1647
|
-
"
|
|
1648
|
-
"
|
|
1649
|
-
"
|
|
1650
|
-
"e.preventDefault();"
|
|
1651
|
-
"e
|
|
1652
|
-
"
|
|
1653
|
-
"e.
|
|
1654
|
-
"
|
|
1655
|
-
"
|
|
1656
|
-
"
|
|
1657
|
-
"
|
|
1658
|
-
"
|
|
1659
|
-
"
|
|
1660
|
-
"
|
|
1661
|
-
"
|
|
1662
|
-
"
|
|
1663
|
-
"
|
|
1664
|
-
"});"
|
|
1665
|
-
"
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1630
|
+
// Block browser default drag-drop while allowing drop zone visual feedback.
|
|
1631
|
+
// File delivery is handled natively by macOS drag APIs, not browser events.
|
|
1632
|
+
if (win.pImpl->config.disableWebviewDragDrop ||
|
|
1633
|
+
win.pImpl->config.enableFileDrop) {
|
|
1634
|
+
NSString *disableDragDropScript =
|
|
1635
|
+
@"(function() {"
|
|
1636
|
+
"if (window.__plusui_dropzone_init) return;"
|
|
1637
|
+
"window.__plusui_dropzone_init = true;"
|
|
1638
|
+
"var activeZone = null;"
|
|
1639
|
+
"var findDropZone = function(e) {"
|
|
1640
|
+
"var target = null;"
|
|
1641
|
+
"if (e && typeof e.clientX === 'number' && document.elementFromPoint) {"
|
|
1642
|
+
"target = document.elementFromPoint(e.clientX, e.clientY);"
|
|
1643
|
+
"}"
|
|
1644
|
+
"if (!target && e && e.target) target = e.target;"
|
|
1645
|
+
"if (!target || !target.closest) return null;"
|
|
1646
|
+
"return target.closest('[data-dropzone]');"
|
|
1647
|
+
"};"
|
|
1648
|
+
"var updateActiveZone = function(zone) {"
|
|
1649
|
+
"if (activeZone === zone) return;"
|
|
1650
|
+
"if (activeZone) activeZone.classList.remove('dropzone-active');"
|
|
1651
|
+
"activeZone = zone;"
|
|
1652
|
+
"if (activeZone) activeZone.classList.add('dropzone-active');"
|
|
1653
|
+
"};"
|
|
1654
|
+
"document.addEventListener('dragenter', function(e) {"
|
|
1655
|
+
"e.preventDefault();"
|
|
1656
|
+
"var zone = findDropZone(e);"
|
|
1657
|
+
"updateActiveZone(zone);"
|
|
1658
|
+
"if (e.dataTransfer) { try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {} }"
|
|
1659
|
+
"}, true);"
|
|
1660
|
+
"document.addEventListener('dragover', function(e) {"
|
|
1661
|
+
"e.preventDefault();"
|
|
1662
|
+
"var zone = findDropZone(e);"
|
|
1663
|
+
"updateActiveZone(zone);"
|
|
1664
|
+
"if (e.dataTransfer) { try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {} }"
|
|
1665
|
+
"}, true);"
|
|
1666
|
+
"document.addEventListener('dragleave', function(e) {"
|
|
1667
|
+
"e.preventDefault();"
|
|
1668
|
+
"var zone = findDropZone(e);"
|
|
1669
|
+
"updateActiveZone(zone);"
|
|
1670
|
+
"}, true);"
|
|
1671
|
+
"document.addEventListener('drop', function(e) {"
|
|
1672
|
+
"e.preventDefault();"
|
|
1673
|
+
"updateActiveZone(null);"
|
|
1674
|
+
"}, true);"
|
|
1675
|
+
"window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);"
|
|
1676
|
+
"window.addEventListener('drop', function(e) { e.preventDefault(); }, true);"
|
|
1677
|
+
"})();";
|
|
1678
|
+
|
|
1679
|
+
WKUserScript *userScript = [[WKUserScript alloc]
|
|
1680
|
+
initWithSource:disableDragDropScript
|
|
1681
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
1682
|
+
forMainFrameOnly:NO];
|
|
1683
|
+
[config.userContentController addUserScript:userScript];
|
|
1684
|
+
}
|
|
1673
1685
|
|
|
1674
1686
|
// Hide scrollbars if disabled
|
|
1675
1687
|
if (!win.pImpl->config.scrollbars) {
|
|
@@ -2114,87 +2126,75 @@ void Window::injectCSS(const std::string &css) {
|
|
|
2114
2126
|
executeScript(script);
|
|
2115
2127
|
}
|
|
2116
2128
|
|
|
2117
|
-
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
(
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
window.__plusui_dragDropBlocker = block;
|
|
2187
|
-
|
|
2188
|
-
window.__plusui_dragDropEvents.forEach(function(eventName) {
|
|
2189
|
-
window.addEventListener(eventName, block, true);
|
|
2190
|
-
document.addEventListener(eventName, block, true);
|
|
2191
|
-
});
|
|
2192
|
-
})();
|
|
2193
|
-
)";
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
executeScript(script);
|
|
2197
|
-
}
|
|
2129
|
+
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
2130
|
+
if (enabled) {
|
|
2131
|
+
// Remove the dropzone init so it can be re-applied if needed later
|
|
2132
|
+
executeScript(R"(
|
|
2133
|
+
(function() {
|
|
2134
|
+
delete window.__plusui_dropzone_init;
|
|
2135
|
+
})();
|
|
2136
|
+
)");
|
|
2137
|
+
} else {
|
|
2138
|
+
// Install the same dropzone script used at init time
|
|
2139
|
+
executeScript(R"(
|
|
2140
|
+
(function() {
|
|
2141
|
+
if (window.__plusui_dropzone_init) return;
|
|
2142
|
+
window.__plusui_dropzone_init = true;
|
|
2143
|
+
|
|
2144
|
+
var activeZone = null;
|
|
2145
|
+
|
|
2146
|
+
var findDropZone = function(e) {
|
|
2147
|
+
var target = null;
|
|
2148
|
+
if (e && typeof e.clientX === 'number' && document.elementFromPoint) {
|
|
2149
|
+
target = document.elementFromPoint(e.clientX, e.clientY);
|
|
2150
|
+
}
|
|
2151
|
+
if (!target && e && e.target) target = e.target;
|
|
2152
|
+
if (!target || !target.closest) return null;
|
|
2153
|
+
return target.closest('[data-dropzone]');
|
|
2154
|
+
};
|
|
2155
|
+
|
|
2156
|
+
var updateActiveZone = function(zone) {
|
|
2157
|
+
if (activeZone === zone) return;
|
|
2158
|
+
if (activeZone) activeZone.classList.remove('dropzone-active');
|
|
2159
|
+
activeZone = zone;
|
|
2160
|
+
if (activeZone) activeZone.classList.add('dropzone-active');
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
document.addEventListener('dragenter', function(e) {
|
|
2164
|
+
e.preventDefault();
|
|
2165
|
+
var zone = findDropZone(e);
|
|
2166
|
+
updateActiveZone(zone);
|
|
2167
|
+
if (e.dataTransfer) {
|
|
2168
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
2169
|
+
}
|
|
2170
|
+
}, true);
|
|
2171
|
+
|
|
2172
|
+
document.addEventListener('dragover', function(e) {
|
|
2173
|
+
e.preventDefault();
|
|
2174
|
+
var zone = findDropZone(e);
|
|
2175
|
+
updateActiveZone(zone);
|
|
2176
|
+
if (e.dataTransfer) {
|
|
2177
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
2178
|
+
}
|
|
2179
|
+
}, true);
|
|
2180
|
+
|
|
2181
|
+
document.addEventListener('dragleave', function(e) {
|
|
2182
|
+
e.preventDefault();
|
|
2183
|
+
var zone = findDropZone(e);
|
|
2184
|
+
updateActiveZone(zone);
|
|
2185
|
+
}, true);
|
|
2186
|
+
|
|
2187
|
+
document.addEventListener('drop', function(e) {
|
|
2188
|
+
e.preventDefault();
|
|
2189
|
+
updateActiveZone(null);
|
|
2190
|
+
}, true);
|
|
2191
|
+
|
|
2192
|
+
window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);
|
|
2193
|
+
window.addEventListener('drop', function(e) { e.preventDefault(); }, true);
|
|
2194
|
+
})();
|
|
2195
|
+
)");
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
2198
|
|
|
2199
2199
|
void Window::onNavigationStart(NavigationCallback callback) {
|
|
2200
2200
|
pImpl->navigationCallback = callback;
|