@crossbind/extension-page-dialogs 0.0.0-stage → 2.0.0-beta.69

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Buğra Sarı and crossbind contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,20 @@
1
- # Temporary Holding Version
1
+ # @crossbind/extension-page-dialogs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A page's alert, confirm and prompt, as the system's dialogs.
4
+
5
+ An app has it by having `@crossbind/core-app-host`, which lists it as a dependency: there is nothing to install or list.
6
+
7
+ ## What it does
8
+
9
+ The page's `alert()`, `confirm()` and `prompt()` show as the system's own dialogs, under the app's name, and the page
10
+ waits for their answer, as in a browser.
11
+
12
+ ## Platforms
13
+
14
+ macOS, iOS, Linux, Windows and Android.
15
+
16
+ ## Replace or leave out
17
+
18
+ It holds the role `page-dialogs`. An extension that gives `replaces: 'page-dialogs'` takes it; `app: { without: ['page-dialogs'] }` in `crossbind.config.js` leaves it out. [The guide](https://crossbind.dev/guide/extensions/#replace) shows how, and [extensions.md](https://github.com/crossbind/crossbind/blob/main/docs/api/extensions.md#the-hosts-own-extensions) what a replacement answers.
19
+
20
+ [extensions.md](https://github.com/crossbind/crossbind/blob/main/docs/api/extensions.md#the-pages-requests) has the hooks a replacement answers.
@@ -0,0 +1,14 @@
1
+ export default function pageDialogs() {
2
+ return {
3
+ name: 'page-dialogs',
4
+ contract: 1,
5
+ root: import.meta.dirname,
6
+ app: {
7
+ macos: { sources: ['native/dialogs.cpp', 'native/darwin.mm'] },
8
+ ios: { sources: ['native/dialogs.cpp', 'native/ios.mm'] },
9
+ linux: { sources: ['native/dialogs.cpp', 'native/linux.cpp'] },
10
+ windows: { sources: ['native/dialogs.cpp', 'native/windows.cpp'], libraries: ['comctl32'] },
11
+ android: { java: 'native/android', class: 'dev.crossbind.extension.pagedialogs.PageDialogsExtension' },
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,42 @@
1
+ package dev.crossbind.extension.pagedialogs;
2
+
3
+ import android.app.Activity;
4
+ import android.app.AlertDialog;
5
+ import android.widget.EditText;
6
+
7
+ import org.json.JSONObject;
8
+
9
+ import dev.crossbind.app.Extension;
10
+ import dev.crossbind.app.ExtensionHost;
11
+
12
+ // The host's page-dialogs on Android: the page's alert(), confirm() and prompt() as the system's dialog in the activity,
13
+ // titled with the app's name. A dialog its user dismisses answers as cancelled.
14
+ public final class PageDialogsExtension implements Extension {
15
+ @Override
16
+ public void call(ExtensionHost host, String function, JSONObject options, Call call) {
17
+ call.reject("crossbind: page-dialogs has no function " + function);
18
+ }
19
+
20
+ @Override
21
+ public void onPageDialog(ExtensionHost host, PageDialog dialog) {
22
+ Activity activity = host.activity();
23
+ if (activity.isFinishing() || activity.isDestroyed()) {
24
+ dialog.useDefault();
25
+ return;
26
+ }
27
+ EditText field = dialog.kind() == PageDialog.Kind.PROMPT ? new EditText(activity) : null;
28
+ if (field != null) {
29
+ field.setText(dialog.defaultText());
30
+ field.setSingleLine(true);
31
+ field.selectAll();
32
+ }
33
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity)
34
+ .setTitle(activity.getApplicationInfo().loadLabel(activity.getPackageManager()))
35
+ .setMessage(dialog.message())
36
+ .setPositiveButton(android.R.string.ok, (shown, which) -> dialog.accept(field == null ? "" : field.getText().toString()))
37
+ .setOnCancelListener((shown) -> dialog.cancel());
38
+ if (field != null) builder.setView(field);
39
+ if (dialog.kind() != PageDialog.Kind.ALERT) builder.setNegativeButton(android.R.string.cancel, (shown, which) -> dialog.cancel());
40
+ builder.show();
41
+ }
42
+ }
@@ -0,0 +1,57 @@
1
+ #import <Cocoa/Cocoa.h>
2
+
3
+ #include "dialogs.h"
4
+
5
+ using crossbind::PageDialog;
6
+
7
+ namespace {
8
+
9
+ NSString *ns(const std::string &text) { return [NSString stringWithUTF8String:text.c_str()] ?: @""; }
10
+
11
+ NSAlert *alertOf(const PageDialog &dialog, NSArray<NSString *> *buttons) {
12
+ NSAlert *alert = [NSAlert new];
13
+ alert.messageText = ns(crossbind::appName());
14
+ alert.informativeText = ns(dialog.message());
15
+ for (NSString *button in buttons) [alert addButtonWithTitle:button];
16
+ return alert;
17
+ }
18
+
19
+ // A sheet on the page's window, as Safari shows them; without one, a dialog of its own.
20
+ void show(NSAlert *alert, const PageDialog &dialog, void (^done)(NSModalResponse)) {
21
+ NSWindow *window = (__bridge NSWindow *)windowOf(dialog);
22
+ if (window) {
23
+ [alert beginSheetModalForWindow:window completionHandler:done];
24
+ } else {
25
+ done([alert runModal]);
26
+ }
27
+ }
28
+
29
+ } // namespace
30
+
31
+ void showPageDialog(PageDialog dialog) {
32
+ switch (dialog.kind()) {
33
+ case PageDialog::Kind::alert: {
34
+ show(alertOf(dialog, @[ @"OK" ]), dialog, ^(NSModalResponse) { dialog.accept(); });
35
+ return;
36
+ }
37
+ case PageDialog::Kind::confirm: {
38
+ show(alertOf(dialog, @[ @"OK", @"Cancel" ]), dialog, ^(NSModalResponse response) {
39
+ if (response == NSAlertFirstButtonReturn) dialog.accept();
40
+ else dialog.cancel();
41
+ });
42
+ return;
43
+ }
44
+ case PageDialog::Kind::prompt: {
45
+ NSAlert *alert = alertOf(dialog, @[ @"OK", @"Cancel" ]);
46
+ NSTextField *field = [NSTextField textFieldWithString:ns(dialog.defaultText())];
47
+ field.frame = NSMakeRect(0, 0, 260, 24);
48
+ alert.accessoryView = field;
49
+ alert.window.initialFirstResponder = field;
50
+ show(alert, dialog, ^(NSModalResponse response) {
51
+ if (response == NSAlertFirstButtonReturn) dialog.accept(field.stringValue.UTF8String ?: "");
52
+ else dialog.cancel();
53
+ });
54
+ return;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,5 @@
1
+ #include "dialogs.h"
2
+
3
+ // The host's page-dialogs: the page's alert(), confirm() and prompt() in the platform's own dialog, as an app's
4
+ // extension of this name may show them otherwise.
5
+ void crossbind_extension_page_dialogs(crossbind::Registry &registry) { registry.onPageDialog(showPageDialog); }
@@ -0,0 +1,13 @@
1
+ #pragma once
2
+
3
+ #include "crossbind/extension.h"
4
+
5
+ // The page's dialog in the platform's own, titled with the app's name, over the page's window; the page waits for its
6
+ // answer.
7
+ void showPageDialog(crossbind::PageDialog dialog);
8
+
9
+ // The platform's window of the page that asks, on the main thread: the app's oldest for a page whose window went.
10
+ inline void *windowOf(const crossbind::PageDialog &dialog) {
11
+ const std::optional<crossbind::Window> open = crossbind::windowOf(dialog.page().windowId());
12
+ return open ? open->native() : crossbind::appWindow();
13
+ }
package/native/ios.mm ADDED
@@ -0,0 +1,37 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #include "dialogs.h"
4
+
5
+ using crossbind::PageDialog;
6
+
7
+ namespace {
8
+
9
+ NSString *ns(const std::string &text) { return [NSString stringWithUTF8String:text.c_str()] ?: @""; }
10
+
11
+ // Over what the page's window shows on top, if it shows anything.
12
+ void presentOnTop(UIViewController *controller, const PageDialog &dialog) {
13
+ UIViewController *top = ((__bridge UIWindow *)windowOf(dialog)).rootViewController;
14
+ while (top.presentedViewController) top = top.presentedViewController;
15
+ if (top && !top.isBeingDismissed) [top presentViewController:controller animated:YES completion:nil];
16
+ }
17
+
18
+ } // namespace
19
+
20
+ // An alert that goes without an action, or that nothing shows, lets go of the dialog, which then answers as cancelled:
21
+ // WebKit wants an answer to every one.
22
+ void showPageDialog(PageDialog dialog) {
23
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:ns(crossbind::appName()) message:ns(dialog.message())
24
+ preferredStyle:UIAlertControllerStyleAlert];
25
+ const PageDialog::Kind kind = dialog.kind();
26
+ if (kind == PageDialog::Kind::prompt) {
27
+ [alert addTextFieldWithConfigurationHandler:^(UITextField *field) { field.text = ns(dialog.defaultText()); }];
28
+ }
29
+ if (kind != PageDialog::Kind::alert) {
30
+ [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *) { dialog.cancel(); }]];
31
+ }
32
+ __weak UIAlertController *shown = alert;
33
+ [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *) {
34
+ dialog.accept(kind == PageDialog::Kind::prompt ? (shown.textFields.firstObject.text.UTF8String ?: "") : "");
35
+ }]];
36
+ presentOnTop(alert, dialog);
37
+ }
@@ -0,0 +1,42 @@
1
+ #include "dialogs.h"
2
+
3
+ #include <gtk/gtk.h>
4
+
5
+ using crossbind::PageDialog;
6
+
7
+ namespace {
8
+
9
+ // The dialog holds the page's until it goes, which then answers it as cancelled unless its user answered.
10
+ void forget(gpointer dialog, GClosure *) { delete static_cast<PageDialog *>(dialog); }
11
+
12
+ void responded(GtkDialog *box, gint response, gpointer data) {
13
+ const auto *dialog = static_cast<const PageDialog *>(data);
14
+ auto *entry = static_cast<GtkEntry *>(g_object_get_data(G_OBJECT(box), "crossbind-entry"));
15
+ if (response == GTK_RESPONSE_OK) dialog->accept(entry ? gtk_entry_get_text(entry) : "");
16
+ else dialog->cancel();
17
+ gtk_widget_destroy(GTK_WIDGET(box));
18
+ }
19
+
20
+ } // namespace
21
+
22
+ // A dialog of the page's window, with the app's name over the message, as GTK shows its own.
23
+ void showPageDialog(PageDialog dialog) {
24
+ const std::string title = crossbind::appName();
25
+ GtkWidget *box = gtk_message_dialog_new(static_cast<GtkWindow *>(windowOf(dialog)), GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
26
+ GTK_MESSAGE_OTHER, dialog.kind() == PageDialog::Kind::alert ? GTK_BUTTONS_OK : GTK_BUTTONS_OK_CANCEL, "%s",
27
+ title.c_str());
28
+ gtk_window_set_title(GTK_WINDOW(box), title.c_str());
29
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(box), "%s", dialog.message().c_str());
30
+ if (dialog.kind() == PageDialog::Kind::prompt) {
31
+ GtkWidget *entry = gtk_entry_new();
32
+ gtk_entry_set_text(GTK_ENTRY(entry), dialog.defaultText().c_str());
33
+ gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
34
+ gtk_container_add(GTK_CONTAINER(gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(box))), entry);
35
+ gtk_widget_show(entry);
36
+ gtk_widget_grab_focus(entry);
37
+ g_object_set_data(G_OBJECT(box), "crossbind-entry", entry);
38
+ }
39
+ gtk_dialog_set_default_response(GTK_DIALOG(box), GTK_RESPONSE_OK);
40
+ g_signal_connect_data(box, "response", G_CALLBACK(responded), new PageDialog(std::move(dialog)), forget, GConnectFlags(0));
41
+ gtk_window_present(GTK_WINDOW(box));
42
+ }
@@ -0,0 +1,212 @@
1
+ #include "dialogs.h"
2
+
3
+ #include <windows.h>
4
+ #include <commctrl.h>
5
+
6
+ #include <algorithm>
7
+ #include <optional>
8
+ #include <string>
9
+ #include <vector>
10
+
11
+ using crossbind::PageDialog;
12
+
13
+ namespace {
14
+
15
+ constexpr WORD MESSAGE_ID = 100;
16
+ constexpr WORD TEXT_ID = 101;
17
+ // A prompt's layout in dialog units, which Windows scales with the window's font and DPI.
18
+ constexpr short WIDTH = 260;
19
+ constexpr short MARGIN = 7;
20
+ constexpr short LINE = 8;
21
+ constexpr short EDIT_HEIGHT = 14;
22
+ constexpr short BUTTON_WIDTH = 50;
23
+ constexpr short BUTTON_HEIGHT = 14;
24
+
25
+ std::wstring wide(const std::string &text) {
26
+ const int length = MultiByteToWideChar(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), nullptr, 0);
27
+ std::wstring out(static_cast<size_t>(length), L'\0');
28
+ MultiByteToWideChar(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), out.data(), length);
29
+ return out;
30
+ }
31
+
32
+ std::string utf8(const std::wstring &text) {
33
+ const int length = WideCharToMultiByte(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), nullptr, 0, nullptr, nullptr);
34
+ std::string out(static_cast<size_t>(length), '\0');
35
+ WideCharToMultiByte(CP_UTF8, 0, text.data(), static_cast<int>(text.size()), out.data(), length, nullptr, nullptr);
36
+ return out;
37
+ }
38
+
39
+ // An alert or a confirm as the system's task dialog, modal to the page's window, as a message box is.
40
+ bool confirmed(const PageDialog &dialog) {
41
+ const std::wstring title = wide(crossbind::appName());
42
+ const std::wstring message = wide(dialog.message());
43
+ TASKDIALOGCONFIG config{};
44
+ config.cbSize = sizeof config;
45
+ config.hwndParent = static_cast<HWND>(windowOf(dialog));
46
+ // Escape and closing the dialog answer IDCANCEL.
47
+ config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW;
48
+ config.dwCommonButtons = dialog.kind() == PageDialog::Kind::alert ? TDCBF_OK_BUTTON : TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON;
49
+ config.pszWindowTitle = title.c_str();
50
+ config.pszContent = message.c_str();
51
+ int clicked = IDCANCEL;
52
+ return SUCCEEDED(TaskDialogIndirect(&config, &clicked, nullptr, nullptr)) && clicked == IDOK;
53
+ }
54
+
55
+ // A dialog template in memory, as a resource file would hold it: words, a string after each item's class, and each item
56
+ // on a 4-byte boundary.
57
+ class Template {
58
+ public:
59
+ void word(WORD value) { words_.push_back(value); }
60
+ void dword(DWORD value) {
61
+ word(LOWORD(value));
62
+ word(HIWORD(value));
63
+ }
64
+ void text(const wchar_t *value) {
65
+ for (; *value; ++value) word(*value);
66
+ word(0);
67
+ }
68
+ void align() {
69
+ if (words_.size() % 2) word(0);
70
+ }
71
+ void item(DWORD style, short x, short y, short cx, short cy, WORD id, WORD atom, const wchar_t *title) {
72
+ align();
73
+ dword(style | WS_CHILD | WS_VISIBLE);
74
+ dword(0);
75
+ for (const short value : {x, y, cx, cy}) word(static_cast<WORD>(value));
76
+ word(id);
77
+ word(0xFFFF);
78
+ word(atom);
79
+ text(title);
80
+ word(0);
81
+ }
82
+ const DLGTEMPLATE *data() const { return reinterpret_cast<const DLGTEMPLATE *>(words_.data()); }
83
+
84
+ private:
85
+ std::vector<WORD> words_;
86
+ };
87
+
88
+ struct Prompt {
89
+ std::wstring message;
90
+ std::wstring text;
91
+ bool isAnswered = false;
92
+ };
93
+
94
+ // Grows the message to the lines its text takes, as far as the screen lets it, with the text box and the buttons below
95
+ // it, and centers the prompt on the app's window, as the task dialog is.
96
+ void fit(HWND box) {
97
+ HWND message = GetDlgItem(box, MESSAGE_ID);
98
+ RECT area{};
99
+ GetWindowRect(message, &area);
100
+ const int width = area.right - area.left;
101
+ const int length = GetWindowTextLengthW(message);
102
+ std::wstring text(static_cast<size_t>(length) + 1, L'\0');
103
+ GetWindowTextW(message, text.data(), length + 1);
104
+ HDC dc = GetDC(message);
105
+ HGDIOBJ font = SelectObject(dc, reinterpret_cast<HFONT>(SendMessageW(box, WM_GETFONT, 0, 0)));
106
+ RECT needed{0, 0, width, 0};
107
+ DrawTextW(dc, text.c_str(), length, &needed, DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX);
108
+ SelectObject(dc, font);
109
+ ReleaseDC(message, dc);
110
+ RECT frame{};
111
+ GetWindowRect(box, &frame);
112
+ MONITORINFO monitor{};
113
+ monitor.cbSize = sizeof monitor;
114
+ GetMonitorInfoW(MonitorFromWindow(box, MONITOR_DEFAULTTONEAREST), &monitor);
115
+ const RECT &work = monitor.rcWork;
116
+ const LONG room = std::max(0L, (work.bottom - work.top) - (frame.bottom - frame.top));
117
+ const LONG grow = std::clamp((needed.bottom - needed.top) - (area.bottom - area.top), 0L, room);
118
+ if (grow > 0) {
119
+ SetWindowPos(message, nullptr, 0, 0, width, area.bottom - area.top + grow, SWP_NOMOVE | SWP_NOZORDER);
120
+ for (const int id : {static_cast<int>(TEXT_ID), IDOK, IDCANCEL}) {
121
+ HWND child = GetDlgItem(box, id);
122
+ RECT place{};
123
+ GetWindowRect(child, &place);
124
+ POINT at{place.left, place.top};
125
+ ScreenToClient(box, &at);
126
+ SetWindowPos(child, nullptr, at.x, at.y + grow, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
127
+ }
128
+ }
129
+ const LONG boxWidth = frame.right - frame.left;
130
+ const LONG boxHeight = frame.bottom - frame.top + grow;
131
+ RECT over = work;
132
+ if (HWND owner = GetWindow(box, GW_OWNER)) GetWindowRect(owner, &over);
133
+ const LONG x = std::clamp((over.left + over.right - boxWidth) / 2, work.left, std::max(work.left, work.right - boxWidth));
134
+ const LONG y = std::clamp((over.top + over.bottom - boxHeight) / 2, work.top, std::max(work.top, work.bottom - boxHeight));
135
+ SetWindowPos(box, nullptr, x, y, boxWidth, boxHeight, SWP_NOZORDER);
136
+ }
137
+
138
+ INT_PTR CALLBACK promptProc(HWND box, UINT message, WPARAM w, LPARAM l) {
139
+ auto *prompt = reinterpret_cast<Prompt *>(GetWindowLongPtrW(box, DWLP_USER));
140
+ switch (message) {
141
+ case WM_INITDIALOG:
142
+ SetWindowLongPtrW(box, DWLP_USER, l);
143
+ prompt = reinterpret_cast<Prompt *>(l);
144
+ SetWindowTextW(box, wide(crossbind::appName()).c_str());
145
+ SetDlgItemTextW(box, MESSAGE_ID, prompt->message.c_str());
146
+ SetDlgItemTextW(box, TEXT_ID, prompt->text.c_str());
147
+ fit(box);
148
+ SendDlgItemMessageW(box, TEXT_ID, EM_SETSEL, 0, -1);
149
+ SetFocus(GetDlgItem(box, TEXT_ID));
150
+ // The text box has the focus.
151
+ return FALSE;
152
+ case WM_COMMAND:
153
+ if (LOWORD(w) == IDOK) {
154
+ HWND edit = GetDlgItem(box, TEXT_ID);
155
+ const int length = GetWindowTextLengthW(edit);
156
+ std::wstring text(static_cast<size_t>(length) + 1, L'\0');
157
+ GetWindowTextW(edit, text.data(), length + 1);
158
+ text.resize(static_cast<size_t>(length));
159
+ prompt->text = std::move(text);
160
+ prompt->isAnswered = true;
161
+ EndDialog(box, IDOK);
162
+ return TRUE;
163
+ }
164
+ if (LOWORD(w) == IDCANCEL) {
165
+ EndDialog(box, IDCANCEL);
166
+ return TRUE;
167
+ }
168
+ return FALSE;
169
+ default:
170
+ return FALSE;
171
+ }
172
+ }
173
+
174
+ // A prompt as a dialog of the system's controls, which Windows has no ready one of: its message, a text box with the
175
+ // prompt's text, and OK and Cancel, in the task dialog's font. Enter is OK and Escape is Cancel.
176
+ std::optional<std::string> prompted(const PageDialog &dialog) {
177
+ constexpr short TEXT_Y = MARGIN + 2 * LINE + 4;
178
+ constexpr short BUTTONS_Y = TEXT_Y + EDIT_HEIGHT + MARGIN;
179
+ constexpr short HEIGHT = BUTTONS_Y + BUTTON_HEIGHT + MARGIN;
180
+ Template layout;
181
+ layout.dword(DS_MODALFRAME | DS_SETFONT | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU);
182
+ layout.dword(0);
183
+ layout.word(4);
184
+ for (const short value : {short(0), short(0), WIDTH, HEIGHT}) layout.word(static_cast<WORD>(value));
185
+ layout.word(0);
186
+ layout.word(0);
187
+ layout.text(L"");
188
+ layout.word(9);
189
+ layout.text(L"Segoe UI");
190
+ layout.item(SS_LEFT | SS_NOPREFIX, MARGIN, MARGIN, WIDTH - 2 * MARGIN, 2 * LINE, MESSAGE_ID, 0x0082, L"");
191
+ layout.item(ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP, MARGIN, TEXT_Y, WIDTH - 2 * MARGIN, EDIT_HEIGHT, TEXT_ID, 0x0081, L"");
192
+ layout.item(BS_DEFPUSHBUTTON | WS_TABSTOP, WIDTH - MARGIN - 2 * BUTTON_WIDTH - 4, BUTTONS_Y, BUTTON_WIDTH, BUTTON_HEIGHT, IDOK, 0x0080, L"OK");
193
+ layout.item(BS_PUSHBUTTON | WS_TABSTOP, WIDTH - MARGIN - BUTTON_WIDTH, BUTTONS_Y, BUTTON_WIDTH, BUTTON_HEIGHT, IDCANCEL, 0x0080, L"Cancel");
194
+ Prompt prompt{wide(dialog.message()), wide(dialog.defaultText())};
195
+ DialogBoxIndirectParamW(GetModuleHandleW(nullptr), layout.data(), static_cast<HWND>(windowOf(dialog)), promptProc, reinterpret_cast<LPARAM>(&prompt));
196
+ if (!prompt.isAnswered) return std::nullopt;
197
+ return utf8(prompt.text);
198
+ }
199
+
200
+ } // namespace
201
+
202
+ // Each modal to the app's window, as the host hands it over once the webview's event that asked has returned.
203
+ void showPageDialog(PageDialog dialog) {
204
+ if (dialog.kind() != PageDialog::Kind::prompt) {
205
+ if (confirmed(dialog)) dialog.accept();
206
+ else dialog.cancel();
207
+ return;
208
+ }
209
+ const std::optional<std::string> text = prompted(dialog);
210
+ if (text) dialog.accept(*text);
211
+ else dialog.cancel();
212
+ }
package/package.json CHANGED
@@ -1,6 +1,29 @@
1
1
  {
2
2
  "name": "@crossbind/extension-page-dialogs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.0.0-beta.69",
4
+ "description": "crossbind app extension: a page's alert, confirm and prompt, as the system's dialogs.",
5
+ "homepage": "https://github.com/crossbind/crossbind/tree/main/extensions/page-dialogs#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/crossbind/crossbind.git"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "exports": {
13
+ "./crossbind.extension.js": "./crossbind.extension.js"
14
+ },
15
+ "files": [
16
+ "crossbind.extension.js",
17
+ "native"
18
+ ],
19
+ "crossbind": {
20
+ "extension": "./crossbind.extension.js"
21
+ },
22
+ "keywords": [
23
+ "crossbind-extension",
24
+ "crossbind",
25
+ "page",
26
+ "dialogs",
27
+ "app"
28
+ ]
6
29
  }