plusui-native-core 0.1.104 → 0.1.105
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.
|
@@ -1,76 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PlusUI Connect API —
|
|
2
|
+
* PlusUI Connect API — Custom Frontend ↔ Backend Communication
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* IMPORTANT: `connect` is for CUSTOM user-defined communication only!
|
|
7
|
-
* Built-in features (window, clipboard, app, etc.) use direct imports:
|
|
8
|
-
* import plusui from 'plusui';
|
|
9
|
-
* plusui.window.minimize();
|
|
10
|
-
* plusui.clipboard.setText('hello');
|
|
11
|
-
*
|
|
12
|
-
* For custom communication, use `connect.namespace.method()`:
|
|
4
|
+
* Use `connect` for YOUR custom communication channels only!
|
|
13
5
|
*
|
|
14
6
|
* import { connect } from 'plusui';
|
|
15
7
|
*
|
|
16
|
-
* // Request/Response
|
|
8
|
+
* // Request/Response
|
|
17
9
|
* const user = await connect.user.fetch(123);
|
|
18
10
|
*
|
|
19
|
-
* // Fire & Forget
|
|
11
|
+
* // Fire & Forget (one-way)
|
|
20
12
|
* connect.files.upload({ file: myFile });
|
|
21
13
|
*
|
|
22
|
-
* //
|
|
14
|
+
* // Subscribe to events
|
|
23
15
|
* connect.app.onNotify((msg) => console.log(msg));
|
|
24
16
|
*
|
|
25
|
-
* // Register
|
|
17
|
+
* // Register handler (backend calls frontend)
|
|
26
18
|
* connect.ui.handlePrompt = async (data) => {
|
|
27
19
|
* return await Dialog.confirm(data.msg);
|
|
28
20
|
* };
|
|
29
21
|
*
|
|
30
|
-
*
|
|
22
|
+
* Run `plusui connect` to generate typed bindings.
|
|
31
23
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* - `connect.x.y()` → FIRE (Simplex, one-way)
|
|
24
|
+
* BUILT-IN FEATURES use direct imports:
|
|
25
|
+
* import plusui from 'plusui';
|
|
26
|
+
* plusui.window.minimize();
|
|
27
|
+
* plusui.clipboard.setText('hello');
|
|
37
28
|
*/
|
|
38
29
|
|
|
39
30
|
import { connect, _client, createFeatureConnect } from '../Connection/connect';
|
|
40
31
|
import type { FeatureConnect, ConnectionKind, ConnectionEnvelope } from '../Connection/connect';
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
* Usage:
|
|
46
|
-
* connect.namespace.method(...args)
|
|
47
|
-
*
|
|
48
|
-
* The proxy routes to the correct wire format automatically:
|
|
49
|
-
* connect.user.fetch(123) → _client.call('user.fetch', 123)
|
|
50
|
-
* connect.app.onNotify(cb) → _client.on('app.onNotify', cb)
|
|
51
|
-
* connect.ui.handlePrompt = fn → _client.handle('ui.handlePrompt', fn)
|
|
52
|
-
* connect.system.minimize() → _client.fire('system.minimize', {})
|
|
53
|
-
*
|
|
54
|
-
* Works dynamically even before running `plusui connect`.
|
|
55
|
-
* After running `plusui connect`, you get IDE autocomplete.
|
|
56
|
-
*/
|
|
57
|
-
export { connect };
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* _client — Low-level connection client
|
|
61
|
-
*
|
|
62
|
-
* Direct access to the underlying client for advanced use cases.
|
|
63
|
-
* Prefer using `connect.namespace.method()` for normal communication.
|
|
64
|
-
*/
|
|
65
|
-
export { _client };
|
|
33
|
+
export { connect, _client, createFeatureConnect };
|
|
34
|
+
export type { FeatureConnect, ConnectionKind, ConnectionEnvelope };
|
|
66
35
|
|
|
67
36
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* Creates a connect API scoped to a feature namespace.
|
|
71
|
-
* Used internally by PlusUI features like window, clipboard, etc.
|
|
37
|
+
* Low-level client methods (use `connect` instead when possible)
|
|
72
38
|
*/
|
|
73
|
-
export
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
39
|
+
export type ClientAPI = {
|
|
40
|
+
call: <TOut = unknown, TIn = Record<string, unknown>>(name: string, payload: TIn) => Promise<TOut>;
|
|
41
|
+
fire: <TIn = Record<string, unknown>>(name: string, payload: TIn) => void;
|
|
42
|
+
on: <TData = unknown>(name: string, callback: (payload: TData) => void) => () => void;
|
|
43
|
+
handle: (name: string, handler: (...args: any[]) => any) => () => void;
|
|
44
|
+
};
|
|
@@ -102,27 +102,30 @@ export function createDropZone(name: string, element?: HTMLElement | null): Drop
|
|
|
102
102
|
el.addEventListener('drop', (e: DragEvent) => {
|
|
103
103
|
e.preventDefault();
|
|
104
104
|
el.classList.remove('dropzone-active');
|
|
105
|
+
el.classList.remove('filedrop-active');
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
el.addEventListener('dragleave', (e: DragEvent) => {
|
|
108
109
|
const related = e.relatedTarget as Node | null;
|
|
109
110
|
if (!related || !el.contains(related)) {
|
|
110
111
|
el.classList.remove('dropzone-active');
|
|
112
|
+
el.classList.remove('filedrop-active');
|
|
111
113
|
}
|
|
112
114
|
});
|
|
113
115
|
|
|
114
116
|
el.addEventListener('dragenter', (e: DragEvent) => {
|
|
115
117
|
e.preventDefault();
|
|
116
118
|
el.classList.add('dropzone-active');
|
|
119
|
+
el.classList.add('filedrop-active');
|
|
117
120
|
if (e.dataTransfer) {
|
|
118
|
-
try { e.dataTransfer.dropEffect = '
|
|
121
|
+
try { e.dataTransfer.dropEffect = 'copy'; } catch (_) {}
|
|
119
122
|
}
|
|
120
123
|
});
|
|
121
124
|
|
|
122
125
|
el.addEventListener('dragover', (e: DragEvent) => {
|
|
123
126
|
e.preventDefault();
|
|
124
127
|
if (e.dataTransfer) {
|
|
125
|
-
try { e.dataTransfer.dropEffect = '
|
|
128
|
+
try { e.dataTransfer.dropEffect = 'copy'; } catch (_) {}
|
|
126
129
|
}
|
|
127
130
|
});
|
|
128
131
|
}
|
|
@@ -174,6 +174,7 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
174
174
|
window.__plusui_dropzone_init = true;
|
|
175
175
|
|
|
176
176
|
var activeZone = null;
|
|
177
|
+
var dragDepth = 0;
|
|
177
178
|
|
|
178
179
|
var findDropZone = function(e) {
|
|
179
180
|
var target = null;
|
|
@@ -187,13 +188,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
187
188
|
|
|
188
189
|
var updateActiveZone = function(zone) {
|
|
189
190
|
if (activeZone === zone) return;
|
|
190
|
-
if (activeZone)
|
|
191
|
+
if (activeZone) {
|
|
192
|
+
activeZone.classList.remove('dropzone-active');
|
|
193
|
+
activeZone.classList.remove('filedrop-active');
|
|
194
|
+
}
|
|
191
195
|
activeZone = zone;
|
|
192
|
-
if (activeZone)
|
|
196
|
+
if (activeZone) {
|
|
197
|
+
activeZone.classList.add('dropzone-active');
|
|
198
|
+
activeZone.classList.add('filedrop-active');
|
|
199
|
+
}
|
|
193
200
|
};
|
|
194
201
|
|
|
195
202
|
document.addEventListener('dragenter', function(e) {
|
|
196
203
|
e.preventDefault();
|
|
204
|
+
dragDepth++;
|
|
197
205
|
var zone = findDropZone(e);
|
|
198
206
|
updateActiveZone(zone);
|
|
199
207
|
if (e.dataTransfer) {
|
|
@@ -212,16 +220,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
212
220
|
|
|
213
221
|
document.addEventListener('dragleave', function(e) {
|
|
214
222
|
e.preventDefault();
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
223
|
+
dragDepth--;
|
|
224
|
+
if (dragDepth <= 0) {
|
|
225
|
+
dragDepth = 0;
|
|
218
226
|
updateActiveZone(null);
|
|
227
|
+
} else {
|
|
228
|
+
var zone = findDropZone(e);
|
|
229
|
+
updateActiveZone(zone);
|
|
219
230
|
}
|
|
220
231
|
}, true);
|
|
221
232
|
|
|
222
233
|
document.addEventListener('drop', function(e) {
|
|
223
234
|
e.preventDefault();
|
|
224
235
|
updateActiveZone(null);
|
|
236
|
+
dragDepth = 0;
|
|
225
237
|
}, true);
|
|
226
238
|
|
|
227
239
|
window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);
|
|
@@ -346,6 +346,10 @@ struct Window::Impl {
|
|
|
346
346
|
"var files=" +
|
|
347
347
|
filesJson +
|
|
348
348
|
";"
|
|
349
|
+
// Clear any leftover visual feedback from the drag
|
|
350
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
351
|
+
".forEach(function(z){z.classList.remove('dropzone-active');"
|
|
352
|
+
"z.classList.remove('filedrop-active');});"
|
|
349
353
|
// Global event — always fires
|
|
350
354
|
"window.dispatchEvent(new "
|
|
351
355
|
"CustomEvent('plusui:fileDrop.filesDropped',"
|
|
@@ -1104,18 +1108,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1104
1108
|
Window::Impl::embeddedWebviewByParent[parentHwnd] =
|
|
1105
1109
|
pImpl.get();
|
|
1106
1110
|
|
|
1107
|
-
//
|
|
1108
|
-
//
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1111
|
+
// AllowExternalDrop must be TRUE so that drag-
|
|
1112
|
+
// related DOM events (dragenter, dragover, dragleave,
|
|
1113
|
+
// drop) fire inside the webview. Our injected JS
|
|
1114
|
+
// calls preventDefault() on all of them to stop the
|
|
1115
|
+
// browser from navigating to the dropped file.
|
|
1116
|
+
// The parent HWND still receives WM_DROPFILES via
|
|
1117
|
+
// DragAcceptFiles, which is where we extract file
|
|
1118
|
+
// metadata and push it into JavaScript.
|
|
1113
1119
|
ComPtr<ICoreWebView2Controller4> controller4;
|
|
1114
1120
|
if (controller &&
|
|
1115
1121
|
SUCCEEDED(controller->QueryInterface(
|
|
1116
1122
|
IID_PPV_ARGS(&controller4))) &&
|
|
1117
1123
|
controller4) {
|
|
1118
|
-
controller4->put_AllowExternalDrop(
|
|
1124
|
+
controller4->put_AllowExternalDrop(TRUE);
|
|
1119
1125
|
}
|
|
1120
1126
|
|
|
1121
1127
|
pImpl->nativeWebView = pImpl->webview.Get();
|
|
@@ -1157,6 +1163,7 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1157
1163
|
window.__plusui_dropzone_init = true;
|
|
1158
1164
|
|
|
1159
1165
|
var activeZone = null;
|
|
1166
|
+
var dragDepth = 0;
|
|
1160
1167
|
|
|
1161
1168
|
var findDropZone = function(e) {
|
|
1162
1169
|
var target = null;
|
|
@@ -1170,19 +1177,28 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1170
1177
|
|
|
1171
1178
|
var updateActiveZone = function(zone) {
|
|
1172
1179
|
if (activeZone === zone) return;
|
|
1173
|
-
if (activeZone)
|
|
1180
|
+
if (activeZone) {
|
|
1181
|
+
activeZone.classList.remove('dropzone-active');
|
|
1182
|
+
activeZone.classList.remove('filedrop-active');
|
|
1183
|
+
}
|
|
1174
1184
|
activeZone = zone;
|
|
1175
|
-
if (activeZone)
|
|
1185
|
+
if (activeZone) {
|
|
1186
|
+
activeZone.classList.add('dropzone-active');
|
|
1187
|
+
activeZone.classList.add('filedrop-active');
|
|
1188
|
+
}
|
|
1176
1189
|
};
|
|
1177
1190
|
|
|
1178
1191
|
// Always preventDefault to stop browser from navigating to file,
|
|
1179
|
-
// but show visual feedback when over a drop zone
|
|
1192
|
+
// but show visual feedback when over a drop zone.
|
|
1193
|
+
// dragDepth tracks nested dragenter/dragleave pairs so we know
|
|
1194
|
+
// when the drag truly leaves the window (depth returns to 0).
|
|
1180
1195
|
document.addEventListener('dragenter', function(e) {
|
|
1181
1196
|
e.preventDefault();
|
|
1197
|
+
dragDepth++;
|
|
1182
1198
|
var zone = findDropZone(e);
|
|
1183
1199
|
updateActiveZone(zone);
|
|
1184
1200
|
if (e.dataTransfer) {
|
|
1185
|
-
try { e.dataTransfer.dropEffect = zone ? '
|
|
1201
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1186
1202
|
}
|
|
1187
1203
|
}, true);
|
|
1188
1204
|
|
|
@@ -1191,18 +1207,25 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1191
1207
|
var zone = findDropZone(e);
|
|
1192
1208
|
updateActiveZone(zone);
|
|
1193
1209
|
if (e.dataTransfer) {
|
|
1194
|
-
try { e.dataTransfer.dropEffect = zone ? '
|
|
1210
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1195
1211
|
}
|
|
1196
1212
|
}, true);
|
|
1197
1213
|
|
|
1198
1214
|
document.addEventListener('dragleave', function(e) {
|
|
1199
1215
|
e.preventDefault();
|
|
1200
|
-
|
|
1201
|
-
|
|
1216
|
+
dragDepth--;
|
|
1217
|
+
if (dragDepth <= 0) {
|
|
1218
|
+
dragDepth = 0;
|
|
1219
|
+
updateActiveZone(null);
|
|
1220
|
+
} else {
|
|
1221
|
+
var zone = findDropZone(e);
|
|
1222
|
+
updateActiveZone(zone);
|
|
1223
|
+
}
|
|
1202
1224
|
}, true);
|
|
1203
1225
|
|
|
1204
1226
|
document.addEventListener('drop', function(e) {
|
|
1205
1227
|
e.preventDefault();
|
|
1228
|
+
dragDepth = 0;
|
|
1206
1229
|
updateActiveZone(null);
|
|
1207
1230
|
}, true);
|
|
1208
1231
|
|