craft-native 0.0.77 → 0.0.79
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/api/process.d.ts +9 -3
- package/dist/api/window.d.ts +17 -11
- package/dist/cli.js +1 -1
- package/dist/index.cjs +6 -2
- package/dist/index.js +6 -2
- package/dist/ios/templates/CraftApp.swift +145 -28
- package/dist/types.d.ts +137 -12
- package/package.json +1 -1
- package/dist/bridge/android.d.ts +0 -573
- package/dist/bridge/ios.d.ts +0 -527
package/dist/api/process.d.ts
CHANGED
|
@@ -62,9 +62,15 @@ export interface SpawnOptions {
|
|
|
62
62
|
cwd?: string;
|
|
63
63
|
env?: Record<string, string>;
|
|
64
64
|
/**
|
|
65
|
-
* Run
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* Run `command` as a command line through a shell (cmd.exe on Windows,
|
|
66
|
+
* /bin/sh elsewhere) instead of executing it directly.
|
|
67
|
+
*
|
|
68
|
+
* Off by default, as in Node. Without it `command` is a program and `args`
|
|
69
|
+
* are its arguments, so a `;` or `|` inside an argument reaches the program
|
|
70
|
+
* as a plain character rather than as shell syntax.
|
|
71
|
+
*
|
|
72
|
+
* A shell takes one string, so `args` cannot be passed with it — supplying
|
|
73
|
+
* both is refused rather than silently honouring one of them.
|
|
68
74
|
*/
|
|
69
75
|
shell?: boolean;
|
|
70
76
|
}
|
package/dist/api/window.d.ts
CHANGED
|
@@ -101,11 +101,6 @@ export interface WindowCreateOptions {
|
|
|
101
101
|
webSidebarMaterialOpacity?: number;
|
|
102
102
|
/** Titlebar style (macOS) */
|
|
103
103
|
titlebarStyle?: 'default' | 'hidden' | 'hiddenInset' | 'customButtonsOnHover';
|
|
104
|
-
/** Traffic light position (macOS) */
|
|
105
|
-
trafficLightPosition?: {
|
|
106
|
-
x: number;
|
|
107
|
-
y: number;
|
|
108
|
-
};
|
|
109
104
|
/** Vibrancy effect (macOS) */
|
|
110
105
|
vibrancy?: 'appearance-based' | 'light' | 'dark' | 'titlebar' | 'selection' | 'menu' | 'popover' | 'sidebar' | 'header' | 'sheet' | 'window' | 'hud' | 'fullscreen-ui' | 'tooltip' | 'content' | 'under-window' | 'under-page';
|
|
111
106
|
/** Background material (Windows 11) */
|
|
@@ -338,12 +333,23 @@ export declare class Window {
|
|
|
338
333
|
*/
|
|
339
334
|
setVibrancy(vibrancy: WindowCreateOptions['vibrancy'] | null): Promise<void>;
|
|
340
335
|
/**
|
|
341
|
-
*
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
336
|
+
* Where the platform's window buttons are — read, not written.
|
|
337
|
+
*
|
|
338
|
+
* There was a `setTrafficLightPosition` here, and a `trafficLightPosition`
|
|
339
|
+
* window option beside it. Neither ever moved anything: the host has no
|
|
340
|
+
* handler for the call, and AppKit re-lays out the standard window buttons
|
|
341
|
+
* after any one-shot `setFrameOrigin:`, which is why `macos.zig` leaves them
|
|
342
|
+
* where the window server puts them and says so at length.
|
|
343
|
+
*
|
|
344
|
+
* What a layout actually needs is the opposite direction — where they *are*,
|
|
345
|
+
* which the host measures and publishes on every window:
|
|
346
|
+
*
|
|
347
|
+
* window.craft.windowControls { style, x, y, width, height, ... }
|
|
348
|
+
* --craft-window-controls-width the room to leave, in CSS
|
|
349
|
+
*
|
|
350
|
+
* See `CraftWindowControls`, and `docs/features/window-management.md`.
|
|
351
|
+
*/
|
|
352
|
+
get windowControls(): import('../types.js').CraftWindowControls | undefined;
|
|
347
353
|
/**
|
|
348
354
|
* Set window level (macOS)
|
|
349
355
|
*/
|
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -2532,8 +2532,8 @@ class Window {
|
|
|
2532
2532
|
async setVibrancy(vibrancy) {
|
|
2533
2533
|
await this._call("setVibrancy", { vibrancy });
|
|
2534
2534
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2535
|
+
get windowControls() {
|
|
2536
|
+
return globalThis.craft?.windowControls;
|
|
2537
2537
|
}
|
|
2538
2538
|
async setWindowLevel(level) {
|
|
2539
2539
|
await this._call("setWindowLevel", { level });
|
|
@@ -10745,6 +10745,10 @@ class CraftApp {
|
|
|
10745
10745
|
args.push("--titlebar-hidden");
|
|
10746
10746
|
if (window3?.headless)
|
|
10747
10747
|
args.push("--headless");
|
|
10748
|
+
if (window3?.keepRunning === true)
|
|
10749
|
+
args.push("--keep-running");
|
|
10750
|
+
else if (window3?.keepRunning === false)
|
|
10751
|
+
args.push("--quit-on-close");
|
|
10748
10752
|
if (window3?.webSidebarMaterial) {
|
|
10749
10753
|
args.push("--web-sidebar-material");
|
|
10750
10754
|
if (window3?.webSidebarWidth)
|
package/dist/index.js
CHANGED
|
@@ -2302,8 +2302,8 @@ class Window {
|
|
|
2302
2302
|
async setVibrancy(vibrancy) {
|
|
2303
2303
|
await this._call("setVibrancy", { vibrancy });
|
|
2304
2304
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2305
|
+
get windowControls() {
|
|
2306
|
+
return globalThis.craft?.windowControls;
|
|
2307
2307
|
}
|
|
2308
2308
|
async setWindowLevel(level) {
|
|
2309
2309
|
await this._call("setWindowLevel", { level });
|
|
@@ -10515,6 +10515,10 @@ class CraftApp {
|
|
|
10515
10515
|
args.push("--titlebar-hidden");
|
|
10516
10516
|
if (window3?.headless)
|
|
10517
10517
|
args.push("--headless");
|
|
10518
|
+
if (window3?.keepRunning === true)
|
|
10519
|
+
args.push("--keep-running");
|
|
10520
|
+
else if (window3?.keepRunning === false)
|
|
10521
|
+
args.push("--quit-on-close");
|
|
10518
10522
|
if (window3?.webSidebarMaterial) {
|
|
10519
10523
|
args.push("--web-sidebar-material");
|
|
10520
10524
|
if (window3?.webSidebarWidth)
|
|
@@ -364,7 +364,12 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
364
364
|
func updateUIView(_ webView: WKWebView, context: Context) {}
|
|
365
365
|
|
|
366
366
|
func makeCoordinator() -> Coordinator {
|
|
367
|
-
Coordinator(config: config)
|
|
367
|
+
let coordinator = Coordinator(config: config)
|
|
368
|
+
// The Zig dispatcher finds CraftSwiftShim by class name; the shim finds
|
|
369
|
+
// its way back to the live coordinator through this. Weak, because
|
|
370
|
+
// SwiftUI owns the coordinator's lifetime.
|
|
371
|
+
CraftSwiftShim.coordinator = coordinator
|
|
372
|
+
return coordinator
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
// MARK: - Coordinator (Native Bridge)
|
|
@@ -511,8 +516,19 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
511
516
|
guard let body = message.body as? [String: Any],
|
|
512
517
|
let action = body["action"] as? String else { return }
|
|
513
518
|
|
|
514
|
-
|
|
519
|
+
dispatch(action: action, body: body, callbackId: body["callbackId"] as? String)
|
|
520
|
+
}
|
|
515
521
|
|
|
522
|
+
/// The action switch, reachable from two directions: the
|
|
523
|
+
/// WKScriptMessageHandler above, and `CraftSwiftShim.handleAction` when
|
|
524
|
+
/// the Zig dispatcher hands over an action it has not migrated yet.
|
|
525
|
+
///
|
|
526
|
+
/// A hand-off call arrives with a synthetic callbackId of the form
|
|
527
|
+
/// "zig:<requestId>". The reply helpers recognise that prefix and route
|
|
528
|
+
/// the answer back through the Zig runtime's exported
|
|
529
|
+
/// `craft_ios_deliver_result` instead of evaluating JavaScript here —
|
|
530
|
+
/// one reply path, owned by whichever side received the page's message.
|
|
531
|
+
func dispatch(action: String, body: [String: Any], callbackId: String?) {
|
|
516
532
|
switch action {
|
|
517
533
|
case "startListening":
|
|
518
534
|
if config.enableSpeechRecognition { startSpeechRecognition() }
|
|
@@ -2222,41 +2238,33 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
2222
2238
|
_progressCallbacks: [],
|
|
2223
2239
|
_statusCallbacks: [],
|
|
2224
2240
|
|
|
2241
|
+
// OTA is not implemented natively. These five used to post
|
|
2242
|
+
// to actions the switch below does not handle, and register
|
|
2243
|
+
// a resolve/reject pair that nothing would ever call — they
|
|
2244
|
+
// also bypassed _createCallback, which is what owns the 30s
|
|
2245
|
+
// timeout, so the promises hung forever rather than
|
|
2246
|
+
// rejecting. An immediate rejection is the honest answer:
|
|
2247
|
+
// an app can catch it, where it could never catch a hang.
|
|
2248
|
+
_unavailable: function(name) {
|
|
2249
|
+
return Promise.reject(new Error(
|
|
2250
|
+
'craft.ota.' + name + ' is not implemented on this platform'
|
|
2251
|
+
));
|
|
2252
|
+
},
|
|
2225
2253
|
configure: function(options) {
|
|
2226
2254
|
this._config = options;
|
|
2227
|
-
|
|
2255
|
+
console.warn('[craft] ota.configure stored locally; OTA is not implemented natively');
|
|
2228
2256
|
},
|
|
2229
2257
|
checkForUpdate: function() {
|
|
2230
|
-
|
|
2231
|
-
var id = 'cb_' + (++self._callbackId);
|
|
2232
|
-
window.webkit.messageHandlers.craft.postMessage({action: 'otaCheckForUpdate', callbackId: id});
|
|
2233
|
-
return new Promise(function(resolve, reject) {
|
|
2234
|
-
self._callbacks[id] = {resolve: resolve, reject: reject};
|
|
2235
|
-
});
|
|
2258
|
+
return window.craft.ota._unavailable('checkForUpdate');
|
|
2236
2259
|
},
|
|
2237
2260
|
downloadUpdate: function(options) {
|
|
2238
|
-
|
|
2239
|
-
var id = 'cb_' + (++self._callbackId);
|
|
2240
|
-
window.webkit.messageHandlers.craft.postMessage({action: 'otaDownloadUpdate', options: options || {}, callbackId: id});
|
|
2241
|
-
return new Promise(function(resolve, reject) {
|
|
2242
|
-
self._callbacks[id] = {resolve: resolve, reject: reject};
|
|
2243
|
-
});
|
|
2261
|
+
return window.craft.ota._unavailable('downloadUpdate');
|
|
2244
2262
|
},
|
|
2245
2263
|
applyUpdate: function() {
|
|
2246
|
-
|
|
2247
|
-
var id = 'cb_' + (++self._callbackId);
|
|
2248
|
-
window.webkit.messageHandlers.craft.postMessage({action: 'otaApplyUpdate', callbackId: id});
|
|
2249
|
-
return new Promise(function(resolve, reject) {
|
|
2250
|
-
self._callbacks[id] = {resolve: resolve, reject: reject};
|
|
2251
|
-
});
|
|
2264
|
+
return window.craft.ota._unavailable('applyUpdate');
|
|
2252
2265
|
},
|
|
2253
2266
|
rollback: function() {
|
|
2254
|
-
|
|
2255
|
-
var id = 'cb_' + (++self._callbackId);
|
|
2256
|
-
window.webkit.messageHandlers.craft.postMessage({action: 'otaRollback', callbackId: id});
|
|
2257
|
-
return new Promise(function(resolve, reject) {
|
|
2258
|
-
self._callbacks[id] = {resolve: resolve, reject: reject};
|
|
2259
|
-
});
|
|
2267
|
+
return window.craft.ota._unavailable('rollback');
|
|
2260
2268
|
},
|
|
2261
2269
|
getCurrentBundle: function() {
|
|
2262
2270
|
// This returns synchronously from stored data
|
|
@@ -2434,6 +2442,10 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
2434
2442
|
rejectCallback(callbackId, error: "Native result could not be serialized", code: "SERIALIZATION_ERROR")
|
|
2435
2443
|
return
|
|
2436
2444
|
}
|
|
2445
|
+
// A hand-off from the Zig dispatcher replies through Zig, which
|
|
2446
|
+
// owns the wire format, the request id, and the escaping. Replying
|
|
2447
|
+
// by JavaScript here as well would give the page two answers.
|
|
2448
|
+
if CraftSwiftShim.deliverResultIfHandOff(id, json: resultStr) { return }
|
|
2437
2449
|
let script = "window.craft._resolveCallback('\(id)', \(resultStr));"
|
|
2438
2450
|
DispatchQueue.main.async { self.webView?.evaluateJavaScript(script, completionHandler: nil) }
|
|
2439
2451
|
}
|
|
@@ -2444,7 +2456,17 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
2444
2456
|
|
|
2445
2457
|
private func rejectCallback(_ callbackId: String?, error: String, code: String = "CRAFT_ERROR") {
|
|
2446
2458
|
guard let id = callbackId else { return }
|
|
2447
|
-
|
|
2459
|
+
// A hand-off rejection goes back through Zig's error route, so the
|
|
2460
|
+
// page's promise *rejects*. Delivering it as a result would run the
|
|
2461
|
+
// app's then-branch with an error-shaped object — fabricated
|
|
2462
|
+
// success wearing a different hat.
|
|
2463
|
+
if CraftSwiftShim.deliverErrorIfHandOff(id, message: error, code: code) { return }
|
|
2464
|
+
// Backslashes first: escaping ' and \n but not \ let an error
|
|
2465
|
+
// message containing a backslash break out of the string literal.
|
|
2466
|
+
let escapedError = error
|
|
2467
|
+
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
2468
|
+
.replacingOccurrences(of: "'", with: "\\'")
|
|
2469
|
+
.replacingOccurrences(of: "\n", with: "\\n")
|
|
2448
2470
|
let script = "window.craft._rejectCallback('\(id)', '\(escapedError)', '\(code)');"
|
|
2449
2471
|
DispatchQueue.main.async { self.webView?.evaluateJavaScript(script, completionHandler: nil) }
|
|
2450
2472
|
}
|
|
@@ -5167,3 +5189,98 @@ extension CraftWebView.Coordinator: WCSessionDelegate {
|
|
|
5167
5189
|
}
|
|
5168
5190
|
}
|
|
5169
5191
|
}
|
|
5192
|
+
|
|
5193
|
+
|
|
5194
|
+
// MARK: - Zig hand-off shim
|
|
5195
|
+
|
|
5196
|
+
/// The seam the Zig dispatcher hands unmigrated actions through.
|
|
5197
|
+
///
|
|
5198
|
+
/// Discovery is symmetric and both directions are runtime-only. Zig finds this
|
|
5199
|
+
/// class with `objc_getClass("CraftSwiftShim")`; this class finds Zig's
|
|
5200
|
+
/// delivery exports with `dlsym`. Neither side links the other, so a
|
|
5201
|
+
/// pure-Swift app (no Zig runtime) and a fully-migrated Zig app (no shim work
|
|
5202
|
+
/// left) both build and run without dead dependencies.
|
|
5203
|
+
///
|
|
5204
|
+
/// The shim decides *what* the answer is, never *how* it reaches the page.
|
|
5205
|
+
/// Replies go back through Zig's `craft_ios_deliver_result` /
|
|
5206
|
+
/// `craft_ios_deliver_error`, which own the wire format, the request id, and
|
|
5207
|
+
/// the escaping. Two components replying to one page by different routes is
|
|
5208
|
+
/// how this codebase accumulated five envelopes.
|
|
5209
|
+
@objc(CraftSwiftShim)
|
|
5210
|
+
final class CraftSwiftShim: NSObject {
|
|
5211
|
+
/// The coordinator serving hand-offs. Set by `makeCoordinator`.
|
|
5212
|
+
static weak var coordinator: CraftWebView.Coordinator?
|
|
5213
|
+
|
|
5214
|
+
private typealias DeliverResultFn = @convention(c) (
|
|
5215
|
+
UnsafePointer<CChar>, UInt, UnsafePointer<CChar>, UInt, Int64
|
|
5216
|
+
) -> Void
|
|
5217
|
+
private typealias DeliverErrorFn = @convention(c) (
|
|
5218
|
+
UnsafePointer<CChar>, UInt, UnsafePointer<CChar>, UInt,
|
|
5219
|
+
UnsafePointer<CChar>, UInt, Int64
|
|
5220
|
+
) -> Void
|
|
5221
|
+
|
|
5222
|
+
private static let deliverResult: DeliverResultFn? = {
|
|
5223
|
+
guard let sym = dlsym(dlopen(nil, RTLD_NOW), "craft_ios_deliver_result") else { return nil }
|
|
5224
|
+
return unsafeBitCast(sym, to: DeliverResultFn.self)
|
|
5225
|
+
}()
|
|
5226
|
+
|
|
5227
|
+
private static let deliverError: DeliverErrorFn? = {
|
|
5228
|
+
guard let sym = dlsym(dlopen(nil, RTLD_NOW), "craft_ios_deliver_error") else { return nil }
|
|
5229
|
+
return unsafeBitCast(sym, to: DeliverErrorFn.self)
|
|
5230
|
+
}()
|
|
5231
|
+
|
|
5232
|
+
/// Entry point for the Zig dispatcher. Selector: handleAction:payload:requestId:
|
|
5233
|
+
///
|
|
5234
|
+
/// Returns false when this shim cannot serve the call — no live
|
|
5235
|
+
/// coordinator, or no Zig runtime to reply through — so Zig answers the
|
|
5236
|
+
/// page with UnknownAction instead of the call vanishing.
|
|
5237
|
+
@objc static func handleAction(_ action: String, payload: String, requestId: Int64) -> Bool {
|
|
5238
|
+
guard let coordinator, deliverResult != nil else { return false }
|
|
5239
|
+
|
|
5240
|
+
let body = ((try? JSONSerialization.jsonObject(with: Data(payload.utf8))) as? [String: Any]) ?? [:]
|
|
5241
|
+
|
|
5242
|
+
// The synthetic callbackId routes this call's reply back through Zig.
|
|
5243
|
+
// It carries the action too, because the reply helpers do not otherwise
|
|
5244
|
+
// know it, and Zig's reply names the action for the page's
|
|
5245
|
+
// action-matching fallback.
|
|
5246
|
+
coordinator.dispatch(action: action, body: body, callbackId: "zig:\(requestId):\(action)")
|
|
5247
|
+
return true
|
|
5248
|
+
}
|
|
5249
|
+
|
|
5250
|
+
/// Route a resolve through Zig when the callbackId marks a hand-off.
|
|
5251
|
+
/// Returns true when the reply has been (or could only be) handled here.
|
|
5252
|
+
static func deliverResultIfHandOff(_ callbackId: String, json: String) -> Bool {
|
|
5253
|
+
guard let (requestId, action) = parseHandOffId(callbackId) else { return false }
|
|
5254
|
+
guard let deliverResult else { return true } // hand-off id but no runtime: drop, never eval JS
|
|
5255
|
+
action.withCString { a in
|
|
5256
|
+
json.withCString { j in
|
|
5257
|
+
deliverResult(a, UInt(strlen(a)), j, UInt(strlen(j)), requestId)
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
return true
|
|
5261
|
+
}
|
|
5262
|
+
|
|
5263
|
+
/// Route a rejection through Zig's error path, so the page's promise
|
|
5264
|
+
/// rejects rather than resolving with an error-shaped object.
|
|
5265
|
+
static func deliverErrorIfHandOff(_ callbackId: String, message: String, code: String) -> Bool {
|
|
5266
|
+
guard let (requestId, action) = parseHandOffId(callbackId) else { return false }
|
|
5267
|
+
guard let deliverError else { return true }
|
|
5268
|
+
action.withCString { a in
|
|
5269
|
+
message.withCString { m in
|
|
5270
|
+
code.withCString { c in
|
|
5271
|
+
deliverError(a, UInt(strlen(a)), m, UInt(strlen(m)), c, UInt(strlen(c)), requestId)
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
return true
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5278
|
+
/// "zig:<requestId>:<action>" -> (requestId, action). Nil for ordinary
|
|
5279
|
+
/// page-issued callback ids, which keep their JavaScript reply path.
|
|
5280
|
+
private static func parseHandOffId(_ callbackId: String) -> (Int64, String)? {
|
|
5281
|
+
guard callbackId.hasPrefix("zig:") else { return nil }
|
|
5282
|
+
let rest = callbackId.dropFirst(4)
|
|
5283
|
+
guard let sep = rest.firstIndex(of: ":"), let id = Int64(rest[..<sep]) else { return nil }
|
|
5284
|
+
return (id, String(rest[rest.index(after: sep)...]))
|
|
5285
|
+
}
|
|
5286
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -73,6 +73,22 @@ export interface WindowOptions {
|
|
|
73
73
|
* error rather than a silent no-op.
|
|
74
74
|
*/
|
|
75
75
|
headless?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Keep the process running after the last window closes (macOS).
|
|
78
|
+
*
|
|
79
|
+
* Craft decides this from the shape of the app when it is not set: an app
|
|
80
|
+
* with a tray icon or in menubar-only mode stays running, because outliving
|
|
81
|
+
* its window is what those are for; an ordinary windowed app quits.
|
|
82
|
+
*
|
|
83
|
+
* Set it to `true` for a windowed app that should stay resident and be
|
|
84
|
+
* brought back by clicking the Dock icon, or `false` for a tray app that
|
|
85
|
+
* should genuinely go away when its window closes.
|
|
86
|
+
*
|
|
87
|
+
* Before this existed the answer was AppKit's default, `false`, for every
|
|
88
|
+
* app: closing the last window left a process with no window and no way to
|
|
89
|
+
* get one back.
|
|
90
|
+
*/
|
|
91
|
+
keepRunning?: boolean;
|
|
76
92
|
/**
|
|
77
93
|
* Remember this window's size and position across launches, under this name
|
|
78
94
|
* (macOS).
|
|
@@ -673,11 +689,46 @@ export interface AppInfo {
|
|
|
673
689
|
/**
|
|
674
690
|
* Notification options
|
|
675
691
|
*/
|
|
692
|
+
/**
|
|
693
|
+
* A button on a notification banner.
|
|
694
|
+
*
|
|
695
|
+
* macOS shows two directly on a banner and puts any others behind an
|
|
696
|
+
* "Options" disclosure, so craft accepts at most four — past that they are
|
|
697
|
+
* a menu the user has to go looking for rather than a choice they can see.
|
|
698
|
+
*
|
|
699
|
+
* Two spellings are accepted. `{ id, label }` is the one to use. `{ action,
|
|
700
|
+
* title }` is what this type has said since before anything implemented it,
|
|
701
|
+
* and it keeps working rather than being deleted out from under whoever wrote
|
|
702
|
+
* against it — the field was published, it just never did anything.
|
|
703
|
+
*/
|
|
704
|
+
export type NotificationAction = {
|
|
705
|
+
/** Comes back as `actionId` in `craft.notifications.onAction`. */
|
|
706
|
+
id: string;
|
|
707
|
+
/** The text on the button. */
|
|
708
|
+
label: string;
|
|
709
|
+
} | {
|
|
710
|
+
/** @deprecated Use `id`. */
|
|
711
|
+
action: string;
|
|
712
|
+
/** @deprecated Use `label`. */
|
|
713
|
+
title: string;
|
|
714
|
+
};
|
|
676
715
|
export interface NotificationOptions {
|
|
677
716
|
/**
|
|
678
717
|
* Notification title (required)
|
|
679
718
|
*/
|
|
680
719
|
title: string;
|
|
720
|
+
/**
|
|
721
|
+
* Buttons on the banner.
|
|
722
|
+
*
|
|
723
|
+
* Pressing one brings the app forward and fires
|
|
724
|
+
* `craft.notifications.onAction` with `{ notificationId, actionId }` —
|
|
725
|
+
* which is what lets a prompt be answered without switching to the app
|
|
726
|
+
* first.
|
|
727
|
+
*
|
|
728
|
+
* Two buttons cannot share an `id`: the response names the button by id,
|
|
729
|
+
* and for an Approve/Deny prompt that name is the entire answer.
|
|
730
|
+
*/
|
|
731
|
+
actions?: NotificationAction[];
|
|
681
732
|
/**
|
|
682
733
|
* Notification body text
|
|
683
734
|
*/
|
|
@@ -694,13 +745,6 @@ export interface NotificationOptions {
|
|
|
694
745
|
* - Or any system sound name
|
|
695
746
|
*/
|
|
696
747
|
sound?: string;
|
|
697
|
-
/**
|
|
698
|
-
* Action buttons (platform dependent)
|
|
699
|
-
*/
|
|
700
|
-
actions?: Array<{
|
|
701
|
-
action: string;
|
|
702
|
-
title: string;
|
|
703
|
-
}>;
|
|
704
748
|
/**
|
|
705
749
|
* Notification tag (for grouping/replacing)
|
|
706
750
|
*/
|
|
@@ -916,7 +960,83 @@ export interface CraftMobileAPI {
|
|
|
916
960
|
* defines the CraftBridge interface with additional mobile-only features
|
|
917
961
|
* such as AR, ML, deep links, OTA updates, widgets, and auth persistence.
|
|
918
962
|
*/
|
|
963
|
+
/**
|
|
964
|
+
* Where the platform drew this window's window buttons — the macOS traffic
|
|
965
|
+
* lights, and their equivalents elsewhere.
|
|
966
|
+
*
|
|
967
|
+
* The buttons are the window server's on every desktop window Craft opens that
|
|
968
|
+
* is not frameless: real, correctly styled, wired to the keyboard and to
|
|
969
|
+
* accessibility. A page must never draw replicas beside them, and this is what
|
|
970
|
+
* makes that unnecessary — it says where they are, so a layout can leave room
|
|
971
|
+
* instead of inventing its own.
|
|
972
|
+
*
|
|
973
|
+
* Every number is measured from the live window and re-sent when it changes: a
|
|
974
|
+
* resize, a fullscreen transition, a new document. Listen for the
|
|
975
|
+
* `craft:windowcontrols` event on `window` for layout that CSS cannot express;
|
|
976
|
+
* everything else is better served by the four CSS variables, which the host
|
|
977
|
+
* sets before the document is parsed.
|
|
978
|
+
*
|
|
979
|
+
* The same facts reach CSS as `--craft-window-controls-width` / `-height` /
|
|
980
|
+
* `-inset-x` / `-inset-y` / `-replicas`, and the document as
|
|
981
|
+
* `<html data-craft-window-controls="...">`.
|
|
982
|
+
*/
|
|
983
|
+
export interface CraftWindowControls {
|
|
984
|
+
/**
|
|
985
|
+
* `titlebar` — real buttons, in a titlebar above the page. Nothing to do.
|
|
986
|
+
* `overlay` — real buttons, over the page's own top-left corner. Leave room.
|
|
987
|
+
* `custom` — a frameless window: no buttons, and the page's own are the only
|
|
988
|
+
* ones there can be.
|
|
989
|
+
* `none` — no window chrome in this environment at all (iOS, Android).
|
|
990
|
+
*/
|
|
991
|
+
style: 'titlebar' | 'overlay' | 'custom' | 'none';
|
|
992
|
+
/** The platform drew real buttons for this window. */
|
|
993
|
+
native: boolean;
|
|
994
|
+
/**
|
|
995
|
+
* ...and they are on screen right now. False in fullscreen, where macOS
|
|
996
|
+
* takes them into an auto-hiding titlebar — which is why a layout should
|
|
997
|
+
* reserve `reserveWidth` rather than a remembered constant.
|
|
998
|
+
*/
|
|
999
|
+
visible: boolean;
|
|
1000
|
+
/**
|
|
1001
|
+
* The block's true position, in CSS px from the top-left of the web
|
|
1002
|
+
* viewport. Negative where the buttons are not over the page at all: above
|
|
1003
|
+
* it in a plain titlebar window, to its left in a window whose web content
|
|
1004
|
+
* starts after a native sidebar.
|
|
1005
|
+
*/
|
|
1006
|
+
x: number;
|
|
1007
|
+
y: number;
|
|
1008
|
+
width: number;
|
|
1009
|
+
height: number;
|
|
1010
|
+
/**
|
|
1011
|
+
* The room to leave inside the page — the block's far edge, or zero when it
|
|
1012
|
+
* does not reach into the page. `--craft-window-controls-width` / `-height`.
|
|
1013
|
+
*/
|
|
1014
|
+
reserveWidth: number;
|
|
1015
|
+
reserveHeight: number;
|
|
1016
|
+
/**
|
|
1017
|
+
* Where the block starts inside the page, zero unless it overlaps.
|
|
1018
|
+
* `--craft-window-controls-inset-x` / `-inset-y`.
|
|
1019
|
+
*/
|
|
1020
|
+
insetX: number;
|
|
1021
|
+
insetY: number;
|
|
1022
|
+
/**
|
|
1023
|
+
* The `display` a replica should take: `'none'` wherever real buttons exist
|
|
1024
|
+
* and wherever there is no window to control, and `null` in a frameless
|
|
1025
|
+
* window, where the page's own controls are the real ones. Published as
|
|
1026
|
+
* `--craft-window-controls-replicas`, which is *removed* rather than set when
|
|
1027
|
+
* this is null, so the page's own fallback applies.
|
|
1028
|
+
*/
|
|
1029
|
+
replicas: 'none' | null;
|
|
1030
|
+
}
|
|
919
1031
|
export interface CraftBridgeAPI {
|
|
1032
|
+
/**
|
|
1033
|
+
* Where the platform drew this window's close/minimise/zoom buttons.
|
|
1034
|
+
*
|
|
1035
|
+
* Present in every Craft window, so a UI that has to lay out around them can
|
|
1036
|
+
* ask instead of guessing — and so a UI shared with the browser can tell the
|
|
1037
|
+
* two apart. See `CraftWindowControls`.
|
|
1038
|
+
*/
|
|
1039
|
+
windowControls?: CraftWindowControls;
|
|
920
1040
|
/**
|
|
921
1041
|
* Trackpad gesture phases (desktop only).
|
|
922
1042
|
*
|
|
@@ -1038,13 +1158,18 @@ export interface CapabilityNamespace {
|
|
|
1038
1158
|
export interface CraftCapabilities {
|
|
1039
1159
|
namespaces: Record<string, CapabilityNamespace>;
|
|
1040
1160
|
/**
|
|
1041
|
-
* Every `craft:*` event channel, and
|
|
1161
|
+
* Every `craft:*` event channel, and what craft can say about it.
|
|
1162
|
+
*
|
|
1163
|
+
* `'live'` means something in this build took out a permit to emit on it.
|
|
1164
|
+
* `'unknown'` means craft cannot prove it either way — subscribe and see, and
|
|
1165
|
+
* do **not** disable a feature over it.
|
|
1042
1166
|
*
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1045
|
-
*
|
|
1167
|
+
* There is deliberately no `'dead'`. Craft cannot establish absence: the
|
|
1168
|
+
* `craft:window:*` names are composed in JavaScript from
|
|
1169
|
+
* `__craftDeliverWindowEvent('focus')`, so no source scan finds the literal
|
|
1170
|
+
* even though the emitter is right there.
|
|
1046
1171
|
*/
|
|
1047
|
-
channels: Record<string,
|
|
1172
|
+
channels: Record<string, 'live' | 'unknown'>;
|
|
1048
1173
|
}
|
|
1049
1174
|
/**
|
|
1050
1175
|
* The only value types `craft.prefs` stores.
|